public BlazeCommandResult exec()

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());
  }