Introduce CommandEnvironment and pass it instead of BlazeRuntime.

The very long term plan is to make BlazeRuntime immutable, so that we can
run multiple commands in the same server.

--
MOS_MIGRATED_REVID=103080946
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index 7b248e0..7e90a11 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -64,8 +64,8 @@
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeModule;
-import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
 import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
 import com.google.devtools.build.lib.util.Clock;
@@ -119,9 +119,9 @@
   }
 
   @Override
-  public void beforeCommand(BlazeRuntime runtime, Command command) {
-    downloadFunction.setReporter(runtime.getReporter());
-    gitCloneFunction.setReporter(runtime.getReporter());
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    downloadFunction.setReporter(env.getReporter());
+    gitCloneFunction.setReporter(env.getReporter());
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelShutdownLoggerModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelShutdownLoggerModule.java
index 3c32611..bbad58b 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelShutdownLoggerModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelShutdownLoggerModule.java
@@ -27,7 +27,6 @@
  * Shutdown log output when Bazel runs in batch mode
  */
 public class BazelShutdownLoggerModule extends BlazeModule {
-
   private Logger globalLogger;
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java
index 17996f4..87a06cb 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java
@@ -41,6 +41,7 @@
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.GotOptionsEvent;
 import com.google.devtools.build.lib.shell.CommandException;
 import com.google.devtools.build.lib.shell.CommandResult;
@@ -258,9 +259,9 @@
   private WorkspaceStatusAction.Options options;
 
   @Override
-  public void beforeCommand(BlazeRuntime runtime, Command command) {
-    this.runtime = runtime;
-    runtime.getEventBus().register(this);
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    this.runtime = env.getRuntime();
+    env.getEventBus().register(this);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java b/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java
index e879f75..943c4cd 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java
@@ -29,6 +29,7 @@
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.commands.QueryCommand;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.util.ExitCode;
@@ -56,12 +57,13 @@
   public static final String NAME = "fetch";
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) { }
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) { }
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
     if (options.getResidue().isEmpty()) {
-      runtime.getReporter().handle(Event.error(String.format(
+      env.getReporter().handle(Event.error(String.format(
           "missing fetch expression. Type '%s help fetch' for syntax and help",
           Constants.PRODUCT_NAME)));
       return ExitCode.COMMAND_LINE_ERROR;
@@ -72,16 +74,16 @@
           options.getOptions(PackageCacheOptions.class),
           runtime.getDefaultsPackageContent());
     } catch (InterruptedException e) {
-      runtime.getReporter().handle(Event.error("fetch interrupted"));
+      env.getReporter().handle(Event.error("fetch interrupted"));
       return ExitCode.INTERRUPTED;
     } catch (AbruptExitException e) {
-      runtime.getReporter().handle(Event.error(null, "Unknown error: " + e.getMessage()));
+      env.getReporter().handle(Event.error(null, "Unknown error: " + e.getMessage()));
       return e.getExitCode();
     }
 
     PackageCacheOptions pkgOptions = options.getOptions(PackageCacheOptions.class);
     if (!pkgOptions.fetch) {
-      runtime.getReporter().handle(Event.error(null, "You cannot run fetch with --fetch=false"));
+      env.getReporter().handle(Event.error(null, "You cannot run fetch with --fetch=false"));
       return ExitCode.COMMAND_LINE_ERROR;
     }
 
@@ -102,33 +104,31 @@
     String query = Joiner.on(" union ").join(labelsToLoad.build());
     query = "deps(" + query + ")";
 
-    AbstractBlazeQueryEnvironment<Target> env = QueryCommand.newQueryEnvironment(
+    AbstractBlazeQueryEnvironment<Target> queryEnv = QueryCommand.newQueryEnvironment(
         runtime, options.getOptions(FetchOptions.class).keepGoing, false,
         Lists.<String>newArrayList(), 200, Sets.<Setting>newHashSet());
 
     // 1. Parse query:
     QueryExpression expr;
     try {
-      expr = QueryExpression.parse(query, env);
+      expr = QueryExpression.parse(query, queryEnv);
     } catch (QueryException e) {
-      runtime.getReporter().handle(Event.error(
+      env.getReporter().handle(Event.error(
           null, "Error while parsing '" + query + "': " + e.getMessage()));
       return ExitCode.COMMAND_LINE_ERROR;
     }
 
     // 2. Evaluate expression:
     try {
-      env.evaluateQuery(expr);
+      queryEnv.evaluateQuery(expr);
     } catch (QueryException | InterruptedException e) {
       // Keep consistent with reportBuildFileError()
-      runtime.getReporter().handle(Event.error(e.getMessage()));
+      env.getReporter().handle(Event.error(e.getMessage()));
       return ExitCode.COMMAND_LINE_ERROR;
     }
 
-    runtime.getReporter().handle(
+    env.getReporter().handle(
         Event.progress("All external dependencies fetched successfully."));
     return ExitCode.SUCCESS;
   }
-
-
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/dash/DashModule.java b/src/main/java/com/google/devtools/build/lib/bazel/dash/DashModule.java
index 8918992..ad051f3 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/dash/DashModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/dash/DashModule.java
@@ -23,12 +23,14 @@
 import com.google.devtools.build.lib.bazel.dash.DashProtos.BuildData.Target.TestData;
 import com.google.devtools.build.lib.bazel.dash.DashProtos.Log;
 import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Reporter;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.pkgcache.TargetParsingCompleteEvent;
 import com.google.devtools.build.lib.rules.test.TestResult;
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.CommandStartEvent;
 import com.google.devtools.build.lib.runtime.GotOptionsEvent;
 import com.google.devtools.build.lib.util.io.OutErr;
@@ -77,9 +79,9 @@
   }
 
   @Override
-  public void beforeCommand(BlazeRuntime runtime, Command command) {
-    this.runtime = runtime;
-    runtime.getEventBus().register(this);
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    this.runtime = env.getRuntime();
+    env.getEventBus().register(this);
   }
 
   @Override
@@ -93,7 +95,7 @@
   public void handleOptions(OptionsProvider optionsProvider) {
     DashOptions options = optionsProvider.getOptions(DashOptions.class);
     sender = (options == null || !options.useDash)
-      ? new NoOpSender() : new Sender(options.url, runtime, executorService);
+      ? new NoOpSender() : new Sender(options.url, runtime, runtime.getReporter(), executorService);
     if (optionsBuildData != null) {
       sender.send("options", optionsBuildData);
     }
@@ -229,13 +231,13 @@
     private final OutErr outErr;
     private final ExecutorService executorService;
 
-    public Sender(String url, BlazeRuntime runtime, ExecutorService executorService) {
+    public Sender(String url, BlazeRuntime runtime, Reporter reporter,
+        ExecutorService executorService) {
       this.url = url;
       this.buildId = runtime.getCommandId().toString();
-      this.outErr = runtime.getReporter().getOutErr();
+      this.outErr = reporter.getOutErr();
       this.executorService = executorService;
-      runtime
-          .getReporter()
+      reporter
           .handle(Event.info("Results are being streamed to " + url + "/result/" + buildId));
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java
index 7c720dc..5a63d34 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.GotOptionsEvent;
 import com.google.devtools.build.lib.skyframe.PrecomputedValue;
 import com.google.devtools.common.options.Converters.AssignmentConverter;
@@ -131,9 +132,9 @@
   private OptionsProvider optionsProvider;
 
   @Override
-  public void beforeCommand(BlazeRuntime blazeRuntime, Command command) {
-    this.runtime = blazeRuntime;
-    runtime.getEventBus().register(this);
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    this.runtime = env.getRuntime();
+    env.getEventBus().register(this);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index c50d659..af5e238 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -20,7 +20,6 @@
 import com.google.common.base.Throwables;
 import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.eventbus.EventBus;
 import com.google.devtools.build.lib.actions.BuildFailedException;
 import com.google.devtools.build.lib.actions.ExecutorInitException;
 import com.google.devtools.build.lib.actions.TestExecException;
@@ -71,7 +70,7 @@
 import com.google.devtools.build.lib.profiler.ProfilePhase;
 import com.google.devtools.build.lib.profiler.Profiler;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
-import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.syntax.Label;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.util.ExitCode;
@@ -99,19 +98,21 @@
  *
  * <p>Most of analysis is handled in {@link BuildView}, and execution in {@link ExecutionTool}.
  */
-public class BuildTool {
+public final class BuildTool {
 
   private static final Logger LOG = Logger.getLogger(BuildTool.class.getName());
 
-  protected final BlazeRuntime runtime;
+  private final CommandEnvironment env;
+  private final BlazeRuntime runtime;
 
   /**
    * Constructs a BuildTool.
    *
-   * @param runtime a reference to the blaze runtime.
+   * @param env a reference to the command environment of the currently executing command
    */
-  public BuildTool(BlazeRuntime runtime) {
-    this.runtime = runtime;
+  public BuildTool(CommandEnvironment env) {
+    this.env = env;
+    this.runtime = env.getRuntime();
   }
 
   /**
@@ -151,9 +152,9 @@
     LoadingResult loadingResult = null;
     BuildConfigurationCollection configurations = null;
     try {
-      getEventBus().post(new BuildStartingEvent(runtime.getOutputFileSystem(), request));
+      env.getEventBus().post(new BuildStartingEvent(runtime.getOutputFileSystem(), request));
       LOG.info("Build identifier: " + request.getId());
-      executionTool = new ExecutionTool(runtime, request);
+      executionTool = new ExecutionTool(env, request);
       if (needsExecutionPhase(request.getBuildOptions())) {
         // Initialize the execution tool early if we need it. This hides the latency of setting up
         // the execution backends.
@@ -175,16 +176,17 @@
               + "'test' right now!");
         }
       }
-      configurations = getConfigurations(buildOptions, request.getMultiCpus(),
-          request.getViewOptions().keepGoing);
+      configurations = runtime.getSkyframeExecutor().createConfigurations(
+            runtime.getConfigurationFactory(), buildOptions, runtime.getDirectories(),
+            request.getMultiCpus(), request.getViewOptions().keepGoing);
 
-      getEventBus().post(new ConfigurationsCreatedEvent(configurations));
+      env.getEventBus().post(new ConfigurationsCreatedEvent(configurations));
       runtime.throwPendingException();
       if (configurations.getTargetConfigurations().size() == 1) {
         // TODO(bazel-team): This is not optimal - we retain backwards compatibility in the case
         // where there's only a single configuration, but we don't send an event in the multi-config
         // case. Can we do better? [multi-config]
-        getEventBus().post(new MakeEnvironmentEvent(
+        env.getEventBus().post(new MakeEnvironmentEvent(
             configurations.getTargetConfigurations().get(0).getMakeEnvironment()));
       }
       LOG.info("Configurations created");
@@ -202,8 +204,7 @@
       if (needsExecutionPhase(request.getBuildOptions())) {
         runtime.getSkyframeExecutor().injectTopLevelContext(request.getTopLevelArtifactContext());
         executionTool.executeBuild(request.getId(), analysisResult, result,
-            runtime.getSkyframeExecutor(), configurations,
-            transformPackageRoots(loadingResult.getPackageRoots()));
+            configurations, transformPackageRoots(loadingResult.getPackageRoots()));
       }
 
       String delayedErrorMsg = analysisResult.getError();
@@ -230,7 +231,7 @@
       // occurs early in the build. Tell a lie so that the event is not missing.
       // If multiple build_info events are sent, only the first is kept, so this does not harm
       // successful runs (which use the workspace status action).
-      getEventBus().post(new BuildInfoEvent(
+      env.getEventBus().post(new BuildInfoEvent(
           runtime.getworkspaceStatusActionFactory().createDummyWorkspaceStatus()));
     }
 
@@ -324,7 +325,7 @@
    */
   public BuildResult processRequest(BuildRequest request, TargetValidator validator) {
     BuildResult result = new BuildResult(request.getStartTime());
-    runtime.getEventBus().register(result);
+    env.getEventBus().register(result);
     Throwable catastrophe = null;
     ExitCode exitCode = ExitCode.BLAZE_INTERNAL_ERROR;
     try {
@@ -342,8 +343,8 @@
       exitCode = ExitCode.BUILD_FAILURE;
     } catch (InterruptedException e) {
       exitCode = ExitCode.INTERRUPTED;
-      getReporter().handle(Event.error("build interrupted"));
-      getEventBus().post(new BuildInterruptedEvent());
+      env.getReporter().handle(Event.error("build interrupted"));
+      env.getEventBus().post(new BuildInterruptedEvent());
     } catch (TargetParsingException | LoadingFailedException | ViewCreationFailedException e) {
       exitCode = ExitCode.PARSING_FAILURE;
       reportExceptionError(e);
@@ -369,17 +370,6 @@
     return result;
   }
 
-  private final BuildConfigurationCollection getConfigurations(BuildOptions buildOptions,
-      Set<String> multiCpu, boolean keepGoing)
-      throws InvalidConfigurationException, InterruptedException {
-    SkyframeExecutor executor = runtime.getSkyframeExecutor();
-    // TODO(bazel-team): consider a possibility of moving ConfigurationFactory construction into
-    // skyframe.
-    return executor.createConfigurations(
-        runtime.getConfigurationFactory(), buildOptions, runtime.getDirectories(), multiCpu,
-        keepGoing);
-  }
-
   @VisibleForTesting
   protected final LoadingResult runLoadingPhase(final BuildRequest request,
                                                 final TargetValidator validator)
@@ -407,7 +397,7 @@
     };
 
     LoadingResult result = runtime.getLoadingPhaseRunner().execute(getReporter(),
-        getEventBus(), request.getTargets(), request.getLoadingOptions(),
+        env.getEventBus(), request.getTargets(), request.getLoadingOptions(),
         runtime.createBuildOptions(request).getAllLabels(), keepGoing,
         request.shouldRunTests(), callback);
     runtime.throwPendingException();
@@ -450,20 +440,20 @@
     Profiler.instance().markPhase(ProfilePhase.ANALYZE);
 
     AnalysisResult analysisResult =
-        getView()
+        runtime.getView()
             .update(
                 loadingResult,
                 configurations,
                 request.getAspects(),
                 request.getViewOptions(),
                 request.getTopLevelArtifactContext(),
-                getReporter(),
-                getEventBus());
+                env.getReporter(),
+                env.getEventBus());
 
     // TODO(bazel-team): Merge these into one event.
-    getEventBus().post(new AnalysisPhaseCompleteEvent(analysisResult.getTargetsToBuild(),
-        getView().getTargetsVisited(), timer.stop().elapsed(TimeUnit.MILLISECONDS)));
-    getEventBus().post(new TestFilteringCompleteEvent(analysisResult.getTargetsToBuild(),
+    env.getEventBus().post(new AnalysisPhaseCompleteEvent(analysisResult.getTargetsToBuild(),
+        runtime.getView().getTargetsVisited(), timer.stop().elapsed(TimeUnit.MILLISECONDS)));
+    env.getEventBus().post(new TestFilteringCompleteEvent(analysisResult.getTargetsToBuild(),
         analysisResult.getTargetsToTest()));
 
     // Check licenses.
@@ -501,7 +491,7 @@
     result.setExitCondition(exitCondition);
     // The stop time has to be captured before we send the BuildCompleteEvent.
     result.setStopTime(runtime.getClock().currentTimeMillis());
-    getEventBus().post(new BuildCompleteEvent(request, result));
+    env.getEventBus().post(new BuildCompleteEvent(request, result));
   }
 
   private void reportTargets(AnalysisResult analysisResult) {
@@ -598,15 +588,7 @@
     }
   }
 
-  public BuildView getView() {
-    return runtime.getView();
-  }
-
   private Reporter getReporter() {
-    return runtime.getReporter();
-  }
-
-  private EventBus getEventBus() {
-    return runtime.getEventBus();
+    return env.getReporter();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index 9c18133..47f0558 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -24,7 +24,6 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Table;
-import com.google.common.eventbus.EventBus;
 import com.google.devtools.build.lib.Constants;
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.ActionCacheChecker;
@@ -85,6 +84,7 @@
 import com.google.devtools.build.lib.rules.test.TestActionContext;
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.skyframe.AspectValue;
 import com.google.devtools.build.lib.skyframe.Builder;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
@@ -115,8 +115,6 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.annotation.Nullable;
-
 /**
  * This class manages the execution phase. The entry point is {@link #executeBuild}.
  *
@@ -174,6 +172,7 @@
 
   static final Logger LOG = Logger.getLogger(ExecutionTool.class.getName());
 
+  private final CommandEnvironment env;
   private final BlazeRuntime runtime;
   private final BuildRequest request;
   private BlazeExecutor executor;
@@ -184,28 +183,9 @@
       new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
   private List<ActionContext> strategies = new ArrayList<>();
 
-  private ImmutableList<ActionContextConsumer> getActionContextConsumersFromModules(
-      ActionContextConsumer... extraConsumers) {
-    ImmutableList.Builder<ActionContextConsumer> builder = ImmutableList.builder();
-    for (BlazeModule module : runtime.getBlazeModules()) {
-      builder.addAll(module.getActionContextConsumers());
-    }
-    builder.add(extraConsumers);
-    return builder.build();
-  }
-
-  private ImmutableList<ActionContextProvider> getActionContextProvidersFromModules(
-      ActionContextProvider... extraProviders) {
-    ImmutableList.Builder<ActionContextProvider> builder = ImmutableList.builder();
-    for (BlazeModule module : runtime.getBlazeModules()) {
-      builder.addAll(module.getActionContextProviders());
-    }
-    builder.add(extraProviders);
-    return builder.build();
-  }
-
-  ExecutionTool(final BlazeRuntime runtime, BuildRequest request) throws ExecutorInitException {
-    this.runtime = runtime;
+  ExecutionTool(CommandEnvironment env, BuildRequest request) throws ExecutorInitException {
+    this.env = env;
+    this.runtime = env.getRuntime();
     this.request = request;
 
     // Create tools before getting the strategies from the modules as some of them need tools to
@@ -214,14 +194,16 @@
 
     this.actionContextProviders =
         getActionContextProvidersFromModules(
+            runtime,
             new FilesetActionContextImpl.Provider(
-                runtime.getReporter(), runtime.getWorkspaceName()),
+                env.getReporter(), runtime.getWorkspaceName()),
             new SimpleActionContextProvider(
                 new SymlinkTreeStrategy(runtime.getOutputService(), runtime.getBinTools())));
     StrategyConverter strategyConverter = new StrategyConverter(actionContextProviders);
 
     ImmutableList<ActionContextConsumer> actionContextConsumers =
         getActionContextConsumersFromModules(
+            runtime,
             // TODO(philwo) - the ExecutionTool should not add arbitrary dependencies on its own,
             // instead these dependencies should be added to the ActionContextConsumer of the module
             // that actually depends on them.
@@ -284,6 +266,26 @@
     }
   }
 
+  private static ImmutableList<ActionContextConsumer> getActionContextConsumersFromModules(
+      BlazeRuntime runtime, ActionContextConsumer... extraConsumers) {
+    ImmutableList.Builder<ActionContextConsumer> builder = ImmutableList.builder();
+    for (BlazeModule module : runtime.getBlazeModules()) {
+      builder.addAll(module.getActionContextConsumers());
+    }
+    builder.add(extraConsumers);
+    return builder.build();
+  }
+
+  private static ImmutableList<ActionContextProvider> getActionContextProvidersFromModules(
+      BlazeRuntime runtime, ActionContextProvider... extraProviders) {
+    ImmutableList.Builder<ActionContextProvider> builder = ImmutableList.builder();
+    for (BlazeModule module : runtime.getBlazeModules()) {
+      builder.addAll(module.getActionContextProviders());
+    }
+    builder.add(extraProviders);
+    return builder.build();
+  }
+
   private static ExecutorInitException makeExceptionForInvalidStrategyValue(String value,
       String strategy, String validValues) {
     return new ExecutorInitException(String.format(
@@ -307,7 +309,7 @@
         runtime.getDirectories().getExecRoot(),
         runtime.getDirectories().getOutputPath(),
         getReporter(),
-        getEventBus(),
+        env.getEventBus(),
         runtime.getClock(),
         request,
         request.getOptions(ExecutionOptions.class).verboseFailures,
@@ -339,7 +341,7 @@
    * creation
    */
   void executeBuild(UUID buildId, AnalysisResult analysisResult,
-      BuildResult buildResult, @Nullable SkyframeExecutor skyframeExecutor,
+      BuildResult buildResult,
       BuildConfigurationCollection configurations,
       ImmutableMap<PathFragment, Path> packageRoots)
       throws BuildFailedException, InterruptedException, TestExecException, AbruptExitException {
@@ -375,6 +377,7 @@
     }
 
     ActionCache actionCache = getActionCache();
+    SkyframeExecutor skyframeExecutor = runtime.getSkyframeExecutor();
     Builder builder = createBuilder(request, executor, actionCache, skyframeExecutor);
 
     //
@@ -383,7 +386,7 @@
     //
 
     Collection<ConfiguredTarget> configuredTargets = buildResult.getActualTargets();
-    getEventBus().post(new ExecutionStartingEvent(configuredTargets));
+    env.getEventBus().post(new ExecutionStartingEvent(configuredTargets));
 
     getReporter().handle(Event.progress("Building..."));
 
@@ -457,7 +460,7 @@
         getReporter().handle(Event.progress("Building complete."));
       }
 
-      runtime.getEventBus().post(new ExecutionFinishedEvent(ImmutableMap.<String, Long> of(), 0L,
+      env.getEventBus().post(new ExecutionFinishedEvent(ImmutableMap.<String, Long> of(), 0L,
           skyframeExecutor.getOutputDirtyFilesAndClear(),
           skyframeExecutor.getModifiedFilesDuringPreviousBuildAndClear()));
 
@@ -638,7 +641,7 @@
         successfulTargets.add(target);
       }
     }
-    getEventBus().post(
+    env.getEventBus().post(
         new ExecutionPhaseCompleteEvent(timer.stop().elapsed(TimeUnit.MILLISECONDS)));
     result.setSuccessfulTargets(successfulTargets);
   }
@@ -882,7 +885,7 @@
     } finally {
       actionCacheSaveTime = p.completeAndGetElapsedTimeNanos();
     }
-    runtime.getEventBus().post(new CachesSavedEvent(
+    env.getEventBus().post(new CachesSavedEvent(
         actionCacheSaveTime, actionCacheSizeInBytes));
   }
 
@@ -906,11 +909,7 @@
   }
 
   private Reporter getReporter() {
-    return runtime.getReporter();
-  }
-
-  private EventBus getEventBus() {
-    return runtime.getEventBus();
+    return env.getReporter();
   }
 
   private BuildView getView() {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommand.java
index 61f46a8..c050043 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommand.java
@@ -39,7 +39,7 @@
    * shut down and the exit status will be 0 (in case the shutdown succeeds
    * without error).
    *
-   * @param runtime The Blaze runtime requesting the execution of the command
+   * @param env The environment for the current command invocation
    * @param options A parsed options instance initialized with the values for
    *     the options specified in {@link Command#options()}.
    *
@@ -47,7 +47,7 @@
    * @throws BlazeCommandDispatcher.ShutdownBlazeServerException Indicates
    *     that the command wants to shutdown the Blaze server.
    */
-  ExitCode exec(BlazeRuntime runtime, OptionsProvider options)
+  ExitCode exec(CommandEnvironment env, OptionsProvider options)
       throws BlazeCommandDispatcher.ShutdownBlazeServerException;
 
   /**
@@ -55,9 +55,10 @@
    * requirements. This method is called after all command-line and rc file options have been
    * parsed.
    *
-   * @param runtime The Blaze runtime requesting the execution of the command
+   * @param env the command environment of the currently running command
+   * @param optionsParser the options parser for the current command
    *
    * @throws AbruptExitException if something went wrong
    */
-  void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) throws AbruptExitException;
+  void editOptions(CommandEnvironment env, OptionsParser optionsParser) throws AbruptExitException;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index 9b901e6..cb0a0f9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -239,7 +239,7 @@
     // the command's execution - that's why we do this separately,
     // rather than in runtime.beforeCommand().
     runtime.getTimestampGranularityMonitor().setCommandStartTime();
-    runtime.initEventBus();
+    CommandEnvironment env = runtime.initCommand();
 
     if (args.isEmpty()) { // Default to help command if no arguments specified.
       args = HELP_COMMAND;
@@ -262,7 +262,7 @@
     AbruptExitException exitCausingException = null;
     for (BlazeModule module : runtime.getBlazeModules()) {
       try {
-        module.beforeCommand(runtime, commandAnnotation);
+        module.beforeCommand(runtime, commandAnnotation, env);
       } catch (AbruptExitException e) {
         // Don't let one module's complaints prevent the other modules from doing necessary
         // setup. We promised to call beforeCommand exactly once per-module before each command
@@ -340,7 +340,7 @@
     PrintStream savedErr = System.err;
 
     EventHandler handler = createEventHandler(outErr, eventHandlerOptions);
-    Reporter reporter = runtime.getReporter();
+    Reporter reporter = env.getReporter();
     reporter.addHandler(handler);
 
     // We register an ANSI-allowing handler associated with {@code handler} so that ANSI control
@@ -372,9 +372,10 @@
 
       try {
         // Notify the BlazeRuntime, so it can do some initial setup.
-        runtime.beforeCommand(commandAnnotation, optionsParser, commonOptions, execStartTimeNanos);
+        runtime.beforeCommand(commandAnnotation, env, optionsParser, commonOptions,
+            execStartTimeNanos);
         // Allow the command to edit options after parsing:
-        command.editOptions(runtime, optionsParser);
+        command.editOptions(env, optionsParser);
       } catch (AbruptExitException e) {
         reporter.handle(Event.error(e.getMessage()));
         return e.getExitCode().getNumericExitCode();
@@ -385,8 +386,8 @@
         reporter.handle(Event.warn(warning));
       }
 
-      ExitCode outcome = command.exec(runtime, optionsParser);
-      outcome = runtime.precompleteCommand(outcome);
+      ExitCode outcome = command.exec(env, optionsParser);
+      outcome = runtime.precompleteCommand(env, outcome);
       numericExitCode = outcome.getNumericExitCode();
       return numericExitCode;
     } catch (ShutdownBlazeServerException e) {
@@ -400,7 +401,7 @@
           : ExitCode.BLAZE_INTERNAL_ERROR.getNumericExitCode();
       throw new ShutdownBlazeServerException(numericExitCode, e);
     } finally {
-      runtime.afterCommand(numericExitCode);
+      runtime.afterCommand(env, numericExitCode);
       // Swallow IOException, as we are already in a finally clause
       Flushables.flushQuietly(outErr.getOutputStream());
       Flushables.flushQuietly(outErr.getErrorStream());
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
index bfc1d8f..68eed55 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
@@ -221,8 +221,16 @@
    * Called before each command.
    */
   @SuppressWarnings("unused")
-  public void beforeCommand(BlazeRuntime blazeRuntime, Command command)
+  public void beforeCommand(Command command, CommandEnvironment env) throws AbruptExitException {
+  }
+
+  /**
+   * Called before each command.
+   */
+  @SuppressWarnings("unused")
+  public void beforeCommand(BlazeRuntime blazeRuntime, Command command, CommandEnvironment env)
       throws AbruptExitException {
+    beforeCommand(command, env);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
index 7e4e41c..c1ec448 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
@@ -51,7 +51,6 @@
 import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
 import com.google.devtools.build.lib.analysis.config.DefaultsPackage;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
-import com.google.devtools.build.lib.buildtool.BuildTool;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.OutputFilter;
 import com.google.devtools.build.lib.events.Reporter;
@@ -154,10 +153,11 @@
 import javax.annotation.Nullable;
 
 /**
- * The BlazeRuntime class encapsulates the runtime settings and services that
- * are available to most parts of any Blaze application for the duration of the
- * batch run or server lifetime. A single instance of this runtime will exist
- * and will be passed around as needed.
+ * The BlazeRuntime class encapsulates the immutable configuration of the current instance. These
+ * runtime settings and services are available to most parts of any Blaze application for the
+ * duration of the batch run or server lifetime.
+ *
+ * <p>The parts specific to the current command are stored in {@link CommandEnvironment}.
  */
 public final class BlazeRuntime {
   public static final String DO_NOT_BUILD_FILE_NAME = "DO_NOT_BUILD_HERE";
@@ -177,7 +177,6 @@
   private final SkyframeExecutor skyframeExecutor;
 
   private final Reporter reporter;
-  private EventBus eventBus;
   private final LoadingPhaseRunner loadingPhaseRunner;
   private final PackageFactory packageFactory;
   private final PackageRootResolver packageRootResolver;
@@ -187,7 +186,6 @@
   private ActionCache actionCache;
   private final TimestampGranularityMonitor timestampGranularityMonitor;
   private final Clock clock;
-  private final BuildTool buildTool;
 
   private OutputService outputService;
 
@@ -270,8 +268,6 @@
 
     this.eventBusExceptionHandler = eventBusExceptionHandler;
     this.blazeModuleEnvironment = new BlazeModuleEnvironment();
-    this.buildTool = new BuildTool(this);
-    initEventBus();
 
     if (inWorkspace()) {
       writeOutputBaseReadmeFile();
@@ -311,26 +307,22 @@
     return outputFileSystem;
   }
 
-  @VisibleForTesting
-  public void initEventBus() {
-    setEventBus(new EventBus(eventBusExceptionHandler));
+  public CommandEnvironment initCommand() {
+    EventBus eventBus = new EventBus(eventBusExceptionHandler);
+    skyframeExecutor.setEventBus(eventBus);
+    return new CommandEnvironment(this, eventBus);
   }
 
   private void clearEventBus() {
     // EventBus does not have an unregister() method, so this is how we release memory associated
     // with handlers.
-    setEventBus(null);
-  }
-
-  private void setEventBus(EventBus eventBus) {
-    this.eventBus = eventBus;
-    skyframeExecutor.setEventBus(eventBus);
+    skyframeExecutor.setEventBus(null);
   }
 
   /**
    * Conditionally enable profiling.
    */
-  private final boolean initProfiler(CommonCommandOptions options,
+  private final boolean initProfiler(CommandEnvironment env, CommonCommandOptions options,
       UUID buildID, long execStartTimeNanos) {
     OutputStream out = null;
     boolean recordFullProfilerData = false;
@@ -342,7 +334,7 @@
 
         recordFullProfilerData = options.recordFullProfilerData;
         out = new BufferedOutputStream(profilePath.getOutputStream(), 1024 * 1024);
-        getReporter().handle(Event.info("Writing profile data to '" + profilePath + "'"));
+        env.getReporter().handle(Event.info("Writing profile data to '" + profilePath + "'"));
         profiledTasks = ProfiledTaskKinds.ALL;
       } else if (options.alwaysProfileSlowOperations) {
         recordFullProfilerData = false;
@@ -357,7 +349,7 @@
         return true;
       }
     } catch (IOException e) {
-      getReporter().handle(Event.error("Error while creating profile file: " + e.getMessage()));
+      env.getReporter().handle(Event.error("Error while creating profile file: " + e.getMessage()));
     }
     return false;
   }
@@ -517,13 +509,6 @@
     return reporter;
   }
 
-  /**
-   * Returns the current event bus. Only valid within the scope of a single Blaze command.
-   */
-  public EventBus getEventBus() {
-    return eventBus;
-  }
-
   public BinTools getBinTools() {
     return binTools;
   }
@@ -542,13 +527,6 @@
     return packageFactory;
   }
 
-  /**
-   * Returns the build tool.
-   */
-  public BuildTool getBuildTool() {
-    return buildTool;
-  }
-
   public ImmutableList<OutputFormatter> getQueryOutputFormatters() {
     ImmutableList.Builder<OutputFormatter> result = ImmutableList.builder();
     result.addAll(OutputFormatter.getDefaultFormatters());
@@ -702,12 +680,12 @@
    * @param options The CommonCommandOptions used by every command.
    * @throws AbruptExitException if this command is unsuitable to be run as specified
    */
-  void beforeCommand(Command command, OptionsParser optionsParser,
+  void beforeCommand(Command command, CommandEnvironment env, OptionsParser optionsParser,
       CommonCommandOptions options, long execStartTimeNanos)
       throws AbruptExitException {
     commandStartTime -= options.startupTime;
 
-    eventBus.post(new GotOptionsEvent(startupOptionsProvider,
+    env.getEventBus().post(new GotOptionsEvent(startupOptionsProvider,
         optionsParser));
     throwPendingException();
 
@@ -753,7 +731,7 @@
     // Conditionally enable profiling
     // We need to compensate for launchTimeNanos (measurements taken outside of the jvm).
     long startupTimeNanos = options.startupTime * 1000000L;
-    if (initProfiler(options, this.getCommandId(), execStartTimeNanos - startupTimeNanos)) {
+    if (initProfiler(env, options, this.getCommandId(), execStartTimeNanos - startupTimeNanos)) {
       Profiler profiler = Profiler.instance();
 
       // Instead of logEvent() we're calling the low level function to pass the timings we took in
@@ -770,7 +748,7 @@
       try {
         MemoryProfiler.instance().start(memoryProfilePath.getOutputStream());
       } catch (IOException e) {
-        getReporter().handle(
+        env.getReporter().handle(
             Event.error("Error while creating memory profile file: " + e.getMessage()));
       }
     }
@@ -802,7 +780,8 @@
       module.handleOptions(optionsParser);
     }
 
-    eventBus.post(new CommandStartEvent(command.name(), commandId, clientEnv, workingDirectory));
+    env.getEventBus().post(
+        new CommandStartEvent(command.name(), commandId, clientEnv, workingDirectory));
     // Initialize exit code to dummy value for afterCommand.
     storedExitCode.set(ExitCode.RESERVED.getNumericExitCode());
   }
@@ -811,8 +790,8 @@
    * Hook method called by the BlazeCommandDispatcher right before the dispatch
    * of each command ends (while its outcome can still be modified).
    */
-  ExitCode precompleteCommand(ExitCode originalExit) {
-    eventBus.post(new CommandPrecompleteEvent(originalExit));
+  ExitCode precompleteCommand(CommandEnvironment env, ExitCode originalExit) {
+    env.getEventBus().post(new CommandPrecompleteEvent(originalExit));
     // If Blaze did not suffer an infrastructure failure, check for errors in modules.
     ExitCode exitCode = originalExit;
     if (!originalExit.isInfrastructureFailure() && pendingException != null) {
@@ -833,7 +812,7 @@
       // thread won the race (unlikely, but possible), this may be incorrectly logged as a success.
       return;
     }
-    eventBus.post(new CommandCompleteEvent(exitCode));
+    skyframeExecutor.getEventBus().post(new CommandCompleteEvent(exitCode));
   }
 
   /**
@@ -841,9 +820,9 @@
    * command.
    */
   @VisibleForTesting
-  public void afterCommand(int exitCode) {
+  public void afterCommand(CommandEnvironment env, int exitCode) {
     // Remove any filters that the command might have added to the reporter.
-    getReporter().setOutputFilter(OutputFilter.OUTPUT_EVERYTHING);
+    env.getReporter().setOutputFilter(OutputFilter.OUTPUT_EVERYTHING);
 
     notifyCommandComplete(exitCode);
 
@@ -857,7 +836,7 @@
       Profiler.instance().stop();
       MemoryProfiler.instance().stop();
     } catch (IOException e) {
-      getReporter().handle(Event.error("Error while writing profile file: " + e.getMessage()));
+      env.getReporter().handle(Event.error("Error while writing profile file: " + e.getMessage()));
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java b/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
index d0b70f1..337f97f 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
@@ -41,9 +41,9 @@
   private Reporter reporter;
 
   @Override
-  public void beforeCommand(BlazeRuntime runtime, Command command) {
-    this.reporter = runtime.getReporter();
-    this.eventBus = runtime.getEventBus();
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    this.reporter = env.getReporter();
+    this.eventBus = env.getEventBus();
     eventBus.register(this);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
new file mode 100644
index 0000000..02f7652
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
@@ -0,0 +1,58 @@
+// Copyright 2015 Google Inc. 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;
+
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.events.Reporter;
+
+import java.util.Map;
+
+/**
+ * Encapsulates the state needed for a single command. The environment is dropped when the current
+ * command is done and all corresponding objects are garbage collected.
+ */
+public final class CommandEnvironment {
+  private final BlazeRuntime runtime;
+  private final EventBus eventBus;
+
+  public CommandEnvironment(BlazeRuntime runtime, EventBus eventBus) {
+    this.runtime = runtime;
+    this.eventBus = eventBus;
+  }
+
+  public BlazeRuntime getRuntime() {
+    return runtime;
+  }
+
+  public BlazeDirectories getDirectories() {
+    return runtime.getDirectories();
+  }
+
+  /**
+   * Returns the reporter for events.
+   */
+  public Reporter getReporter() {
+    return runtime.getReporter();
+  }
+
+  public EventBus getEventBus() {
+    return eventBus;
+  }
+
+  public Map<String, String> getClientEnv() {
+    return runtime.getClientEnv();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
index 94a0ada..0082d71 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
@@ -17,12 +17,14 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.buildtool.BuildRequest;
 import com.google.devtools.build.lib.buildtool.BuildRequest.BuildRequestOptions;
+import com.google.devtools.build.lib.buildtool.BuildTool;
 import com.google.devtools.build.lib.exec.ExecutionOptions;
 import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner;
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.common.options.OptionsParser;
@@ -51,20 +53,21 @@
 public final class BuildCommand implements BlazeCommand {
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser)
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser)
       throws AbruptExitException {
-    ProjectFileSupport.handleProjectFiles(runtime, optionsParser, "build");
+    ProjectFileSupport.handleProjectFiles(env, optionsParser, "build");
   }
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
     List<String> targets = ProjectFileSupport.getTargets(runtime, options);
 
     BuildRequest request = BuildRequest.create(
         getClass().getAnnotation(Command.class).name(), options,
         runtime.getStartupOptionsProvider(),
         targets,
-        runtime.getReporter().getOutErr(), runtime.getCommandId(), runtime.getCommandStartTime());
-    return runtime.getBuildTool().processRequest(request, null).getExitCondition();
+        env.getReporter().getOutErr(), runtime.getCommandId(), runtime.getCommandStartTime());
+    return new BuildTool(env).processRequest(request, null).getExitCondition();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java
index 82d503f..1b5b1cd 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/CanonicalizeCommand.java
@@ -19,6 +19,7 @@
 import com.google.devtools.build.lib.runtime.BlazeCommandUtils;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionsBase;
@@ -51,11 +52,12 @@
   }
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
     String commandName = options.getOptions(Options.class).forCommand;
     BlazeCommand command = runtime.getCommandMap().get(commandName);
     if (command == null) {
-      runtime.getReporter().handle(Event.error("Not a valid command: '" + commandName
+      env.getReporter().handle(Event.error("Not a valid command: '" + commandName
           + "' (should be one of " + Joiner.on(", ").join(runtime.getCommandMap().keySet()) + ")"));
       return ExitCode.COMMAND_LINE_ERROR;
     }
@@ -65,15 +67,15 @@
     try {
       List<String> result = OptionsParser.canonicalize(optionsClasses, options.getResidue());
       for (String piece : result) {
-        runtime.getReporter().getOutErr().printOutLn(piece);
+        env.getReporter().getOutErr().printOutLn(piece);
       }
     } catch (OptionsParsingException e) {
-      runtime.getReporter().handle(Event.error(e.getMessage()));
+      env.getReporter().handle(Event.error(e.getMessage()));
       return ExitCode.COMMAND_LINE_ERROR;
     }
     return ExitCode.SUCCESS;
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
index 5be4f90..f8d236c 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.ShutdownBlazeServerException;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.shell.CommandException;
 import com.google.devtools.build.lib.util.CommandBuilder;
 import com.google.devtools.build.lib.util.ExitCode;
@@ -83,15 +84,16 @@
   private static Logger LOG = Logger.getLogger(CleanCommand.class.getName());
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options)
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options)
       throws ShutdownBlazeServerException {
+    BlazeRuntime runtime = env.getRuntime();
     Options cleanOptions = options.getOptions(Options.class);
     cleanOptions.expunge_async = cleanOptions.cleanStyle.equals("expunge_async");
     cleanOptions.expunge = cleanOptions.cleanStyle.equals("expunge");
 
     if (!cleanOptions.expunge && !cleanOptions.expunge_async
         && !cleanOptions.cleanStyle.isEmpty()) {
-      runtime.getReporter().handle(Event.error(
+      env.getReporter().handle(Event.error(
           null, "Invalid clean_style value '" + cleanOptions.cleanStyle + "'"));
       return ExitCode.COMMAND_LINE_ERROR;
     }
@@ -101,27 +103,28 @@
         "Starting clean (this may take a while). " +
             "Consider using --expunge_async if the clean takes more than several minutes.";
 
-    runtime.getReporter().handle(Event.info(null/*location*/, cleanBanner));
+    env.getReporter().handle(Event.info(null/*location*/, cleanBanner));
     try {
       String symlinkPrefix =
           options.getOptions(BuildRequest.BuildRequestOptions.class).symlinkPrefix;
-      actuallyClean(runtime, runtime.getOutputBase(), cleanOptions, symlinkPrefix);
+      actuallyClean(env, runtime.getOutputBase(), cleanOptions, symlinkPrefix);
       return ExitCode.SUCCESS;
     } catch (IOException e) {
-      runtime.getReporter().handle(Event.error(e.getMessage()));
+      env.getReporter().handle(Event.error(e.getMessage()));
       return ExitCode.LOCAL_ENVIRONMENTAL_ERROR;
     } catch (CommandException | ExecException e) {
-      runtime.getReporter().handle(Event.error(e.getMessage()));
+      env.getReporter().handle(Event.error(e.getMessage()));
       return ExitCode.RUN_FAILURE;
     } catch (InterruptedException e) {
-      runtime.getReporter().handle(Event.error("clean interrupted"));
+      env.getReporter().handle(Event.error("clean interrupted"));
       return ExitCode.INTERRUPTED;
     }
   }
 
-  private void actuallyClean(BlazeRuntime runtime,
+  private void actuallyClean(CommandEnvironment env,
       Path outputBase, Options cleanOptions, String symlinkPrefix) throws IOException,
       ShutdownBlazeServerException, CommandException, ExecException, InterruptedException {
+    BlazeRuntime runtime = env.getRuntime();
     if (runtime.getOutputService() != null) {
       runtime.getOutputService().clean();
     }
@@ -142,7 +145,7 @@
       // same file system, and therefore the mv will be atomic and fast.
       Path tempOutputBase = outputBase.getParentDirectory().getChild(tempBaseName);
       outputBase.renameTo(tempOutputBase);
-      runtime.getReporter().handle(Event.info(
+      env.getReporter().handle(Event.info(
           null, "Output base moved to " + tempOutputBase + " for deletion"));
 
       // Daemonize the shell and use the double-fork idiom to ensure that the shell
@@ -178,5 +181,5 @@
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
index 1a0f8de..26eafa4 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.runtime.BlazeCommandUtils;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.common.options.EnumConverter;
@@ -55,7 +56,7 @@
 
   /**
    * NB! Any changes to this class must be kept in sync with anyOutput variable
-   * value in the {@link DumpCommand#exec(BlazeRuntime,OptionsProvider)} method below.
+   * value in the {@link DumpCommand#exec(CommandEnvironment,OptionsProvider)} method below.
    */
   public static class DumpOptions extends OptionsBase {
 
@@ -116,10 +117,11 @@
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
     DumpOptions dumpOptions = options.getOptions(DumpOptions.class);
 
     boolean anyOutput = dumpOptions.dumpPackages || dumpOptions.dumpVfs
@@ -131,14 +133,14 @@
       Collection<Class<? extends OptionsBase>> optionList = new ArrayList<>();
       optionList.add(DumpOptions.class);
 
-      runtime.getReporter().getOutErr().printErrLn(BlazeCommandUtils.expandHelpTopic(
+      env.getReporter().getOutErr().printErrLn(BlazeCommandUtils.expandHelpTopic(
           getClass().getAnnotation(Command.class).name(),
           getClass().getAnnotation(Command.class).help(),
           getClass(),
           optionList, categories, OptionsParser.HelpVerbosity.LONG));
       return ExitCode.ANALYSIS_FAILURE;
     }
-    PrintStream out = new PrintStream(runtime.getReporter().getOutErr().getOutputStream());
+    PrintStream out = new PrintStream(env.getReporter().getOutErr().getOutputStream());
     try {
       out.println("Warning: this information is intended for consumption by developers");
       out.println("only, and may change at any time.  Script against it at your own risk!");
@@ -158,7 +160,7 @@
 
       if (dumpOptions.dumpArtifacts) {
         success = false;
-        runtime.getReporter().handle(Event.error("Cannot dump artifacts in Skyframe full mode. "
+        env.getReporter().handle(Event.error("Cannot dump artifacts in Skyframe full mode. "
             + "Use --skyframe instead"));
       }
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
index 4418f1e..7ee0ca6 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
@@ -27,6 +27,7 @@
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.util.io.OutErr;
 import com.google.devtools.common.options.Converters;
@@ -129,11 +130,12 @@
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
-    OutErr outErr = runtime.getReporter().getOutErr();
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
+    OutErr outErr = env.getReporter().getOutErr();
     Options helpOptions = options.getOptions(Options.class);
     if (options.getResidue().isEmpty()) {
       emitBlazeVersionInfo(outErr);
@@ -141,7 +143,7 @@
       return ExitCode.SUCCESS;
     }
     if (options.getResidue().size() != 1) {
-      runtime.getReporter().handle(Event.error("You must specify exactly one command"));
+      env.getReporter().handle(Event.error("You must specify exactly one command"));
       return ExitCode.COMMAND_LINE_ERROR;
     }
     String helpSubject = options.getResidue().get(0);
@@ -170,7 +172,7 @@
         outErr.printOut(BlazeRuleHelpPrinter.getRuleDoc(helpSubject, provider));
         return ExitCode.SUCCESS;
       } else {
-        runtime.getReporter().handle(Event.error(
+        env.getReporter().handle(Event.error(
             null, "'" + helpSubject + "' is neither a command nor a build rule"));
         return ExitCode.COMMAND_LINE_ERROR;
       }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
index 1a0566b..38f913e 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
@@ -38,6 +38,7 @@
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.util.OsUtils;
@@ -177,13 +178,14 @@
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) { }
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) { }
 
   @Override
-  public ExitCode exec(final BlazeRuntime runtime, final OptionsProvider optionsProvider) {
-    runtime.getReporter().switchToAnsiAllowingHandler();
+  public ExitCode exec(final CommandEnvironment env, final OptionsProvider optionsProvider) {
+    final BlazeRuntime runtime = env.getRuntime();
+    env.getReporter().switchToAnsiAllowingHandler();
     Options infoOptions = optionsProvider.getOptions(Options.class);
-    OutErr outErr = runtime.getReporter().getOutErr();
+    OutErr outErr = env.getReporter().getOutErr();
     // Creating a BuildConfiguration is expensive and often unnecessary. Delay the creation until
     // it is needed.
     Supplier<BuildConfiguration> configurationSupplier = new Supplier<BuildConfiguration>() {
@@ -206,13 +208,13 @@
               .getTargetConfigurations().get(0);
           return configuration;
         } catch (InvalidConfigurationException e) {
-          runtime.getReporter().handle(Event.error(e.getMessage()));
+          env.getReporter().handle(Event.error(e.getMessage()));
           throw new ExitCausingRuntimeException(ExitCode.COMMAND_LINE_ERROR);
         } catch (AbruptExitException e) {
           throw new ExitCausingRuntimeException("unknown error: " + e.getMessage(),
               e.getExitCode());
         } catch (InterruptedException e) {
-          runtime.getReporter().handle(Event.error("interrupted"));
+          env.getReporter().handle(Event.error("interrupted"));
           throw new ExitCausingRuntimeException(ExitCode.INTERRUPTED);
         }
       }
@@ -231,7 +233,7 @@
 
       List<String> residue = optionsProvider.getResidue();
       if (residue.size() > 1) {
-        runtime.getReporter().handle(Event.error("at most one key may be specified"));
+        env.getReporter().handle(Event.error("at most one key may be specified"));
         return ExitCode.COMMAND_LINE_ERROR;
       }
 
@@ -241,14 +243,14 @@
         if (items.containsKey(key)) {
           value = items.get(key).get(configurationSupplier);
         } else {
-          runtime.getReporter().handle(Event.error("unknown key: '" + key + "'"));
+          env.getReporter().handle(Event.error("unknown key: '" + key + "'"));
           return ExitCode.COMMAND_LINE_ERROR;
         }
         try {
           outErr.getOutputStream().write(value);
           outErr.getOutputStream().flush();
         } catch (IOException e) {
-          runtime.getReporter().handle(Event.error("Cannot write info block: " + e.getMessage()));
+          env.getReporter().handle(Event.error("Cannot write info block: " + e.getMessage()));
           return ExitCode.ANALYSIS_FAILURE;
         }
       } else { // print them all
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/MobileInstallCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/MobileInstallCommand.java
index 13e2837..9701289 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/MobileInstallCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/MobileInstallCommand.java
@@ -16,12 +16,14 @@
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.analysis.OutputGroupProvider;
 import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.buildtool.BuildTool;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.rules.android.WriteAdbArgsAction;
 import com.google.devtools.build.lib.rules.android.WriteAdbArgsAction.StartType;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.common.options.Option;
@@ -65,11 +67,12 @@
   }
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
     Options mobileInstallOptions = options.getOptions(Options.class);
     WriteAdbArgsAction.Options adbOptions = options.getOptions(WriteAdbArgsAction.Options.class);
     if (adbOptions.start == StartType.WARM && !mobileInstallOptions.incremental) {
-      runtime.getReporter().handle(Event.warn(
+      env.getReporter().handle(Event.warn(
          "Warm start is enabled, but will have no effect on a non-incremental build"));
     }
 
@@ -77,12 +80,12 @@
     BuildRequest request = BuildRequest.create(
         this.getClass().getAnnotation(Command.class).name(), options,
         runtime.getStartupOptionsProvider(), targets,
-        runtime.getReporter().getOutErr(), runtime.getCommandId(), runtime.getCommandStartTime());
-    return runtime.getBuildTool().processRequest(request, null).getExitCondition();
+        env.getReporter().getOutErr(), runtime.getCommandId(), runtime.getCommandStartTime());
+    return new BuildTool(env).processRequest(request, null).getExitCondition();
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser)
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser)
       throws AbruptExitException {
     try {
       String outputGroup =
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java
index 0992aad..89d6bbb 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.util.StringUtil;
 import com.google.devtools.build.lib.util.TimeUtilities;
@@ -138,10 +139,11 @@
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 
   @Override
-  public ExitCode exec(final BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(final CommandEnvironment env, OptionsProvider options) {
+    final BlazeRuntime runtime = env.getRuntime();
     ProfileOptions opts =
         options.getOptions(ProfileOptions.class);
 
@@ -160,9 +162,9 @@
       }
     };
 
-    PrintStream out = new PrintStream(runtime.getReporter().getOutErr().getOutputStream());
+    PrintStream out = new PrintStream(env.getReporter().getOutErr().getOutputStream());
     try {
-      runtime.getReporter().handle(Event.warn(
+      env.getReporter().handle(Event.warn(
           null, "This information is intended for consumption by Blaze developers"
               + " only, and may change at any time.  Script against it at your own risk"));
 
@@ -177,7 +179,7 @@
             Path htmlFile =
                 profileFile.getParentDirectory().getChild(profileFile.getBaseName() + ".html");
 
-            runtime.getReporter().handle(Event.info("Creating HTML output in " + htmlFile));
+            env.getReporter().handle(Event.info("Creating HTML output in " + htmlFile));
 
             HtmlCreator.createHtml(
                 info,
@@ -189,7 +191,7 @@
             createText(runtime, info, out, opts);
           }
         } catch (IOException e) {
-          runtime.getReporter().handle(Event.error(
+          env.getReporter().handle(Event.error(
               null, "Failed to process file " + name + ": " + e.getMessage()));
         }
       }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/ProjectFileSupport.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/ProjectFileSupport.java
index e3beaa4..63552b4 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/ProjectFileSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/ProjectFileSupport.java
@@ -18,6 +18,7 @@
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.CommonCommandOptions;
 import com.google.devtools.build.lib.runtime.ProjectFile;
 import com.google.devtools.build.lib.util.AbruptExitException;
@@ -44,8 +45,9 @@
    * accordingly. If project files cannot be read or if they contain unparsable options, or if they
    * are not enabled, then it throws an exception instead.
    */
-  public static void handleProjectFiles(BlazeRuntime runtime, OptionsParser optionsParser,
+  public static void handleProjectFiles(CommandEnvironment env, OptionsParser optionsParser,
       String command) throws AbruptExitException {
+    BlazeRuntime runtime = env.getRuntime();
     List<String> targets = optionsParser.getResidue();
     ProjectFile.Provider projectFileProvider = runtime.getProjectFileProvider();
     if (projectFileProvider != null && !targets.isEmpty()
@@ -65,11 +67,11 @@
       List<Path> packagePath = PathPackageLocator.create(
           runtime.getOutputBase(),
           optionsParser.getOptions(PackageCacheOptions.class).packagePath,
-          runtime.getReporter(),
+          env.getReporter(),
           runtime.getWorkspace(),
           runtime.getWorkingDirectory()).getPathEntries();
       ProjectFile projectFile = projectFileProvider.getProjectFile(packagePath, projectFilePath);
-      runtime.getReporter().handle(Event.info("Using " + projectFile.getName()));
+      env.getReporter().handle(Event.info("Using " + projectFile.getName()));
 
       try {
         optionsParser.parse(
@@ -77,7 +79,7 @@
       } catch (OptionsParsingException e) {
         throw new AbruptExitException(e.getMessage(), ExitCode.COMMAND_LINE_ERROR);
       }
-      runtime.getEventBus().post(new GotProjectFileEvent(projectFile.getName()));
+      env.getEventBus().post(new GotProjectFileEvent(projectFile.getName()));
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
index 04e2b04..fe30b64 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.common.options.OptionsParser;
@@ -59,7 +60,7 @@
 public final class QueryCommand implements BlazeCommand {
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) { }
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) { }
 
   /**
    * Exit codes:
@@ -70,7 +71,8 @@
    *        (only when --keep_going is in effect.)
    */
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
     QueryOptions queryOptions = options.getOptions(QueryOptions.class);
 
     try {
@@ -78,15 +80,15 @@
           options.getOptions(PackageCacheOptions.class),
           runtime.getDefaultsPackageContent());
     } catch (InterruptedException e) {
-      runtime.getReporter().handle(Event.error("query interrupted"));
+      env.getReporter().handle(Event.error("query interrupted"));
       return ExitCode.INTERRUPTED;
     } catch (AbruptExitException e) {
-      runtime.getReporter().handle(Event.error(null, "Unknown error: " + e.getMessage()));
+      env.getReporter().handle(Event.error(null, "Unknown error: " + e.getMessage()));
       return e.getExitCode();
     }
 
     if (options.getResidue().isEmpty()) {
-      runtime.getReporter().handle(Event.error(String.format(
+      env.getReporter().handle(Event.error(String.format(
           "missing query expression. Type '%s help query' for syntax and help",
           Constants.PRODUCT_NAME)));
       return ExitCode.COMMAND_LINE_ERROR;
@@ -96,7 +98,7 @@
     OutputFormatter formatter =
         OutputFormatter.getFormatter(formatters, queryOptions.outputFormat);
     if (formatter == null) {
-      runtime.getReporter().handle(Event.error(
+      env.getReporter().handle(Event.error(
           String.format("Invalid output format '%s'. Valid values are: %s",
               queryOptions.outputFormat, OutputFormatter.formatterNames(formatters))));
       return ExitCode.COMMAND_LINE_ERROR;
@@ -105,7 +107,7 @@
     String query = Joiner.on(' ').join(options.getResidue());
 
     Set<Setting> settings = queryOptions.toSettings();
-    AbstractBlazeQueryEnvironment<Target> env = newQueryEnvironment(
+    AbstractBlazeQueryEnvironment<Target> queryEnv = newQueryEnvironment(
         runtime,
         queryOptions.keepGoing,
         QueryOutputUtils.orderResults(queryOptions, formatter),
@@ -115,9 +117,9 @@
     // 1. Parse query:
     QueryExpression expr;
     try {
-      expr = QueryExpression.parse(query, env);
+      expr = QueryExpression.parse(query, queryEnv);
     } catch (QueryException e) {
-      runtime.getReporter().handle(Event.error(
+      env.getReporter().handle(Event.error(
           null, "Error while parsing '" + query + "': " + e.getMessage()));
       return ExitCode.COMMAND_LINE_ERROR;
     }
@@ -125,35 +127,34 @@
     // 2. Evaluate expression:
     QueryEvalResult<Target> result;
     try {
-      result = env.evaluateQuery(expr);
+      result = queryEnv.evaluateQuery(expr);
     } catch (QueryException | InterruptedException e) {
       // Keep consistent with reportBuildFileError()
-      runtime
-          .getReporter()
+      env.getReporter()
           // TODO(bazel-team): this is a kludge to fix a bug observed in the wild. We should make
           // sure no null error messages ever get in.
           .handle(Event.error(e.getMessage() == null ? e.toString() : e.getMessage()));
       return ExitCode.ANALYSIS_FAILURE;
     }
 
-    runtime.getReporter().switchToAnsiAllowingHandler();
+    env.getReporter().switchToAnsiAllowingHandler();
     // 3. Output results:
-    PrintStream output = new PrintStream(runtime.getReporter().getOutErr().getOutputStream());
+    PrintStream output = new PrintStream(env.getReporter().getOutErr().getOutputStream());
     try {
       QueryOutputUtils.output(queryOptions, result, formatter, output,
           queryOptions.aspectDeps.createResolver(
-              runtime.getPackageManager(), runtime.getReporter()));
+              runtime.getPackageManager(), env.getReporter()));
     } catch (ClosedByInterruptException | InterruptedException e) {
-      runtime.getReporter().handle(Event.error("query interrupted"));
+      env.getReporter().handle(Event.error("query interrupted"));
       return ExitCode.INTERRUPTED;
     } catch (IOException e) {
-      runtime.getReporter().handle(Event.error("I/O error: " + e.getMessage()));
+      env.getReporter().handle(Event.error("I/O error: " + e.getMessage()));
       return ExitCode.LOCAL_ENVIRONMENTAL_ERROR;
     } finally {
       output.flush();
     }
     if (result.getResultSet().isEmpty()) {
-      runtime.getReporter().handle(Event.info("Empty results"));
+      env.getReporter().handle(Event.info("Empty results"));
     }
 
     return result.getSuccess() ? ExitCode.SUCCESS : ExitCode.PARTIAL_ANALYSIS_FAILURE;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
index 16ed00a..5efe25e 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
@@ -27,6 +27,7 @@
 import com.google.devtools.build.lib.buildtool.BuildRequest;
 import com.google.devtools.build.lib.buildtool.BuildRequest.BuildRequestOptions;
 import com.google.devtools.build.lib.buildtool.BuildResult;
+import com.google.devtools.build.lib.buildtool.BuildTool;
 import com.google.devtools.build.lib.buildtool.OutputDirectoryLinksUtils;
 import com.google.devtools.build.lib.buildtool.TargetValidator;
 import com.google.devtools.build.lib.events.Event;
@@ -42,6 +43,7 @@
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.shell.AbnormalTerminationException;
 import com.google.devtools.build.lib.shell.BadExitStatusException;
 import com.google.devtools.build.lib.shell.CommandException;
@@ -109,35 +111,36 @@
   private static final FileType RUNFILES_MANIFEST = FileType.of(".runfiles_manifest");
 
   @VisibleForTesting  // productionVisibility = Visibility.PRIVATE
-  protected BuildResult processRequest(final BlazeRuntime runtime, BuildRequest request) {
-    return runtime.getBuildTool().processRequest(request, new TargetValidator() {
+  protected BuildResult processRequest(final CommandEnvironment env, BuildRequest request) {
+    return new BuildTool(env).processRequest(request, new TargetValidator() {
       @Override
       public void validateTargets(Collection<Target> targets, boolean keepGoing)
           throws LoadingFailedException {
-        RunCommand.this.validateTargets(runtime.getReporter(), targets, keepGoing);
+        RunCommand.this.validateTargets(env.getReporter(), targets, keepGoing);
       }
     });
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) { }
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) { }
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
+    BlazeRuntime runtime = env.getRuntime();
     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()) {
-      runtime.getReporter().handle(Event.error("Must specify a target to run"));
+      env.getReporter().handle(Event.error("Must specify a target to run"));
       return ExitCode.COMMAND_LINE_ERROR;
     }
     String targetString = targetAndArgs.get(0);
     List<String> runTargetArgs = targetAndArgs.subList(1, targetAndArgs.size());
     RunUnder runUnder = options.getOptions(BuildConfiguration.Options.class).runUnder;
 
-    OutErr outErr = runtime.getReporter().getOutErr();
+    OutErr outErr = env.getReporter().getOutErr();
     List<String> targets = (runUnder != null) && (runUnder.getLabel() != null)
         ? ImmutableList.of(targetString, runUnder.getLabel().toString())
         : ImmutableList.of(targetString);
@@ -149,13 +152,13 @@
     currentRunUnder = runUnder;
     BuildResult result;
     try {
-      result = processRequest(runtime, request);
+      result = processRequest(env, request);
     } finally {
       currentRunUnder = null;
     }
 
     if (!result.getSuccess()) {
-      runtime.getReporter().handle(Event.error("Build failed. Not running target"));
+      env.getReporter().handle(Event.error("Build failed. Not running target"));
       return result.getExitCondition();
     }
 
@@ -170,7 +173,7 @@
     if (targetsBuilt != null) {
       int maxTargets = runUnder != null && runUnder.getLabel() != null ? 2 : 1;
       if (targetsBuilt.size() > maxTargets) {
-        runtime.getReporter().handle(Event.error(SINGLE_TARGET_MESSAGE));
+        env.getReporter().handle(Event.error(SINGLE_TARGET_MESSAGE));
         return ExitCode.COMMAND_LINE_ERROR;
       }
       for (ConfiguredTarget target : targetsBuilt) {
@@ -180,7 +183,7 @@
         }
         if (runUnder != null && target.getLabel().equals(runUnder.getLabel())) {
           if (runUnderTarget != null) {
-            runtime.getReporter().handle(Event.error(
+            env.getReporter().handle(Event.error(
                 null, "Can't identify the run_under target from multiple options?"));
             return ExitCode.COMMAND_LINE_ERROR;
           }
@@ -188,7 +191,7 @@
         } else if (targetToRun == null) {
           targetToRun = target;
         } else {
-          runtime.getReporter().handle(Event.error(SINGLE_TARGET_MESSAGE));
+          env.getReporter().handle(Event.error(SINGLE_TARGET_MESSAGE));
           return ExitCode.COMMAND_LINE_ERROR;
         }
       }
@@ -198,7 +201,7 @@
       targetToRun = runUnderTarget;
     }
     if (targetToRun == null) {
-      runtime.getReporter().handle(Event.error(NO_TARGET_MESSAGE));
+      env.getReporter().handle(Event.error(NO_TARGET_MESSAGE));
       return ExitCode.COMMAND_LINE_ERROR;
     }
 
@@ -208,14 +211,14 @@
     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 = runtime.getBuildTool().getView().getConfigurationCollection()
+      configuration = runtime.getView().getConfigurationCollection()
           .getTargetConfigurations().get(0);
     }
     Path workingDir;
     try {
       workingDir = ensureRunfilesBuilt(runtime, targetToRun);
     } catch (CommandException e) {
-      runtime.getReporter().handle(Event.error("Error creating runfiles: " + e.getMessage()));
+      env.getReporter().handle(Event.error("Error creating runfiles: " + e.getMessage()));
       return ExitCode.LOCAL_ENVIRONMENTAL_ERROR;
     }
 
@@ -298,7 +301,7 @@
       }
     }
 
-    runtime.getReporter().handle(Event.info(
+    env.getReporter().handle(Event.info(
         null, "Running command line: " + ShellEscaper.escapeJoinAll(prettyCmdLine)));
 
     com.google.devtools.build.lib.shell.Command command = new CommandBuilder()
@@ -307,7 +310,7 @@
     try {
       // Restore a raw EventHandler if it is registered. This allows for blaze run to produce the
       // actual output of the command being run even if --color=no is specified.
-      runtime.getReporter().switchToAnsiAllowingHandler();
+      env.getReporter().switchToAnsiAllowingHandler();
 
       // The command API is a little strange in that the following statement
       // will return normally only if the program exits with exit code 0.
@@ -322,13 +325,13 @@
       String message = "Non-zero return code '"
                        + e.getResult().getTerminationStatus().getExitCode()
                        + "' from command: " + e.getMessage();
-      runtime.getReporter().handle(Event.error(message));
+      env.getReporter().handle(Event.error(message));
       return ExitCode.RUN_FAILURE;
     } catch (AbnormalTerminationException e) {
       // The process was likely terminated by a signal in this case.
       return ExitCode.INTERRUPTED;
     } catch (CommandException e) {
-      runtime.getReporter().handle(Event.error("Error running program: " + e.getMessage()));
+      env.getReporter().handle(Event.error("Error running program: " + e.getMessage()));
       return ExitCode.RUN_FAILURE;
     }
   }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/ShutdownCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/ShutdownCommand.java
index fe606e6..4e63e7e 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/ShutdownCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/ShutdownCommand.java
@@ -15,8 +15,8 @@
 
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.ShutdownBlazeServerException;
-import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionsBase;
@@ -47,12 +47,11 @@
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options)
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options)
       throws ShutdownBlazeServerException {
-
     int limit = options.getOptions(Options.class).heapSizeLimit;
 
     // Iff limit is non-zero, shut down the server if total memory exceeds the
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
index 962e0c0..9d9ca9d 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
@@ -18,6 +18,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.buildtool.BuildRequest;
 import com.google.devtools.build.lib.buildtool.BuildResult;
+import com.google.devtools.build.lib.buildtool.BuildTool;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.exec.ExecutionOptions;
 import com.google.devtools.build.lib.rules.test.TestStrategy;
@@ -27,6 +28,7 @@
 import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.TerminalTestResultNotifier;
 import com.google.devtools.build.lib.runtime.TerminalTestResultNotifier.TestSummaryOptions;
 import com.google.devtools.build.lib.runtime.TestResultAnalyzer;
@@ -63,15 +65,15 @@
   }
 
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser)
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser)
       throws AbruptExitException {
-    ProjectFileSupport.handleProjectFiles(runtime, optionsParser, commandName());
+    ProjectFileSupport.handleProjectFiles(env, optionsParser, commandName());
 
     TestOutputFormat testOutput = optionsParser.getOptions(ExecutionOptions.class).testOutput;
 
     try {
       if (testOutput == TestStrategy.TestOutputFormat.STREAMED) {
-        runtime.getReporter().handle(Event.warn(
+        env.getReporter().handle(Event.warn(
             "Streamed test output requested so all tests will be run locally, without sharding, " +
              "one at a time"));
         optionsParser.parse(OptionPriority.SOFTWARE_REQUIREMENT,
@@ -84,36 +86,37 @@
   }
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
     TestResultAnalyzer resultAnalyzer = new TestResultAnalyzer(
-        runtime.getExecRoot(),
+        env.getDirectories().getExecRoot(),
         options.getOptions(TestSummaryOptions.class),
         options.getOptions(ExecutionOptions.class),
-        runtime.getEventBus());
+        env.getEventBus());
 
-    printer = new AnsiTerminalPrinter(runtime.getReporter().getOutErr().getOutputStream(),
+    printer = new AnsiTerminalPrinter(env.getReporter().getOutErr().getOutputStream(),
         options.getOptions(BlazeCommandEventHandler.Options.class).useColor());
 
     // Initialize test handler.
     AggregatingTestListener testListener = new AggregatingTestListener(
-        resultAnalyzer, runtime.getEventBus(), runtime.getReporter());
+        resultAnalyzer, env.getEventBus(), env.getReporter());
 
-    runtime.getEventBus().register(testListener);
-    return doTest(runtime, options, testListener);
+    env.getEventBus().register(testListener);
+    return doTest(env, options, testListener);
   }
 
-  private ExitCode doTest(BlazeRuntime runtime,
+  private ExitCode doTest(CommandEnvironment env,
       OptionsProvider options,
       AggregatingTestListener testListener) {
+    BlazeRuntime runtime = env.getRuntime();
     // Run simultaneous build and test.
     List<String> targets = ProjectFileSupport.getTargets(runtime, options);
     BuildRequest request = BuildRequest.create(
         getClass().getAnnotation(Command.class).name(), options,
         runtime.getStartupOptionsProvider(), targets,
-        runtime.getReporter().getOutErr(), runtime.getCommandId(), runtime.getCommandStartTime());
+        env.getReporter().getOutErr(), runtime.getCommandId(), runtime.getCommandStartTime());
     request.setRunTests();
 
-    BuildResult buildResult = runtime.getBuildTool().processRequest(request, null);
+    BuildResult buildResult = new BuildTool(env).processRequest(request, null);
 
     Collection<ConfiguredTarget> testTargets = buildResult.getTestTargets();
     // TODO(bazel-team): don't handle isEmpty here or fix up a bunch of tests
@@ -121,13 +124,13 @@
       // This can happen if there were errors in the target parsing or loading phase
       // (original exitcode=BUILD_FAILURE) or if there weren't but --noanalyze was given
       // (original exitcode=SUCCESS).
-      runtime.getReporter().handle(Event.error("Couldn't start the build. Unable to run tests"));
+      env.getReporter().handle(Event.error("Couldn't start the build. Unable to run tests"));
       return buildResult.getSuccess() ? ExitCode.PARSING_FAILURE : buildResult.getExitCondition();
     }
     // TODO(bazel-team): the check above shadows NO_TESTS_FOUND, but switching the conditions breaks
     // more tests
     if (testTargets.isEmpty()) {
-      runtime.getReporter().handle(Event.error(
+      env.getReporter().handle(Event.error(
           null, "No test targets were found, yet testing was requested"));
       return buildResult.getSuccess() ? ExitCode.NO_TESTS_FOUND : buildResult.getExitCondition();
     }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/VersionCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/VersionCommand.java
index 2d7b9ae..9ac1bb3 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/VersionCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/VersionCommand.java
@@ -16,8 +16,8 @@
 import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
-import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.common.options.OptionsParser;
 import com.google.devtools.common.options.OptionsProvider;
@@ -34,16 +34,16 @@
          shortDescription = "Prints version information for %{product}.")
 public final class VersionCommand implements BlazeCommand {
   @Override
-  public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+  public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 
   @Override
-  public ExitCode exec(BlazeRuntime runtime, OptionsProvider options) {
+  public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
     BlazeVersionInfo info = BlazeVersionInfo.instance();
     if (info.getSummary() == null) {
-      runtime.getReporter().handle(Event.error("Version information not available"));
+      env.getReporter().handle(Event.error("Version information not available"));
       return ExitCode.COMMAND_LINE_ERROR;
     }
-    runtime.getReporter().getOutErr().printOutLn(info.getSummary());
+    env.getReporter().getOutErr().printOutLn(info.getSummary());
     return ExitCode.SUCCESS;
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
index 5287405..d9e0dc9 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.OS;
 import com.google.devtools.common.options.OptionsBase;
 
@@ -37,6 +38,7 @@
 public class SandboxModule extends BlazeModule {
   private final ExecutorService backgroundWorkers = Executors.newCachedThreadPool();
   private BuildRequest buildRequest;
+  private CommandEnvironment env;
   private BlazeRuntime runtime;
   private Boolean sandboxingSupported = null;
 
@@ -48,7 +50,7 @@
   @Override
   public Iterable<ActionContextProvider> getActionContextProviders() {
     Preconditions.checkNotNull(buildRequest);
-    Preconditions.checkNotNull(runtime);
+    Preconditions.checkNotNull(env);
 
     // Cache
     if (sandboxingSupported == null) {
@@ -64,7 +66,7 @@
     // warning to the user if they can't do anything about it.
     if (!buildRequest.getOptions(SandboxOptions.class).ignoreUnsupportedSandboxing
         && OS.getCurrent() == OS.LINUX) {
-      runtime.getReporter().handle(Event.warn(SANDBOX_NOT_SUPPORTED_MESSAGE));
+      env.getReporter().handle(Event.warn(SANDBOX_NOT_SUPPORTED_MESSAGE));
     }
 
     return ImmutableList.of();
@@ -72,7 +74,7 @@
 
   @Override
   public Iterable<ActionContextConsumer> getActionContextConsumers() {
-    Preconditions.checkNotNull(runtime);
+    Preconditions.checkNotNull(env);
 
     if (sandboxingSupported == null) {
       sandboxingSupported = NamespaceSandboxRunner.isSupported(runtime);
@@ -93,14 +95,10 @@
   }
 
   @Override
-  public void beforeCommand(BlazeRuntime runtime, Command command) {
-    if (this.runtime == null) {
-      this.runtime = runtime;
-    } else {
-      // The BlazeRuntime is guaranteed to never change.
-      Preconditions.checkArgument(runtime == this.runtime);
-    }
-    runtime.getEventBus().register(this);
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    this.env = env;
+    this.runtime = env.getRuntime();
+    env.getEventBus().register(this);
   }
 
   @Subscribe
diff --git a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java
index 005af2a..e880043 100644
--- a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java
+++ b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 
 /**
  * StandaloneModule provides pluggable functionality for blaze.
@@ -36,9 +37,9 @@
   }
 
   @Override
-  public void beforeCommand(BlazeRuntime runtime, Command command) {
-    this.runtime = runtime;
-    runtime.getEventBus().register(this);
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    this.runtime = env.getRuntime();
+    env.getEventBus().register(this);
   }
 
   @Subscribe
diff --git a/src/main/java/com/google/devtools/build/lib/webstatusserver/WebStatusServerModule.java b/src/main/java/com/google/devtools/build/lib/webstatusserver/WebStatusServerModule.java
index 507eaa5..fb82205 100644
--- a/src/main/java/com/google/devtools/build/lib/webstatusserver/WebStatusServerModule.java
+++ b/src/main/java/com/google/devtools/build/lib/webstatusserver/WebStatusServerModule.java
@@ -18,9 +18,9 @@
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
 import com.google.devtools.build.lib.runtime.BlazeModule;
-import com.google.devtools.build.lib.runtime.BlazeRuntime;
 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.util.AbruptExitException;
 import com.google.devtools.build.lib.util.Clock;
 import com.google.devtools.common.options.OptionsBase;
@@ -91,12 +91,13 @@
   }
 
   @Override
-  public void beforeCommand(BlazeRuntime blazeRuntime, Command command) throws AbruptExitException {
+  public void beforeCommand(Command command, CommandEnvironment env)
+      throws AbruptExitException {
     if (!running) {
       return;
     }
     collector =
-        new WebStatusEventCollector(blazeRuntime.getEventBus(), blazeRuntime.getReporter(), this);
+        new WebStatusEventCollector(env.getEventBus(), env.getReporter(), this);
   }
 
   public void commandStarted() {
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
index 07943ec..cb14872 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
@@ -24,8 +24,8 @@
 import com.google.devtools.build.lib.buildtool.buildevent.BuildStartingEvent;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.runtime.BlazeModule;
-import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.common.options.OptionsBase;
 
@@ -37,9 +37,10 @@
  * A module that adds the WorkerActionContextProvider to the available action context providers.
  */
 public class WorkerModule extends BlazeModule {
-  private BlazeRuntime blazeRuntime;
-  private BuildRequest buildRequest;
   private WorkerPool workers;
+
+  private CommandEnvironment env;
+  private BuildRequest buildRequest;
   private boolean verbose;
 
   @Override
@@ -50,16 +51,16 @@
   }
 
   @Override
-  public void beforeCommand(BlazeRuntime blazeRuntime, Command command) {
-    this.blazeRuntime = Preconditions.checkNotNull(blazeRuntime);
-    blazeRuntime.getEventBus().register(this);
+  public void beforeCommand(Command command, CommandEnvironment env) {
+    this.env = env;
+    env.getEventBus().register(this);
 
     if (workers == null) {
-      Path logDir = blazeRuntime.getOutputBase().getRelative("worker-logs");
+      Path logDir = env.getRuntime().getOutputBase().getRelative("worker-logs");
       try {
         logDir.createDirectory();
       } catch (IOException e) {
-        blazeRuntime
+        env
             .getReporter()
             .handle(Event.error("Could not create directory for worker logs: " + logDir));
       }
@@ -68,7 +69,7 @@
       config.setTimeBetweenEvictionRunsMillis(10 * 1000);
 
       workers = new WorkerPool(new WorkerFactory(), config);
-      workers.setReporter(blazeRuntime.getReporter());
+      workers.setReporter(env.getReporter());
       workers.setLogDirectory(logDir);
     }
   }
@@ -89,13 +90,13 @@
 
   @Override
   public Iterable<ActionContextProvider> getActionContextProviders() {
-    Preconditions.checkNotNull(blazeRuntime);
+    Preconditions.checkNotNull(env);
     Preconditions.checkNotNull(buildRequest);
     Preconditions.checkNotNull(workers);
 
     return ImmutableList.<ActionContextProvider>of(
         new WorkerActionContextProvider(
-            blazeRuntime, buildRequest, workers, blazeRuntime.getEventBus()));
+            env.getRuntime(), buildRequest, workers, env.getEventBus()));
   }
 
   @Override
@@ -107,7 +108,7 @@
   public void buildComplete(BuildCompleteEvent event) {
     if (workers != null && buildRequest.getOptions(WorkerOptions.class).workerQuitAfterBuild) {
       if (verbose) {
-        blazeRuntime
+        env
             .getReporter()
             .handle(Event.info("Build completed, shutting down worker pool..."));
       }
@@ -123,7 +124,7 @@
   public void buildInterrupted(BuildInterruptedEvent event) {
     if (workers != null) {
       if (verbose) {
-        blazeRuntime
+        env
             .getReporter()
             .handle(Event.info("Build interrupted, shutting down worker pool..."));
       }
@@ -134,7 +135,8 @@
 
   @Override
   public void afterCommand() {
-    this.blazeRuntime = null;
+    this.env = null;
     this.buildRequest = null;
+    this.verbose = false;
   }
 }