diff --git a/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformUtils.java b/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformUtils.java index 2773421656eee8..7eec12934b276a 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformUtils.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformUtils.java @@ -17,6 +17,7 @@ import build.bazel.remote.execution.v2.Platform; import build.bazel.remote.execution.v2.Platform.Property; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.actions.Spawn; @@ -64,6 +65,13 @@ public static Platform buildPlatformProto(Map executionPropertie @Nullable public static Platform getPlatformProto(Spawn spawn, @Nullable RemoteOptions remoteOptions) throws UserExecException { + return getPlatformProto(spawn, remoteOptions, ImmutableMap.of()); + } + + @Nullable + public static Platform getPlatformProto( + Spawn spawn, @Nullable RemoteOptions remoteOptions, Map additionalProperties) + throws UserExecException { SortedMap defaultExecProperties = remoteOptions != null ? remoteOptions.getRemoteDefaultExecProperties() @@ -71,34 +79,35 @@ public static Platform getPlatformProto(Spawn spawn, @Nullable RemoteOptions rem if (spawn.getExecutionPlatform() == null && spawn.getCombinedExecProperties().isEmpty() - && defaultExecProperties.isEmpty()) { + && defaultExecProperties.isEmpty() + && additionalProperties.isEmpty()) { return null; } - Platform.Builder platformBuilder = Platform.newBuilder(); - + Map properties; if (!spawn.getCombinedExecProperties().isEmpty()) { - Map combinedExecProperties; // Apply default exec properties if the execution platform does not already set // exec_properties if (spawn.getExecutionPlatform() == null || spawn.getExecutionPlatform().execProperties().isEmpty()) { - combinedExecProperties = new HashMap<>(); - combinedExecProperties.putAll(defaultExecProperties); - combinedExecProperties.putAll(spawn.getCombinedExecProperties()); + properties = new HashMap<>(); + properties.putAll(defaultExecProperties); + properties.putAll(spawn.getCombinedExecProperties()); } else { - combinedExecProperties = spawn.getCombinedExecProperties(); - } - - for (Map.Entry entry : combinedExecProperties.entrySet()) { - platformBuilder.addPropertiesBuilder().setName(entry.getKey()).setValue(entry.getValue()); + properties = spawn.getCombinedExecProperties(); } } else if (spawn.getExecutionPlatform() != null && !Strings.isNullOrEmpty(spawn.getExecutionPlatform().remoteExecutionProperties())) { - // Try and get the platform info from the execution properties. + properties = new HashMap<>(); + // Try and get the platform info from the execution properties. This is pretty inefficient; it + // would be better to store the parsed properties instead of the String text proto. try { + Platform.Builder platformBuilder = Platform.newBuilder(); TextFormat.getParser() .merge(spawn.getExecutionPlatform().remoteExecutionProperties(), platformBuilder); + for (Property property : platformBuilder.getPropertiesList()) { + properties.put(property.getName(), property.getValue()); + } } catch (ParseException e) { String message = String.format( @@ -108,12 +117,23 @@ public static Platform getPlatformProto(Spawn spawn, @Nullable RemoteOptions rem e, createFailureDetail(message, Code.INVALID_REMOTE_EXECUTION_PROPERTIES)); } } else { - for (Map.Entry property : defaultExecProperties.entrySet()) { - platformBuilder.addProperties( - Property.newBuilder().setName(property.getKey()).setValue(property.getValue()).build()); + properties = defaultExecProperties; + } + + if (!additionalProperties.isEmpty()) { + if (properties.isEmpty()) { + properties = additionalProperties; + } else { + // Merge the two maps. + properties = new HashMap<>(properties); + properties.putAll(additionalProperties); } } + Platform.Builder platformBuilder = Platform.newBuilder(); + for (Map.Entry entry : properties.entrySet()) { + platformBuilder.addPropertiesBuilder().setName(entry.getKey()).setValue(entry.getValue()); + } sortPlatformProperties(platformBuilder); return platformBuilder.build(); } diff --git a/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java b/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java index c93008ca175830..614e652ec5faca 100644 --- a/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java +++ b/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java @@ -24,6 +24,7 @@ * probably should not exist, but is currently necessary for our local MacOS support. */ public interface LocalEnvProvider { + LocalEnvProvider NOOP = (env, binTools, fallbackTmpDir) -> ImmutableMap.copyOf(env); /** * Creates a local environment provider for the current OS. diff --git a/src/main/java/com/google/devtools/build/lib/remote/BUILD b/src/main/java/com/google/devtools/build/lib/remote/BUILD index 9a08c88a75084e..1d94ad850c1905 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/BUILD +++ b/src/main/java/com/google/devtools/build/lib/remote/BUILD @@ -74,6 +74,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/exec:spawn_input_expander", "//src/main/java/com/google/devtools/build/lib/exec:spawn_runner", "//src/main/java/com/google/devtools/build/lib/exec:spawn_strategy_registry", + "//src/main/java/com/google/devtools/build/lib/exec/local", "//src/main/java/com/google/devtools/build/lib/packages", "//src/main/java/com/google/devtools/build/lib/packages/semantics", "//src/main/java/com/google/devtools/build/lib/profiler", @@ -90,6 +91,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/sandbox", "//src/main/java/com/google/devtools/build/lib/skyframe:mutable_supplier", "//src/main/java/com/google/devtools/build/lib/skyframe:tree_artifact_value", + "//src/main/java/com/google/devtools/build/lib/util", "//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception", "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code", "//src/main/java/com/google/devtools/build/lib/util:exit_code", @@ -98,6 +100,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/vfs", "//src/main/java/com/google/devtools/build/lib/vfs:output_service", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", + "//src/main/java/com/google/devtools/build/lib/worker", "//src/main/java/com/google/devtools/common/options", "//src/main/protobuf:failure_details_java_proto", "//third_party:auth", diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java index 63af731ebd421b..c25b6e899456d8 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java +++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java @@ -75,7 +75,6 @@ import com.google.devtools.build.lib.actions.Spawn; import com.google.devtools.build.lib.actions.SpawnResult; import com.google.devtools.build.lib.actions.Spawns; -import com.google.devtools.build.lib.actions.UserExecException; import com.google.devtools.build.lib.actions.cache.MetadataInjector; import com.google.devtools.build.lib.analysis.platform.PlatformUtils; import com.google.devtools.build.lib.buildtool.buildevent.BuildInterruptedEvent; @@ -83,6 +82,7 @@ import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.exec.SpawnInputExpander.InputWalker; import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext; +import com.google.devtools.build.lib.exec.local.LocalEnvProvider; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.profiler.ProfilerTask; import com.google.devtools.build.lib.profiler.SilentCloseable; @@ -108,10 +108,15 @@ import com.google.devtools.build.lib.remote.util.Utils.InMemoryOutput; import com.google.devtools.build.lib.server.FailureDetails.RemoteExecution; import com.google.devtools.build.lib.skyframe.TreeArtifactValue; +import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.util.io.FileOutErr; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.lib.worker.WorkerKey; +import com.google.devtools.build.lib.worker.WorkerOptions; +import com.google.devtools.build.lib.worker.WorkerParser; +import com.google.devtools.common.options.Options; import com.google.protobuf.ByteString; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; @@ -365,13 +370,14 @@ private SortedMap buildOutputDirMap(Spawn spawn) { return outputDirMap; } - private MerkleTree buildInputMerkleTree(Spawn spawn, SpawnExecutionContext context) + private MerkleTree buildInputMerkleTree( + Spawn spawn, SpawnExecutionContext context, ToolSignature toolSignature) throws IOException, ForbiddenActionInputException { // Add output directories to inputs so that they are created as empty directories by the // executor. The spec only requires the executor to create the parent directory of an output // directory, which differs from the behavior of both local and sandboxed execution. SortedMap outputDirMap = buildOutputDirMap(spawn); - if (remoteOptions.remoteMerkleTreeCache) { + if (remoteOptions.remoteMerkleTreeCache && toolSignature == null) { MetadataProvider metadataProvider = context.getMetadataProvider(); ConcurrentLinkedQueue subMerkleTrees = new ConcurrentLinkedQueue<>(); remotePathResolver.walkInputs( @@ -394,7 +400,12 @@ private MerkleTree buildInputMerkleTree(Spawn spawn, SpawnExecutionContext conte newInputMap.putAll(outputDirMap); inputMap = newInputMap; } - return MerkleTree.build(inputMap, context.getMetadataProvider(), execRoot, digestUtil); + return MerkleTree.build( + inputMap, + toolSignature == null ? ImmutableSet.of() : toolSignature.toolInputs, + context.getMetadataProvider(), + execRoot, + digestUtil); } } @@ -441,11 +452,24 @@ private static ByteString buildSalt(Spawn spawn) { /** Creates a new {@link RemoteAction} instance from spawn. */ public RemoteAction buildRemoteAction(Spawn spawn, SpawnExecutionContext context) - throws IOException, UserExecException, ForbiddenActionInputException { - final MerkleTree merkleTree = buildInputMerkleTree(spawn, context); + throws IOException, ExecException, ForbiddenActionInputException, InterruptedException { + ToolSignature toolSignature = + remoteOptions.markToolInputs + && Spawns.supportsWorkers(spawn) + && !spawn.getToolFiles().isEmpty() + ? computePersistentWorkerSignature(spawn, context) + : null; + final MerkleTree merkleTree = buildInputMerkleTree(spawn, context, toolSignature); // Get the remote platform properties. Platform platform = PlatformUtils.getPlatformProto(spawn, remoteOptions); + if (toolSignature != null) { + platform = + PlatformUtils.getPlatformProto( + spawn, remoteOptions, ImmutableMap.of("persistentWorkerKey", toolSignature.key)); + } else { + platform = PlatformUtils.getPlatformProto(spawn, remoteOptions); + } Command command = buildCommand( @@ -485,6 +509,21 @@ public RemoteAction buildRemoteAction(Spawn spawn, SpawnExecutionContext context actionKey); } + @Nullable + private ToolSignature computePersistentWorkerSignature(Spawn spawn, SpawnExecutionContext context) + throws IOException, ExecException, InterruptedException { + WorkerParser workerParser = + new WorkerParser( + execRoot, Options.getDefaults(WorkerOptions.class), LocalEnvProvider.NOOP, null); + WorkerKey workerKey = workerParser.compute(spawn, context).getWorkerKey(); + Fingerprint fingerprint = new Fingerprint(); + fingerprint.addBytes(workerKey.getWorkerFilesCombinedHash().asBytes()); + fingerprint.addIterableStrings(workerKey.getArgs()); + fingerprint.addStringMap(workerKey.getEnv()); + return new ToolSignature( + fingerprint.hexDigestAndReset(), workerKey.getWorkerFilesWithHashes().keySet()); + } + /** A value class representing the result of remotely executed {@link RemoteAction}. */ public static class RemoteActionResult { private final ActionResult actionResult; @@ -1468,4 +1507,18 @@ void report(Event evt) { reporter.handle(evt); } } + + /** + * A simple value class combining a hash of the tool inputs (and their digests) as well as a set + * of the relative paths of all tool inputs. + */ + private static final class ToolSignature { + private final String key; + private final Set toolInputs; + + private ToolSignature(String key, Set toolInputs) { + this.key = key; + this.toolInputs = toolInputs; + } + } } diff --git a/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTree.java b/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTree.java index 96654ba6d3c84b..abfb1abfa8c4e3 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTree.java +++ b/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTree.java @@ -73,6 +73,7 @@ static class FileNode extends Node { private final ByteString data; private final Digest digest; private final boolean isExecutable; + private final boolean toolInput; /** * Create a FileNode with its executable bit set. @@ -83,27 +84,41 @@ static class FileNode extends Node { * https://github.com/bazelbuild/bazel/issues/13262 for more details. */ static FileNode createExecutable(String pathSegment, Path path, Digest digest) { - return new FileNode(pathSegment, path, digest, /* isExecutable= */ true); + return new FileNode(pathSegment, path, digest, /* isExecutable= */ true, false); } - static FileNode createExecutable(String pathSegment, ByteString data, Digest digest) { - return new FileNode(pathSegment, data, digest, /* isExecutable= */ true); + static FileNode createExecutable( + String pathSegment, Path path, Digest digest, boolean toolInput) { + return new FileNode(pathSegment, path, digest, /* isExecutable= */ true, toolInput); } - private FileNode(String pathSegment, Path path, Digest digest, boolean isExecutable) { + static FileNode createExecutable( + String pathSegment, ByteString data, Digest digest, boolean toolInput) { + return new FileNode(pathSegment, data, digest, /* isExecutable= */ true, toolInput); + } + + private FileNode( + String pathSegment, Path path, Digest digest, boolean isExecutable, boolean toolInput) { super(pathSegment); this.path = Preconditions.checkNotNull(path, "path"); this.data = null; this.digest = Preconditions.checkNotNull(digest, "digest"); this.isExecutable = isExecutable; + this.toolInput = toolInput; } - private FileNode(String pathSegment, ByteString data, Digest digest, boolean isExecutable) { + private FileNode( + String pathSegment, + ByteString data, + Digest digest, + boolean isExecutable, + boolean toolInput) { super(pathSegment); this.path = null; this.data = Preconditions.checkNotNull(data, "data"); this.digest = Preconditions.checkNotNull(digest, "digest"); this.isExecutable = isExecutable; + this.toolInput = toolInput; } Digest getDigest() { @@ -122,9 +137,13 @@ public boolean isExecutable() { return isExecutable; } + boolean isToolInput() { + return toolInput; + } + @Override public int hashCode() { - return Objects.hash(super.hashCode(), path, data, digest, isExecutable); + return Objects.hash(super.hashCode(), path, data, digest, toolInput, isExecutable); } @Override @@ -135,6 +154,7 @@ public boolean equals(Object o) { && Objects.equals(path, other.path) && Objects.equals(data, other.data) && Objects.equals(digest, other.digest) + && toolInput == other.toolInput && isExecutable == other.isExecutable; } return false; diff --git a/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeBuilder.java b/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeBuilder.java index f188b234ef18d6..10ecc76d717967 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeBuilder.java @@ -15,6 +15,7 @@ import build.bazel.remote.execution.v2.Digest; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.actions.ActionInput; import com.google.devtools.build.lib.actions.ActionInputHelper; import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact; @@ -32,6 +33,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -59,8 +61,19 @@ static DirectoryTree fromActionInputs( Path execRoot, DigestUtil digestUtil) throws IOException { + return fromActionInputs(inputs, ImmutableSet.of(), metadataProvider, execRoot, digestUtil); + } + + static DirectoryTree fromActionInputs( + SortedMap inputs, + Set toolInputs, + MetadataProvider metadataProvider, + Path execRoot, + DigestUtil digestUtil) + throws IOException { Map tree = new HashMap<>(); - int numFiles = buildFromActionInputs(inputs, metadataProvider, execRoot, digestUtil, tree); + int numFiles = + buildFromActionInputs(inputs, toolInputs, metadataProvider, execRoot, digestUtil, tree); return new DirectoryTree(tree, numFiles); } @@ -117,6 +130,7 @@ private static int buildFromPaths( */ private static int buildFromActionInputs( SortedMap inputs, + Set toolInputs, MetadataProvider metadataProvider, Path execRoot, DigestUtil digestUtil, @@ -132,7 +146,10 @@ private static int buildFromActionInputs( boolean childAdded = currDir.addChild( FileNode.createExecutable( - path.getBaseName(), virtualActionInput.getBytes(), d)); + path.getBaseName(), + virtualActionInput.getBytes(), + d, + toolInputs.contains(path))); return childAdded ? 1 : 0; } @@ -146,14 +163,16 @@ private static int buildFromActionInputs( Digest d = DigestUtil.buildDigest(metadata.getDigest(), metadata.getSize()); Path inputPath = ActionInputHelper.toInputPath(input, execRoot); boolean childAdded = - currDir.addChild(FileNode.createExecutable(path.getBaseName(), inputPath, d)); + currDir.addChild( + FileNode.createExecutable( + path.getBaseName(), inputPath, d, toolInputs.contains(path))); return childAdded ? 1 : 0; case DIRECTORY: SortedMap directoryInputs = explodeDirectory(input.getExecPath(), execRoot); return buildFromActionInputs( - directoryInputs, metadataProvider, execRoot, digestUtil, tree); + directoryInputs, toolInputs, metadataProvider, execRoot, digestUtil, tree); case SYMLINK: throw new IllegalStateException( diff --git a/src/main/java/com/google/devtools/build/lib/remote/merkletree/MerkleTree.java b/src/main/java/com/google/devtools/build/lib/remote/merkletree/MerkleTree.java index 97df07398a06a7..ba1548a5fc69c4 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/merkletree/MerkleTree.java +++ b/src/main/java/com/google/devtools/build/lib/remote/merkletree/MerkleTree.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; @@ -47,6 +48,7 @@ /** A merkle tree representation as defined by the remote execution api. */ public class MerkleTree { + private static final String BAZEL_TOOL_INPUT_MARKER = "bazel_tool_input"; /** A path or contents */ public static class PathOrBytes { @@ -219,6 +221,32 @@ public static MerkleTree build( } } + /** + * Constructs a merkle tree from a lexicographically sorted map of inputs (files). + * + * @param inputs a map of path to input. The map is required to be sorted lexicographically by + * paths. Inputs of type tree artifacts are not supported and are expected to have been + * expanded before. + * @param metadataProvider provides metadata for all {@link ActionInput}s in {@code inputs}, as + * well as any {@link ActionInput}s being discovered via directory expansion. + * @param execRoot all paths in {@code inputs} need to be relative to this {@code execRoot}. + * @param digestUtil a hashing utility + */ + public static MerkleTree build( + SortedMap inputs, + Set toolInputs, + MetadataProvider metadataProvider, + Path execRoot, + DigestUtil digestUtil) + throws IOException { + try (SilentCloseable c = Profiler.instance().profile("MerkleTree.build(ActionInput)")) { + DirectoryTree tree = + DirectoryTreeBuilder.fromActionInputs( + inputs, toolInputs, metadataProvider, execRoot, digestUtil); + return build(tree, digestUtil); + } + } + /** * Constructs a merkle tree from a lexicographically sorted map of files. * @@ -333,11 +361,19 @@ private static MerkleTree buildMerkleTree( } private static FileNode buildProto(DirectoryTree.FileNode file) { - return FileNode.newBuilder() - .setName(file.getPathSegment()) - .setDigest(file.getDigest()) - .setIsExecutable(file.isExecutable()) - .build(); + FileNode.Builder builder = + FileNode.newBuilder() + .setName(file.getPathSegment()) + .setDigest(file.getDigest()) + .setIsExecutable(file.isExecutable()); + if (file.isToolInput()) { + builder + .getNodePropertiesBuilder() + .addPropertiesBuilder() + .setName(BAZEL_TOOL_INPUT_MARKER) + .setValue(""); + } + return builder.build(); } private static DirectoryNode buildProto(String baseName, MerkleTree dir) { diff --git a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java index f729ab42b84040..c9513b5eda3da5 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java +++ b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java @@ -591,6 +591,16 @@ public RemoteOutputsStrategyConverter() { + " support)") public String remoteDownloadRegex; + @Option( + name = "experimental_remote_mark_tool_inputs", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.REMOTE, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "If set to true, Bazel will mark inputs as tool inputs for the remote executor. This " + + "can be used to implement remote persistent workers.") + public boolean markToolInputs; + // The below options are not configurable by users, only tests. // This is part of the effort to reduce the overall number of flags. diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerKey.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerKey.java index 0540f671d76141..4ae4847a065e6c 100644 --- a/src/main/java/com/google/devtools/build/lib/worker/WorkerKey.java +++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerKey.java @@ -32,7 +32,7 @@ *

We expect a small number of WorkerKeys per mnemonic. Unbounded creation of WorkerKeys will * break various things as well as render the workers less useful. */ -final class WorkerKey { +public final class WorkerKey { /** Build options. */ private final ImmutableList args; /** Environment variables. */ diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerParser.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerParser.java index a45cddef9a7c86..2c1c6ede054d0e 100644 --- a/src/main/java/com/google/devtools/build/lib/worker/WorkerParser.java +++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerParser.java @@ -41,7 +41,7 @@ * persistent worker process (actions with equal keys are allowed to use the same worker process), * and a separate list of flag files. The result is encapsulated as a {@link WorkerConfig}. */ -class WorkerParser { +public class WorkerParser { public static final String ERROR_MESSAGE_PREFIX = "Worker strategy cannot execute this %s action, "; public static final String REASON_NO_FLAGFILE = diff --git a/src/test/java/com/google/devtools/build/lib/remote/merkletree/ActionInputDirectoryTreeTest.java b/src/test/java/com/google/devtools/build/lib/remote/merkletree/ActionInputDirectoryTreeTest.java index 393d610d715574..6af2ab698c80ff 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/merkletree/ActionInputDirectoryTreeTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/merkletree/ActionInputDirectoryTreeTest.java @@ -72,7 +72,7 @@ public void virtualActionInputShouldWork() throws Exception { FileNode expectedFooNode = FileNode.createExecutable("foo.cc", foo.getPath(), digestUtil.computeAsUtf8("foo")); FileNode expectedBarNode = - FileNode.createExecutable("bar.cc", bar.getBytes(), digestUtil.computeAsUtf8("bar")); + FileNode.createExecutable("bar.cc", bar.getBytes(), digestUtil.computeAsUtf8("bar"), false); assertThat(fileNodesAtDepth(tree, 0)).isEmpty(); assertThat(fileNodesAtDepth(tree, 1)).containsExactly(expectedFooNode, expectedBarNode); }