Strip the execution root from target paths in FilesetOutputSymlink.
RELNOTES: None.
PiperOrigin-RevId: 215754628
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FilesetOutputSymlink.java b/src/main/java/com/google/devtools/build/lib/actions/FilesetOutputSymlink.java
index fd89e58..73a9499 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FilesetOutputSymlink.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FilesetOutputSymlink.java
@@ -26,8 +26,16 @@
public abstract PathFragment getName();
/**
- * Target of the symlink. This may be relative to the target's location if the target itself is a
- * relative symlink. We can override it by using FilesetEntry.symlinks = 'dereference'.
+ * Target of the symlink.
+ *
+ * <p>This path is one of the following:
+ *
+ * <ol>
+ * <li>Relative to the execution root, in which case {@link #isRelativeToExecRoot} will return
+ * {@code true}.
+ * <li>An absolute path to the source tree.
+ * <li>A relative path that should be considered relative to the link.
+ * </ol>
*/
public abstract PathFragment getTargetPath();
@@ -41,8 +49,21 @@
/** true if the target is a generated artifact */
public abstract boolean isGeneratedTarget();
+ /** Returns {@code true} if this symlink is relative to the execution root. */
+ public abstract boolean isRelativeToExecRoot();
+
+ /**
+ * Reconstitutes the original target path of this symlink.
+ *
+ * <p>This method essentially performs the inverse of what is done in {@link #create}. If the
+ * execution root was stripped originally, it is re-prepended.
+ */
+ public final PathFragment reconstituteTargetPath(PathFragment execRoot) {
+ return isRelativeToExecRoot() ? execRoot.getRelative(getTargetPath()) : getTargetPath();
+ }
+
@Override
- public String toString() {
+ public final String toString() {
if (getMetadata() == STRIPPED_METADATA) {
return String.format(
"FilesetOutputSymlink(%s -> %s)",
@@ -55,19 +76,60 @@
}
@VisibleForTesting
- public static FilesetOutputSymlink createForTesting(PathFragment name, PathFragment target) {
- return new AutoValue_FilesetOutputSymlink(name, target, STRIPPED_METADATA, false);
+ public static FilesetOutputSymlink createForTesting(
+ PathFragment name, PathFragment target, PathFragment execRoot) {
+ return create(name, target, STRIPPED_METADATA, false, execRoot);
+ }
+
+ @VisibleForTesting
+ public static FilesetOutputSymlink createAlreadyRelativizedForTesting(
+ PathFragment name, PathFragment target, boolean isRelativeToExecRoot) {
+ return createAlreadyRelativized(name, target, STRIPPED_METADATA, false, isRelativeToExecRoot);
}
/**
+ * Creates a {@link FilesetOutputSymlink}.
+ *
+ * <p>To facilitate cross-device sharing, {@code target} will have the machine-local {@code
+ * execRoot} stripped if necessary. If this happens, {@link #isRelativeToExecRoot} will return
+ * {@code true}.
+ *
* @param name relative path under the Fileset's output directory, including FilesetEntry.destdir
* with and FilesetEntry.strip_prefix applied (if applicable)
* @param target relative or absolute value of the link
* @param metadata metadata corresponding to the target.
* @param isGeneratedTarget true if the target is generated.
+ * @param execRoot the execution root
*/
public static FilesetOutputSymlink create(
- PathFragment name, PathFragment target, Object metadata, boolean isGeneratedTarget) {
- return new AutoValue_FilesetOutputSymlink(name, target, metadata, isGeneratedTarget);
+ PathFragment name,
+ PathFragment target,
+ Object metadata,
+ boolean isGeneratedTarget,
+ PathFragment execRoot) {
+ boolean isRelativeToExecRoot = false;
+ // Check if the target is under the execution root. This is not always the case because the
+ // target may point to a source artifact or it may point to another symlink, in which case the
+ // target path is already relative.
+ if (target.startsWith(execRoot)) {
+ target = target.relativeTo(execRoot);
+ isRelativeToExecRoot = true;
+ }
+ return createAlreadyRelativized(
+ name, target, metadata, isGeneratedTarget, isRelativeToExecRoot);
+ }
+
+ /**
+ * Same as {@link #create}, except assumes that {@code target} already had the execution root
+ * stripped if necessary.
+ */
+ public static FilesetOutputSymlink createAlreadyRelativized(
+ PathFragment name,
+ PathFragment target,
+ Object metadata,
+ boolean isGeneratedTarget,
+ boolean isRelativeToExecRoot) {
+ return new AutoValue_FilesetOutputSymlink(
+ name, target, metadata, isGeneratedTarget, isRelativeToExecRoot);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
index 133aaf3..53bfb09 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
@@ -22,6 +22,7 @@
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
+import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
import com.google.devtools.build.lib.actions.CommandLineItem;
@@ -358,7 +359,8 @@
FilesetManifest.constructFilesetManifest(
artifactExpander.getFileset(fileset),
fileset.getExecPath(),
- RelativeSymlinkBehavior.IGNORE);
+ RelativeSymlinkBehavior.IGNORE,
+ extractExecRoot(fileset));
for (PathFragment relativePath : filesetManifest.getEntries().keySet()) {
expandedValues.add(new FilesetSymlinkFile(fileset, relativePath));
}
@@ -367,6 +369,14 @@
}
}
+ // TODO(b/117267351): Pass in the exec root more cleanly, or put relative paths in the manifest.
+ private static PathFragment extractExecRoot(Artifact fileset) {
+ Preconditions.checkArgument(!fileset.isSourceArtifact(), fileset);
+ ArtifactRoot root = fileset.getRoot();
+ PathFragment rootFrag = root.getRoot().asPath().asFragment();
+ return rootFrag.subFragment(0, rootFrag.segmentCount() - root.getExecPath().segmentCount());
+ }
+
private int addToFingerprint(
List<Object> arguments,
int argi,
diff --git a/src/main/java/com/google/devtools/build/lib/exec/FilesetManifest.java b/src/main/java/com/google/devtools/build/lib/exec/FilesetManifest.java
index 40e3842..220beec 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/FilesetManifest.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/FilesetManifest.java
@@ -16,6 +16,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.LineProcessor;
import com.google.devtools.build.lib.actions.FileArtifactValue;
@@ -75,16 +76,17 @@
public static FilesetManifest constructFilesetManifest(
List<FilesetOutputSymlink> outputSymlinks,
PathFragment targetPrefix,
- RelativeSymlinkBehavior relSymlinkbehavior)
+ RelativeSymlinkBehavior relSymlinkBehavior,
+ PathFragment execRoot)
throws IOException {
LinkedHashMap<PathFragment, String> entries = new LinkedHashMap<>();
Map<PathFragment, String> relativeLinks = new HashMap<>();
Map<String, FileArtifactValue> artifactValues = new HashMap<>();
for (FilesetOutputSymlink outputSymlink : outputSymlinks) {
PathFragment fullLocation = targetPrefix.getRelative(outputSymlink.getName());
- String artifact = outputSymlink.getTargetPath().getPathString();
- artifact = artifact.isEmpty() ? null : artifact;
- addSymlinkEntry(artifact, fullLocation, relSymlinkbehavior, entries, relativeLinks);
+ PathFragment linkTarget = outputSymlink.reconstituteTargetPath(execRoot);
+ String artifact = Strings.emptyToNull(linkTarget.getPathString());
+ addSymlinkEntry(artifact, fullLocation, relSymlinkBehavior, entries, relativeLinks);
if (outputSymlink.getMetadata() instanceof FileArtifactValue) {
artifactValues.put(artifact, (FileArtifactValue) outputSymlink.getMetadata());
}
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java b/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java
index 113ff88..25d4c81 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java
@@ -182,7 +182,7 @@
ImmutableList<FilesetOutputSymlink> outputSymlinks = filesetMappings.get(fileset);
FilesetManifest filesetManifest =
FilesetManifest.constructFilesetManifest(
- outputSymlinks, fileset.getExecPath(), relSymlinkBehavior);
+ outputSymlinks, fileset.getExecPath(), relSymlinkBehavior, execRoot.asFragment());
for (Map.Entry<PathFragment, String> mapping : filesetManifest.getEntries().entrySet()) {
String value = mapping.getValue();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
index aa155b1..5312ca9 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
@@ -47,6 +47,12 @@
}
}
+ private final PathFragment execRoot;
+
+ public FilesetEntryFunction(PathFragment execRoot) {
+ this.execRoot = execRoot;
+ }
+
@Override
public SkyValue compute(SkyKey key, Environment env)
throws FilesetEntryFunctionException, InterruptedException {
@@ -180,7 +186,7 @@
}
/** Stores an output symlink unless it would overwrite an existing one. */
- private static void maybeStoreSymlink(
+ private void maybeStoreSymlink(
PathFragment linkName,
PathFragment linkTarget,
Object metadata,
@@ -190,7 +196,8 @@
linkName = destPath.getRelative(linkName);
if (!result.containsKey(linkName)) {
result.put(
- linkName, FilesetOutputSymlink.create(linkName, linkTarget, metadata, isGenerated));
+ linkName,
+ FilesetOutputSymlink.create(linkName, linkTarget, metadata, isGenerated, execRoot));
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 300cd4e..bf7ae1d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -552,7 +552,9 @@
this.actionExecutionFunction = actionExecutionFunction;
map.put(SkyFunctions.RECURSIVE_FILESYSTEM_TRAVERSAL,
new RecursiveFilesystemTraversalFunction());
- map.put(SkyFunctions.FILESET_ENTRY, new FilesetEntryFunction());
+ map.put(
+ SkyFunctions.FILESET_ENTRY,
+ new FilesetEntryFunction(directories.getExecRoot().asFragment()));
map.put(
SkyFunctions.ACTION_TEMPLATE_EXPANSION,
new ActionTemplateExpansionFunction(actionKeyContext));
diff --git a/src/test/java/com/google/devtools/build/lib/actions/FilesetOutputSymlinkTest.java b/src/test/java/com/google/devtools/build/lib/actions/FilesetOutputSymlinkTest.java
new file mode 100644
index 0000000..bc526c6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/actions/FilesetOutputSymlinkTest.java
@@ -0,0 +1,61 @@
+// 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.actions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.vfs.PathFragment;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link FilesetOutputSymlink}. */
+@RunWith(JUnit4.class)
+public final class FilesetOutputSymlinkTest {
+
+ private static final PathFragment EXEC_ROOT = PathFragment.create("/example/execroot");
+
+ private static final FilesetOutputSymlink createSymlinkTo(String path) {
+ return FilesetOutputSymlink.create(
+ PathFragment.create("any/path"), PathFragment.create(path), new Object(), false, EXEC_ROOT);
+ }
+
+ @Test
+ public void stripsExecRootFromTarget() throws Exception {
+ FilesetOutputSymlink symlink = createSymlinkTo("/example/execroot/some/path");
+ PathFragment targetPath = symlink.getTargetPath();
+ assertThat(targetPath.getPathString()).isEqualTo("some/path");
+ }
+
+ @Test
+ public void reconstitutesTargetPath_underExecRoot() throws Exception {
+ FilesetOutputSymlink symlink = createSymlinkTo("/example/execroot/some/path");
+ PathFragment targetPath = symlink.reconstituteTargetPath(EXEC_ROOT);
+ assertThat(targetPath.getPathString()).isEqualTo("/example/execroot/some/path");
+ }
+
+ @Test
+ public void reconstitutesTargetPath_differentExecRoot() throws Exception {
+ FilesetOutputSymlink symlink = createSymlinkTo("/example/execroot/some/path");
+ PathFragment targetPath = symlink.reconstituteTargetPath(PathFragment.create("/new/execroot"));
+ assertThat(targetPath.getPathString()).isEqualTo("/new/execroot/some/path");
+ }
+
+ @Test
+ public void reconstitutesTargetPath_notUnderExecRoot() throws Exception {
+ FilesetOutputSymlink symlink = createSymlinkTo("some/path");
+ PathFragment targetPath = symlink.reconstituteTargetPath(EXEC_ROOT);
+ assertThat(targetPath.getPathString()).isEqualTo("some/path");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java b/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
index b7fd511..5b59a16 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
@@ -388,16 +388,16 @@
.containsEntry(PathFragment.create("out/foo/bar"), ActionInputHelper.fromPath("/some"));
}
- private FilesetOutputSymlink filesetSymlink(String from, String to) {
+ private static FilesetOutputSymlink filesetSymlink(String from, String to) {
return FilesetOutputSymlink.createForTesting(
- PathFragment.create(from), PathFragment.create(to));
+ PathFragment.create(from), PathFragment.create(to), PathFragment.create("/root"));
}
private ImmutableMap<Artifact, ImmutableList<FilesetOutputSymlink>> simpleFilesetManifest() {
return ImmutableMap.of(
createFileset("out"),
ImmutableList.of(
- filesetSymlink("workspace/bar", "foo"), filesetSymlink("workspace/foo", "/foo/bar")));
+ filesetSymlink("workspace/bar", "foo"), filesetSymlink("workspace/foo", "/root/bar")));
}
private SpecialArtifact createFileset(String execPath) {
@@ -426,7 +426,7 @@
expander.addFilesetManifests(simpleFilesetManifest(), entries);
assertThat(entries)
.containsExactly(
- PathFragment.create("out/workspace/foo"), ActionInputHelper.fromPath("/foo/bar"));
+ PathFragment.create("out/workspace/foo"), ActionInputHelper.fromPath("/root/bar"));
}
@Test
@@ -436,7 +436,7 @@
expander.addFilesetManifests(simpleFilesetManifest(), entries);
assertThat(entries)
.containsExactly(
- PathFragment.create("out/workspace/bar"), ActionInputHelper.fromPath("/foo/bar"),
- PathFragment.create("out/workspace/foo"), ActionInputHelper.fromPath("/foo/bar"));
+ PathFragment.create("out/workspace/bar"), ActionInputHelper.fromPath("/root/bar"),
+ PathFragment.create("out/workspace/foo"), ActionInputHelper.fromPath("/root/bar"));
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
index 5e5ff14..f82b763 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
@@ -59,10 +59,8 @@
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
-import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -124,7 +122,8 @@
new BlacklistedPackagePrefixesFunction(
/*hardcodedBlacklistedPackagePrefixes=*/ ImmutableSet.of(),
/*additionalBlacklistedPackagePrefixesFile=*/ PathFragment.EMPTY_FRAGMENT));
- skyFunctions.put(SkyFunctions.FILESET_ENTRY, new FilesetEntryFunction());
+ skyFunctions.put(
+ SkyFunctions.FILESET_ENTRY, new FilesetEntryFunction(rootDirectory.asFragment()));
skyFunctions.put(SkyFunctions.LOCAL_REPOSITORY_LOOKUP, new LocalRepositoryLookupFunction());
differencer = new SequencedRecordingDifferencer();
@@ -191,31 +190,32 @@
return result.get(key);
}
- private static FilesetOutputSymlink symlink(String from, Artifact to) {
- return FilesetOutputSymlink.createForTesting(
- PathFragment.create(from), to.getPath().asFragment());
+ private FilesetOutputSymlink symlink(String from, Artifact to) {
+ return symlink(PathFragment.create(from), to.getPath().asFragment());
}
- private static FilesetOutputSymlink symlink(String from, String to) {
- return FilesetOutputSymlink.createForTesting(
- PathFragment.create(from), PathFragment.create(to));
+ private FilesetOutputSymlink symlink(String from, String to) {
+ return symlink(PathFragment.create(from), PathFragment.create(to));
}
- private static FilesetOutputSymlink symlink(String from, RootedPath to) {
- return FilesetOutputSymlink.createForTesting(
- PathFragment.create(from), to.asPath().asFragment());
+ private FilesetOutputSymlink symlink(String from, RootedPath to) {
+ return symlink(PathFragment.create(from), to.asPath().asFragment());
+ }
+
+ private FilesetOutputSymlink symlink(PathFragment from, PathFragment to) {
+ return FilesetOutputSymlink.createForTesting(from, to, rootDirectory.asFragment());
}
private void assertSymlinksCreatedInOrder(
FilesetTraversalParams request, FilesetOutputSymlink... expectedSymlinks) throws Exception {
- List<FilesetOutputSymlink> expected = Arrays.asList(expectedSymlinks);
Collection<FilesetOutputSymlink> actual =
Collections2.transform(
evalFilesetTraversal(request).getSymlinks(),
// Strip the metadata from the actual results.
(input) ->
- FilesetOutputSymlink.createForTesting(input.getName(), input.getTargetPath()));
- assertThat(actual).containsExactlyElementsIn(expected).inOrder();
+ FilesetOutputSymlink.createAlreadyRelativizedForTesting(
+ input.getName(), input.getTargetPath(), input.isRelativeToExecRoot()));
+ assertThat(actual).containsExactlyElementsIn(expectedSymlinks).inOrder();
}
private static Label label(String label) throws Exception {