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());