in src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java [263:613]
public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
RunOptions runOptions = options.getOptions(RunOptions.class);
// This list should look like: ["//executable:target", "arg1", "arg2"]
List<String> targetAndArgs = options.getResidue();
// The user must at the least specify an executable target.
if (targetAndArgs.isEmpty()) {
return reportAndCreateFailureResult(
env, "Must specify a target to run", Code.NO_TARGET_SPECIFIED);
}
String targetString = targetAndArgs.get(0);
List<String> commandLineArgs = targetAndArgs.subList(1, targetAndArgs.size());
RunUnder runUnder = options.getOptions(CoreOptions.class).runUnder;
OutErr outErr = env.getReporter().getOutErr();
List<String> targets = (runUnder != null) && (runUnder.getLabel() != null)
? ImmutableList.of(targetString, runUnder.getLabel().toString())
: ImmutableList.of(targetString);
BuildRequest request =
BuildRequest.builder()
.setCommandName(this.getClass().getAnnotation(Command.class).name())
.setId(env.getCommandId())
.setOptions(options)
.setStartupOptions(env.getRuntime().getStartupOptionsProvider())
.setOutErr(outErr)
.setTargets(targets)
.setStartTimeMillis(env.getCommandStartTime())
.build();
currentRunUnder = runUnder;
BuildResult result;
try {
result = processRequest(env, request);
} finally {
currentRunUnder = null;
}
if (!result.getSuccess()) {
env.getReporter().handle(Event.error("Build failed. Not running target"));
return BlazeCommandResult.detailedExitCode(result.getDetailedExitCode());
}
// Make sure that we have exactly 1 built target (excluding --run_under),
// and that it is executable.
// These checks should only fail if keepGoing is true, because we already did
// validation before the build began. See {@link #validateTargets()}.
Collection<ConfiguredTarget> topLevelTargets = result.getSuccessfulTargets();
ConfiguredTarget targetToRun = null;
ConfiguredTarget runUnderTarget = null;
if (topLevelTargets != null) {
int maxTargets = runUnder != null && runUnder.getLabel() != null ? 2 : 1;
if (topLevelTargets.size() > maxTargets) {
return reportAndCreateFailureResult(
env,
makeErrorMessageForNotHavingASingleTarget(
targetString, Iterables.transform(topLevelTargets, ct -> ct.getLabel().toString())),
Code.TOO_MANY_TARGETS_SPECIFIED);
}
for (ConfiguredTarget target : topLevelTargets) {
BlazeCommandResult targetValidation = fullyValidateTarget(env, target);
if (!targetValidation.isSuccess()) {
return targetValidation;
}
if (runUnder != null && target.getLabel().equals(runUnder.getLabel())) {
if (runUnderTarget != null) {
return reportAndCreateFailureResult(
env,
"Can't identify the run_under target from multiple options?",
Code.RUN_UNDER_TARGET_NOT_BUILT);
}
runUnderTarget = target;
} else if (targetToRun == null) {
targetToRun = target;
} else {
return reportAndCreateFailureResult(
env,
makeErrorMessageForNotHavingASingleTarget(
targetString,
Iterables.transform(topLevelTargets, ct -> ct.getLabel().toString())),
Code.TOO_MANY_TARGETS_SPECIFIED);
}
}
}
// Handle target & run_under referring to the same target.
if (targetToRun == null && runUnderTarget != null) {
targetToRun = runUnderTarget;
}
if (targetToRun == null) {
return reportAndCreateFailureResult(env, NO_TARGET_MESSAGE, Code.NO_TARGET_SPECIFIED);
}
BuildConfigurationValue configuration =
env.getSkyframeExecutor()
.getConfiguration(env.getReporter(), targetToRun.getConfigurationKey());
if (configuration == null) {
// The target may be an input file, which doesn't have a configuration. In that case, we
// choose any target configuration.
configuration = result.getBuildConfigurationCollection().getTargetConfigurations().get(0);
}
if (!configuration.buildRunfilesManifests()) {
return reportAndCreateFailureResult(
env,
"--nobuild_runfile_manifests is incompatible with the \"run\" command",
Code.RUN_PREREQ_UNMET);
}
// Ensure runfiles directories are constructed, both for the target to run
// and the --run_under target. The path of the runfiles directory of the
// target to run needs to be preserved, as it acts as the working directory.
Path targetToRunRunfilesDir = null;
RunfilesSupport targetToRunRunfilesSupport = null;
for (ConfiguredTarget target : topLevelTargets) {
FilesToRunProvider provider = target.getProvider(FilesToRunProvider.class);
RunfilesSupport runfilesSupport = provider == null ? null : provider.getRunfilesSupport();
if (runfilesSupport == null) {
continue;
}
try {
Path runfilesDir =
ensureRunfilesBuilt(
env,
runfilesSupport,
env.getSkyframeExecutor()
.getConfiguration(env.getReporter(), target.getConfigurationKey()));
if (target == targetToRun) {
targetToRunRunfilesDir = runfilesDir;
targetToRunRunfilesSupport = runfilesSupport;
}
} catch (RunfilesException e) {
env.getReporter().handle(Event.error(e.getMessage()));
return BlazeCommandResult.failureDetail(e.createFailureDetail());
} catch (InterruptedException e) {
env.getReporter().handle(Event.error("Interrupted"));
return BlazeCommandResult.failureDetail(
FailureDetail.newBuilder()
.setInterrupted(Interrupted.newBuilder().setCode(Interrupted.Code.INTERRUPTED))
.build());
}
}
Map<String, String> runEnvironment = new TreeMap<>();
List<String> cmdLine = new ArrayList<>();
List<String> prettyCmdLine = new ArrayList<>();
Path workingDir;
runEnvironment.put("BUILD_WORKSPACE_DIRECTORY", env.getWorkspace().getPathString());
runEnvironment.put("BUILD_WORKING_DIRECTORY", env.getWorkingDirectory().getPathString());
if (targetToRun.getProvider(TestProvider.class) != null) {
// This is a test. Provide it with a reasonable approximation of the actual test environment
ImmutableList<Artifact.DerivedArtifact> statusArtifacts =
TestProvider.getTestStatusArtifacts(targetToRun);
if (statusArtifacts.size() != 1) {
return reportAndCreateFailureResult(
env, MULTIPLE_TESTS_MESSAGE, Code.TOO_MANY_TEST_SHARDS_OR_RUNS);
}
TestRunnerAction testAction = (TestRunnerAction) env.getSkyframeExecutor()
.getActionGraph(env.getReporter()).getGeneratingAction(
Iterables.getOnlyElement(statusArtifacts));
TestTargetExecutionSettings settings = testAction.getExecutionSettings();
// ensureRunfilesBuilt does build the runfiles, but an extra consistency check won't hurt.
Preconditions.checkState(
settings.getRunfilesSymlinksCreated()
== options.getOptions(CoreOptions.class).buildRunfiles);
ExecutionOptions executionOptions = options.getOptions(ExecutionOptions.class);
Path tmpDirRoot = TestStrategy.getTmpRoot(
env.getWorkspace(), env.getExecRoot(), executionOptions);
PathFragment maybeRelativeTmpDir =
tmpDirRoot.startsWith(env.getExecRoot())
? tmpDirRoot.relativeTo(env.getExecRoot())
: tmpDirRoot.asFragment();
Duration timeout =
configuration
.getFragment(TestConfiguration.class)
.getTestTimeout()
.get(testAction.getTestProperties().getTimeout());
runEnvironment.putAll(
testPolicy.computeTestEnvironment(
testAction,
env.getClientEnv(),
timeout,
settings.getRunfilesDir().relativeTo(env.getExecRoot()),
maybeRelativeTmpDir.getRelative(TestStrategy.getTmpDirName(testAction))));
workingDir = env.getExecRoot();
try {
testAction.prepare(
env.getExecRoot(),
ArtifactPathResolver.IDENTITY,
/*bulkDeleter=*/ null,
/*cleanupArchivedArtifacts=*/ false);
} catch (IOException e) {
return reportAndCreateFailureResult(
env,
"Error while setting up test: " + e.getMessage(),
Code.TEST_ENVIRONMENT_SETUP_FAILURE);
} catch (InterruptedException e) {
return reportAndCreateFailureResult(
env,
"Error while setting up test: " + e.getMessage(),
Code.TEST_ENVIRONMENT_SETUP_INTERRUPTED);
}
try {
cmdLine.addAll(TestStrategy.getArgs(testAction));
cmdLine.addAll(commandLineArgs);
prettyCmdLine.addAll(cmdLine);
} catch (ExecException e) {
return reportAndCreateFailureResult(
env, Strings.nullToEmpty(e.getMessage()), Code.COMMAND_LINE_EXPANSION_FAILURE);
} catch (InterruptedException e) {
String message = "run: command line expansion interrupted";
env.getReporter().handle(Event.error(message));
return BlazeCommandResult.detailedExitCode(
InterruptedFailureDetails.detailedExitCode(message));
}
} else {
workingDir =
targetToRunRunfilesDir != null ? targetToRunRunfilesDir : env.getWorkingDirectory();
if (targetToRunRunfilesSupport != null) {
targetToRunRunfilesSupport
.getActionEnvironment()
.resolve(runEnvironment, env.getClientEnv());
}
try {
List<String> args = computeArgs(targetToRun, commandLineArgs);
constructCommandLine(
cmdLine, prettyCmdLine, env, configuration, targetToRun, runUnderTarget, args);
} catch (NoShellFoundException e) {
return reportAndCreateFailureResult(
env,
"the \"run\" command needs a shell with \"--run_under\"; use the"
+ " --shell_executable=<path> flag to specify its path, e.g."
+ " --shell_executable=/bin/bash",
Code.NO_SHELL_SPECIFIED);
} catch (InterruptedException e) {
String message = "run: command line expansion interrupted";
env.getReporter().handle(Event.error(message));
return BlazeCommandResult.detailedExitCode(
InterruptedFailureDetails.detailedExitCode(message));
} catch (CommandLineExpansionException e) {
return reportAndCreateFailureResult(
env, Strings.nullToEmpty(e.getMessage()), Code.COMMAND_LINE_EXPANSION_FAILURE);
}
}
if (runOptions.scriptPath != null) {
String unisolatedCommand =
CommandFailureUtils.describeCommand(
CommandDescriptionForm.COMPLETE_UNISOLATED,
/* prettyPrintArgs= */ false,
cmdLine,
runEnvironment,
workingDir.getPathString(),
configuration.checksum(),
/* executionPlatform= */ null);
PathFragment shExecutable = ShToolchain.getPath(configuration);
if (shExecutable.isEmpty()) {
return reportAndCreateFailureResult(
env,
"the \"run\" command needs a shell with \"--script_path\"; use the"
+ " --shell_executable=<path> flag to specify its path, e.g."
+ " --shell_executable=/bin/bash",
Code.NO_SHELL_SPECIFIED);
}
try {
writeScript(env, shExecutable, runOptions.scriptPath, unisolatedCommand);
return BlazeCommandResult.success();
} catch (IOException e) {
String message = "Error writing run script: " + e.getMessage();
return reportAndCreateFailureResult(env, message, Code.SCRIPT_WRITE_FAILURE);
}
}
// We need to do update runEnvironment so that the environment of --batch is not contaminated
// with that required for the server. Note that some differences between the environment of
// the process being run and the environment of the client are still possible if the environment
// variables added for the server were not in the original client environment.
//
// This is done after writing the script for --script_path so that that is not contaminated
// with the original client environment (CommandFailureUtils.describeCommand() puts
// runEnvironment into the written script)
boolean batchMode = env.getRuntime().getStartupOptionsProvider()
.getOptions(BlazeServerStartupOptions.class).batch;
if (batchMode) {
runEnvironment.putAll(env.getClientEnv());
}
env.getReporter().handle(Event.info(
null, "Running command line: " + ShellEscaper.escapeJoinAll(prettyCmdLine)));
ExecRequest.Builder execDescription = ExecRequest.newBuilder()
.setWorkingDirectory(
ByteString.copyFrom(workingDir.getPathString(), StandardCharsets.ISO_8859_1));
if (OS.getCurrent() == OS.WINDOWS) {
boolean isBinary = true;
for (String arg : cmdLine) {
if (!isBinary) {
// All but the first element in `cmdLine` have to be escaped. The first element is the
// binary, which must not be escaped.
arg = ShellUtils.windowsEscapeArg(arg);
}
execDescription.addArgv(ByteString.copyFrom(arg, StandardCharsets.ISO_8859_1));
isBinary = false;
}
} else {
PathFragment shExecutable = ShToolchain.getPath(configuration);
if (shExecutable.isEmpty()) {
return reportAndCreateFailureResult(
env,
"the \"run\" command needs a shell with; use the --shell_executable=<path> "
+ "flag to specify the shell's path, e.g. --shell_executable=/bin/bash",
Code.NO_SHELL_SPECIFIED);
}
String shellEscaped = ShellEscaper.escapeJoinAll(cmdLine);
if (OS.getCurrent() == OS.WINDOWS) {
// On Windows, we run Bash as a subprocess of the client (via CreateProcessW).
// Bash uses its own (Bash-style) flag parsing logic, not the default logic for which
// ShellUtils.windowsEscapeArg escapes, so we escape the flags once again Bash-style.
shellEscaped = "\"" + shellEscaped.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
}
ImmutableList<String> shellCmdLine =
ImmutableList.<String>of(shExecutable.getPathString(), "-c", shellEscaped);
for (String arg : shellCmdLine) {
execDescription.addArgv(ByteString.copyFrom(arg, StandardCharsets.ISO_8859_1));
}
}
for (Map.Entry<String, String> variable : runEnvironment.entrySet()) {
execDescription.addEnvironmentVariable(EnvironmentVariable.newBuilder()
.setName(ByteString.copyFrom(variable.getKey(), StandardCharsets.ISO_8859_1))
.setValue(ByteString.copyFrom(variable.getValue(), StandardCharsets.ISO_8859_1))
.build());
}
return BlazeCommandResult.execute(execDescription.build());
}