Move output artifact pretty-printing to a helper class
Pre-resolve all the convenience symlink to avoid doing a lot of readlink
calls for null builds with a lot of output files.
For an example target with ~15,000 output files, this reduces null build
time by about a third.
PiperOrigin-RevId: 215373507
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java
index 1335887..82879e8 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResultPrinter.java
@@ -66,6 +66,15 @@
// problem where the summary message and the exit code disagree. The logic
// here is already complex.
+ String productName = env.getRuntime().getProductName();
+ PathPrettyPrinter prettyPrinter =
+ OutputDirectoryLinksUtils.getPathPrettyPrinter(
+ request.getBuildOptions().getSymlinkPrefix(productName),
+ productName,
+ env.getWorkspace(),
+ request.getBuildOptions().printWorkspaceInOutputPathsIfNeeded
+ ? env.getWorkingDirectory()
+ : env.getWorkspace());
OutErr outErr = request.getOutErr();
Collection<ConfiguredTarget> targetsToPrint = filterTargetsToPrint(configuredTargets);
Collection<AspectValue> aspectsToPrint = filterAspectsToPrint(aspects);
@@ -100,7 +109,7 @@
outErr.printErr("Target " + label + " up-to-date:\n");
headerFlag = false;
}
- outErr.printErrLn(formatArtifactForShowResults(artifact, request));
+ outErr.printErrLn(formatArtifactForShowResults(prettyPrinter, artifact));
}
}
if (headerFlag) {
@@ -113,21 +122,10 @@
// For failed compilation, it is still useful to examine temp artifacts,
// (ie, preprocessed and assembler files).
OutputGroupInfo topLevelProvider = OutputGroupInfo.get(target);
- String productName = env.getRuntime().getProductName();
if (topLevelProvider != null) {
for (Artifact temp : topLevelProvider.getOutputGroup(OutputGroupInfo.TEMP_FILES)) {
if (temp.getPath().exists()) {
- outErr.printErrLn(
- " See temp at "
- + OutputDirectoryLinksUtils.getPrettyPath(
- temp.getPath(),
- env.getWorkspaceName(),
- env.getWorkspace(),
- request.getBuildOptions().printWorkspaceInOutputPathsIfNeeded
- ? env.getWorkingDirectory()
- : env.getWorkspace(),
- request.getBuildOptions().getSymlinkPrefix(productName),
- productName));
+ outErr.printErrLn(" See temp at " + prettyPrinter.getPrettyPath(temp.getPath()));
}
}
}
@@ -158,7 +156,7 @@
headerFlag = false;
}
if (shouldPrint(importantArtifact)) {
- outErr.printErrLn(formatArtifactForShowResults(importantArtifact, request));
+ outErr.printErrLn(formatArtifactForShowResults(prettyPrinter, importantArtifact));
}
}
if (headerFlag) {
@@ -182,18 +180,8 @@
return !artifact.isSourceArtifact() && !artifact.isMiddlemanArtifact();
}
- private String formatArtifactForShowResults(Artifact artifact, BuildRequest request) {
- String productName = env.getRuntime().getProductName();
- return " "
- + OutputDirectoryLinksUtils.getPrettyPath(
- artifact.getPath(),
- env.getWorkspaceName(),
- env.getWorkspace(),
- request.getBuildOptions().printWorkspaceInOutputPathsIfNeeded
- ? env.getWorkingDirectory()
- : env.getWorkspace(),
- request.getBuildOptions().getSymlinkPrefix(productName),
- productName);
+ private String formatArtifactForShowResults(PathPrettyPrinter prettyPrinter, Artifact artifact) {
+ return " " + prettyPrinter.getPrettyPath(artifact.getPath());
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java b/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java
index 1a71624..70e7b8e 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java
@@ -39,7 +39,7 @@
* Static utilities for managing output directory symlinks.
*/
public class OutputDirectoryLinksUtils {
- private static interface SymlinkDefinition {
+ static interface SymlinkDefinition {
String getLinkName(String symlinkPrefix, String productName, String workspaceBaseName);
Optional<Path> getLinkPath(
@@ -218,60 +218,10 @@
}
}
- /**
- * Returns a convenient path to the specified file, relativizing it and using output-dir symlinks
- * if possible. Otherwise, return the absolute path.
- *
- * <p>This method must be called after the symlinks are created at the end of a build. If called
- * before, the pretty path may be incorrect if the symlinks end up pointing somewhere new.
- */
- public static PathFragment getPrettyPath(
- Path file,
- String workspaceName,
- Path workspaceDirectory,
- Path workingDirectory,
- String symlinkPrefix,
- String productName) {
- if (NO_CREATE_SYMLINKS_PREFIX.equals(symlinkPrefix)) {
- return file.asFragment();
- }
-
- String workspaceBaseName = workspaceDirectory.getBaseName();
- for (SymlinkDefinition link : LINK_DEFINITIONS) {
- PathFragment result =
- relativize(
- file,
- workspaceDirectory,
- workingDirectory,
- link.getLinkName(symlinkPrefix, productName, workspaceBaseName));
- if (result != null) {
- return result;
- }
- }
-
- return file.asFragment();
- }
-
- // Helper to getPrettyPath. Returns file, relativized w.r.t. the referent of
- // "linkname", or null if it was a not a child.
- private static PathFragment relativize(
- Path file, Path workspaceDirectory, Path workingDirectory, String linkname) {
- PathFragment link = PathFragment.create(linkname);
- try {
- Path dir = workspaceDirectory.getRelative(link);
- PathFragment levelOneLinkTarget = dir.readSymbolicLink();
- if (levelOneLinkTarget.isAbsolute() &&
- file.startsWith(dir = file.getRelative(levelOneLinkTarget))) {
- PathFragment outputLink =
- workingDirectory.equals(workspaceDirectory)
- ? link
- : workspaceDirectory.getRelative(link).asFragment();
- return outputLink.getRelative(file.relativeTo(dir));
- }
- } catch (IOException e) {
- /* ignore */
- }
- return null;
+ public static PathPrettyPrinter getPathPrettyPrinter(
+ String symlinkPrefix, String productName, Path workspaceDirectory, Path workingDirectory) {
+ return new PathPrettyPrinter(
+ LINK_DEFINITIONS, symlinkPrefix, productName, workspaceDirectory, workingDirectory);
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/PathPrettyPrinter.java b/src/main/java/com/google/devtools/build/lib/buildtool/PathPrettyPrinter.java
new file mode 100644
index 0000000..8d94c87
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/PathPrettyPrinter.java
@@ -0,0 +1,99 @@
+// Copyright 2018 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.devtools.build.lib.buildtool;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.buildtool.OutputDirectoryLinksUtils.SymlinkDefinition;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** Uses information about the convenience symlinks to print shorter paths for output artifacts. */
+public final class PathPrettyPrinter {
+ private static final String NO_CREATE_SYMLINKS_PREFIX = "/";
+
+ private final Map<PathFragment, Path> resolvedSymlinks;
+ private final String symlinkPrefix;
+ private final String productName;
+ private final Path workspaceDirectory;
+ private final Path workingDirectory;
+
+ /**
+ * Creates a path pretty printer, immediately resolving the symlink definitions by reading the
+ * current symlinks _from disk_.
+ */
+ PathPrettyPrinter(
+ ImmutableList<SymlinkDefinition> symlinkDefinitions,
+ String symlinkPrefix,
+ String productName,
+ Path workspaceDirectory,
+ Path workingDirectory) {
+ this.symlinkPrefix = symlinkPrefix;
+ this.productName = productName;
+ this.workspaceDirectory = workspaceDirectory;
+ this.workingDirectory = workingDirectory;
+ this.resolvedSymlinks = resolve(symlinkDefinitions);
+ }
+
+ private Map<PathFragment, Path> resolve(ImmutableList<SymlinkDefinition> symlinkDefinitions) {
+ Map<PathFragment, Path> result = new LinkedHashMap<>();
+ String workspaceBaseName = workspaceDirectory.getBaseName();
+ for (SymlinkDefinition link : symlinkDefinitions) {
+ String linkName = link.getLinkName(symlinkPrefix, productName, workspaceBaseName);
+ PathFragment linkFragment = PathFragment.create(linkName);
+ Path dir = workspaceDirectory.getRelative(linkFragment);
+ try {
+ PathFragment levelOneLinkTarget = dir.readSymbolicLink();
+ if (levelOneLinkTarget.isAbsolute()) {
+ result.put(linkFragment, dir.getRelative(levelOneLinkTarget));
+ }
+ } catch (IOException ignored) {
+ // We don't guarantee that the convenience symlinks exist - e.g., we might be running in a
+ // readonly directory. We silently fall back to printing the full path in that case. As an
+ // alternative, we could capture that information when we create the symlinks and pass that
+ // here instead of reading files back from local disk.
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a convenient path to the specified file, relativizing it and using output-dir symlinks
+ * if possible. Otherwise, return the absolute path.
+ *
+ * <p>This method must be called after the symlinks are created at the end of a build. If called
+ * before, the pretty path may be incorrect if the symlinks end up pointing somewhere new.
+ */
+ public PathFragment getPrettyPath(Path file) {
+ if (NO_CREATE_SYMLINKS_PREFIX.equals(symlinkPrefix)) {
+ return file.asFragment();
+ }
+
+ for (Map.Entry<PathFragment, Path> e : resolvedSymlinks.entrySet()) {
+ PathFragment linkFragment = e.getKey();
+ Path linkTarget = e.getValue();
+ if (file.startsWith(linkTarget)) {
+ PathFragment outputLink =
+ workingDirectory.equals(workspaceDirectory)
+ ? linkFragment
+ : workspaceDirectory.getRelative(linkFragment).asFragment();
+ return outputLink.getRelative(file.relativeTo(linkTarget));
+ }
+ }
+
+ return file.asFragment();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
index 47fac97..22b38d9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
@@ -39,6 +39,7 @@
import com.google.devtools.build.lib.buildtool.BuildResult;
import com.google.devtools.build.lib.buildtool.BuildTool;
import com.google.devtools.build.lib.buildtool.OutputDirectoryLinksUtils;
+import com.google.devtools.build.lib.buildtool.PathPrettyPrinter;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.exec.ExecutionOptions;
@@ -188,16 +189,15 @@
BuildRequestOptions requestOptions = env.getOptions().getOptions(BuildRequestOptions.class);
PathFragment executablePath = executable.getPath().asFragment();
- PathFragment prettyExecutablePath =
- OutputDirectoryLinksUtils.getPrettyPath(
- executable.getPath(),
- env.getWorkspaceName(),
+ PathPrettyPrinter prettyPrinter =
+ OutputDirectoryLinksUtils.getPathPrettyPrinter(
+ requestOptions.getSymlinkPrefix(productName),
+ productName,
env.getWorkspace(),
requestOptions.printWorkspaceInOutputPathsIfNeeded
? env.getWorkingDirectory()
- : env.getWorkspace(),
- requestOptions.getSymlinkPrefix(productName),
- productName);
+ : env.getWorkspace());
+ PathFragment prettyExecutablePath = prettyPrinter.getPrettyPath(executable.getPath());
RunUnder runUnder = env.getOptions().getOptions(BuildConfiguration.Options.class).runUnder;
// Insert the command prefix specified by the "--run_under=<command-prefix>" option