| // Copyright 2014 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.devtools.build.lib.runtime.commands; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static java.nio.charset.StandardCharsets.ISO_8859_1; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSortedMap; |
| import com.google.common.collect.ImmutableSortedSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.devtools.build.lib.actions.ActionEnvironment; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.ArtifactPathResolver; |
| import com.google.devtools.build.lib.actions.CommandLine; |
| import com.google.devtools.build.lib.actions.CommandLineExpansionException; |
| import com.google.devtools.build.lib.actions.ExecException; |
| import com.google.devtools.build.lib.analysis.AliasProvider; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.RunEnvironmentInfo; |
| import com.google.devtools.build.lib.analysis.RunfilesSupport; |
| import com.google.devtools.build.lib.analysis.ShToolchain; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.config.CoreOptions; |
| import com.google.devtools.build.lib.analysis.config.RunUnder; |
| import com.google.devtools.build.lib.analysis.test.TestConfiguration; |
| import com.google.devtools.build.lib.analysis.test.TestProvider; |
| import com.google.devtools.build.lib.analysis.test.TestRunnerAction; |
| import com.google.devtools.build.lib.analysis.test.TestStrategy; |
| import com.google.devtools.build.lib.analysis.test.TestTargetExecutionSettings; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId; |
| import com.google.devtools.build.lib.buildtool.BuildRequest; |
| import com.google.devtools.build.lib.buildtool.BuildRequestOptions; |
| import com.google.devtools.build.lib.buildtool.BuildResult; |
| import com.google.devtools.build.lib.buildtool.BuildTool; |
| import com.google.devtools.build.lib.buildtool.PathPrettyPrinter; |
| import com.google.devtools.build.lib.buildtool.buildevent.ExecRequestEvent; |
| import com.google.devtools.build.lib.buildtool.buildevent.RunBuildCompleteEvent; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| 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.RunfilesTreeUpdater; |
| import com.google.devtools.build.lib.exec.TestPolicy; |
| import com.google.devtools.build.lib.packages.InputFile; |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; |
| import com.google.devtools.build.lib.packages.OutputFile; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.packages.TargetUtils; |
| import com.google.devtools.build.lib.packages.Type; |
| import com.google.devtools.build.lib.pkgcache.LoadingFailedException; |
| import com.google.devtools.build.lib.runtime.BlazeCommand; |
| import com.google.devtools.build.lib.runtime.BlazeCommandResult; |
| import com.google.devtools.build.lib.runtime.BlazeServerStartupOptions; |
| import com.google.devtools.build.lib.runtime.Command; |
| import com.google.devtools.build.lib.runtime.CommandEnvironment; |
| import com.google.devtools.build.lib.server.CommandProtos; |
| import com.google.devtools.build.lib.server.CommandProtos.EnvironmentVariable; |
| import com.google.devtools.build.lib.server.CommandProtos.ExecRequest; |
| import com.google.devtools.build.lib.server.CommandProtos.PathToReplace; |
| import com.google.devtools.build.lib.server.FailureDetails; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.server.FailureDetails.Interrupted; |
| import com.google.devtools.build.lib.server.FailureDetails.RunCommand.Code; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.ExitCode; |
| import com.google.devtools.build.lib.util.InterruptedFailureDetails; |
| import com.google.devtools.build.lib.util.OS; |
| import com.google.devtools.build.lib.util.OptionsUtils; |
| import com.google.devtools.build.lib.util.ScriptUtil; |
| 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.common.options.Option; |
| import com.google.devtools.common.options.OptionDocumentationCategory; |
| import com.google.devtools.common.options.OptionEffectTag; |
| import com.google.devtools.common.options.OptionsBase; |
| import com.google.devtools.common.options.OptionsParser; |
| import com.google.devtools.common.options.OptionsParsingResult; |
| import com.google.protobuf.ByteString; |
| import java.io.IOException; |
| import java.time.Duration; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import javax.annotation.Nullable; |
| |
| /** Builds and run a target with the given command line arguments. */ |
| @Command( |
| name = "run", |
| builds = true, |
| options = {RunCommand.RunOptions.class}, |
| inherits = {BuildCommand.class}, |
| shortDescription = "Runs the specified target.", |
| help = "resource:run.txt", |
| allowResidue = true, |
| hasSensitiveResidue = true, |
| completion = "label-bin") |
| public class RunCommand implements BlazeCommand { |
| /** Options for the "run" command. */ |
| public static class RunOptions extends OptionsBase { |
| @Option( |
| name = "script_path", |
| defaultValue = "null", |
| documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, |
| effectTags = {OptionEffectTag.AFFECTS_OUTPUTS, OptionEffectTag.EXECUTION}, |
| converter = OptionsUtils.PathFragmentConverter.class, |
| help = |
| "If set, write a shell script to the given file which invokes the target. If this" |
| + " option is set, the target is not run from %{product}. Use '%{product} run" |
| + " --script_path=foo //foo && ./foo' to invoke target '//foo' This differs from" |
| + " '%{product} run //foo' in that the %{product} lock is released and the" |
| + " executable is connected to the terminal's stdin.") |
| public PathFragment scriptPath; |
| |
| @Option( |
| name = "emit_script_path_in_exec_request", |
| defaultValue = "false", |
| documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, |
| effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, |
| help = |
| "If true, emits the ExecRequest with --script_path file value and script contents" |
| + " instead of writing the script.") |
| public boolean emitScriptPathInExecRequest; |
| |
| @Option( |
| name = "run", |
| defaultValue = "true", |
| documentationCategory = OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS, |
| effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, |
| help = |
| "If false, skip running the command line constructed for the built target. Note that" |
| + " this flag is ignored for all --script_path builds.") |
| public boolean runBuiltTarget; |
| |
| @Option( |
| name = "portable_paths", |
| defaultValue = "false", |
| documentationCategory = OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS, |
| effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, |
| help = |
| "If true, includes paths to replace in ExecRequest to make the resulting paths" |
| + " portable.") |
| public boolean portablePaths; |
| } |
| |
| private static final String NO_TARGET_MESSAGE = "No targets found to run"; |
| |
| private static final String MULTIPLE_TESTS_MESSAGE = |
| "'run' only works with tests with one shard ('--test_sharding_strategy=disabled' is okay) " |
| + "and without --runs_per_test"; |
| |
| private static final ImmutableList<String> ENV_VARIABLES_TO_CLEAR = |
| ImmutableList.of( |
| // These variables are all used by runfiles libraries to locate the runfiles directory or |
| // manifest and can cause incorrect behavior when set for the top-level binary run with |
| // bazel run. |
| "JAVA_RUNFILES", |
| "RUNFILES_DIR", |
| "RUNFILES_MANIFEST_FILE", |
| "RUNFILES_MANIFEST_ONLY", |
| "TEST_SRCDIR"); |
| |
| /** The test policy to determine the environment variables from when running tests */ |
| private final TestPolicy testPolicy; |
| |
| public RunCommand(TestPolicy testPolicy) { |
| this.testPolicy = testPolicy; |
| } |
| |
| @Override |
| public void editOptions(OptionsParser optionsParser) {} |
| |
| /** Returns the arguments in a {@link ConfiguredTarget}'s {@code args} attribute. */ |
| private static ImmutableList<String> getBinaryArgs(ConfiguredTarget targetToRun) |
| throws InterruptedException, CommandLineExpansionException { |
| List<String> args = Lists.newArrayList(); |
| |
| FilesToRunProvider provider = targetToRun.getProvider(FilesToRunProvider.class); |
| RunfilesSupport runfilesSupport = provider == null ? null : provider.getRunfilesSupport(); |
| if (runfilesSupport != null && runfilesSupport.getArgs() != null) { |
| CommandLine targetArgs = runfilesSupport.getArgs(); |
| Iterables.addAll(args, targetArgs.arguments()); |
| } |
| return ImmutableList.copyOf(args); |
| } |
| |
| @Override |
| 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()) { |
| env.getReporter() |
| .post( |
| new RunBuildCompleteEvent( |
| ExitCode.COMMAND_LINE_ERROR, env.getRuntime().getClock().currentTimeMillis())); |
| return reportAndCreateFailureResult( |
| env, "Must specify a target to run", Code.NO_TARGET_SPECIFIED); |
| } |
| String targetString = targetAndArgs.get(0); |
| RunUnder runUnder = options.getOptions(CoreOptions.class).runUnder; |
| |
| BuiltTargets builtTargets; |
| try { |
| builtTargets = runBuild(env, options, targetString, runUnder); |
| } catch (RunCommandException e) { |
| env.getReporter() |
| .post( |
| new RunBuildCompleteEvent( |
| e.result.getDetailedExitCode().getExitCode(), e.finishTimeMillis)); |
| return e.result; |
| } |
| ImmutableList.Builder<BuildEventId> runCompleteChildrenEvents = |
| ImmutableList.<BuildEventId>builder() |
| .add(BuildEventIdUtil.buildToolLogs()) |
| .add(BuildEventIdUtil.buildMetrics()); |
| if (runOptions.scriptPath == null) { |
| runCompleteChildrenEvents.add(BuildEventIdUtil.execRequestId()); |
| } |
| env.getReporter() |
| .post( |
| new RunBuildCompleteEvent( |
| // If the build returned non-zero exit code, an error would have already been |
| // thrown. |
| ExitCode.SUCCESS, builtTargets.stopTime, runCompleteChildrenEvents.build())); |
| ImmutableList<String> argsFromResidue = |
| ImmutableList.copyOf(targetAndArgs.subList(1, targetAndArgs.size())); |
| RunCommandLine runCommandLine; |
| try { |
| runCommandLine = getCommandLineInfo(env, builtTargets, options, argsFromResidue, testPolicy); |
| } catch (RunCommandException e) { |
| return e.result; |
| } |
| boolean batchMode = |
| env.getRuntime() |
| .getStartupOptionsProvider() |
| .getOptions(BlazeServerStartupOptions.class) |
| .batch; |
| TreeMap<String, String> finalRunEnv = new TreeMap<>(runCommandLine.getEnvironment()); |
| if (batchMode) { |
| // In --batch, prioritize original client env-var values over those added by the c++ launcher. |
| // Only necessary in --batch since the command runs as a subprocess of the java server. |
| finalRunEnv.putAll(env.getClientEnv()); |
| } |
| ExecRequest.Builder execRequest; |
| try { |
| boolean shouldRunTarget = runOptions.scriptPath == null && runOptions.runBuiltTarget; |
| ImmutableList<PathToReplace> pathsToReplace = |
| runOptions.portablePaths |
| ? getPathsToReplace( |
| env, |
| /* testLogDir= */ builtTargets |
| .configuration |
| .getTestLogsDirectory(RepositoryName.MAIN) |
| .getExecPathString(), |
| runCommandLine.isTestTarget()) |
| : ImmutableList.of(); |
| |
| execRequest = |
| execRequestBuilder( |
| env, |
| runCommandLine, |
| ImmutableSortedMap.copyOf(finalRunEnv), |
| ENV_VARIABLES_TO_CLEAR, |
| builtTargets.configuration, |
| builtTargets.stopTime, |
| shouldRunTarget, |
| pathsToReplace); |
| } catch (RunCommandException e) { |
| return e.result; |
| } |
| |
| if (runOptions.scriptPath != null) { |
| return handleScriptPath(runOptions, execRequest, runCommandLine, env, builtTargets); |
| } |
| if (runOptions.runBuiltTarget) { |
| env.getReporter() |
| .handle(Event.info(null, "Running command line: " + runCommandLine.getPrettyArgs())); |
| } else { |
| env.getReporter() |
| .handle(Event.info(null, "Runnable command line: " + runCommandLine.getPrettyArgs())); |
| } |
| |
| try { |
| env.getReporter() |
| .post( |
| new ExecRequestEvent( |
| execRequest.build(), |
| /* redactedArgv= */ options.getOptions(BuildEventProtocolOptions.class) |
| .includeResidueInRunBepEvent |
| ? ImmutableList.copyOf(execRequest.getArgvList()) |
| : getArgvWithoutResidue( |
| env, runCommandLine, builtTargets.configuration, builtTargets.stopTime))); |
| return BlazeCommandResult.execute(execRequest.build()); |
| } catch (RunCommandException e) { |
| return e.result; |
| } |
| } |
| |
| private static BuiltTargets runBuild( |
| CommandEnvironment env, |
| OptionsParsingResult options, |
| String targetString, |
| @Nullable RunUnder runUnder) |
| throws RunCommandException { |
| ImmutableList<String> targetsToBuild = |
| (runUnder != null) && (runUnder.getLabel() != null) |
| ? ImmutableList.of(targetString, runUnder.getLabel().toString()) |
| : ImmutableList.of(targetString); |
| BuildRequest request = |
| BuildRequest.builder() |
| .setCommandName(RunCommand.class.getAnnotation(Command.class).name()) |
| .setId(env.getCommandId()) |
| .setOptions(options) |
| .setStartupOptions(env.getRuntime().getStartupOptionsProvider()) |
| .setOutErr(env.getReporter().getOutErr()) |
| .setTargets(targetsToBuild) |
| .setStartTimeMillis(env.getCommandStartTime()) |
| .build(); |
| |
| BuildResult buildResult = |
| new BuildTool(env) |
| .processRequest( |
| request, |
| (Collection<Target> tgts, boolean keepGoing) -> |
| validateTargets( |
| env.getReporter(), request.getTargets(), tgts, runUnder, keepGoing)); |
| if (!buildResult.getSuccess()) { |
| env.getReporter().handle(Event.error("Build failed. Not running target")); |
| throw new RunCommandException( |
| BlazeCommandResult.detailedExitCode(buildResult.getDetailedExitCode()), |
| buildResult.getStopTime()); |
| } |
| // Build succeeded - make sure outputs are available before attempting to use them. |
| flushOutputs(env); |
| |
| return getBuiltTargets(buildResult, env, targetString, runUnder); |
| } |
| |
| private static BuiltTargets getBuiltTargets( |
| BuildResult result, CommandEnvironment env, String targetString, RunUnder runUnder) |
| throws RunCommandException { |
| Collection<ConfiguredTarget> topLevelTargets = result.getSuccessfulTargets(); |
| ConfiguredTarget targetToRun = null; |
| ConfiguredTarget runUnderTarget = null; |
| |
| if (topLevelTargets != null) { |
| // 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 in validateTargets(). |
| int maxTargets = runUnder != null && runUnder.getLabel() != null ? 2 : 1; |
| if (topLevelTargets.size() > maxTargets) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, |
| makeErrorMessageForNotHavingASingleTarget( |
| targetString, |
| Iterables.transform(topLevelTargets, ct -> ct.getLabel().toString())), |
| Code.TOO_MANY_TARGETS_SPECIFIED), |
| result.getStopTime()); |
| } |
| |
| for (ConfiguredTarget target : topLevelTargets) { |
| BlazeCommandResult targetValidationResult = fullyValidateTarget(env, target); |
| if (!targetValidationResult.isSuccess()) { |
| throw new RunCommandException(targetValidationResult, result.getStopTime()); |
| } |
| if (runUnder != null && target.getOriginalLabel().equals(runUnder.getLabel())) { |
| if (runUnderTarget != null) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, |
| "Can't identify the run_under target from multiple options?", |
| Code.RUN_UNDER_TARGET_NOT_BUILT), |
| result.getStopTime()); |
| } |
| runUnderTarget = target; |
| } else if (targetToRun == null) { |
| targetToRun = target; |
| } else { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, |
| makeErrorMessageForNotHavingASingleTarget( |
| targetString, |
| Iterables.transform(topLevelTargets, ct -> ct.getLabel().toString())), |
| Code.TOO_MANY_TARGETS_SPECIFIED), |
| result.getStopTime()); |
| } |
| } |
| } |
| |
| // Handle target & run_under referring to the same target. |
| if (targetToRun == null && runUnderTarget != null) { |
| targetToRun = runUnderTarget; |
| } |
| |
| if (targetToRun == null) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult(env, NO_TARGET_MESSAGE, Code.NO_TARGET_SPECIFIED), |
| result.getStopTime()); |
| } |
| |
| 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.getBuildConfiguration(); |
| } |
| |
| if (!configuration.buildRunfileManifests()) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, |
| "--nobuild_runfile_manifests is incompatible with the \"run\" command", |
| Code.RUN_PREREQ_UNMET), |
| result.getStopTime()); |
| } |
| |
| // 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; |
| RunfilesTreeUpdater runfilesTreeUpdater = RunfilesTreeUpdater.forCommandEnvironment(env); |
| 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()), |
| runfilesTreeUpdater); |
| if (target == targetToRun) { |
| targetToRunRunfilesDir = runfilesDir; |
| targetToRunRunfilesSupport = runfilesSupport; |
| } |
| } catch (RunfilesException e) { |
| env.getReporter().handle(Event.error(e.getMessage())); |
| throw new RunCommandException( |
| BlazeCommandResult.failureDetail(e.createFailureDetail()), result.getStopTime()); |
| } catch (InterruptedException e) { |
| env.getReporter().handle(Event.error("Interrupted")); |
| throw new RunCommandException( |
| BlazeCommandResult.failureDetail( |
| FailureDetail.newBuilder() |
| .setInterrupted(Interrupted.newBuilder().setCode(Interrupted.Code.INTERRUPTED)) |
| .build()), |
| result.getStopTime()); |
| } |
| } |
| return new BuiltTargets( |
| targetToRun, |
| targetToRunRunfilesDir, |
| targetToRunRunfilesSupport, |
| runUnderTarget, |
| configuration, |
| result.getConvenienceSymlinks(), |
| result.getStopTime()); |
| } |
| |
| private static ExecRequest.Builder execRequestBuilder( |
| CommandEnvironment env, |
| RunCommandLine runCommandLine, |
| ImmutableSortedMap<String, String> runEnv, |
| ImmutableList<String> runEnvToClear, |
| BuildConfigurationValue configuration, |
| long stopTime, |
| boolean shouldRunTarget, |
| ImmutableList<PathToReplace> pathsToReplace) |
| throws RunCommandException { |
| ExecRequest.Builder execDescription = |
| ExecRequest.newBuilder() |
| .setWorkingDirectory( |
| ByteString.copyFrom(runCommandLine.getWorkingDir().getPathString(), ISO_8859_1)) |
| .addAllArgv(getArgvForExecRequest(env, runCommandLine, configuration, stopTime)); |
| |
| for (Map.Entry<String, String> variable : runEnv.entrySet()) { |
| execDescription.addEnvironmentVariable( |
| EnvironmentVariable.newBuilder() |
| .setName(ByteString.copyFrom(variable.getKey(), ISO_8859_1)) |
| .setValue(ByteString.copyFrom(variable.getValue(), ISO_8859_1)) |
| .build()); |
| } |
| execDescription.addAllEnvironmentVariableToClear( |
| runEnvToClear.stream() |
| .map(s -> ByteString.copyFrom(s, ISO_8859_1)) |
| .collect(toImmutableList())); |
| execDescription.setShouldExec(shouldRunTarget); |
| execDescription.addAllPathToReplace(pathsToReplace); |
| return execDescription; |
| } |
| |
| private static ImmutableList<PathToReplace> getPathsToReplace( |
| CommandEnvironment env, String testLogDir, boolean isTestTarget) { |
| ImmutableList.Builder<PathToReplace> pathsToReplace = |
| ImmutableList.<PathToReplace>builder() |
| .add( |
| CommandProtos.PathToReplace.newBuilder() |
| .setType(PathToReplace.Type.OUTPUT_BASE) |
| .setValue(ByteString.copyFrom(env.getOutputBase().getPathString(), ISO_8859_1)) |
| .build()) |
| .add( |
| CommandProtos.PathToReplace.newBuilder() |
| .setType(PathToReplace.Type.BUILD_WORKING_DIRECTORY) |
| .setValue( |
| ByteString.copyFrom(env.getWorkingDirectory().getPathString(), ISO_8859_1)) |
| .build()) |
| .add( |
| CommandProtos.PathToReplace.newBuilder() |
| .setType(PathToReplace.Type.BUILD_WORKSPACE_DIRECTORY) |
| .setValue( |
| ByteString.copyFrom(env.getWorkingDirectory().getPathString(), ISO_8859_1)) |
| .build()); |
| if (isTestTarget) { |
| pathsToReplace.add( |
| CommandProtos.PathToReplace.newBuilder() |
| .setType(PathToReplace.Type.TEST_LOG_SUBDIR) |
| .setValue(ByteString.copyFrom(testLogDir, ISO_8859_1)) |
| .build()); |
| } |
| return pathsToReplace.build(); |
| } |
| |
| private static ImmutableList<ByteString> getArgvForExecRequest( |
| CommandEnvironment env, |
| RunCommandLine runCommandLine, |
| BuildConfigurationValue configuration, |
| long stopTime) |
| throws RunCommandException { |
| return getArgv(env, runCommandLine, /* includeResidue= */ true, configuration, stopTime); |
| } |
| |
| private static ImmutableList<ByteString> getArgvWithoutResidue( |
| CommandEnvironment env, |
| RunCommandLine runCommandLine, |
| BuildConfigurationValue configuration, |
| long stopTime) |
| throws RunCommandException { |
| return getArgv(env, runCommandLine, /* includeResidue= */ false, configuration, stopTime); |
| } |
| |
| private static ImmutableList<ByteString> getArgv( |
| CommandEnvironment env, |
| RunCommandLine runCommandLine, |
| boolean includeResidue, |
| BuildConfigurationValue configuration, |
| long stopTime) |
| throws RunCommandException { |
| String shExecutable = null; |
| if (runCommandLine.requiresShExecutable()) { |
| shExecutable = getShellExecutableOrThrow(env, configuration, /* reason= */ "", stopTime); |
| } |
| ImmutableList<String> args = |
| includeResidue |
| ? runCommandLine.getArgs(shExecutable) |
| : runCommandLine.getArgsWithoutResidue(shExecutable); |
| return args.stream().map(s -> ByteString.copyFrom(s, ISO_8859_1)).collect(toImmutableList()); |
| } |
| |
| private BlazeCommandResult handleScriptPath( |
| RunOptions runOptions, |
| ExecRequest.Builder execRequest, |
| RunCommandLine runCommandLine, |
| CommandEnvironment env, |
| BuiltTargets builtTargets) { |
| String shExecutable; |
| try { |
| shExecutable = |
| getShellExecutableOrThrow( |
| env, builtTargets.configuration, "with \"--script_path\"", builtTargets.stopTime); |
| } catch (RunCommandException e) { |
| return e.result; |
| } |
| |
| String scriptContents = |
| generateScriptContents( |
| shExecutable, |
| runCommandLine.getWorkingDir().getPathString(), |
| runCommandLine.getEnvironment(), |
| runCommandLine.getScriptPathCommandLine()); |
| if (runOptions.emitScriptPathInExecRequest) { |
| execRequest.setScriptPath( |
| CommandProtos.ScriptPath.newBuilder() |
| .setScriptPath(ByteString.copyFrom(runOptions.scriptPath.toString(), ISO_8859_1)) |
| .setScriptContents(ByteString.copyFrom(scriptContents, ISO_8859_1)) |
| .build()); |
| return BlazeCommandResult.execute(execRequest.build()); |
| } else { |
| try { |
| writeScript(env, runOptions.scriptPath, scriptContents); |
| } catch (IOException e) { |
| String message = "Error writing run script: " + e.getMessage(); |
| return reportAndCreateFailureResult(env, message, Code.SCRIPT_WRITE_FAILURE); |
| } |
| return BlazeCommandResult.success(); |
| } |
| } |
| |
| private static RunCommandLine getCommandLineInfo( |
| CommandEnvironment env, |
| BuiltTargets builtTargets, |
| OptionsParsingResult options, |
| ImmutableList<String> argsFromResidue, |
| TestPolicy testPolicy) |
| throws RunCommandException { |
| if (builtTargets.targetToRun.getProvider(TestProvider.class) != null) { |
| return getTestCommandLine(env, builtTargets, options, argsFromResidue, testPolicy); |
| } |
| |
| ActionEnvironment actionEnvironment = ActionEnvironment.EMPTY; |
| if (builtTargets.targetToRunRunfilesSupport != null) { |
| actionEnvironment = builtTargets.targetToRunRunfilesSupport.getActionEnvironment(); |
| } |
| RunEnvironmentInfo environmentProvider = |
| builtTargets.targetToRun.get(RunEnvironmentInfo.PROVIDER); |
| if (environmentProvider != null) { |
| actionEnvironment = |
| actionEnvironment.withAdditionalVariables( |
| environmentProvider.getEnvironment(), |
| ImmutableSet.copyOf(environmentProvider.getInheritedEnvironment())); |
| } |
| TreeMap<String, String> runEnvironment = makeMutableRunEnvironment(env); |
| actionEnvironment.resolve(runEnvironment, env.getClientEnv()); |
| |
| ImmutableList<String> argsFromBinary; |
| try { |
| argsFromBinary = getBinaryArgs(builtTargets.targetToRun); |
| } catch (InterruptedException e) { |
| String message = "run: command line expansion interrupted"; |
| env.getReporter().handle(Event.error(message)); |
| throw new RunCommandException( |
| BlazeCommandResult.detailedExitCode(InterruptedFailureDetails.detailedExitCode(message)), |
| builtTargets.stopTime); |
| } catch (CommandLineExpansionException e) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, Strings.nullToEmpty(e.getMessage()), Code.COMMAND_LINE_EXPANSION_FAILURE), |
| builtTargets.stopTime); |
| } |
| |
| return constructCommandLine( |
| env, |
| builtTargets, |
| ImmutableSortedMap.copyOf(runEnvironment), |
| argsFromBinary, |
| argsFromResidue); |
| } |
| |
| /** |
| * Returns the command line for the test, making a best effort to mimic the environment had we run |
| * `test //target`. |
| */ |
| private static RunCommandLine getTestCommandLine( |
| CommandEnvironment env, |
| BuiltTargets builtTargets, |
| OptionsParsingResult options, |
| ImmutableList<String> argsFromResidue, |
| TestPolicy testPolicy) |
| throws RunCommandException { |
| ImmutableList<Artifact.DerivedArtifact> statusArtifacts = |
| TestProvider.getTestStatusArtifacts(builtTargets.targetToRun); |
| if (statusArtifacts.size() != 1) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, MULTIPLE_TESTS_MESSAGE, Code.TOO_MANY_TEST_SHARDS_OR_RUNS), |
| builtTargets.stopTime); |
| } |
| |
| 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).buildRunfileLinks); |
| |
| 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 = |
| builtTargets |
| .configuration |
| .getFragment(TestConfiguration.class) |
| .getTestTimeout() |
| .get(testAction.getTestProperties().getTimeout()); |
| TreeMap<String, String> runEnvironment = makeMutableRunEnvironment(env); |
| runEnvironment.putAll( |
| testPolicy.computeTestEnvironment( |
| testAction, |
| env.getClientEnv(), |
| timeout, |
| settings.getRunfilesDir().relativeTo(env.getExecRoot()), |
| maybeRelativeTmpDir.getRelative(TestStrategy.getTmpDirName(testAction)))); |
| |
| try { |
| testAction.prepare( |
| env.getExecRoot(), |
| ArtifactPathResolver.IDENTITY, |
| /* bulkDeleter= */ null, |
| /* cleanupArchivedArtifacts= */ false); |
| } catch (IOException e) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, |
| "Error while setting up test: " + e.getMessage(), |
| Code.TEST_ENVIRONMENT_SETUP_FAILURE), |
| builtTargets.stopTime); |
| } catch (InterruptedException e) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, |
| "Error while setting up test: " + e.getMessage(), |
| Code.TEST_ENVIRONMENT_SETUP_INTERRUPTED), |
| builtTargets.stopTime); |
| } |
| |
| ImmutableList<String> testArgs; |
| try { |
| testArgs = TestStrategy.getArgs(testAction); |
| } catch (ExecException e) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, Strings.nullToEmpty(e.getMessage()), Code.COMMAND_LINE_EXPANSION_FAILURE), |
| builtTargets.stopTime); |
| } catch (InterruptedException e) { |
| String message = "run: command line expansion interrupted"; |
| env.getReporter().handle(Event.error(message)); |
| throw new RunCommandException( |
| BlazeCommandResult.detailedExitCode(InterruptedFailureDetails.detailedExitCode(message)), |
| builtTargets.stopTime); |
| } |
| |
| return new RunCommandLine.Builder( |
| ImmutableSortedMap.copyOf(runEnvironment), env.getExecRoot(), /* isTestTarget= */ true) |
| .addArgs(testArgs) |
| .addArgsFromResidue(argsFromResidue) |
| .build(); |
| } |
| |
| /** |
| * Returns a new {@link TreeMap} with environment variables common to all run invocations. The |
| * return value is a new, mutable instance - this is necessary since we want to maintain order and |
| * overwrite existing keys, something which isn't supported by current immutable implementations. |
| */ |
| @SuppressWarnings("NonApiType") // Sorted, mutable map is desired - see javadoc. |
| private static TreeMap<String, String> makeMutableRunEnvironment(CommandEnvironment env) { |
| TreeMap<String, String> result = new TreeMap<>(); |
| result.put("BUILD_WORKSPACE_DIRECTORY", env.getWorkspace().getPathString()); |
| result.put("BUILD_WORKING_DIRECTORY", env.getWorkingDirectory().getPathString()); |
| return result; |
| } |
| |
| private static RunCommandLine constructCommandLine( |
| CommandEnvironment env, |
| BuiltTargets builtTargets, |
| ImmutableSortedMap<String, String> runEnvironment, |
| ImmutableList<String> argsFromBinary, |
| ImmutableList<String> argsFromResidue) { |
| BuildRequestOptions requestOptions = env.getOptions().getOptions(BuildRequestOptions.class); |
| PathPrettyPrinter prettyPrinter = |
| new PathPrettyPrinter( |
| requestOptions.getSymlinkPrefix(env.getRuntime().getProductName()), |
| builtTargets.convenienceSymlinks); |
| |
| RunCommandLine.Builder runCommandLine = |
| new RunCommandLine.Builder( |
| runEnvironment, |
| /* workingDir= */ builtTargets.targetToRunRunfilesDir != null |
| ? builtTargets.targetToRunRunfilesDir |
| : env.getWorkingDirectory(), |
| /* isTestTarget= */ false); |
| |
| RunUnder runUnder = env.getOptions().getOptions(CoreOptions.class).runUnder; |
| // Insert the command prefix specified by the "--run_under=<command-prefix>" option |
| // at the start of the command line. |
| if (runUnder != null) { |
| if (builtTargets.runUnderTarget != null) { |
| // --run_under specifies a target. Get the corresponding executable, this will be an |
| // absolute path because the run_under target is only in the runfiles of test targets |
| Path runUnderPath = |
| builtTargets |
| .runUnderTarget |
| .getProvider(FilesToRunProvider.class) |
| .getExecutable() |
| .getPath(); |
| runCommandLine.setRunUnderTarget(runUnderPath, runUnder.getOptions(), prettyPrinter); |
| } else { |
| runCommandLine.setRunUnderPrefix(runUnder.getValue()); |
| } |
| } |
| |
| Artifact executable = |
| builtTargets.targetToRun.getProvider(FilesToRunProvider.class).getExecutable(); |
| return runCommandLine |
| .addArg(executable.getPath(), prettyPrinter) |
| .addArgs(argsFromBinary) |
| .addArgsFromResidue(argsFromResidue) |
| .build(); |
| } |
| |
| private static String getShellExecutableOrThrow( |
| CommandEnvironment env, BuildConfigurationValue configuration, String reason, long stopTime) |
| throws RunCommandException { |
| PathFragment shExecutable = ShToolchain.getPathForHost(configuration); |
| if (shExecutable.isEmpty()) { |
| throw new RunCommandException( |
| reportAndCreateFailureResult( |
| env, |
| "the \"run\" command needs a shell" |
| + reason |
| + "; use the --shell_executable=<path> " |
| + "flag to specify the shell's path, e.g. --shell_executable=/bin/bash", |
| Code.NO_SHELL_SPECIFIED), |
| stopTime); |
| } |
| return shExecutable.getPathString(); |
| } |
| |
| private static class RunCommandException extends Exception { |
| private final BlazeCommandResult result; |
| private final long finishTimeMillis; |
| |
| private RunCommandException(BlazeCommandResult result, long finishTimeMillis) { |
| Preconditions.checkArgument(!result.isSuccess(), "Success is not exceptional: %s", result); |
| this.result = result; |
| this.finishTimeMillis = finishTimeMillis; |
| } |
| } |
| |
| /** Contains the targets built as part of a run-command invocation. */ |
| private static class BuiltTargets { |
| private final ConfiguredTarget targetToRun; |
| private final Path targetToRunRunfilesDir; |
| private final RunfilesSupport targetToRunRunfilesSupport; |
| @Nullable private final ConfiguredTarget runUnderTarget; |
| private final BuildConfigurationValue configuration; |
| private final ImmutableMap<PathFragment, PathFragment> convenienceSymlinks; |
| private final long stopTime; |
| |
| private BuiltTargets( |
| ConfiguredTarget targetToRun, |
| Path targetToRunRunfilesDir, |
| RunfilesSupport targetToRunRunfilesSupport, |
| @Nullable ConfiguredTarget runUnderTarget, |
| BuildConfigurationValue configuration, |
| ImmutableMap<PathFragment, PathFragment> convenienceSymlinks, |
| long stopTime) { |
| this.targetToRun = targetToRun; |
| this.runUnderTarget = runUnderTarget; |
| this.targetToRunRunfilesDir = targetToRunRunfilesDir; |
| this.targetToRunRunfilesSupport = targetToRunRunfilesSupport; |
| this.configuration = configuration; |
| this.convenienceSymlinks = convenienceSymlinks; |
| this.stopTime = stopTime; |
| } |
| } |
| |
| /** |
| * When using an output service (e.g. Build without the Bytes), flushes the output tree, waiting |
| * for downloads to complete. This is necessary since outputs might still be downloading in the |
| * background. |
| */ |
| private static void flushOutputs(CommandEnvironment env) { |
| if (env.getOutputService() != null) { |
| try { |
| env.getOutputService().flushOutputTree(); |
| } catch (InterruptedException ignored) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } |
| |
| private static BlazeCommandResult reportAndCreateFailureResult( |
| CommandEnvironment env, String message, Code detailedCode) { |
| env.getReporter().handle(Event.error(message)); |
| return BlazeCommandResult.failureDetail(createFailureDetail(message, detailedCode)); |
| } |
| |
| /** |
| * Ensures that runfiles are built for the specified target. If they already are, does nothing, |
| * otherwise builds them. |
| */ |
| private static Path ensureRunfilesBuilt( |
| CommandEnvironment env, |
| RunfilesSupport runfilesSupport, |
| BuildConfigurationValue configuration, |
| RunfilesTreeUpdater runfilesTreeUpdater) |
| throws RunfilesException, InterruptedException { |
| PathFragment runfilesDir = runfilesSupport.getRunfilesTree().getExecPath(); |
| Path workingDir = env.getExecRoot().getRelative(runfilesDir); |
| // On Windows, runfiles tree is disabled. |
| // Workspace name directory doesn't exist, so don't add it. |
| if (configuration.runfilesEnabled()) { |
| workingDir = workingDir.getRelative(runfilesSupport.getRunfiles().getSuffix()); |
| } |
| |
| // Always create runfiles directory and the workspace-named directory underneath, even if we |
| // run with --enable_runfiles=no (which is the default on Windows as of 2020-01-24). |
| // If the binary we run is in fact a test, it will expect to be able to chdir into the runfiles |
| // directory. See https://github.com/bazelbuild/bazel/issues/10621 |
| try { |
| runfilesSupport |
| .getRunfilesDirectory() |
| .getRelative(runfilesSupport.getRunfilesTree().getWorkspaceName()) |
| .createDirectoryAndParents(); |
| } catch (IOException e) { |
| throw new RunfilesException( |
| "Failed to create runfiles directories: " + e.getMessage(), |
| Code.RUNFILES_DIRECTORIES_CREATION_FAILURE, |
| e); |
| } |
| |
| try { |
| runfilesTreeUpdater.updateRunfiles( |
| ImmutableList.of(runfilesSupport.getRunfilesTree()), |
| /* env= */ ImmutableMap.of(), |
| /* outErr= */ null); |
| } catch (ExecException | IOException e) { |
| throw new RunfilesException( |
| "Failed to create runfiles symlinks: " + e.getMessage(), |
| Code.RUNFILES_SYMLINKS_CREATION_FAILURE, |
| e); |
| } |
| return workingDir; |
| } |
| |
| private static void writeScript( |
| CommandEnvironment env, PathFragment scriptPathFrag, String scriptContent) |
| throws IOException { |
| Path scriptPath = env.getWorkingDirectory().getRelative(scriptPathFrag); |
| FileSystemUtils.writeContent(scriptPath, ISO_8859_1, scriptContent); |
| scriptPath.setExecutable(true); |
| } |
| |
| // Make sure we are building exactly 1 binary target. |
| // If keepGoing, we'll build all the targets even if they are non-binary. |
| private static void validateTargets( |
| Reporter reporter, |
| List<String> targetPatternStrings, |
| Collection<Target> targets, |
| RunUnder runUnder, |
| boolean keepGoing) |
| throws LoadingFailedException { |
| Target targetToRun = null; |
| Target runUnderTarget = null; |
| |
| boolean singleTargetWarningWasOutput = false; |
| int maxTargets = runUnder != null && runUnder.getLabel() != null ? 2 : 1; |
| if (targets.size() > maxTargets) { |
| warningOrException( |
| reporter, |
| makeErrorMessageForNotHavingASingleTarget( |
| targetPatternStrings.get(0), |
| Iterables.transform(targets, t -> t.getLabel().toString())), |
| keepGoing, |
| Code.TOO_MANY_TARGETS_SPECIFIED); |
| singleTargetWarningWasOutput = true; |
| } |
| for (Target target : targets) { |
| if (!isExecutable(target)) { |
| warningOrException( |
| reporter, notExecutableError(target), keepGoing, Code.TARGET_NOT_EXECUTABLE); |
| } |
| |
| if (runUnder != null && target.getLabel().equals(runUnder.getLabel())) { |
| // It's impossible to have two targets with the same label. |
| Preconditions.checkState(runUnderTarget == null); |
| runUnderTarget = target; |
| } else if (targetToRun == null) { |
| targetToRun = target; |
| } else { |
| if (!singleTargetWarningWasOutput) { |
| warningOrException( |
| reporter, |
| makeErrorMessageForNotHavingASingleTarget( |
| targetPatternStrings.get(0), |
| Iterables.transform(targets, t -> t.getLabel().toString())), |
| keepGoing, |
| Code.TOO_MANY_TARGETS_SPECIFIED); |
| } |
| return; |
| } |
| } |
| // Handle target & run_under referring to the same target. |
| if ((targetToRun == null) && (runUnderTarget != null)) { |
| targetToRun = runUnderTarget; |
| } |
| if (targetToRun == null) { |
| warningOrException(reporter, NO_TARGET_MESSAGE, keepGoing, Code.NO_TARGET_SPECIFIED); |
| } |
| } |
| |
| /** |
| * If keepGoing, print a warning and return the given collection. Otherwise, throw |
| * InvalidTargetException. |
| */ |
| private static void warningOrException( |
| Reporter reporter, String message, boolean keepGoing, Code detailedCode) |
| throws LoadingFailedException { |
| if (keepGoing) { |
| reporter.handle(Event.warn(message + ". Will continue anyway")); |
| } else { |
| throw new LoadingFailedException( |
| message, DetailedExitCode.of(createFailureDetail(message, detailedCode))); |
| } |
| } |
| |
| private static String notExecutableError(Target target) { |
| return "Cannot run target " + target.getLabel() + ": Not executable"; |
| } |
| |
| /** |
| * Performs all available validation checks on an individual target. |
| * |
| * @param configuredTarget ConfiguredTarget to validate |
| * @return BlazeCommandResult.exitCode(ExitCode.SUCCESS) if all checks succeeded, otherwise a |
| * result describing the failure. |
| * @throws IllegalStateException if unable to find a target from the package manager. |
| */ |
| private static BlazeCommandResult fullyValidateTarget( |
| CommandEnvironment env, ConfiguredTarget configuredTarget) { |
| |
| Target target; |
| try { |
| target = env.getPackageManager().getTarget(env.getReporter(), configuredTarget.getLabel()); |
| } catch (InterruptedException e) { |
| String message = "run command interrupted"; |
| env.getReporter().handle(Event.error(message)); |
| return BlazeCommandResult.detailedExitCode( |
| InterruptedFailureDetails.detailedExitCode(message)); |
| } catch (NoSuchTargetException | NoSuchPackageException e) { |
| env.getReporter().handle(Event.error("Failed to find a target to validate. " + e)); |
| throw new IllegalStateException("Failed to find a target to validate", e); |
| } |
| |
| if (!isExecutable(target)) { |
| return reportAndCreateFailureResult( |
| env, notExecutableError(target), Code.TARGET_NOT_EXECUTABLE); |
| } |
| |
| Artifact executable = |
| Preconditions.checkNotNull( |
| configuredTarget.getProvider(FilesToRunProvider.class), configuredTarget) |
| .getExecutable(); |
| if (executable == null) { |
| return reportAndCreateFailureResult( |
| env, notExecutableError(target), Code.TARGET_NOT_EXECUTABLE); |
| } |
| |
| Path executablePath = executable.getPath(); |
| try { |
| if (!executablePath.exists() || !executablePath.isExecutable()) { |
| return reportAndCreateFailureResult( |
| env, |
| "Non-existent or non-executable " + executablePath, |
| Code.TARGET_BUILT_BUT_PATH_NOT_EXECUTABLE); |
| } |
| } catch (IOException e) { |
| return reportAndCreateFailureResult( |
| env, |
| "Error checking " + executablePath.getPathString() + ": " + e.getMessage(), |
| Code.TARGET_BUILT_BUT_PATH_VALIDATION_FAILED); |
| } |
| |
| return BlazeCommandResult.success(); |
| } |
| |
| /** |
| * Return true iff it is possible that {@code target} is a rule that has an executable file. This |
| * *_test rules, *_binary rules, aliases, generated outputs, and inputs. |
| * |
| * <p>Determining definitively whether a rule produces an executable can only be done after |
| * analysis. This is only an early check to quickly catch most mistakes. |
| */ |
| private static boolean isExecutable(Target target) { |
| return isPlainFile(target) |
| || isExecutableNonTestRule(target) |
| || TargetUtils.isTestRule(target) |
| || AliasProvider.mayBeAlias(target); |
| } |
| |
| /** |
| * Return true iff {@code target} is a rule that generates an executable file and is user-executed |
| * code. |
| */ |
| private static boolean isExecutableNonTestRule(Target target) { |
| if (!(target instanceof Rule)) { |
| return false; |
| } |
| Rule rule = ((Rule) target); |
| if (rule.getRuleClassObject().hasAttr("$is_executable", Type.BOOLEAN)) { |
| return NonconfigurableAttributeMapper.of(rule).get("$is_executable", Type.BOOLEAN); |
| } |
| return false; |
| } |
| |
| private static boolean isPlainFile(Target target) { |
| return (target instanceof OutputFile) || (target instanceof InputFile); |
| } |
| |
| private static String makeErrorMessageForNotHavingASingleTarget( |
| String targetPatternString, Iterable<String> expandedTargetNames) { |
| final int maxNumExpandedTargetsToIncludeInErrorMessage = 5; |
| boolean truncateTargetNameList = Iterables.size(expandedTargetNames) > 5; |
| Iterable<String> targetNamesToIncludeInErrorMessage = |
| truncateTargetNameList |
| ? Iterables.limit(expandedTargetNames, maxNumExpandedTargetsToIncludeInErrorMessage) |
| : expandedTargetNames; |
| return String.format( |
| "Only a single target can be run. Your target pattern %s expanded to the targets %s%s", |
| targetPatternString, |
| Joiner.on(", ").join(ImmutableSortedSet.copyOf(targetNamesToIncludeInErrorMessage)), |
| truncateTargetNameList ? "[TRUNCATED]" : ""); |
| } |
| |
| private static FailureDetail createFailureDetail(String message, Code detailedCode) { |
| return FailureDetail.newBuilder() |
| .setMessage(message) |
| .setRunCommand(FailureDetails.RunCommand.newBuilder().setCode(detailedCode)) |
| .build(); |
| } |
| |
| private static class RunfilesException extends Exception { |
| private final FailureDetails.RunCommand.Code detailedCode; |
| |
| private RunfilesException(String message, Code detailedCode, Exception cause) { |
| super("Error creating runfiles: " + message, cause); |
| this.detailedCode = detailedCode; |
| } |
| |
| private FailureDetail createFailureDetail() { |
| return RunCommand.createFailureDetail(getMessage(), detailedCode); |
| } |
| } |
| |
| private static String generateScriptContents( |
| String shExecutable, String workingDir, Map<String, String> environment, String commandLine) { |
| StringBuilder result = new StringBuilder(); |
| if (OS.getCurrent() == OS.WINDOWS) { |
| result.append("@echo off\n"); |
| } else { |
| result.append("#!").append(shExecutable).append("\n"); |
| } |
| |
| // cd <cwd> && |
| ScriptUtil.emitChangeDirectory(result, workingDir); |
| // exec ... |
| ScriptUtil.emitExec(result); |
| // env -u UNSET_ME ... KEY=VAL ... |
| ScriptUtil.emitEnvPrefix( |
| result, /* ignoreEnvironment= */ false, environment, ENV_VARIABLES_TO_CLEAR); |
| |
| result.append(commandLine); |
| |
| result.append(OS.getCurrent() == OS.WINDOWS ? " %*" : " \"$@\""); |
| |
| return result.toString(); |
| } |
| } |