Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature flags for resolve virtual includes using heuristics or clangd #6387

Merged
merged 7 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion base/src/META-INF/blaze-base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,14 @@
<registryKey defaultValue="false" description="Allow to have optional imports in project view" key="bazel.projectview.optional.imports"/>
<registryKey
key="bazel.sync.resolve.virtual.includes"
description="Apply heuristics to detect correct location of headers which are accessed from _virtual_includes during build"
description="Apply heuristics to detect correct location of headers which are accessed from _virtual_includes during build. (requires re-sync)"
defaultValue="true"
/>
<registryKey
key="bazel.sync.clangd.virtual.includes"
ujohnny marked this conversation as resolved.
Show resolved Hide resolved
description="Use clangd to resolve virtual includes. Only available with legacy engine. (requires re-sync)"
defaultValue="false"
ujohnny marked this conversation as resolved.
Show resolved Hide resolved
/>
<registryKey defaultValue="true"
description="Enables opening the project view file the first time the project is imported"
key="bazel.project.import.open_project_view"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,20 @@ public class ExecutionRootPathResolver {
private final File executionRoot;
private final File outputBase;
private final WorkspacePathResolver workspacePathResolver;
private final TargetMap targetMap;

public ExecutionRootPathResolver(
BuildSystemProvider buildSystemProvider,
WorkspaceRoot workspaceRoot,
File executionRoot,
File outputBase,
WorkspacePathResolver workspacePathResolver) {
WorkspacePathResolver workspacePathResolver,
TargetMap targetMap) {
this.buildArtifactDirectories = buildArtifactDirectories(buildSystemProvider, workspaceRoot);
this.executionRoot = executionRoot;
this.outputBase = outputBase;
this.workspacePathResolver = workspacePathResolver;
this.targetMap = targetMap;
}

@Nullable
Expand All @@ -72,7 +75,8 @@ public static ExecutionRootPathResolver fromProject(Project project) {
WorkspaceRoot.fromProject(project),
projectData.getBlazeInfo().getExecutionRoot(),
projectData.getBlazeInfo().getOutputBase(),
projectData.getWorkspacePathResolver());
projectData.getWorkspacePathResolver(),
projectData.getTargetMap());
}

private static ImmutableList<String> buildArtifactDirectories(
Expand Down Expand Up @@ -109,7 +113,24 @@ public ImmutableList<File> 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<File> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@

import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
ujohnny marked this conversation as resolved.
Show resolved Hide resolved
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;

/**
Expand All @@ -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") && !useClangd();
}

public static boolean useClangd() {
return Registry.is("bazel.sync.clangd.virtual.includes");
}

private static Path trimStart(Path value, @Nullable Path prefix) {
if (prefix == null || !value.startsWith(prefix)) {
Expand Down Expand Up @@ -64,7 +91,7 @@ private static Path pathOf(@Nullable String value) {
}
}

private static void collectTargetIncludeHints(
private static void collectClangdTargetIncludeHints(
Path root,
TargetKey targetKey,
TargetIdeInfo targetIdeInfo,
Expand Down Expand Up @@ -123,7 +150,7 @@ private static void collectTargetIncludeHints(
}
}

private static ImmutableList<String> doCollectIncludeHints(
private static ImmutableList<String> doCollectClangdIncludeHints(
Path root,
TargetKey targetKey,
BlazeProjectData projectData,
Expand All @@ -148,7 +175,8 @@ private static ImmutableList<String> doCollectIncludeHints(
continue;
}

collectTargetIncludeHints(root, currentKey, currentIdeInfo, resolver, indicator, includes);
collectClangdTargetIncludeHints(root, currentKey, currentIdeInfo, resolver, indicator,
includes);

for (Dependency dep : currentIdeInfo.getDependencies()) {
frontier.push(dep.getTargetKey());
Expand All @@ -163,7 +191,7 @@ private static ImmutableList<String> doCollectIncludeHints(
* mappings. The mappings are used to resolve headers which use an 'include_prefix' or a
* 'strip_include_prefix'.
*/
public static ImmutableList<String> collectIncludeHints(
public static ImmutableList<String> collectClangdIncludeHints(
Path projectRoot,
TargetKey targetKey,
BlazeProjectData projectData,
Expand All @@ -178,7 +206,7 @@ public static ImmutableList<String> collectIncludeHints(
indicator.setText2("Collecting include hints...");

Stopwatch stopwatch = Stopwatch.createStarted();
ImmutableList<String> result = doCollectIncludeHints(projectRoot, targetKey, projectData,
ImmutableList<String> result = doCollectClangdIncludeHints(projectRoot, targetKey, projectData,
resolver, indicator);

long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
Expand All @@ -188,4 +216,121 @@ public static ImmutableList<String> collectIncludeHints(

return result;
}

private static List<Path> 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<File> 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 (key.getLabel().externalWorkspaceName() != null) {
ujohnny marked this conversation as resolved.
Show resolved Hide resolved
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<Path> 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<Path> 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;
}
}
}
Loading
Loading