diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml index e51c3fb4fc2..f7e616459be 100644 --- a/base/src/META-INF/blaze-base.xml +++ b/base/src/META-INF/blaze-base.xml @@ -387,7 +387,12 @@ + buildArtifactDirectories( @@ -109,7 +113,24 @@ public ImmutableList resolveToIncludeDirectories(ExecutionRootPath path) { } String firstPathComponent = getFirstPathComponent(path.getAbsoluteOrRelativeFile().getPath()); if (buildArtifactDirectories.contains(firstPathComponent)) { - return ImmutableList.of(path.getFileRootedAt(executionRoot)); + // Build artifacts accumulate under the execution root, independent of symlink settings + + if(VirtualIncludesHandler.useHeuristic() && VirtualIncludesHandler.containsVirtualInclude(path)) { + // Resolve virtual_include from execution root either to local or external workspace for correct code insight + ImmutableList resolved = ImmutableList.of(); + try { + resolved = VirtualIncludesHandler.resolveVirtualInclude(path, outputBase, + workspacePathResolver, targetMap); + } catch (Throwable throwable) { + LOG.error("Failed to resolve virtual includes for " + path, throwable); + } + + return resolved.isEmpty() + ? ImmutableList.of(path.getFileRootedAt(executionRoot)) + : resolved; + } else { + return ImmutableList.of(path.getFileRootedAt(executionRoot)); + } } if (firstPathComponent.equals(externalPrefix)) { // In external workspace // External workspaces accumulate under the output base. diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/VirtualIncludesHandler.java b/base/src/com/google/idea/blaze/base/sync/workspace/VirtualIncludesHandler.java index 6a6f324903c..259c2f53a15 100644 --- a/base/src/com/google/idea/blaze/base/sync/workspace/VirtualIncludesHandler.java +++ b/base/src/com/google/idea/blaze/base/sync/workspace/VirtualIncludesHandler.java @@ -2,22 +2,34 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.ideinfo.CIdeInfo; import com.google.idea.blaze.base.ideinfo.Dependency; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.TargetName; +import com.google.idea.blaze.base.model.primitives.WorkspacePath; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; + +import java.io.File; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.Stack; +import java.util.List; import java.util.concurrent.TimeUnit; + +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -34,8 +46,23 @@ * {@code bazel-out/.../bin/.../_virtual_includes/...} */ public class VirtualIncludesHandler { + static final Path VIRTUAL_INCLUDES_DIRECTORY = Path.of("_virtual_includes"); + private static final Logger LOG = Logger.getInstance(VirtualIncludesHandler.class); private static final String ABSOLUTE_LABEL_PREFIX = "//"; + private static final int EXTERNAL_DIRECTORY_IDX = 3; + private static final int EXTERNAL_WORKSPACE_NAME_IDX = 4; + private static final int WORKSPACE_PATH_START_FOR_EXTERNAL_WORKSPACE = 5; + private static final int WORKSPACE_PATH_START_FOR_LOCAL_WORKSPACE = 3; + private static final int ABSOLUTE_LABEL_PREFIX_LENGTH = ABSOLUTE_LABEL_PREFIX.length(); + + public static boolean useHeuristic() { + return Registry.is("bazel.sync.resolve.virtual.includes") && !useHints(); + } + + public static boolean useHints() { + return Registry.is("bazel.sync.collect.virtual.includes.hints"); + } private static Path trimStart(Path value, @Nullable Path prefix) { if (prefix == null || !value.startsWith(prefix)) { @@ -148,7 +175,8 @@ private static ImmutableList doCollectIncludeHints( continue; } - collectTargetIncludeHints(root, currentKey, currentIdeInfo, resolver, indicator, includes); + collectTargetIncludeHints(root, currentKey, currentIdeInfo, resolver, indicator, + includes); for (Dependency dep : currentIdeInfo.getDependencies()) { frontier.push(dep.getTargetKey()); @@ -169,10 +197,6 @@ public static ImmutableList collectIncludeHints( BlazeProjectData projectData, ExecutionRootPathResolver resolver, ProgressIndicator indicator) { - if (Registry.is("bazel.cpp.sync.workspace.collect.include.prefix.hints.disabled")) { - return ImmutableList.of(); - } - indicator.pushState(); indicator.setIndeterminate(true); indicator.setText2("Collecting include hints..."); @@ -188,4 +212,121 @@ public static ImmutableList collectIncludeHints( return result; } + + private static List splitExecutionPath(ExecutionRootPath executionRootPath) { + return Lists.newArrayList(executionRootPath.getAbsoluteOrRelativeFile().toPath().iterator()); + } + + static boolean containsVirtualInclude(ExecutionRootPath executionRootPath) { + return splitExecutionPath(executionRootPath).contains(VIRTUAL_INCLUDES_DIRECTORY); + } + + /** + * Resolves execution root path to {@code _virtual_includes} directory to the matching workspace + * location + * + * @return list of resolved paths if required information is obtained from execution root path and + * target data or empty list if resolution has failed + */ + @NotNull + static ImmutableList resolveVirtualInclude(ExecutionRootPath executionRootPath, + File externalWorkspacePath, + WorkspacePathResolver workspacePathResolver, + TargetMap targetMap) { + TargetKey key = null; + try { + key = guessTargetKey(executionRootPath); + } catch (IndexOutOfBoundsException exception) { + // report to intellij EA + LOG.error( + "Failed to detect target from execution root path: " + executionRootPath, + exception); + } + + if (key == null) { + return ImmutableList.of(); + } + + TargetIdeInfo info = targetMap.get(key); + if (info == null) { + return ImmutableList.of(); + } + + CIdeInfo cIdeInfo = info.getcIdeInfo(); + if (cIdeInfo == null) { + return ImmutableList.of(); + } + + if (!info.getcIdeInfo().getIncludePrefix().isEmpty()) { + LOG.debug( + "_virtual_includes cannot be handled for targets with include_prefix attribute"); + return ImmutableList.of(); + } + + String stripPrefix = info.getcIdeInfo().getStripIncludePrefix(); + if (!stripPrefix.isEmpty()) { + if (stripPrefix.endsWith("/")) { + stripPrefix = stripPrefix.substring(0, stripPrefix.length() - 1); + } + String externalWorkspaceName = key.getLabel().externalWorkspaceName(); + WorkspacePath stripPrefixWorkspacePath = stripPrefix.startsWith(ABSOLUTE_LABEL_PREFIX) ? + new WorkspacePath(stripPrefix.substring(ABSOLUTE_LABEL_PREFIX_LENGTH)) : + new WorkspacePath(key.getLabel().blazePackage(), stripPrefix); + if (externalWorkspaceName != null) { + ExecutionRootPath external = new ExecutionRootPath( + ExecutionRootPathResolver.externalPath.toPath() + .resolve(externalWorkspaceName) + .resolve(stripPrefixWorkspacePath.toString()).toString()); + + return ImmutableList.of(external.getFileRootedAt(externalWorkspacePath)); + } else { + return workspacePathResolver.resolveToIncludeDirectories( + stripPrefixWorkspacePath); + } + } else { + return ImmutableList.of(); + } + + } + + /** + * @throws IndexOutOfBoundsException if executionRootPath has _virtual_includes but its content is + * unexpected + */ + @Nullable + private static TargetKey guessTargetKey(ExecutionRootPath executionRootPath) { + List split = splitExecutionPath(executionRootPath); + int virtualIncludesIdx = split.indexOf(VIRTUAL_INCLUDES_DIRECTORY); + + if (virtualIncludesIdx > -1) { + String externalWorkspaceName = + split.get(EXTERNAL_DIRECTORY_IDX) + .equals(ExecutionRootPathResolver.externalPath.toPath()) + ? split.get(EXTERNAL_WORKSPACE_NAME_IDX).toString() + : null; + + int workspacePathStart = externalWorkspaceName != null + ? WORKSPACE_PATH_START_FOR_EXTERNAL_WORKSPACE + : WORKSPACE_PATH_START_FOR_LOCAL_WORKSPACE; + + List workspacePaths = (workspacePathStart <= virtualIncludesIdx) + ? split.subList(workspacePathStart, virtualIncludesIdx) + : Collections.emptyList(); + + String workspacePathString = + FileUtil.toSystemIndependentName( + workspacePaths.stream().reduce(Path.of(""), Path::resolve).toString()); + + TargetName target = TargetName.create(split.get(virtualIncludesIdx + 1).toString()); + WorkspacePath workspacePath = WorkspacePath.createIfValid(workspacePathString); + if (workspacePath == null) { + return null; + } + + return TargetKey.forPlainTarget( + Label.create(externalWorkspaceName, workspacePath, target)); + } else { + return null; + } + } } diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ExecutionRootPathResolverTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ExecutionRootPathResolverTest.java index 196dba37b41..f05722dcd7a 100644 --- a/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ExecutionRootPathResolverTest.java +++ b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ExecutionRootPathResolverTest.java @@ -108,9 +108,37 @@ private static TargetIdeInfo getTargetIdeInfo(TargetName targetName) { .build(); } + @NotNull + private static TargetMap getTargetMap() { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (String workspaceName : WORKSPACES) { + for (WorkspacePath workspacePath : WORKSPACE_PATHS) { + for (TargetName targetName : TARGET_NAMES) { + builder.put( + TargetKey.forPlainTarget(Label.create(workspaceName, workspacePath, targetName)), + getTargetIdeInfo(targetName)); + } + } + + builder.put( + TargetKey.forPlainTarget( + Label.create(workspaceName, ABSOLUTE_STRIP_PREFIX_WORKSPACE_PATH, TARGET_WITH_ABSOLUTE_STRIP_PREFIX)), + getTargetIdeInfo(TARGET_WITH_ABSOLUTE_STRIP_PREFIX)); + } + + builder.put( + TargetKey.forPlainTarget( + Label.create(WORKSPACES.get(0), WORKSPACE_PATHS.get(0), TARGET_WITH_INCLUDE_PREFIX)), + getTargetIdeInfo(TARGET_WITH_INCLUDE_PREFIX)); + + return new TargetMap(builder.build()); + } + @Override protected void initTest(Container applicationServices, Container projectServices) { Registry.get("bazel.sync.resolve.virtual.includes").setValue(true); + Registry.get("bazel.sync.collect.virtual.includes.hints").setValue(false); pathResolver = new ExecutionRootPathResolver( @@ -118,7 +146,8 @@ protected void initTest(Container applicationServices, Container projectServices WORKSPACE_ROOT, new File(EXECUTION_ROOT), new File(OUTPUT_BASE), - new WorkspacePathResolverImpl(WORKSPACE_ROOT)); + new WorkspacePathResolverImpl(WORKSPACE_ROOT), + getTargetMap()); } @Test @@ -161,6 +190,99 @@ public void testIllegalWorkspacePaths() { assertThat(files).isEmpty(); } + @Test + public void testVirtualIncludes() { + for (String workspaceName : WORKSPACES) { + for (WorkspacePath workspacePath : WORKSPACE_PATHS) { + for (TargetName targetName : TARGET_NAMES) { + String workspaceNameString = workspaceName != null ? workspaceName : ""; + + ExecutionRootPath generatedPath = new ExecutionRootPath(Path.of( + "bazel-out/k8-fastbuild/bin", + (workspaceName == null ? "" : ExecutionRootPathResolver.externalPath.getPath()), + workspaceNameString, + workspacePath.toString(), + VirtualIncludesHandler.VIRTUAL_INCLUDES_DIRECTORY.toString(), + targetName.toString()).toFile()); + + ImmutableList files = + pathResolver.resolveToIncludeDirectories(generatedPath); + + String stripPrefix = getStripPrefix(targetName); + if (stripPrefix.endsWith("/")) { + stripPrefix = stripPrefix.substring(0, stripPrefix.length() - 1); + } + String expectedPath = Path.of(workspaceNameString, + workspacePath.toString(), + stripPrefix).toString(); + + if (workspaceName != null) { + // check external workspace + assertThat(files).containsExactly( + Path.of(OUTPUT_BASE, ExecutionRootPathResolver.externalPath.getPath(), expectedPath) + .toFile()); + } else { + // check local + assertThat(files).containsExactly( + WORKSPACE_ROOT.fileForPath(new WorkspacePath(expectedPath))); + } + } + } + } + } + + @Test + public void testVirtualIncludesWithAbsoluteStripPrefix() { + for (String workspaceName : WORKSPACES) { + String workspaceNameString = workspaceName != null ? workspaceName : ""; + + ExecutionRootPath generatedPath = new ExecutionRootPath(Path.of( + "bazel-out/k8-fastbuild/bin", + (workspaceName == null ? "" : ExecutionRootPathResolver.externalPath.getPath()), + workspaceNameString, + ABSOLUTE_STRIP_PREFIX_WORKSPACE_PATH.toString(), + VirtualIncludesHandler.VIRTUAL_INCLUDES_DIRECTORY.toString(), + TARGET_WITH_ABSOLUTE_STRIP_PREFIX.toString()).toFile()); + + ImmutableList files = + pathResolver.resolveToIncludeDirectories(generatedPath); + + String expectedPath = Path.of(workspaceNameString, + ABSOLUTE_STRIP_PREFIX_PATH).toString(); + + if (workspaceName != null) { + // check external workspace + assertThat(files).containsExactly( + Path.of(OUTPUT_BASE, ExecutionRootPathResolver.externalPath.getPath(), expectedPath) + .toFile()); + } else { + // check local + assertThat(files).containsExactly( + WORKSPACE_ROOT.fileForPath(new WorkspacePath(expectedPath))); + } + } + } + + @Test + public void testVirtualIncludesWithIncludePrefix() { + File fileToBeResolved = Path.of( + "bazel-out/k8-fastbuild/bin", + WORKSPACE_PATHS.get(0).toString(), + VirtualIncludesHandler.VIRTUAL_INCLUDES_DIRECTORY.toString(), + TARGET_WITH_INCLUDE_PREFIX.toString(), + INCLUDE_PREFIX).toFile(); + + ExecutionRootPath generatedPath = new ExecutionRootPath(fileToBeResolved); + + ImmutableList files = + pathResolver.resolveToIncludeDirectories(generatedPath); + + // check that include path for target with "include_prefix" attribute is resolved to execution root + assertThat(files).containsExactly( + new File(EXECUTION_ROOT, fileToBeResolved.toString())); + } + + @Test public void testExternalWorkspaceSymlinkToProject() throws IOException { Path expectedPath = Path.of(WORKSPACE_ROOT.toString(), "guava", "src"); diff --git a/cpp/src/META-INF/blaze-cpp.xml b/cpp/src/META-INF/blaze-cpp.xml index 557eb52caba..5aaa18b0f7d 100644 --- a/cpp/src/META-INF/blaze-cpp.xml +++ b/cpp/src/META-INF/blaze-cpp.xml @@ -60,7 +60,6 @@ - "-I" + file.getAbsolutePath()) .collect(toImmutableList()); - Path rootPath = workspaceRoot.directory().toPath(); - ImmutableList includePrefixHints = VirtualIncludesHandler.collectIncludeHints(rootPath, - targetKey, blazeProjectData, executionRootPathResolver, indicator); + ImmutableList includePrefixHints = ImmutableList.of(); + if (VirtualIncludesHandler.useHints()) { + Path rootPath = workspaceRoot.directory().toPath(); + includePrefixHints = VirtualIncludesHandler.collectIncludeHints(rootPath, targetKey, blazeProjectData, + executionRootPathResolver, indicator); + } for (VirtualFile vf : resolveConfiguration.getSources(targetKey)) { OCLanguageKind kind = resolveConfiguration.getDeclaredLanguageKind(vf); diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java index 0a636cd99f8..2436c908526 100644 --- a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java +++ b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java @@ -89,7 +89,8 @@ public BlazeConfigurationResolverResult update( WorkspaceRoot.fromProject(project), blazeProjectData.getBlazeInfo().getExecutionRoot(), blazeProjectData.getBlazeInfo().getOutputBase(), - blazeProjectData.getWorkspacePathResolver()); + blazeProjectData.getWorkspacePathResolver(), + blazeProjectData.getTargetMap()); ImmutableMap toolchainLookupMap = BlazeConfigurationToolchainResolver.buildToolchainLookupMap( context, blazeProjectData.getTargetMap()); diff --git a/cpp/src/com/google/idea/blaze/cpp/IncludeRootFlagsProcessor.java b/cpp/src/com/google/idea/blaze/cpp/IncludeRootFlagsProcessor.java index 7362da39602..609f3c03537 100644 --- a/cpp/src/com/google/idea/blaze/cpp/IncludeRootFlagsProcessor.java +++ b/cpp/src/com/google/idea/blaze/cpp/IncludeRootFlagsProcessor.java @@ -48,7 +48,8 @@ public Optional getProcessor(Project project) { workspaceRoot, projectData.getBlazeInfo().getExecutionRoot(), projectData.getBlazeInfo().getOutputBase(), - projectData.getWorkspacePathResolver()); + projectData.getWorkspacePathResolver(), + projectData.getTargetMap()); return Optional.of(new IncludeRootFlagsProcessor(executionRootPathResolver)); } } diff --git a/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppResolvingTestCase.java b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppResolvingTestCase.java index edbf7d4f38d..5f6ece16803 100644 --- a/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppResolvingTestCase.java +++ b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppResolvingTestCase.java @@ -102,7 +102,8 @@ private ExecutionRootPathResolver executionRootPathResolver(BlazeProjectData pro workspaceRoot, projectData.getBlazeInfo().getExecutionRoot(), projectData.getBlazeInfo().getOutputBase(), - projectData.getWorkspacePathResolver()); + projectData.getWorkspacePathResolver(), + projectData.getTargetMap()); } private HeadersSearchRoot searchRootFromExecRoot( diff --git a/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java index f18bf6a85f4..919af7e0f72 100644 --- a/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java +++ b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java @@ -56,6 +56,7 @@ protected void initTest(Container applicationServices, Container projectServices applicationServices.register(ExperimentService.class, new MockExperimentService()); Registry.get("bazel.sync.resolve.virtual.includes").setValue(true); + Registry.get("bazel.sync.collect.virtual.includes.hints").setValue(true); applicationServices.register(QuerySyncSettings.class, new QuerySyncSettings());