Add --materialize_param_files option.
When set, any action parameter files are written locally upon action execution, even when the action is executed remotely. This is mainly useful for debugging.
This option is effectively implied by --subcommands and --verbose_failures, as it is likely that the user is debugging actions when using these flags.
RELNOTES: Add --materialize_param_files flag to write parameter files even when actions are executed remotely.
PiperOrigin-RevId: 201225566
diff --git a/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java b/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java
index 9f9e335..ec006b9 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java
@@ -49,6 +49,16 @@
public static final ExecutionOptions DEFAULTS = Options.getDefaults(ExecutionOptions.class);
@Option(
+ name = "materialize_param_files",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Writes intermediate parameter files to output tree even when using "
+ + "remote action execution. Useful when debugging actions. ")
+ public boolean materializeParamFiles;
+
+ @Option(
name = "verbose_failures",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteActionContextProvider.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteActionContextProvider.java
index 4bcbb74..e86b41e 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteActionContextProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteActionContextProvider.java
@@ -80,6 +80,7 @@
new RemoteSpawnRunner(
env.getExecRoot(),
remoteOptions,
+ env.getOptions().getOptions(ExecutionOptions.class),
createFallbackRunner(env),
executionOptions.verboseFailures,
env.getReporter(),
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
index 5806b67..1198c2f 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
@@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.MetadataProvider;
@@ -35,6 +36,7 @@
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.exec.ExecutionOptions;
import com.google.devtools.build.lib.exec.SpawnExecException;
import com.google.devtools.build.lib.exec.SpawnRunner;
import com.google.devtools.build.lib.remote.Retrier.RetryException;
@@ -59,6 +61,7 @@
import io.grpc.Context;
import io.grpc.Status.Code;
import java.io.IOException;
+import java.io.OutputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
@@ -77,7 +80,8 @@
private static final int POSIX_TIMEOUT_EXIT_CODE = /*SIGNAL_BASE=*/128 + /*SIGALRM=*/14;
private final Path execRoot;
- private final RemoteOptions options;
+ private final RemoteOptions remoteOptions;
+ private final ExecutionOptions executionOptions;
private final SpawnRunner fallbackRunner;
private final boolean verboseFailures;
@@ -94,7 +98,8 @@
RemoteSpawnRunner(
Path execRoot,
- RemoteOptions options,
+ RemoteOptions remoteOptions,
+ ExecutionOptions executionOptions,
SpawnRunner fallbackRunner,
boolean verboseFailures,
@Nullable Reporter cmdlineReporter,
@@ -105,7 +110,8 @@
DigestUtil digestUtil,
Path logDir) {
this.execRoot = execRoot;
- this.options = options;
+ this.remoteOptions = remoteOptions;
+ this.executionOptions = executionOptions;
this.fallbackRunner = fallbackRunner;
this.remoteCache = remoteCache;
this.remoteExecutor = remoteExecutor;
@@ -136,6 +142,7 @@
SortedMap<PathFragment, ActionInput> inputMap = context.getInputMapping();
TreeNode inputRoot = repository.buildFromActionInputs(inputMap);
repository.computeMerkleDigests(inputRoot);
+ maybeWriteParamFilesLocally(spawn);
Command command = buildCommand(spawn.getArguments(), spawn.getEnvironment());
Action action =
buildAction(
@@ -152,8 +159,8 @@
TracingMetadataUtils.contextWithMetadata(buildRequestId, commandId, actionKey);
Context previous = withMetadata.attach();
try {
- boolean acceptCachedResult = options.remoteAcceptCached && Spawns.mayBeCached(spawn);
- boolean uploadLocalResults = options.remoteUploadLocalResults;
+ boolean acceptCachedResult = remoteOptions.remoteAcceptCached && Spawns.mayBeCached(spawn);
+ boolean uploadLocalResults = remoteOptions.remoteUploadLocalResults;
try {
// Try to lookup the action in the action cache.
@@ -199,7 +206,7 @@
try {
ExecuteRequest.Builder request =
ExecuteRequest.newBuilder()
- .setInstanceName(options.remoteInstanceName)
+ .setInstanceName(remoteOptions.remoteInstanceName)
.setAction(action)
.setSkipCacheLookup(!acceptCachedResult);
ExecuteResponse reply = remoteExecutor.executeRemotely(request.build());
@@ -223,6 +230,25 @@
}
}
+ private void maybeWriteParamFilesLocally(Spawn spawn) throws IOException {
+ if (!executionOptions.materializeParamFiles) {
+ return;
+ }
+ for (ActionInput actionInput : spawn.getInputFiles()) {
+ if (actionInput instanceof ParamFileActionInput) {
+ ParamFileActionInput paramFileActionInput = (ParamFileActionInput) actionInput;
+ Path outputPath = execRoot.getRelative(paramFileActionInput.getExecPath());
+ if (outputPath.exists()) {
+ outputPath.delete();
+ }
+ outputPath.getParentDirectory().createDirectoryAndParents();
+ try (OutputStream out = outputPath.getOutputStream()) {
+ paramFileActionInput.writeTo(out);
+ }
+ }
+ }
+ }
+
private void maybeDownloadServerLogs(ExecuteResponse resp, ActionKey actionKey)
throws InterruptedException {
ActionResult result = resp.getResult();
@@ -271,7 +297,7 @@
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
- if (options.remoteLocalFallback
+ if (remoteOptions.remoteLocalFallback
&& !(cause instanceof RetryException
&& RemoteRetrierUtils.causedByExecTimeout((RetryException) cause))) {
return execLocally(spawn, context, inputMap, uploadLocalResults, remoteCache, actionKey);
diff --git a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
index 5bb5f6a..89962f1 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
@@ -41,6 +41,7 @@
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
import com.google.devtools.build.lib.clock.JavaClock;
+import com.google.devtools.build.lib.exec.ExecutionOptions;
import com.google.devtools.build.lib.exec.SpawnExecException;
import com.google.devtools.build.lib.exec.SpawnInputExpander;
import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
@@ -247,21 +248,25 @@
FileSystemUtils.createDirectoryAndParents(stdout.getParentDirectory());
FileSystemUtils.createDirectoryAndParents(stderr.getParentDirectory());
outErr = new FileOutErr(stdout, stderr);
- RemoteOptions options = Options.getDefaults(RemoteOptions.class);
+ RemoteOptions remoteOptions = Options.getDefaults(RemoteOptions.class);
RemoteRetrier retrier =
new RemoteRetrier(
- options, RemoteRetrier.RETRIABLE_GRPC_ERRORS, retryService, Retrier.ALLOW_ALL_CALLS);
+ remoteOptions,
+ RemoteRetrier.RETRIABLE_GRPC_ERRORS,
+ retryService,
+ Retrier.ALLOW_ALL_CALLS);
Channel channel = InProcessChannelBuilder.forName(fakeServerName).directExecutor().build();
GrpcRemoteExecutor executor =
- new GrpcRemoteExecutor(channel, null, options.remoteTimeout, retrier);
+ new GrpcRemoteExecutor(channel, null, remoteOptions.remoteTimeout, retrier);
CallCredentials creds =
GoogleAuthUtils.newCallCredentials(Options.getDefaults(AuthAndTLSOptions.class));
GrpcRemoteCache remoteCache =
- new GrpcRemoteCache(channel, creds, options, retrier, DIGEST_UTIL);
+ new GrpcRemoteCache(channel, creds, remoteOptions, retrier, DIGEST_UTIL);
client =
new RemoteSpawnRunner(
execRoot,
- options,
+ remoteOptions,
+ Options.getDefaults(ExecutionOptions.class),
null,
true,
/*cmdlineReporter=*/ null,
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
index 0b6e052..9eead30 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.remote;
import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
@@ -28,12 +29,15 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.EventBus;
+import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.SettableFuture;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
+import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecutionRequirements;
import com.google.devtools.build.lib.actions.MetadataProvider;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.actions.SimpleSpawn;
import com.google.devtools.build.lib.actions.Spawn;
@@ -44,6 +48,7 @@
import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.exec.ExecutionOptions;
import com.google.devtools.build.lib.exec.SpawnExecException;
import com.google.devtools.build.lib.exec.SpawnInputExpander;
import com.google.devtools.build.lib.exec.SpawnRunner;
@@ -69,6 +74,8 @@
import com.google.protobuf.ByteString;
import com.google.rpc.Code;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collection;
import java.util.SortedMap;
@@ -139,6 +146,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -198,6 +206,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -251,6 +260,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -300,6 +310,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -335,6 +346,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
false,
reporter,
@@ -392,6 +404,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -432,6 +445,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -469,6 +483,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -504,6 +519,7 @@
new RemoteSpawnRunner(
execRoot,
Options.getDefaults(RemoteOptions.class),
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -544,6 +560,7 @@
new RemoteSpawnRunner(
execRoot,
Options.getDefaults(RemoteOptions.class),
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -587,6 +604,7 @@
new RemoteSpawnRunner(
execRoot,
Options.getDefaults(RemoteOptions.class),
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -625,6 +643,7 @@
new RemoteSpawnRunner(
execRoot,
Options.getDefaults(RemoteOptions.class),
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -667,6 +686,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -709,6 +729,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -757,6 +778,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -803,6 +825,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -844,6 +867,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -881,6 +905,7 @@
new RemoteSpawnRunner(
execRoot,
options,
+ Options.getDefaults(ExecutionOptions.class),
localRunner,
true,
/*cmdlineReporter=*/ null,
@@ -905,6 +930,58 @@
}
}
+ @Test
+ public void testMaterializeParamFiles() throws Exception {
+ ExecutionOptions executionOptions =
+ Options.parse(ExecutionOptions.class, "--materialize_param_files").getOptions();
+ executionOptions.materializeParamFiles = true;
+ RemoteSpawnRunner runner =
+ new RemoteSpawnRunner(
+ execRoot,
+ Options.getDefaults(RemoteOptions.class),
+ executionOptions,
+ localRunner,
+ true,
+ /*cmdlineReporter=*/ null,
+ "build-req-id",
+ "command-id",
+ cache,
+ executor,
+ digestUtil,
+ logDir);
+
+ ExecuteResponse succeeded =
+ ExecuteResponse.newBuilder()
+ .setResult(ActionResult.newBuilder().setExitCode(0).build())
+ .build();
+ when(executor.executeRemotely(any(ExecuteRequest.class))).thenReturn(succeeded);
+
+ ImmutableList<String> args = ImmutableList.of("--foo", "--bar");
+ ParamFileActionInput input =
+ new ParamFileActionInput(
+ PathFragment.create("out/param_file"), args, ParameterFileType.UNQUOTED, ISO_8859_1);
+ Spawn spawn =
+ new SimpleSpawn(
+ new FakeOwner("foo", "bar"),
+ /*arguments=*/ ImmutableList.of(),
+ /*environment=*/ ImmutableMap.of(),
+ /*executionInfo=*/ ImmutableMap.of(),
+ ImmutableList.of(input),
+ /*outputs=*/ ImmutableList.<ActionInput>of(),
+ ResourceSet.ZERO);
+ SpawnExecutionContext policy = new FakeSpawnExecutionContext(spawn);
+ SpawnResult res = runner.exec(spawn, policy);
+ assertThat(res.status()).isEqualTo(Status.SUCCESS);
+ Path paramFile = execRoot.getRelative("out/param_file");
+ assertThat(paramFile.exists()).isTrue();
+ try (InputStream inputStream = paramFile.getInputStream()) {
+ assertThat(
+ new String(ByteStreams.toByteArray(inputStream), StandardCharsets.UTF_8).split("\n"))
+ .asList()
+ .containsExactly("--foo", "--bar");
+ }
+ }
+
private static Spawn newSimpleSpawn() {
return new SimpleSpawn(
new FakeOwner("foo", "bar"),