childData, boolean isRemote) {
this.digest = digest;
this.childData = ImmutableMap.copyOf(childData);
+ this.isRemote = isRemote;
}
/**
* Returns a TreeArtifactValue out of the given Artifact-relative path fragments
* and their corresponding FileArtifactValues.
+ *
+ * All {@code childFileValues} must return the same value for
+ * {@link FileArtifactValue#isRemote()}.
*/
static TreeArtifactValue create(Map childFileValues) {
Map digestBuilder =
Maps.newHashMapWithExpectedSize(childFileValues.size());
+ Boolean isRemote = null;
for (Map.Entry e : childFileValues.entrySet()) {
- digestBuilder.put(e.getKey().getParentRelativePath().getPathString(), e.getValue());
+ FileArtifactValue v = e.getValue();
+ if (isRemote == null) {
+ isRemote = v.isRemote();
+ }
+ Preconditions.checkArgument(v.isRemote() == isRemote, "files in a tree artifact must either"
+ + " be all remote or all local.");
+ digestBuilder.put(e.getKey().getParentRelativePath().getPathString(), v);
}
return new TreeArtifactValue(
DigestUtils.fromMetadata(digestBuilder).getDigestBytesUnsafe(),
- ImmutableMap.copyOf(childFileValues));
+ ImmutableMap.copyOf(childFileValues),
+ isRemote != null ? isRemote : false);
}
FileArtifactValue getSelfData() {
@@ -104,6 +118,13 @@ Map getChildValues() {
return childData;
}
+ /**
+ * Returns {@code true} if the @link TreeFileArtifact}s are only stored remotely.
+ */
+ public boolean isRemote() {
+ return isRemote;
+ }
+
@Override
public BigInteger getValueFingerprint() {
if (valueFingerprint == null) {
@@ -150,7 +171,7 @@ public String toString() {
* Java's concurrent collections disallow null members.
*/
static final TreeArtifactValue MISSING_TREE_ARTIFACT =
- new TreeArtifactValue(null, ImmutableMap.of()) {
+ new TreeArtifactValue(null, ImmutableMap.of(), false) {
@Override
FileArtifactValue getSelfData() {
throw new UnsupportedOperationException();
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
index 62691af58e13ab..4cf04992899128 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
@@ -21,8 +21,10 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.hash.HashCode;
import com.google.common.util.concurrent.Runnables;
import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.actions.ActionLookupValue.ActionLookupKey;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
@@ -32,8 +34,10 @@
import com.google.devtools.build.lib.actions.ArtifactOwner;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.FileArtifactValue;
+import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
import com.google.devtools.build.lib.actions.FileStateValue;
import com.google.devtools.build.lib.actions.FileValue;
+import com.google.devtools.build.lib.actions.cache.DigestUtils;
import com.google.devtools.build.lib.actions.util.TestAction;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ServerDirectories;
@@ -50,6 +54,7 @@
import com.google.devtools.build.lib.util.io.OutErr;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.BatchStat;
+import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileStatus;
import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
import com.google.devtools.build.lib.vfs.FileStatusWithDigestAdapter;
@@ -74,9 +79,11 @@
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.io.IOException;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -787,6 +794,113 @@ private ActionExecutionValue actionValueWithTreeArtifacts(List
/*actionDependsOnBuildId=*/ false);
}
+ private ActionExecutionValue actionValueWithRemoteTreeArtifact(SpecialArtifact output, Map children) {
+ ImmutableMap.Builder childFileValues = ImmutableMap.builder();
+ for (Map.Entry child : children.entrySet()) {
+ childFileValues.put(ActionInputHelper.treeFileArtifact(output, child.getKey()), child.getValue());
+ }
+ TreeArtifactValue treeArtifactValue = TreeArtifactValue.create(childFileValues.build());
+ return ActionExecutionValue.create(Collections.emptyMap(),
+ Collections.singletonMap(output, treeArtifactValue), ImmutableMap.of(), null, null, false);
+ }
+
+ private ActionExecutionValue actionValueWithRemoteArtifact(Artifact output, RemoteFileArtifactValue value) {
+ return ActionExecutionValue.create(
+ Collections.singletonMap(output, ArtifactFileMetadata.PLACEHOLDER), ImmutableMap.of(),
+ Collections.singletonMap(output, value), null, null, false);
+ }
+
+ private RemoteFileArtifactValue createRemoteFileArtifactValue(String contents) {
+ byte[] data = contents.getBytes();
+ DigestHashFunction hashFn = fs.getDigestFunction();
+ HashCode hash = hashFn.getHashFunction().hashBytes(data);
+ return new RemoteFileArtifactValue(hash.asBytes(), data.length, -1);
+ }
+
+ @Test
+ public void testRemoteAndLocalArtifacts() throws Exception {
+ // Test that injected remote artifacts are trusted by the FileSystemValueChecker
+ // and that local files always takes preference over remote files.
+ ActionLookupKey actionLookupKey =
+ new ActionLookupKey() {
+ @Override
+ public SkyFunctionName functionName() {
+ return SkyFunctionName.FOR_TESTING;
+ }
+ };
+ SkyKey actionKey1 = ActionExecutionValue.key(actionLookupKey, 0);
+ SkyKey actionKey2 = ActionExecutionValue.key(actionLookupKey, 1);
+
+ Artifact out1 = createDerivedArtifact("foo");
+ Artifact out2 = createDerivedArtifact("bar");
+ Map metadataToInject = new HashMap<>();
+ metadataToInject.put(actionKey1, actionValueWithRemoteArtifact(out1, createRemoteFileArtifactValue("foo-content")));
+ metadataToInject.put(actionKey2, actionValueWithRemoteArtifact(out2, createRemoteFileArtifactValue("bar-content")));
+ differencer.inject(metadataToInject);
+
+ EvaluationContext evaluationContext =
+ EvaluationContext.newBuilder()
+ .setKeepGoing(false)
+ .setNumThreads(1)
+ .setEventHander(NullEventHandler.INSTANCE)
+ .build();
+ assertThat(driver.evaluate(ImmutableList.of(actionKey1, actionKey2), evaluationContext).hasError()).isFalse();
+ assertThat(new FilesystemValueChecker(null, null).getDirtyActionValues(evaluator.getValues(),
+ null, ModifiedFileSet.EVERYTHING_MODIFIED)).isEmpty();
+
+ // Create the "out1" artifact on the filesystem and test that it invalidates the generating
+ // action's SkyKey.
+ FileSystemUtils.writeContentAsLatin1(out1.getPath(), "new-foo-content");
+ assertThat(
+ new FilesystemValueChecker(null, null)
+ .getDirtyActionValues(
+ evaluator.getValues(), null, ModifiedFileSet.EVERYTHING_MODIFIED))
+ .containsExactly(actionKey1);
+ }
+
+ @Test
+ public void testRemoteAndLocalTreeArtifacts() throws Exception {
+ // Test that injected remote tree artifacts are trusted by the FileSystemValueChecker
+ // and that local files always takes preference over remote files.
+ ActionLookupKey actionLookupKey =
+ new ActionLookupKey() {
+ @Override
+ public SkyFunctionName functionName() {
+ return SkyFunctionName.FOR_TESTING;
+ }
+ };
+ SkyKey actionKey = ActionExecutionValue.key(actionLookupKey, 0);
+
+ SpecialArtifact treeArtifact = createTreeArtifact("dir");
+ treeArtifact.getPath().createDirectoryAndParents();
+ Map treeArtifactMetadata = new HashMap<>();
+ treeArtifactMetadata.put(PathFragment.create("foo"), createRemoteFileArtifactValue("foo-content"));
+ treeArtifactMetadata.put(PathFragment.create("bar"), createRemoteFileArtifactValue("bar-content"));
+
+ Map metadataToInject = new HashMap<>();
+ metadataToInject.put(actionKey, actionValueWithRemoteTreeArtifact(treeArtifact, treeArtifactMetadata));
+ differencer.inject(metadataToInject);
+
+ EvaluationContext evaluationContext =
+ EvaluationContext.newBuilder()
+ .setKeepGoing(false)
+ .setNumThreads(1)
+ .setEventHander(NullEventHandler.INSTANCE)
+ .build();
+ assertThat(driver.evaluate(ImmutableList.of(actionKey), evaluationContext).hasError()).isFalse();
+ assertThat(new FilesystemValueChecker(null, null).getDirtyActionValues(evaluator.getValues(),
+ null, ModifiedFileSet.EVERYTHING_MODIFIED)).isEmpty();
+
+ // Create dir/foo on the local disk and test that it invalidates the associated sky key.
+ TreeFileArtifact fooArtifact = treeFileArtifact(treeArtifact, "foo");
+ FileSystemUtils.writeContentAsLatin1(fooArtifact.getPath(), "new-foo-content");
+ assertThat(
+ new FilesystemValueChecker(null, null)
+ .getDirtyActionValues(
+ evaluator.getValues(), null, ModifiedFileSet.EVERYTHING_MODIFIED))
+ .containsExactly(actionKey);
+ }
+
@Test
public void testPropagatesRuntimeExceptions() throws Exception {
Collection values =