`SymlinkAction` injects metadata from its input.
This reduces filesystem ops. It also allows preserving richer metadata from the input, for example `RemoteFileArtifactValue`.
It is incorrect to do this for directory artifacts. There was already an existing test case that caught this for a fileset, and this change adds one that catches this for a tree artifact.
Added some useful helper methods to `BuildIntegrationTestCase`.
PiperOrigin-RevId: 690849579
Change-Id: I8c44a4d28d21a4d27f216763124a48f93e9ed7a5
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index 5537966..01da8d4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -1604,6 +1604,7 @@
"//src/main/java/com/google/devtools/build/lib/actions",
"//src/main/java/com/google/devtools/build/lib/actions:artifact_expander",
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
+ "//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
"//src/main/java/com/google/devtools/build/lib/analysis/platform",
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
"//src/main/java/com/google/devtools/build/lib/exec:spawn_log_context",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
index f6ca7b9..389f60b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
@@ -27,6 +27,7 @@
import com.google.devtools.build.lib.actions.ActionResult;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactExpander;
+import com.google.devtools.build.lib.actions.FileArtifactValue;
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
@@ -242,6 +243,7 @@
}
}
+ maybeInjectMetadata(actionExecutionContext);
return ActionResult.EMPTY;
}
@@ -326,6 +328,44 @@
}
}
+ /**
+ * Propagates metadata from the input artifact if possible.
+ *
+ * <p>This is an optimization that saves filesystem operations - we know the output is just a
+ * symlink to the input, so we may be able to skip constructing its metadata from the filesystem.
+ *
+ * <p>In addition to reducing filesystem operations, this allows us to provide richer information
+ * for the symlink metadata. For example, if the input metadata is a {@link
+ * com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue}, the output
+ * metadata will be as well.
+ *
+ * <p>In cases where propagating the input metadata is incorrect ({@linkplain Artifact#isDirectory
+ * directory artifacts}) or cases where the input metadata cannot be obtained, this method does
+ * nothing. The output symlink will be read back from the filesystem after this action finishes
+ * executing.
+ */
+ private void maybeInjectMetadata(ActionExecutionContext ctx) {
+ if (ctx.getActionFileSystem() != null) {
+ return; // Action filesystems are responsible for their own metadata injection.
+ }
+ if (targetType == TargetType.FILESET) {
+ return;
+ }
+ Artifact primaryInput = getPrimaryInput();
+ if (primaryInput == null || primaryInput.isDirectory()) {
+ return;
+ }
+ FileArtifactValue metadata;
+ try {
+ metadata = ctx.getInputMetadataProvider().getInputMetadata(primaryInput);
+ } catch (IOException e) {
+ return;
+ }
+ if (metadata != null) {
+ ctx.getOutputMetadataStore().injectFile(getPrimaryOutput(), metadata);
+ }
+ }
+
private String printInputs() {
if (getInputs().isEmpty()) {
return inputPath.getPathString();
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
index 580e58f..7d5db9e 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
@@ -17,10 +17,12 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionExecutionContext.LostInputsCheck;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
+import com.google.devtools.build.lib.actions.cache.OutputMetadataStore;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.actions.util.DummyExecutor;
import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
@@ -72,7 +74,7 @@
execRoot.getPathString(), execRoot.getFileSystem(), SyscallCache.NO_CACHE),
ActionInputPrefetcher.NONE,
actionKeyContext,
- /* outputMetadataStore= */ null,
+ mock(OutputMetadataStore.class),
/* rewindingEnabled= */ false,
LostInputsCheck.NONE,
outErr,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
index 4f6f134..e7fa3fe 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
@@ -15,6 +15,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER;
+import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
@@ -24,7 +25,9 @@
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.DiscoveredModulesPruner;
import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.InputMetadataProvider;
import com.google.devtools.build.lib.actions.ThreadStateReceiver;
+import com.google.devtools.build.lib.actions.cache.OutputMetadataStore;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.events.StoredEventHandler;
@@ -94,10 +97,10 @@
action.execute(
new ActionExecutionContext(
executor,
- /* inputMetadataProvider= */ null,
+ mock(InputMetadataProvider.class),
ActionInputPrefetcher.NONE,
actionKeyContext,
- /* outputMetadataStore= */ null,
+ mock(OutputMetadataStore.class),
/* rewindingEnabled= */ false,
LostInputsCheck.NONE,
/* fileOutErr= */ null,
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BuildIntegrationTestCase.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BuildIntegrationTestCase.java
index 8167acc..91d4b29 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/util/BuildIntegrationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BuildIntegrationTestCase.java
@@ -20,6 +20,7 @@
import static com.google.common.base.Throwables.throwIfUnchecked;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
@@ -39,6 +40,7 @@
import com.google.devtools.build.lib.actions.ActionGraph;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact;
+import com.google.devtools.build.lib.actions.Artifact.SourceArtifact;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.FileArtifactValue;
import com.google.devtools.build.lib.actions.FileArtifactValue.InlineFileArtifactValue;
@@ -704,23 +706,52 @@
* Given a label (which has typically, but not necessarily, just been built), returns the
* collection of files that it produces.
*
- * @param target the label of the target whose artifacts are requested.
+ * @param target the label of the target whose artifacts are requested
*/
protected ImmutableList<Artifact> getArtifacts(String target)
- throws LabelSyntaxException, NoSuchPackageException, NoSuchTargetException,
- InterruptedException, TransitionException, InvalidConfigurationException {
+ throws LabelSyntaxException,
+ NoSuchPackageException,
+ NoSuchTargetException,
+ InterruptedException,
+ TransitionException,
+ InvalidConfigurationException {
return getFilesToBuild(getConfiguredTarget(target)).toList();
}
/**
+ * Given a label (which has typically, but not necessarily, just been built), returns the file it
+ * produces ending with the given suffix.
+ *
+ * <p>It is an error if the given target produces multiple files with the given suffix.
+ *
+ * @param target the label of the target whose artifact is requested
+ * @param suffix suffix of the artifact requested
+ */
+ protected Artifact getArtifact(String target, String suffix)
+ throws LabelSyntaxException,
+ NoSuchPackageException,
+ NoSuchTargetException,
+ InterruptedException,
+ TransitionException,
+ InvalidConfigurationException {
+ return getArtifacts(target).stream()
+ .filter(a -> a.getExecPathString().endsWith(suffix))
+ .collect(onlyElement());
+ }
+
+ /**
* Given a label (which has typically, but not necessarily, just been built), returns the
* configured target for it using the request configuration.
*
* @param target the label of the requested target.
*/
protected ConfiguredTarget getConfiguredTarget(String target)
- throws LabelSyntaxException, NoSuchPackageException, NoSuchTargetException,
- InterruptedException, TransitionException, InvalidConfigurationException {
+ throws LabelSyntaxException,
+ NoSuchPackageException,
+ NoSuchTargetException,
+ InterruptedException,
+ TransitionException,
+ InvalidConfigurationException {
getPackageManager().getTarget(events.reporter(), Label.parseCanonical(target));
return getSkyframeExecutor()
.getConfiguredTargetForTesting(events.reporter(), label(target), getTargetConfiguration());
@@ -992,21 +1023,30 @@
}
protected String readInlineOutput(Artifact output) throws IOException, InterruptedException {
- assertThat(output).isInstanceOf(DerivedArtifact.class);
+ FileArtifactValue metadata = getOutputMetadata(output);
+ assertThat(metadata).isInstanceOf(InlineFileArtifactValue.class);
+ return new String(
+ FileSystemUtils.readContentAsLatin1(((InlineFileArtifactValue) metadata).getInputStream()));
+ }
+ protected FileArtifactValue getOutputMetadata(Artifact output)
+ throws IOException, InterruptedException {
+ assertThat(output).isInstanceOf(DerivedArtifact.class);
SkyValue actionExecutionValue =
getSkyframeExecutor()
.getEvaluator()
.getExistingValue(((DerivedArtifact) output).getGeneratingActionKey());
assertThat(actionExecutionValue).isInstanceOf(ActionExecutionValue.class);
+ return ((ActionExecutionValue) actionExecutionValue).getExistingFileArtifactValue(output);
+ }
- FileArtifactValue fileArtifactValue =
- ((ActionExecutionValue) actionExecutionValue).getExistingFileArtifactValue(output);
- assertThat(fileArtifactValue).isInstanceOf(InlineFileArtifactValue.class);
-
- return new String(
- FileSystemUtils.readContentAsLatin1(
- ((InlineFileArtifactValue) fileArtifactValue).getInputStream()));
+ protected FileArtifactValue getSourceArtifactMetadata(Artifact sourceArtifact)
+ throws InterruptedException {
+ assertThat(sourceArtifact).isInstanceOf(SourceArtifact.class);
+ SkyValue sourceArtifactValue =
+ getSkyframeExecutor().getEvaluator().getExistingValue(sourceArtifact);
+ assertThat(sourceArtifactValue).isInstanceOf(FileArtifactValue.class);
+ return (FileArtifactValue) sourceArtifactValue;
}
/**
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
index b5a67f6..8229001 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -1593,6 +1593,7 @@
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
"//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
"//src/main/java/com/google/devtools/build/lib/analysis:actions/spawn_action_template",
+ "//src/main/java/com/google/devtools/build/lib/analysis:actions/symlink_action",
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/skyframe:action_template_expansion_value",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
index 06f6c77..0be6c1d 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
@@ -46,6 +46,7 @@
import com.google.devtools.build.lib.actions.util.TestAction;
import com.google.devtools.build.lib.actions.util.TestAction.DummyAction;
import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate;
+import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Event;
@@ -884,11 +885,33 @@
TreeFileArtifact.createTreeOutput(treeArtifact, "link/file"));
}
+ @Test
+ public void symlinkActionToTreeArtifact() throws Exception {
+ SpecialArtifact tree1 = createTreeArtifact("tree1");
+ registerAction(
+ new SimpleTestAction(/* output= */ tree1) {
+ @Override
+ void run(ActionExecutionContext context) throws IOException {
+ touchFile(tree1.getPath().getChild("file"));
+ }
+ });
+
+ SpecialArtifact tree2 = createTreeArtifact("tree2");
+ registerAction(
+ SymlinkAction.toArtifact(
+ ActionsTestUtil.NULL_ACTION_OWNER, tree1, tree2, "Symlinking tree2 -> tree1"));
+
+ // The SymlinkAction should produce a TreeArtifactValue with tree2 as the parent.
+ TreeArtifactValue tree2Value = buildArtifact(tree2);
+ assertThat(tree2Value.getChildren())
+ .containsExactly(TreeFileArtifact.createTreeOutput(tree2, "file"));
+ }
+
private abstract static class SimpleTestAction extends TestAction {
private final Button button;
SimpleTestAction(Artifact output) {
- this(/*inputs=*/ ImmutableList.of(), output);
+ this(/* inputs= */ ImmutableList.of(), output);
}
SimpleTestAction(Iterable<Artifact> inputs, Artifact output) {