diff options
3 files changed, 104 insertions, 15 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkExecutionResult.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkExecutionResult.java index b4181aa449..ad2e7b60a8 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkExecutionResult.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkExecutionResult.java @@ -13,6 +13,7 @@ // limitations under the License. package com.google.devtools.build.lib.bazel.repository.skylark; +import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.shell.BadExitStatusException; import com.google.devtools.build.lib.shell.Command; @@ -23,9 +24,11 @@ import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.util.Preconditions; +import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * A structure callable from Skylark that stores the result of repository_ctx.execute() method. It @@ -89,9 +92,12 @@ final class SkylarkExecutionResult { /** * Returns a Builder that can be used to execute a command and build an execution result. + * + * @param environment pass through the list of environment variables from the client to be passed + * to the execution environment. */ - public static Builder builder() { - return new Builder(); + public static Builder builder(Map<String, String> environment) { + return new Builder(environment); } /** @@ -100,9 +106,15 @@ final class SkylarkExecutionResult { static final class Builder { private final List<String> args = new ArrayList<>(); + private File directory = null; + private final ImmutableMap.Builder<String, String> envBuilder = ImmutableMap.builder(); private long timeout = -1; private boolean executed = false; + private Builder(Map<String, String> environment) { + envBuilder.putAll(environment); + } + /** * Adds arguments to the list of arguments to pass to the command. The first argument is * expected to be the binary to execute. The subsequent arguments are the arguments passed @@ -125,6 +137,25 @@ final class SkylarkExecutionResult { } /** + * Set the path to the directory to execute the result process. This method must be called + * before calling {@link #execute()}. + */ + Builder setDirectory(File path) throws EvalException { + this.directory = path; + return this; + } + + /** + * Add an environment variables to be added to the list of environment variables. For all + * key <code>k</code> of <code>variables</code>, the resulting process will have the variable + * <code>k=variables.get(k)</code> defined. + */ + Builder addEnvironmentVariables(Map<String, String> variables) { + this.envBuilder.putAll(variables); + return this; + } + + /** * Sets the timeout, in milliseconds, after which the executed command will be terminated. */ Builder setTimeout(long timeout) { @@ -140,6 +171,7 @@ final class SkylarkExecutionResult { Preconditions.checkArgument(timeout > 0, "Timeout must be set prior to calling execute()."); Preconditions.checkArgument(!args.isEmpty(), "No command specified."); Preconditions.checkState(!executed, "Command was already executed, cannot re-use builder."); + Preconditions.checkNotNull(directory, "Directory must be set before calling execute()."); executed = true; try { @@ -147,7 +179,8 @@ final class SkylarkExecutionResult { for (int i = 0; i < args.size(); i++) { argsArray[i] = args.get(i); } - CommandResult result = new Command(argsArray).execute(new byte[]{}, timeout, false); + Command command = new Command(argsArray, envBuilder.build(), directory); + CommandResult result = command.execute(new byte[]{}, timeout, false); return new SkylarkExecutionResult(result); } catch (BadExitStatusException e) { return new SkylarkExecutionResult(e.getResult()); diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java index e4dd0293c8..35b4e71556 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java @@ -293,23 +293,57 @@ public class SkylarkRepositoryContext { return osObject; } + private void createOutputDirectory() throws RepositoryFunctionException { + try { + if (!outputDirectory.exists()) { + makeDirectories(outputDirectory); + outputDirectory.createDirectory(); + } + } catch (IOException e) { + throw new RepositoryFunctionException(e, Transience.TRANSIENT); + } + } + @SkylarkCallable( name = "execute", doc = "Executes the command given by the list of arguments. The execution time of the command" + " is limited by <code>timeout</code> (in seconds, default 600 seconds). This method" + " returns an <code>exec_result</code> structure containing the output of the" - + " command." + + " command. The <code>environment</code> map can be used to override some environment" + + " variables to be passed to the process." ) - public SkylarkExecutionResult execute(List<Object> arguments, long timeout) throws EvalException { - return SkylarkExecutionResult.builder() - .addArguments(arguments).setTimeout(timeout / 1000).execute(); + public SkylarkExecutionResult execute(List<Object> arguments, Integer timeout, + Map<String, String> environment) throws EvalException, RepositoryFunctionException { + createOutputDirectory(); + return SkylarkExecutionResult.builder(osObject.getEnvironmentVariables()) + .addArguments(arguments) + .setDirectory(outputDirectory.getPathFile()) + .addEnvironmentVariables(environment) + .setTimeout(timeout.longValue() * 1000) + .execute(); + } + + @SkylarkCallable(name = "execute", documented = false) + public SkylarkExecutionResult execute(List<Object> arguments) + throws EvalException, RepositoryFunctionException { + createOutputDirectory(); + return SkylarkExecutionResult.builder(osObject.getEnvironmentVariables()) + .setDirectory(outputDirectory.getPathFile()) + .addArguments(arguments) + .setTimeout(600000) + .execute(); } @SkylarkCallable(name = "execute", documented = false) - public SkylarkExecutionResult execute(List<Object> arguments) throws EvalException { - return SkylarkExecutionResult.builder() - .addArguments(arguments).setTimeout(600000).execute(); + public SkylarkExecutionResult execute(List<Object> arguments, Integer timeout) + throws EvalException, RepositoryFunctionException { + createOutputDirectory(); + return SkylarkExecutionResult.builder(osObject.getEnvironmentVariables()) + .setDirectory(outputDirectory.getPathFile()) + .addArguments(arguments) + .setTimeout(timeout.longValue() * 1000) + .execute(); } @SkylarkCallable( diff --git a/src/test/shell/bazel/skylark_repository_test.sh b/src/test/shell/bazel/skylark_repository_test.sh index d20cb8b033..7d5f30b379 100755 --- a/src/test/shell/bazel/skylark_repository_test.sh +++ b/src/test/shell/bazel/skylark_repository_test.sh @@ -318,6 +318,8 @@ function test_skylark_repository_which_and_execute() { # Our custom repository rule cat >test.bzl <<EOF def _impl(repository_ctx): + # Symlink so a repository is created + repository_ctx.symlink(repository_ctx.path("$repo2"), repository_ctx.path("")) bash = repository_ctx.which("bash") if bash == None: fail("Bash not found!") @@ -326,12 +328,10 @@ def _impl(repository_ctx): fail("bin.sh not found!") result = repository_ctx.execute([bash, "--version"]) if result.return_code != 0: - fail("Non-zero return code from bash: " + result.return_code) + fail("Non-zero return code from bash: " + str(result.return_code)) if result.stderr != "": fail("Non-empty error output: " + result.stderr) print(result.stdout) - # Symlink so a repository is created - repository_ctx.symlink(repository_ctx.path("$repo2"), repository_ctx.path("")) repo = repository_rule(implementation=_impl, local=True) EOF @@ -344,19 +344,41 @@ function test_skylark_repository_execute_stderr() { cat >test.bzl <<EOF def _impl(repository_ctx): + # Symlink so a repository is created + repository_ctx.symlink(repository_ctx.path("$repo2"), repository_ctx.path("")) result = repository_ctx.execute([str(repository_ctx.which("bash")), "-c", "echo erf >&2; exit 1"]) if result.return_code != 1: - fail("Incorrect return code from bash (should be 1): " + result.return_code) + fail("Incorrect return code from bash: %s != 1\n%s" % (result.return_code, result.stderr)) if result.stdout != "": fail("Non-empty output: %s (stderr was %s)" % (result.stdout, result.stderr)) print(result.stderr) +repo = repository_rule(implementation=_impl, local=True) +EOF + + bazel build @foo//:bar >& $TEST_log || fail "Failed to build" + expect_log "erf" +} + + +function test_skylark_repository_execute_env_and_workdir() { + setup_skylark_repository + + cat >test.bzl <<EOF +def _impl(repository_ctx): # Symlink so a repository is created repository_ctx.symlink(repository_ctx.path("$repo2"), repository_ctx.path("")) + result = repository_ctx.execute( + [str(repository_ctx.which("bash")), "-c", "echo PWD=\$PWD TOTO=\$TOTO"], + 1000000, + { "TOTO": "titi" }) + if result.return_code != 0: + fail("Incorrect return code from bash: %s != 0\n%s" % (result.return_code, result.stderr)) + print(result.stdout) repo = repository_rule(implementation=_impl, local=True) EOF bazel build @foo//:bar >& $TEST_log || fail "Failed to build" - expect_log "erf" + expect_log "PWD=$repo2 TOTO=titi" } function test_skylark_repository_environ() { |