diff --git a/.bazelrc b/.bazelrc index 4acbd8f785f..a23686e19b7 100644 --- a/.bazelrc +++ b/.bazelrc @@ -3,8 +3,8 @@ build --tool_java_language_version=21 --tool_java_runtime_version=21 # Delete test data packages, needed for bazel integration tests. Update by running the following command: # bazel run @rules_bazel_integration_test//tools:update_deleted_packages -build --deleted_packages=.bazelbsp,.bazelbsp/aspects,aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata,clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/query_sync/main,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/.clwb/aspects,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/.clwb/aspects,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/go/with_go_source,examples/go/with_go_source/otherlib,examples/go/with_go_source/testa,examples/go/with_go_source/testb,examples/go/with_proto,examples/go/with_proto/go,examples/go/with_proto/go/external,examples/go/with_proto/go/lib,examples/go/with_proto/proto,examples/java/greetings_project,examples/java/greetings_project/.ijwb/aspects,examples/java/greetings_project/greeting_lib,examples/java/greetings_project/hello,examples/java/greetings_project/hi,examples/kotlin/simple_project,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib,examples/scala/with_bzlmod/hello,ijwb/tests/projects/simple,testing/test_deps/projects,testing/test_deps/projects/java_and_deps,testing/test_deps/projects/java_and_deps/deps/no_ide,testing/test_deps/projects/java_and_deps/deps/top_level_lib_1,testing/test_deps/projects/java_and_deps/deps/top_level_lib_2,testing/test_deps/projects/java_and_deps/deps/transitive_dep_lib,testing/test_deps/projects/java_and_deps/project,testing/test_deps/projects/java_and_deps/project/java/com/example/sample/nested,testing/test_deps/projects/simple_java,testing/test_deps/projects/simple_java/java/com/example/sample/nested,testing/test_deps/projects/simple_proto/external,testing/test_deps/projects/simple_proto/project -query --deleted_packages=.bazelbsp,.bazelbsp/aspects,aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata,clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/query_sync/main,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/.clwb/aspects,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/.clwb/aspects,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/go/with_go_source,examples/go/with_go_source/otherlib,examples/go/with_go_source/testa,examples/go/with_go_source/testb,examples/go/with_proto,examples/go/with_proto/go,examples/go/with_proto/go/external,examples/go/with_proto/go/lib,examples/go/with_proto/proto,examples/java/greetings_project,examples/java/greetings_project/.ijwb/aspects,examples/java/greetings_project/greeting_lib,examples/java/greetings_project/hello,examples/java/greetings_project/hi,examples/kotlin/simple_project,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib,examples/scala/with_bzlmod/hello,ijwb/tests/projects/simple,testing/test_deps/projects,testing/test_deps/projects/java_and_deps,testing/test_deps/projects/java_and_deps/deps/no_ide,testing/test_deps/projects/java_and_deps/deps/top_level_lib_1,testing/test_deps/projects/java_and_deps/deps/top_level_lib_2,testing/test_deps/projects/java_and_deps/deps/transitive_dep_lib,testing/test_deps/projects/java_and_deps/project,testing/test_deps/projects/java_and_deps/project/java/com/example/sample/nested,testing/test_deps/projects/simple_java,testing/test_deps/projects/simple_java/java/com/example/sample/nested,testing/test_deps/projects/simple_proto/external,testing/test_deps/projects/simple_proto/project +build --deleted_packages=clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/protobuf,clwb/tests/projects/protobuf/main,clwb/tests/projects/protobuf/proto,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib +query --deleted_packages=clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/protobuf,clwb/tests/projects/protobuf/main,clwb/tests/projects/protobuf/proto,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib common --enable_bzlmod common --noincompatible_disallow_empty_glob diff --git a/aspect/intellij_info_impl.bzl b/aspect/intellij_info_impl.bzl index 417ff49dd5d..dc24bda8bc6 100644 --- a/aspect/intellij_info_impl.bzl +++ b/aspect/intellij_info_impl.bzl @@ -344,7 +344,7 @@ def collect_cc_compilation_context(ctx, target): external_includes = getattr(compilation_context, "external_includes", depset()).to_list() return struct( - direct_headers = [artifact_location(it) for it in compilation_context.direct_headers], + headers = [artifact_location(it) for it in compilation_context.headers.to_list()], defines = compilation_context.defines.to_list(), includes = compilation_context.includes.to_list(), quote_includes = compilation_context.quote_includes.to_list(), diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/CcLibraryTest.java b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/CcLibraryTest.java index fdac8e2eedd..734d8098581 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/CcLibraryTest.java +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/CcLibraryTest.java @@ -58,8 +58,8 @@ public void testCcLibrary() throws Exception { assertThat(compilationCtx.getDefinesList()).containsExactly("VERSION2"); assertThat(compilationCtx.getSystemIncludesList()).contains(testRelative("foo/bar")); assertThat(compilationCtx.getQuoteIncludesList()).contains("."); - assertThat(relativePathsForArtifacts(compilationCtx.getDirectHeadersList())) - .containsExactly(testRelative("simple/simple.h")); + assertThat(relativePathsForArtifacts(compilationCtx.getHeadersList())) + .containsExactly(testRelative("simple/simple.h"), testRelative("simple/simple_textual.h")); // Can't test for this because the cc code stuffs source artifacts into // the output group diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/ArtifactTest.java b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/ArtifactTest.java index b55b2b73c1c..79898cc7d40 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/ArtifactTest.java +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/ArtifactTest.java @@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.devtools.intellij.aspect.Common.ArtifactLocation; import com.google.devtools.intellij.ideinfo.IntellijIdeInfo.CIdeInfo; import com.google.devtools.intellij.ideinfo.IntellijIdeInfo.TargetIdeInfo; import com.google.idea.blaze.BazelIntellijAspectTest; @@ -25,7 +26,9 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Tests the artifact representation */ +/** + * Tests the artifact representation + */ @RunWith(JUnit4.class) public class ArtifactTest extends BazelIntellijAspectTest { @@ -78,4 +81,19 @@ public void testCorrectPathToTargetBuildFile() throws Exception { assertThat(buildFile.getIsExternal()).isTrue(); assertThat(buildFile.getIsSource()).isTrue(); } + + @Test + public void testVirtualIncludesSymlinks() throws Exception { + final var ideInfo = getCIdeInfo("//main:main"); + + final var headers = ideInfo.getCompilationContext().getHeadersList(); + assertThat(headers).hasSize(10); + + final var virtualHeaders = headers.stream() + .filter(it -> it.getRelativePath().contains("virtual_includes")) + .toList(); + + assertThat(virtualHeaders).hasSize(3); + assertThat(virtualHeaders.stream().map(ArtifactLocation::getIsSource).distinct()).containsExactly(false); + } } diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD index 09d63a9b941..547034cad93 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD @@ -19,6 +19,7 @@ java_test( "//aspect/testing/rules:IntellijAspectTest", "//aspect/testing/rules:intellij_aspect_test_fixture_java_proto", "//intellij_platform_sdk:test_libs", + "//proto:common_java_proto", "//proto:intellij_ide_info_java_proto", "//third_party/java/junit", ], diff --git a/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java index 93c125a30c2..24563a3a85c 100644 --- a/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java +++ b/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java @@ -107,7 +107,7 @@ public static abstract class CompilationContext implements ProtoWrapper directHeaders(); + public abstract ImmutableList headers(); public abstract ImmutableList defines(); @@ -119,7 +119,7 @@ public static abstract class CompilationContext implements ProtoWrapper value); + public abstract Builder setHeaders(ImmutableList value); public abstract Builder setDefines(ImmutableList value); diff --git a/clwb/BUILD b/clwb/BUILD index 99c044500bb..5ef98bb4fe4 100644 --- a/clwb/BUILD +++ b/clwb/BUILD @@ -161,7 +161,10 @@ clwb_headless_test( clwb_headless_test( name = "virtual_includes_headless_test", - srcs = ["tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesTest.java"], + srcs = [ + "tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesCacheTest.java", + "tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesTest.java", + ], project = "virtual_includes", ) @@ -207,6 +210,15 @@ clwb_headless_test( project = "clang_cl", ) +clwb_headless_test( + name = "protobuf_headless_test", + srcs = [ + "tests/headlesstests/com/google/idea/blaze/clwb/ProtobufCacheTest.java", + "tests/headlesstests/com/google/idea/blaze/clwb/ProtobufTest.java", + ], + project = "protobuf", +) + test_suite( name = "headless_tests", tests = [ @@ -214,22 +226,23 @@ test_suite( ":example_headless_test", ":execution_headless_test", ":external_includes_headless_test", + ":lib_cpp_headless_test", ":llvm_toolchain_headless_test", + ":protobuf_headless_test", ":simple_headless_test", ":target_compatible_headless_test", ":virtual_includes_headless_test", - ":lib_cpp_headless_test", ], ) clwb_integration_test( - name = "copts_processor", - srcs = ["tests/integrationtests/com/google/idea/blaze/clwb/CoptsProcessorTest.kt"], + name = "copts_processor", + srcs = ["tests/integrationtests/com/google/idea/blaze/clwb/CoptsProcessorTest.kt"], ) test_suite( name = "integration_tests", tests = [ - ":copts_processor", + ":copts_processor", ], ) diff --git a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/ProtobufCacheTest.java b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/ProtobufCacheTest.java new file mode 100644 index 00000000000..8b439ef0129 --- /dev/null +++ b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/ProtobufCacheTest.java @@ -0,0 +1,59 @@ +package com.google.idea.blaze.clwb; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.idea.blaze.clwb.base.Assertions.assertContainsHeader; +import static com.google.idea.blaze.clwb.base.Assertions.assertCachedHeader; +import static com.google.idea.blaze.clwb.base.Utils.setIncludesCacheEnabled; + +import com.google.idea.blaze.base.bazel.BazelVersion; +import com.google.idea.blaze.clwb.base.ClwbHeadlessTestCase; +import com.google.idea.testing.headless.BazelVersionRule; +import com.google.idea.testing.headless.ProjectViewBuilder; +import com.intellij.util.system.OS; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProtobufCacheTest extends ClwbHeadlessTestCase { + + // protobuf requires bazel 7+ + @Rule + public final BazelVersionRule bazelRule = new BazelVersionRule(7, 0); + // on windows clang-cl is required to compile protobuf and therefore also bazel 8+ + @Rule + public final BazelVersionRule bazelWindowsRule = new BazelVersionRule(OS.Windows, 8, 0); + + @Test + public void testClwb() { + setIncludesCacheEnabled(true); + + final var errors = runSync(defaultSyncParams().build()); + errors.assertNoErrors(); + + checkProto(); + } + + @Override + protected ProjectViewBuilder projectViewText(BazelVersion version) { + final var builder = super.projectViewText(version); + + if (OS.CURRENT.equals(OS.Windows)) { + builder.addBuildFlag("--extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl"); + builder.addBuildFlag("--extra_execution_platforms=//:x64_windows-clang-cl"); + } + + return builder; + } + + private void checkProto() { + final var compilerSettings = findFileCompilerSettings("main/main.cc"); + + final var headersSearchRoots = compilerSettings.getHeadersSearchRoots().getAllRoots(); + assertThat(headersSearchRoots).isNotEmpty(); + + assertContainsHeader("proto/addressbook.pb.h", compilerSettings); + assertCachedHeader("proto/addressbook.pb.h", compilerSettings, myProject); + } +} diff --git a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/ProtobufTest.java b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/ProtobufTest.java new file mode 100644 index 00000000000..0c234ebd16d --- /dev/null +++ b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/ProtobufTest.java @@ -0,0 +1,66 @@ +package com.google.idea.blaze.clwb; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.idea.blaze.clwb.base.Assertions.assertContainsHeader; +import static com.google.idea.blaze.clwb.base.Utils.setIncludesCacheEnabled; + +import com.google.idea.blaze.base.bazel.BazelVersion; +import com.google.idea.blaze.clwb.base.AllowedVfsRoot; +import com.google.idea.blaze.clwb.base.ClwbHeadlessTestCase; +import com.google.idea.testing.headless.BazelVersionRule; +import com.google.idea.testing.headless.ProjectViewBuilder; +import com.intellij.util.system.OS; +import java.util.ArrayList; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProtobufTest extends ClwbHeadlessTestCase { + + // protobuf requires bazel 7+ + @Rule + public final BazelVersionRule bazelRule = new BazelVersionRule(7, 0); + + // on windows clang-cl is required to compile protobuf and therefore also bazel 8+ + @Rule + public final BazelVersionRule bazelWindowsRule = new BazelVersionRule(OS.Windows, 8, 0); + + @Test + public void testClwb() { + setIncludesCacheEnabled(false); + + final var errors = runSync(defaultSyncParams().build()); + errors.assertNoErrors(); + + checkProto(); + } + + @Override + protected ProjectViewBuilder projectViewText(BazelVersion version) { + final var builder = super.projectViewText(version); + + if (OS.CURRENT.equals(OS.Windows)) { + builder.addBuildFlag("--extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl"); + builder.addBuildFlag("--extra_execution_platforms=//:x64_windows-clang-cl"); + } + + return builder; + } + + @Override + protected void addAllowedVfsRoots(ArrayList roots) { + super.addAllowedVfsRoots(roots); + roots.add(AllowedVfsRoot.bazelBinRecursive(myBazelInfo, "proto")); + } + + private void checkProto() { + final var compilerSettings = findFileCompilerSettings("main/main.cc"); + + final var headersSearchRoots = compilerSettings.getHeadersSearchRoots().getAllRoots(); + assertThat(headersSearchRoots).isNotEmpty(); + + assertContainsHeader("proto/addressbook.pb.h", compilerSettings); + } +} diff --git a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesCacheTest.java b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesCacheTest.java new file mode 100644 index 00000000000..f34a06a7e67 --- /dev/null +++ b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesCacheTest.java @@ -0,0 +1,82 @@ +package com.google.idea.blaze.clwb; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.idea.blaze.clwb.base.Assertions.assertCachedHeader; +import static com.google.idea.blaze.clwb.base.Assertions.assertContainsHeader; +import static com.google.idea.blaze.clwb.base.Assertions.assertWorkspaceHeader; +import static com.google.idea.blaze.clwb.base.Utils.resolveHeader; +import static com.google.idea.blaze.clwb.base.Utils.setIncludesCacheEnabled; + +import com.google.idea.blaze.clwb.base.ClwbHeadlessTestCase; +import com.google.idea.testing.headless.BazelVersionRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class VirtualIncludesCacheTest extends ClwbHeadlessTestCase { + + // use_repo_rule requires bazel 7+ + @Rule + public final BazelVersionRule bazelRule = new BazelVersionRule(7, 0); + + @Test + public void testClwb() { + setIncludesCacheEnabled(true); + + final var errors = runSync(defaultSyncParams().build()); + errors.assertNoErrors(); + + checkIncludes(); + checkImplDeps(); + checkCoptIncludes(); + } + + private void checkIncludes() { + final var compilerSettings = findFileCompilerSettings("main/main.cc"); + + assertContainsHeader("strip_absolut/strip_absolut.h", compilerSettings); + assertCachedHeader("strip_absolut/strip_absolut.h", compilerSettings, myProject); + + assertContainsHeader("strip_absolut/generated.h", compilerSettings); + assertCachedHeader("strip_absolut/generated.h", compilerSettings, myProject); + + assertContainsHeader("strip_relative.h", compilerSettings); + assertCachedHeader("strip_relative.h", compilerSettings, myProject); + + assertContainsHeader("raw_default.h", compilerSettings); + assertWorkspaceHeader("raw_default.h", compilerSettings, myProject); + + assertContainsHeader("raw_system.h", compilerSettings); + assertWorkspaceHeader("raw_system.h", compilerSettings, myProject); + + assertContainsHeader("raw_quote.h", compilerSettings); + assertWorkspaceHeader("raw_quote.h", compilerSettings, myProject); + + } + + private void checkCoptIncludes() { + final var compilerSettings = findFileCompilerSettings("main/raw.cc"); + + assertContainsHeader("raw_default.h", compilerSettings); + assertWorkspaceHeader("raw_default.h", compilerSettings, myProject); + + assertContainsHeader("raw_system.h", compilerSettings); + assertWorkspaceHeader("raw_system.h", compilerSettings, myProject); + + assertContainsHeader("raw_quote.h", compilerSettings); + assertWorkspaceHeader("raw_quote.h", compilerSettings, myProject); + + } + + private void checkImplDeps() { + final var compilerSettings = findFileCompilerSettings("lib/impl_deps/impl.cc"); + + final var headersSearchRoots = compilerSettings.getHeadersSearchRoots().getAllRoots(); + assertThat(headersSearchRoots).isNotEmpty(); + + assertContainsHeader("strip_relative.h", compilerSettings); + assertCachedHeader("strip_relative.h", compilerSettings, myProject); + } +} diff --git a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesTest.java b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesTest.java index a9caa585c6f..e212a2c5fa2 100644 --- a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesTest.java +++ b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/VirtualIncludesTest.java @@ -2,6 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.idea.blaze.clwb.base.Assertions.assertContainsHeader; +import static com.google.idea.blaze.clwb.base.Utils.resolveHeader; +import static com.google.idea.blaze.clwb.base.Utils.setIncludesCacheEnabled; import com.google.idea.blaze.base.bazel.BazelVersion; import com.google.idea.blaze.clwb.base.AllowedVfsRoot; @@ -21,6 +23,8 @@ public class VirtualIncludesTest extends ClwbHeadlessTestCase { @Test public void testClwb() { + setIncludesCacheEnabled(false); + final var errors = runSync(defaultSyncParams().build()); errors.assertNoErrors(); @@ -48,22 +52,6 @@ protected void addAllowedVfsRoots(ArrayList roots) { roots.add(AllowedVfsRoot.bazelBinRecursive(myBazelInfo, "lib/strip_absolut/_virtual_includes")); } - private @Nullable VirtualFile findHeader(String fileName, OCCompilerSettings settings) { - final var roots = settings.getHeadersSearchRoots().getAllRoots(); - - for (final var root : roots) { - final var rootFile = root.getVirtualFile(); - if (rootFile == null) continue; - - final var headerFile = rootFile.findFileByRelativePath(fileName); - if (headerFile == null) continue; - - return headerFile; - } - - return null; - } - private void checkIncludes() { final var compilerSettings = findFileCompilerSettings("main/main.cc"); @@ -75,22 +63,22 @@ private void checkIncludes() { assertContainsHeader("raw_quote.h", compilerSettings); assertThat(findProjectFile("lib/strip_absolut/strip_absolut.h")) - .isEqualTo(findHeader("strip_absolut/strip_absolut.h", compilerSettings)); + .isEqualTo(resolveHeader("strip_absolut/strip_absolut.h", compilerSettings)); assertThat(findProjectFile("lib/strip_relative/include/strip_relative.h")) - .isEqualTo(findHeader("strip_relative.h", compilerSettings)); + .isEqualTo(resolveHeader("strip_relative.h", compilerSettings)); assertThat(findProjectFile("lib/impl_deps/impl.h")) - .isEqualTo(findHeader("lib/impl_deps/impl.h", compilerSettings)); + .isEqualTo(resolveHeader("lib/impl_deps/impl.h", compilerSettings)); assertThat(findProjectFile("lib/raw_files/default/raw_default.h")) - .isEqualTo(findHeader("raw_default.h", compilerSettings)); + .isEqualTo(resolveHeader("raw_default.h", compilerSettings)); assertThat(findProjectFile("lib/raw_files/system/raw_system.h")) - .isEqualTo(findHeader("raw_system.h", compilerSettings)); + .isEqualTo(resolveHeader("raw_system.h", compilerSettings)); assertThat(findProjectFile("lib/raw_files/quote/raw_quote.h")) - .isEqualTo(findHeader("raw_quote.h", compilerSettings)); + .isEqualTo(resolveHeader("raw_quote.h", compilerSettings)); } private void checkCoptIncludes() { @@ -101,13 +89,13 @@ private void checkCoptIncludes() { assertContainsHeader("raw_quote.h", compilerSettings); assertThat(findProjectFile("lib/raw_files/default/raw_default.h")) - .isEqualTo(findHeader("raw_default.h", compilerSettings)); + .isEqualTo(resolveHeader("raw_default.h", compilerSettings)); assertThat(findProjectFile("lib/raw_files/system/raw_system.h")) - .isEqualTo(findHeader("raw_system.h", compilerSettings)); + .isEqualTo(resolveHeader("raw_system.h", compilerSettings)); assertThat(findProjectFile("lib/raw_files/quote/raw_quote.h")) - .isEqualTo(findHeader("raw_quote.h", compilerSettings)); + .isEqualTo(resolveHeader("raw_quote.h", compilerSettings)); } private void checkImplDeps() { diff --git a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Assertions.java b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Assertions.java index f1880565eb3..3c9535505a7 100644 --- a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Assertions.java +++ b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Assertions.java @@ -2,10 +2,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.idea.blaze.clwb.base.Utils.resolveHeader; import static com.google.idea.testing.headless.Assertions.abort; import com.google.common.truth.StringSubject; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; import com.google.idea.blaze.base.util.VfsUtil; +import com.google.idea.blaze.cpp.sync.HeaderCacheService; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -18,6 +22,7 @@ import java.util.stream.Collectors; public class Assertions { + private final static Pattern defineRx = Pattern.compile("#define ([^ ]+) ?(.*)"); public static void assertContainsHeader(String fileName, OCCompilerSettings settings) { @@ -37,10 +42,14 @@ private static void assertContainsHeader(String fileName, boolean shouldContain, for (final var root : roots) { final var rootFile = root.getVirtualFile(); - if (rootFile == null) continue; + if (rootFile == null) { + continue; + } final var headerFile = rootFile.findFileByRelativePath(fileName); - if (headerFile == null || !headerFile.exists()) continue; + if (headerFile == null || !headerFile.exists()) { + continue; + } found.set(headerFile); foundIn.set(root); @@ -109,4 +118,28 @@ public static void assertVfsLoads(Path executionRoot, List allow abort(String.format("%s not in allowed roots: [%s], debug with: '-Dfile.system.trace.loading=%s'", child, roots, child)); } } + + public static void assertCachedHeader(String fileName, OCCompilerSettings settings, Project project) { + final var header = resolveHeader(fileName, settings); + assertThat(header).isNotNull(); + + final var service = HeaderCacheService.of(project); + assertThat(HeaderCacheService.getEnabled()).isTrue(); + + assertWithMessage(String.format("file does not reside in the include cache: %s", header.getPath())) + .that(header.toNioPath().startsWith(service.getCacheDirectory())) + .isTrue(); + } + + public static void assertWorkspaceHeader(String fileName, OCCompilerSettings compilerSettings, Project project) { + final var header = resolveHeader(fileName, compilerSettings); + assertThat(header).isNotNull(); + + final var importSettings = BlazeImportSettingsManager.getInstance(project).getImportSettings(); + assertThat(importSettings).isNotNull(); + + assertWithMessage(String.format("file does not reside in the workspace: %s", header.getPath())) + .that(header.toNioPath().startsWith(importSettings.getWorkspaceRoot())) + .isTrue(); + } } diff --git a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/ClwbHeadlessTestCase.java b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/ClwbHeadlessTestCase.java index 065c4e56123..d288f28777a 100644 --- a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/ClwbHeadlessTestCase.java +++ b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/ClwbHeadlessTestCase.java @@ -28,11 +28,14 @@ protected void setUp() throws Exception { } @Override - protected void tearDown() { + protected void tearDown() throws Exception { final var roots = new ArrayList(); addAllowedVfsRoots(roots); Assertions.assertVfsLoads(myBazelInfo.executionRoot(), roots); + // HeavyPlatformTestCase.cleanupApplicationCaches(myProject); + + super.tearDown(); } private void setupSandboxBin() { @@ -50,7 +53,9 @@ private void setupSandboxBin() { assertExists(sdkBinPath.toFile()); try { - Files.createSymbolicLink(Path.of(PathManager.getBinPath()), sdkBinPath); + final var link = Path.of(PathManager.getBinPath()); + Files.deleteIfExists(link); + Files.createSymbolicLink(link, sdkBinPath); } catch (IOException e) { abort("could not create bin path symlink", e); } diff --git a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Utils.java b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Utils.java index b227eff978a..76de27a0268 100644 --- a/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Utils.java +++ b/clwb/tests/headlesstests/com/google/idea/blaze/clwb/base/Utils.java @@ -2,10 +2,14 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.idea.blaze.cpp.sync.HeaderCacheService; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.Strings; +import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches.Format; import com.jetbrains.cidr.lang.workspace.OCCompilerSettings; import java.util.List; +import org.jetbrains.annotations.Nullable; public class Utils { @@ -20,4 +24,25 @@ public static List lookupCompilerSwitch(String flag, OCCompilerSettings .map(it -> Strings.trimStart(it.substring(flag.length()), "=")) .toList(); } + + @Nullable + public static VirtualFile resolveHeader(String fileName, OCCompilerSettings settings) { + final var roots = settings.getHeadersSearchRoots().getAllRoots(); + + for (final var root : roots) { + final var rootFile = root.getVirtualFile(); + if (rootFile == null) continue; + + final var headerFile = rootFile.findFileByRelativePath(fileName); + if (headerFile == null) continue; + + return headerFile; + } + + return null; + } + + public static void setIncludesCacheEnabled(boolean enabled) { + Registry.get(HeaderCacheService.ENABLED_KEY).setValue(enabled); + } } diff --git a/clwb/tests/projects/protobuf/BUILD b/clwb/tests/projects/protobuf/BUILD new file mode 100644 index 00000000000..4f91cb38759 --- /dev/null +++ b/clwb/tests/projects/protobuf/BUILD @@ -0,0 +1,8 @@ +platform( + name = "x64_windows-clang-cl", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:windows", + "@bazel_tools//tools/cpp:clang-cl", + ], +) diff --git a/clwb/tests/projects/protobuf/MODULE.bazel b/clwb/tests/projects/protobuf/MODULE.bazel new file mode 100644 index 00000000000..575e39a913a --- /dev/null +++ b/clwb/tests/projects/protobuf/MODULE.bazel @@ -0,0 +1,6 @@ +bazel_dep(name = "rules_cc", version = "0.1.4") +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "protobuf", version = "31.1") + +cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension") +use_repo(cc_configure, "local_config_cc") diff --git a/clwb/tests/projects/protobuf/main/BUILD b/clwb/tests/projects/protobuf/main/BUILD new file mode 100644 index 00000000000..7f39e6d8891 --- /dev/null +++ b/clwb/tests/projects/protobuf/main/BUILD @@ -0,0 +1,11 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary") + +cc_binary( + name = "main", + srcs = ["main.cc"], + deps = [ + "//proto:addressbook_cc_proto", + "@protobuf", + "@protobuf//src/google/protobuf/util:time_util", + ], +) diff --git a/clwb/tests/projects/protobuf/main/main.cc b/clwb/tests/projects/protobuf/main/main.cc new file mode 100644 index 00000000000..da26f63a342 --- /dev/null +++ b/clwb/tests/projects/protobuf/main/main.cc @@ -0,0 +1,94 @@ +#include +#include +#include +#include "proto/addressbook.pb.h" + +using namespace std; + +// This function fills in a Person message based on user input. +void PromptForAddress(tutorial::Person* person) { + cout << "Enter person ID number: "; + int id; + cin >> id; + person->set_id(id); + cin.ignore(256, '\n'); + + cout << "Enter name: "; + getline(cin, *person->mutable_name()); + + cout << "Enter email address (blank for none): "; + string email; + getline(cin, email); + if (!email.empty()) { + person->set_email(email); + } + + while (true) { + cout << "Enter a phone number (or leave blank to finish): "; + string number; + getline(cin, number); + if (number.empty()) { + break; + } + + tutorial::Person::PhoneNumber* phone_number = person->add_phones(); + phone_number->set_number(number); + cout << "Is this a mobile, home, or work phone? "; + + string type; + getline(cin, type); + if (type == "mobile") { + phone_number->set_type(tutorial::Person::PHONE_TYPE_MOBILE); + } else if (type == "home") { + phone_number->set_type(tutorial::Person::PHONE_TYPE_HOME); + } else if (type == "work") { + phone_number->set_type(tutorial::Person::PHONE_TYPE_WORK); + } else { + cout << "Unknown phone type. Using default." << endl; + } + } +} + +// Main function: Reads the entire address book from a file, +// adds one person based on user input, then writes it back out to the same +// file. +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (argc != 2) { + cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; + return -1; + } + + tutorial::AddressBook address_book; + + { + // Read the existing address book. + fstream input(argv[1], ios::in | ios::binary); + if (!input) { + cout << argv[1] << ": File not found. Creating a new file." << endl; + } else if (!address_book.ParseFromIstream(&input)) { + cerr << "Failed to parse address book." << endl; + return -1; + } + } + + // Add an address. + PromptForAddress(address_book.add_people()); + + { + // Write the new address book back to disk. + fstream output(argv[1], ios::out | ios::trunc | ios::binary); + if (!address_book.SerializeToOstream(&output)) { + cerr << "Failed to write address book." << endl; + return -1; + } + } + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +} \ No newline at end of file diff --git a/clwb/tests/projects/protobuf/proto/BUILD b/clwb/tests/projects/protobuf/proto/BUILD new file mode 100644 index 00000000000..712e04eecde --- /dev/null +++ b/clwb/tests/projects/protobuf/proto/BUILD @@ -0,0 +1,14 @@ +load("@protobuf//bazel:cc_proto_library.bzl", "cc_proto_library") +load("@protobuf//bazel:proto_library.bzl", "proto_library") + +proto_library( + name = "addressbook_proto", + srcs = ["addressbook.proto"], + deps = ["@protobuf//:timestamp_proto"], +) + +cc_proto_library( + name = "addressbook_cc_proto", + visibility = ["//visibility:public"], + deps = [":addressbook_proto"], +) diff --git a/clwb/tests/projects/protobuf/proto/addressbook.proto b/clwb/tests/projects/protobuf/proto/addressbook.proto new file mode 100644 index 00000000000..1e16a60ccfa --- /dev/null +++ b/clwb/tests/projects/protobuf/proto/addressbook.proto @@ -0,0 +1,27 @@ +syntax = "proto2"; + +package tutorial; + +message Person { + optional string name = 1; + optional int32 id = 2; + optional string email = 3; + + enum PhoneType { + PHONE_TYPE_UNSPECIFIED = 0; + PHONE_TYPE_MOBILE = 1; + PHONE_TYPE_HOME = 2; + PHONE_TYPE_WORK = 3; + } + + message PhoneNumber { + optional string number = 1; + optional PhoneType type = 2 [default = PHONE_TYPE_HOME]; + } + + repeated PhoneNumber phones = 4; +} + +message AddressBook { + repeated Person people = 1; +} diff --git a/cpp/src/META-INF/blaze-cpp.xml b/cpp/src/META-INF/blaze-cpp.xml index 11387d72eea..0970fe61a54 100644 --- a/cpp/src/META-INF/blaze-cpp.xml +++ b/cpp/src/META-INF/blaze-cpp.xml @@ -37,6 +37,8 @@ + + @@ -80,9 +82,12 @@ - + + diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java index 378dee98d3b..5227968dcb5 100644 --- a/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java +++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java @@ -33,6 +33,7 @@ import com.google.idea.blaze.base.sync.SyncMode; import com.google.idea.blaze.base.sync.workspace.ExecutionRootPathResolver; import com.google.idea.blaze.cpp.copts.CoptsProcessor; +import com.google.idea.blaze.cpp.sync.HeaderCacheService; import com.intellij.build.events.MessageEvent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.TransactionGuard; @@ -65,6 +66,7 @@ import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerKind; import com.jetbrains.cidr.lang.workspace.compiler.TempFilesPool; import java.io.File; +import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -269,14 +271,26 @@ private WorkspaceModel calculateConfigurations( // transitiveDefines are sourced from a target's (and transitive deps) "defines" attribute compilationCtx.defines().forEach(compilerSwitchesBuilder::withMacro); - Function> resolver = - executionRootPath -> - executionRootPathResolver.resolveToIncludeDirectories(executionRootPath).stream(); + final Function> resolver; + if (HeaderCacheService.getEnabled()) { + final var includesCache = HeaderCacheService.of(project); + + resolver = executionRootPath -> Stream.of(includesCache + .resolve(targetKey, executionRootPath) + .map(Path::toFile) + .orElseGet(() -> executionRootPathResolver.resolveExecutionRootPath(executionRootPath)) + ); + } else { + // legacy resolver, use `resolveToIncludesDirectories` and filter with `HeaderRootsTrimmer` + resolver = executionRootPath -> executionRootPathResolver + .resolveToIncludeDirectories(executionRootPath) + .stream() + .filter(configResolveData::isValidHeaderRoot); + } // transitiveIncludeDirectories are sourced from CcSkylarkApiProvider.include_directories compilationCtx.includes().stream() .flatMap(resolver) - .filter(configResolveData::isValidHeaderRoot) .map(File::getAbsolutePath) .forEach(compilerSwitchesBuilder::withIncludePath); @@ -284,7 +298,6 @@ private WorkspaceModel calculateConfigurations( // CcSkylarkApiProvider.quote_include_directories final var quoteIncludePaths = compilationCtx.quoteIncludes().stream() .flatMap(resolver) - .filter(configResolveData::isValidHeaderRoot) .map(File::getAbsolutePath) .collect(ImmutableList.toImmutableList()); quoteIncludePaths.forEach(compilerSwitchesBuilder::withQuoteIncludePath); @@ -295,7 +308,6 @@ private WorkspaceModel calculateConfigurations( // that get built by ClangUtils::addIncludeDirectories (it uses -I for system libraries). compilationCtx.systemIncludes().stream() .flatMap(resolver) - .filter(configResolveData::isValidHeaderRoot) .map(File::getAbsolutePath) .forEach(compilerSwitchesBuilder::withSystemIncludePath); diff --git a/cpp/src/com/google/idea/blaze/cpp/sync/HeaderCacheService.kt b/cpp/src/com/google/idea/blaze/cpp/sync/HeaderCacheService.kt new file mode 100644 index 00000000000..2ea9ffa6ea9 --- /dev/null +++ b/cpp/src/com/google/idea/blaze/cpp/sync/HeaderCacheService.kt @@ -0,0 +1,223 @@ +/* + * Copyright 2025 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.cpp.sync + +import com.google.idea.blaze.base.filecache.FileCache +import com.google.idea.blaze.base.ideinfo.ArtifactLocation +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo +import com.google.idea.blaze.base.ideinfo.TargetKey +import com.google.idea.blaze.base.logging.LoggedDirectoryProvider +import com.google.idea.blaze.base.model.BlazeProjectData +import com.google.idea.blaze.base.model.primitives.ExecutionRootPath +import com.google.idea.blaze.base.projectview.ProjectViewSet +import com.google.idea.blaze.base.scope.BlazeContext +import com.google.idea.blaze.base.scope.Scope +import com.google.idea.blaze.base.scope.scopes.TimingScope +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager +import com.google.idea.blaze.base.sync.SyncMode +import com.google.idea.blaze.base.sync.aspects.BlazeBuildOutputs +import com.google.idea.blaze.base.sync.data.BlazeDataStorage +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.getProjectDataPath +import com.intellij.openapi.util.io.NioFiles +import com.intellij.openapi.util.registry.Registry +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import com.intellij.util.concurrency.annotations.RequiresReadLockAbsence +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption +import java.util.* + +private val LOG = logger() + +private const val CACHE_DIRECTORY = "headerCache" + +@Service(Service.Level.PROJECT) +@Suppress("UnstableApiUsage") +class HeaderCacheService(private val project: Project) { + + companion object { + const val ENABLED_KEY: String = "bazel.cpp.header.cache.enabled" + + @JvmStatic + fun of(project: Project): HeaderCacheService = project.service() + + @JvmStatic + val enabled: Boolean get() = Registry.`is`(ENABLED_KEY) + } + + val cacheDirectory: Path by lazy { + // TODO: do we need a read action here? is the lazy initialization a race? + val importSettings = BlazeImportSettingsManager.getInstance(project).importSettings + + if (importSettings != null) { + BlazeDataStorage.getProjectDataDir(importSettings).toPath().resolve(CACHE_DIRECTORY) + } else { + project.getProjectDataPath(CACHE_DIRECTORY) + } + } + + // tracks headers which are actually stored in the cache + private val cacheTracker: MutableSet = mutableSetOf() + + private fun TargetKey.cacheDirectory(): Path { + // TODO: use different cache directories depending on the configuration + return cacheDirectory.resolve("default") + } + + @Synchronized + @RequiresReadLockAbsence + @RequiresBackgroundThread + @Throws(IOException::class) + private fun clear() { + cacheTracker.clear() + + if (Files.exists(cacheDirectory)) { + // On windows this could be replace with a rename and asynchronous delete for better performance. + NioFiles.deleteRecursively(cacheDirectory) + } + + LOG.trace("cleared cc includes cache") + } + + @Synchronized + @RequiresReadLockAbsence + @RequiresBackgroundThread + fun refresh(projectData: BlazeProjectData, nonInc: Boolean) { + if (nonInc) { + clear() + } else { + // only reset in memory data on incremental sync + cacheTracker.clear() + } + + for ((key, target) in projectData.targetMap.map()) { + refreshTarget(projectData, key, target) + } + } + + private fun refreshTarget(projectData: BlazeProjectData, key: TargetKey, target: TargetIdeInfo) { + val info = target.getcIdeInfo() ?: return + + val targetCacheDirectory = key.cacheDirectory() + + for (header in info.compilationContext().headers()) { + // check if the header is inside bazel-bin + if (!isInBazelBin(header)) continue + + // check if the header is already present in the cache + if (!cacheTracker.add(header.relativePath())) continue + + val path = targetCacheDirectory.resolve(header.relativePath()) + + try { + Files.createDirectories(path.parent) + + // NOTE: this also copies symlinked headers like in _virtual_includes + Files.newOutputStream( + path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, + ).use { dst -> + projectData.artifactLocationDecoder.resolveOutput(header).inputStream.use { src -> + src.transferTo(dst) + } + } + } catch (e: IOException) { + cacheTracker.remove(header.relativePath()) + LOG.warn("failed to copy generated header ${header.relativePath()} for ${key.label}", e) + } + } + } + + @Synchronized + fun resolve(target: TargetKey, executionRootPath: ExecutionRootPath): Optional { + val path = executionRootPath.path() + if (!isInBazelBin(path)) return Optional.empty() + + return Optional.of( + if (path.nameCount <= 3) { + target.cacheDirectory() + } else { + target.cacheDirectory().resolve(path.subpath(3, path.nameCount)) + } + ) + } + + private fun isInBazelBin(path: Path): Boolean { + return path.nameCount >= 3 + && path.getName(0).toString() == "bazel-out" + && path.getName(2).toString() == "bin"; + } + + private fun isInBazelBin(location: ArtifactLocation): Boolean { + return location.rootPath().isNotBlank() && isInBazelBin(Path.of(location.rootPath())) + } +} + +private class HeaderFileCache : FileCache { + + override fun getName(): String = "Header Cache" + + override fun onSync( + project: Project, + parentCtx: BlazeContext, + projectViewSet: ProjectViewSet, + projectData: BlazeProjectData, + oldProjectData: BlazeProjectData?, + syncMode: SyncMode, + ) { + if (!HeaderCacheService.enabled || !syncMode.involvesBlazeBuild()) return + LOG.trace("refresh requested onSync: $syncMode") + + Scope.push(parentCtx) { ctx -> + ctx.push(TimingScope(name, TimingScope.EventType.Other)) + HeaderCacheService.of(project).refresh(projectData, nonInc = syncMode == SyncMode.FULL) + } + } + + override fun refreshFiles( + project: Project, + context: BlazeContext, + buildOutputs: BlazeBuildOutputs, + ) { + if (!HeaderCacheService.enabled) return + LOG.trace("refresh files requested") + + val projectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData() ?: return + HeaderCacheService.of(project).refresh(projectData, nonInc = false) + } + + override fun initialize(project: Project) {} +} + +private class HeaderCacheLoggedDirectory : LoggedDirectoryProvider { + + override fun getLoggedDirectory(project: Project): Optional { + if (!HeaderCacheService.enabled) return Optional.empty() + + return Optional.of( + LoggedDirectoryProvider.LoggedDirectory.builder() + .setPath(HeaderCacheService.of(project).cacheDirectory) + .setOriginatingIdePart("CLwB Header Cache") + .setPurpose("Cache headers from the execution root") + .build() + ) + } +} diff --git a/proto/intellij_ide_info.proto b/proto/intellij_ide_info.proto index 515b4572a23..a33fdf6a0af 100644 --- a/proto/intellij_ide_info.proto +++ b/proto/intellij_ide_info.proto @@ -68,7 +68,7 @@ message CIdeInfo { // Information collected from CcInfo provider. message CompilationContext { - repeated ArtifactLocation direct_headers = 1; + repeated ArtifactLocation headers = 1; repeated string defines = 2; repeated string includes = 3; diff --git a/testing/src/com/google/idea/testing/headless/BazelVersionRule.java b/testing/src/com/google/idea/testing/headless/BazelVersionRule.java index e7f2baf2dfb..f2996c867f5 100644 --- a/testing/src/com/google/idea/testing/headless/BazelVersionRule.java +++ b/testing/src/com/google/idea/testing/headless/BazelVersionRule.java @@ -1,7 +1,9 @@ package com.google.idea.testing.headless; import com.google.idea.blaze.base.bazel.BazelVersion; +import com.intellij.util.system.OS; import java.util.Optional; +import org.jetbrains.annotations.Nullable; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -9,9 +11,15 @@ public class BazelVersionRule implements TestRule { private final BazelVersion min; + private final @Nullable OS os; - public BazelVersionRule(int major, int minor) { + public BazelVersionRule(@Nullable OS os, int major, int minor) { this.min = new BazelVersion(major, minor, 0); + this.os = os; + } + + public BazelVersionRule(int major, int minor) { + this(null, major, minor); } @Override @@ -21,6 +29,11 @@ public Statement apply(Statement base, Description description) { return Statements.fail("Could not read bazel version from BIT_BAZEL_VERSION"); } + // check if the rule applies for the current OS + if (os != null && !OS.CURRENT.equals(os)) { + return base; + } + if (version.get().isAtLeast(min)) { return base; } else {