Add a ServerBuilder, and use that in the module API.

This change is similar to a previous change that introduced WorkspaceBuilder.

--
MOS_MIGRATED_REVID=126799657
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
index 0c8dd59..42c935e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -18,6 +18,7 @@
 import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.ABSTRACT;
 import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.TEST;
 
+import com.google.common.base.Preconditions;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
@@ -55,6 +56,9 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
+
+import javax.annotation.Nullable;
 
 /**
  * Knows about every rule Blaze supports and the associated configuration options.
@@ -108,6 +112,7 @@
         ImmutableList.<Class<?>>builder().addAll(SkylarkModules.MODULES);
     private ImmutableBiMap.Builder<String, Class<? extends TransitiveInfoProvider>>
         registeredSkylarkProviders = ImmutableBiMap.builder();
+    private Map<String, String> platformRegexps = new TreeMap<>();
 
     public void addWorkspaceFilePrefix(String contents) {
       defaultWorkspaceFilePrefix.append(contents);
@@ -211,6 +216,26 @@
       return this;
     }
 
+    /**
+     * Do not use - this only exists for backwards compatibility! Platform regexps are part of a
+     * legacy mechanism - {@code vardef} - that is not exposed in Bazel.
+     *
+     * <p>{@code vardef} needs explicit support in the rule implementations, and cannot express
+     * conditional dependencies, only conditional attribute values. This mechanism will be
+     * supplanted by configuration dependent attributes, and its effect can usually also be achieved
+     * with select().
+     *
+     * <p>This is a map of platform names to regexps. When a name is used as the third argument to
+     * {@code vardef}, the corresponding regexp is used to match on the C++ abi, and the variable is
+     * only set to that value if the regexp matches. For example, the entry
+     * {@code "oldlinux": "i[34]86-libc[345]-linux"} might define a set of platforms representing
+     * certain older linux releases.
+     */
+    public Builder addPlatformRegexps(Map<String, String> platformRegexps) {
+      this.platformRegexps.putAll(Preconditions.checkNotNull(platformRegexps));
+      return this;
+    }
+
     private RuleConfiguredTargetFactory createFactory(
         Class<? extends RuleConfiguredTargetFactory> factoryClass) {
       try {
@@ -304,6 +329,11 @@
     public String getToolsRepository() {
       return toolsRepository;
     }
+
+    @Nullable
+    public Map<String, String> getPlatformRegexps() {
+      return platformRegexps.isEmpty() ? null : ImmutableMap.copyOf(platformRegexps);
+    }
   }
 
   /**
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 12c4bd2..365632d 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
@@ -14,7 +14,6 @@
 
 package com.google.devtools.build.lib.bazel;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
@@ -52,10 +51,10 @@
 import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
-import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.Command;
 import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.runtime.ServerBuilder;
 import com.google.devtools.build.lib.runtime.WorkspaceBuilder;
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
 import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
@@ -132,6 +131,11 @@
       };
 
   @Override
+  public void serverInit(OptionsProvider startupOptions, ServerBuilder builder) {
+    builder.addCommands(new FetchCommand());
+  }
+
+  @Override
   public void workspaceInit(BlazeDirectories directories, WorkspaceBuilder builder) {
     builder.addCustomDirtinessChecker(REPOSITORY_VALUE_CHECKER);
     // Create the repository function everything flows through.
@@ -156,11 +160,6 @@
   }
 
   @Override
-  public Iterable<? extends BlazeCommand> getCommands() {
-    return ImmutableList.of(new FetchCommand());
-  }
-
-  @Override
   public void handleOptions(OptionsProvider optionsProvider) {
     PackageCacheOptions pkgOptions = optionsProvider.getOptions(PackageCacheOptions.class);
     isFetch.set(pkgOptions != null && pkgOptions.fetch);
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 171a1ff..48a4898 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
@@ -493,6 +493,7 @@
       numericExitCode = e.getExitStatus();
       throw e;
     } catch (Throwable e) {
+      e.printStackTrace();
       BugReport.printBug(outErr, e);
       BugReport.sendBugReport(e, args, crashData);
       numericExitCode = e instanceof OutOfMemoryError
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 03580b2..9879ab0 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
@@ -13,7 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.lib.runtime;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.actions.ActionContextConsumer;
@@ -24,11 +23,9 @@
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.exec.OutputService;
-import com.google.devtools.build.lib.packages.AttributeContainer;
 import com.google.devtools.build.lib.packages.NoSuchThingException;
 import com.google.devtools.build.lib.packages.Package;
 import com.google.devtools.build.lib.packages.PackageFactory;
-import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
 import com.google.devtools.build.lib.query2.AbstractBlazeQueryEnvironment;
 import com.google.devtools.build.lib.query2.QueryEnvironmentFactory;
@@ -36,7 +33,6 @@
 import com.google.devtools.build.lib.query2.output.OutputFormatter;
 import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory;
 import com.google.devtools.build.lib.runtime.commands.InfoItem;
-import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.util.Clock;
 import com.google.devtools.build.lib.vfs.FileSystem;
@@ -100,11 +96,18 @@
   }
 
   /**
-   * Called when Blaze initializes a new workspace.
+   * Called to initialize a new server ({@link BlazeRuntime}). Modules can override this method to
+   * affect how the server is configured. This is called after the startup options have been
+   * collected and parsed, and after the file system was setup.
+   *
+   * @param startupOptions the server startup options
+   * @param builder builder class that collects the server configuration
    */
+  public void serverInit(OptionsProvider startupOptions, ServerBuilder builder) {}
+
+  /** Called when Blaze initializes a new workspace. */
   @SuppressWarnings("unused")
-  public void workspaceInit(BlazeDirectories directories, WorkspaceBuilder builder) {
-  }
+  public void workspaceInit(BlazeDirectories directories, WorkspaceBuilder builder) {}
 
   /**
    * Adds the rule classes supported by this module.
@@ -116,15 +119,6 @@
   }
 
   /**
-   * Returns the list of commands this module contributes to Blaze.
-   *
-   * <p>This method will be called during Blaze startup (after #blazeStartup).
-   */
-  public Iterable<? extends BlazeCommand> getCommands() {
-    return ImmutableList.of();
-  }
-
-  /**
    * Returns the list of query output formatters this module provides.
    *
    * <p>This method will be called during Blaze startup (after #blazeStartup).
@@ -134,30 +128,6 @@
   }
 
   /**
-   * PlatformSet is a group of platforms characterized by a regular expression.  For example, the
-   * entry "oldlinux": "i[34]86-libc[345]-linux" might define a set of platforms representing
-   * certain older linux releases.
-   *
-   * <p>Platform-set names are used in BUILD files in the third argument to <tt>vardef</tt>, to
-   * define per-platform tweaks to variables such as CFLAGS.
-   *
-   * <p>vardef is a legacy mechanism: it needs explicit support in the rule implementations,
-   * and cannot express conditional dependencies, only conditional attribute values. This
-   * mechanism will be supplanted by configuration dependent attributes, and its effect can
-   * usually also be achieved with select().
-   *
-   * <p>This method will be called during Blaze startup (after #blazeStartup).
-   */
-  public Map<String, String> getPlatformSetRegexps() {
-    return ImmutableMap.<String, String>of();
-  }
-
-  @Nullable
-  protected Function<RuleClass, AttributeContainer> getAttributeContainerSupplier() {
-    return null;
-  }
-
-  /**
    * Services provided for Blaze modules via BlazeRuntime.
    */
   public interface ModuleEnvironment {
@@ -355,12 +325,4 @@
   public CoverageReportActionFactory getCoverageReportFactory() {
     return null;
   }
-
-  /**
-   * Optionally returns the invocation policy to override options in blaze.
-   */
-  @Nullable
-  public InvocationPolicy getInvocationPolicy() {
-    return null;
-  }
 }
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 ec7c7a0..2a22272 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
@@ -18,7 +18,6 @@
 import com.google.common.base.Function;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -35,10 +34,8 @@
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.OutputFilter;
 import com.google.devtools.build.lib.flags.CommandNameCache;
-import com.google.devtools.build.lib.packages.AttributeContainer;
 import com.google.devtools.build.lib.packages.Package;
 import com.google.devtools.build.lib.packages.PackageFactory;
-import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
 import com.google.devtools.build.lib.profiler.AutoProfiler;
 import com.google.devtools.build.lib.profiler.MemoryProfiler;
@@ -51,19 +48,6 @@
 import com.google.devtools.build.lib.query2.output.OutputFormatter;
 import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory;
 import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.LockingMode;
-import com.google.devtools.build.lib.runtime.commands.BuildCommand;
-import com.google.devtools.build.lib.runtime.commands.CanonicalizeCommand;
-import com.google.devtools.build.lib.runtime.commands.CleanCommand;
-import com.google.devtools.build.lib.runtime.commands.DumpCommand;
-import com.google.devtools.build.lib.runtime.commands.HelpCommand;
-import com.google.devtools.build.lib.runtime.commands.InfoCommand;
-import com.google.devtools.build.lib.runtime.commands.MobileInstallCommand;
-import com.google.devtools.build.lib.runtime.commands.ProfileCommand;
-import com.google.devtools.build.lib.runtime.commands.QueryCommand;
-import com.google.devtools.build.lib.runtime.commands.RunCommand;
-import com.google.devtools.build.lib.runtime.commands.ShutdownCommand;
-import com.google.devtools.build.lib.runtime.commands.TestCommand;
-import com.google.devtools.build.lib.runtime.commands.VersionCommand;
 import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
 import com.google.devtools.build.lib.server.AfUnixServer;
 import com.google.devtools.build.lib.server.RPCServer;
@@ -190,21 +174,6 @@
     this.productName = productName;
   }
 
-  private static InvocationPolicy createInvocationPolicyFromModules(
-      InvocationPolicy initialInvocationPolicy,
-      Iterable<BlazeModule> modules) {
-    InvocationPolicy.Builder builder = InvocationPolicy.newBuilder();
-    builder.mergeFrom(initialInvocationPolicy);
-    // Merge the policies from the modules
-    for (BlazeModule module : modules) {
-      InvocationPolicy modulePolicy = module.getInvocationPolicy();
-      if (modulePolicy != null) {
-        builder.mergeFrom(module.getInvocationPolicy());
-      }
-    }
-    return builder.build();
-  }
-
   public void initWorkspace(BlazeDirectories directories, BinTools binTools)
       throws AbruptExitException {
     boolean watchFS = startupOptionsProvider != null
@@ -248,11 +217,6 @@
     for (BlazeCommand command : commands) {
       addCommand(command);
     }
-    for (BlazeModule module : blazeModules) {
-      for (BlazeCommand command : module.getCommands()) {
-        addCommand(command);
-      }
-    }
   }
 
   public CommandEnvironment initCommand() {
@@ -993,10 +957,10 @@
       LoggingUtil.installRemoteLogger(getTestCrashLogger());
     }
 
+    runtimeBuilder.addBlazeModule(new BuiltinCommandModule());
     for (BlazeModule blazeModule : blazeModules) {
       runtimeBuilder.addBlazeModule(blazeModule);
     }
-    runtimeBuilder.addCommands(getBuiltinCommandList());
 
     BlazeRuntime runtime = runtimeBuilder.build();
     AutoProfiler.setClock(runtime.getClock());
@@ -1056,28 +1020,6 @@
         });
   }
 
-
-  /**
-   * Returns an immutable list containing new instances of each Blaze command.
-   */
-  @VisibleForTesting
-  public static List<BlazeCommand> getBuiltinCommandList() {
-    return ImmutableList.of(
-        new BuildCommand(),
-        new CanonicalizeCommand(),
-        new CleanCommand(),
-        new DumpCommand(),
-        new HelpCommand(),
-        new InfoCommand(),
-        new MobileInstallCommand(),
-        new ProfileCommand(),
-        new QueryCommand(),
-        new RunCommand(),
-        new ShutdownCommand(),
-        new TestCommand(),
-        new VersionCommand());
-  }
-
   public String getProductName() {
     return productName;
   }
@@ -1093,15 +1035,12 @@
    */
   public static class Builder {
     private BlazeDirectories directories;
-    private ConfigurationFactory configurationFactory;
     private Clock clock;
     private OptionsProvider startupOptionsProvider;
     private final List<BlazeModule> blazeModules = new ArrayList<>();
     private SubscriberExceptionHandler eventBusExceptionHandler = new RemoteExceptionHandler();
     private BinTools binTools;
     private UUID instanceId;
-    private final List<BlazeCommand> commands = new ArrayList<>();
-    private InvocationPolicy invocationPolicy = InvocationPolicy.getDefaultInstance();
     private String productName;
 
     public BlazeRuntime build() throws AbruptExitException {
@@ -1117,20 +1056,9 @@
         module.blazeStartup(startupOptionsProvider,
             BlazeVersionInfo.instance(), instanceId, directories, clock);
       }
-
-      QueryEnvironmentFactory queryEnvironmentFactory = null;
+      ServerBuilder serverBuilder = new ServerBuilder();
       for (BlazeModule module : blazeModules) {
-        QueryEnvironmentFactory queryEnvFactory = module.getQueryEnvironmentFactory();
-        if (queryEnvFactory != null) {
-          Preconditions.checkState(queryEnvironmentFactory == null,
-              "At most one query environment factory supported. But found two: %s and %s",
-              queryEnvFactory,
-              queryEnvironmentFactory);
-          queryEnvironmentFactory = queryEnvFactory;
-        }
-      }
-      if (queryEnvironmentFactory == null) {
-        queryEnvironmentFactory = new QueryEnvironmentFactory();
+        module.serverInit(startupOptionsProvider, serverBuilder);
       }
 
       ConfiguredRuleClassProvider.Builder ruleClassBuilder =
@@ -1139,35 +1067,6 @@
         module.initializeRuleClasses(ruleClassBuilder);
       }
 
-      Map<String, String> platformRegexps = null;
-      {
-        ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
-        for (BlazeModule module : blazeModules) {
-          builder.putAll(module.getPlatformSetRegexps());
-        }
-        platformRegexps = builder.build();
-        if (platformRegexps.isEmpty()) {
-          platformRegexps = null; // Use the default.
-        }
-      }
-
-      Function<RuleClass, AttributeContainer> attributeContainerFactory = null;
-      for (BlazeModule module : blazeModules) {
-        Function<RuleClass, AttributeContainer> attrContainerFactory =
-            module.getAttributeContainerSupplier();
-        if (attrContainerFactory != null) {
-          Preconditions.checkState(
-              attributeContainerFactory == null,
-              "At most one attribute container supplier supported. But found two: %s and %s",
-              attrContainerFactory,
-              attributeContainerFactory);
-          attributeContainerFactory = attrContainerFactory;
-        }
-      }
-      if (attributeContainerFactory == null) {
-        attributeContainerFactory = AttributeContainer.ATTRIBUTE_CONTAINER_FACTORY;
-      }
-
       ConfiguredRuleClassProvider ruleClassProvider = ruleClassBuilder.build();
 
       List<PackageFactory.EnvironmentExtension> extensions = new ArrayList<>();
@@ -1192,17 +1091,16 @@
       PackageFactory packageFactory =
           new PackageFactory(
               ruleClassProvider,
-              platformRegexps,
-              attributeContainerFactory,
+              ruleClassBuilder.getPlatformRegexps(),
+              serverBuilder.getAttributeContainerFactory(),
               extensions,
               BlazeVersionInfo.instance().getVersion(),
               packageBuilderHelper);
 
-      if (configurationFactory == null) {
-        configurationFactory = new ConfigurationFactory(
-            ruleClassProvider.getConfigurationCollectionFactory(),
-            ruleClassProvider.getConfigurationFragments());
-      }
+      ConfigurationFactory configurationFactory =
+          new ConfigurationFactory(
+              ruleClassProvider.getConfigurationCollectionFactory(),
+              ruleClassProvider.getConfigurationFragments());
 
       ProjectFile.Provider projectFileProvider = null;
       for (BlazeModule module : blazeModules) {
@@ -1214,12 +1112,20 @@
         }
       }
 
-      invocationPolicy = createInvocationPolicyFromModules(invocationPolicy, blazeModules);
-
-      BlazeRuntime runtime = new BlazeRuntime(queryEnvironmentFactory, packageFactory,
-          ruleClassProvider, configurationFactory, clock, startupOptionsProvider,
-          ImmutableList.copyOf(blazeModules), eventBusExceptionHandler, projectFileProvider,
-          invocationPolicy, commands, productName);
+      BlazeRuntime runtime =
+          new BlazeRuntime(
+              serverBuilder.getQueryEnvironmentFactory(),
+              packageFactory,
+              ruleClassProvider,
+              configurationFactory,
+              clock,
+              startupOptionsProvider,
+              ImmutableList.copyOf(blazeModules),
+              eventBusExceptionHandler,
+              projectFileProvider,
+              serverBuilder.getInvocationPolicy(),
+              serverBuilder.getCommands(),
+              productName);
       runtime.initWorkspace(directories, binTools);
       return runtime;
     }
@@ -1234,29 +1140,15 @@
       return this;
     }
 
-    public Builder setInvocationPolicy(InvocationPolicy invocationPolicy) {
-      this.invocationPolicy = invocationPolicy;
-      return this;
-    }
-
     public Builder setDirectories(BlazeDirectories directories) {
       this.directories = directories;
       return this;
     }
 
-    /**
-     * Creates and sets a new {@link BlazeDirectories} instance with the given
-     * parameters.
-     */
-    public Builder setDirectories(Path installBase, Path outputBase,
-        Path workspace, String productName) {
-      this.directories = new BlazeDirectories(installBase, outputBase, workspace, productName);
-      return this;
-    }
-
-    public Builder setConfigurationFactory(ConfigurationFactory configurationFactory) {
-      this.configurationFactory = configurationFactory;
-      return this;
+    /** Creates and sets a new {@link BlazeDirectories} instance with the given parameters. */
+    public Builder setDirectories(
+        Path installBase, Path outputBase, Path workspace, String productName) {
+      return setDirectories(new BlazeDirectories(installBase, outputBase, workspace, productName));
     }
 
     public Builder setClock(Clock clock) {
@@ -1285,15 +1177,5 @@
       this.eventBusExceptionHandler = eventBusExceptionHandler;
       return this;
     }
-
-    public Builder addCommands(BlazeCommand... commands) {
-      this.commands.addAll(Arrays.asList(commands));
-      return this;
-    }
-
-    public Builder addCommands(Iterable<BlazeCommand> commands) {
-      Iterables.addAll(this.commands, commands);
-      return this;
-    }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuiltinCommandModule.java b/src/main/java/com/google/devtools/build/lib/runtime/BuiltinCommandModule.java
new file mode 100644
index 0000000..506f7ff
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BuiltinCommandModule.java
@@ -0,0 +1,52 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.runtime;
+
+import com.google.devtools.build.lib.runtime.commands.BuildCommand;
+import com.google.devtools.build.lib.runtime.commands.CanonicalizeCommand;
+import com.google.devtools.build.lib.runtime.commands.CleanCommand;
+import com.google.devtools.build.lib.runtime.commands.DumpCommand;
+import com.google.devtools.build.lib.runtime.commands.HelpCommand;
+import com.google.devtools.build.lib.runtime.commands.InfoCommand;
+import com.google.devtools.build.lib.runtime.commands.MobileInstallCommand;
+import com.google.devtools.build.lib.runtime.commands.ProfileCommand;
+import com.google.devtools.build.lib.runtime.commands.QueryCommand;
+import com.google.devtools.build.lib.runtime.commands.RunCommand;
+import com.google.devtools.build.lib.runtime.commands.ShutdownCommand;
+import com.google.devtools.build.lib.runtime.commands.TestCommand;
+import com.google.devtools.build.lib.runtime.commands.VersionCommand;
+import com.google.devtools.common.options.OptionsProvider;
+
+/**
+ * Internal module for the built-in commands.
+ */
+public final class BuiltinCommandModule extends BlazeModule {
+  @Override
+  public void serverInit(OptionsProvider startupOptions, ServerBuilder builder) {
+    builder.addCommands(
+        new BuildCommand(),
+        new CanonicalizeCommand(),
+        new CleanCommand(),
+        new DumpCommand(),
+        new HelpCommand(),
+        new InfoCommand(),
+        new MobileInstallCommand(),
+        new ProfileCommand(),
+        new QueryCommand(),
+        new RunCommand(),
+        new ShutdownCommand(),
+        new TestCommand(),
+        new VersionCommand());
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ServerBuilder.java b/src/main/java/com/google/devtools/build/lib/runtime/ServerBuilder.java
new file mode 100644
index 0000000..3a7a3a2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ServerBuilder.java
@@ -0,0 +1,113 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.runtime;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.packages.AttributeContainer;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.query2.AbstractBlazeQueryEnvironment;
+import com.google.devtools.build.lib.query2.QueryEnvironmentFactory;
+import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
+import com.google.devtools.build.lib.util.Preconditions;
+
+/**
+ * Builder class to create a {@link BlazeRuntime} instance. This class is part of the module API,
+ * which allows modules to affect how the server is initialized.
+ */
+public final class ServerBuilder {
+  private QueryEnvironmentFactory queryEnvironmentFactory;
+  private final InvocationPolicy.Builder invocationPolicyBuilder = InvocationPolicy.newBuilder();
+  private Function<RuleClass, AttributeContainer> attributeContainerFactory;
+  private final ImmutableList.Builder<BlazeCommand> commands = ImmutableList.builder();
+
+  @VisibleForTesting
+  public ServerBuilder() {}
+
+  QueryEnvironmentFactory getQueryEnvironmentFactory() {
+    return queryEnvironmentFactory == null
+        ? new QueryEnvironmentFactory()
+        : queryEnvironmentFactory;
+  }
+
+  InvocationPolicy getInvocationPolicy() {
+    return invocationPolicyBuilder.build();
+  }
+
+  Function<RuleClass, AttributeContainer> getAttributeContainerFactory() {
+    return attributeContainerFactory == null
+        ? AttributeContainer.ATTRIBUTE_CONTAINER_FACTORY
+        : attributeContainerFactory;
+  }
+
+  @VisibleForTesting
+  public ImmutableList<BlazeCommand> getCommands() {
+    return commands.build();
+  }
+
+  /**
+   * Merges the given invocation policy into the per-server invocation policy. While this can accept
+   * any number of policies, the end result is order-dependent if multiple policies attempt to
+   * police the same options, so it's probably a good idea to not have too many modules that call
+   * this.
+   */
+  public ServerBuilder addInvocationPolicy(InvocationPolicy policy) {
+    invocationPolicyBuilder.mergeFrom(Preconditions.checkNotNull(policy));
+    return this;
+  }
+
+  /**
+   * Sets a factory for creating {@link AbstractBlazeQueryEnvironment} instances. Note that only one
+   * factory per server is allowed. If none is set, the server uses the default implementation.
+   */
+  public ServerBuilder setQueryEnvironmentFactory(QueryEnvironmentFactory queryEnvironmentFactory) {
+    Preconditions.checkState(
+        this.queryEnvironmentFactory == null,
+        "At most one query environment factory supported. But found two: %s and %s",
+        this.queryEnvironmentFactory,
+        queryEnvironmentFactory);
+    this.queryEnvironmentFactory = Preconditions.checkNotNull(queryEnvironmentFactory);
+    return this;
+  }
+
+  /**
+   * Sets a factory for creating {@link AttributeContainer} instances. Only one factory per server
+   * is allowed. If none is set, the server uses the default implementation.
+   */
+  public ServerBuilder setAttributeContainerFactory(
+      Function<RuleClass, AttributeContainer> attributeContainerFactory) {
+    Preconditions.checkState(
+        this.attributeContainerFactory == null,
+        "At most one attribute container factory supported. But found two: %s and %s",
+        this.attributeContainerFactory,
+        attributeContainerFactory);
+    this.attributeContainerFactory = Preconditions.checkNotNull(attributeContainerFactory);
+    return this;
+  }
+
+  /**
+   * Adds the given command to the server. This overload only exists to avoid array object creation
+   * in the common case.
+   */
+  public ServerBuilder addCommands(BlazeCommand command) {
+    this.commands.add(Preconditions.checkNotNull(command));
+    return this;
+  }
+
+  /** Adds the given commands to the server. */
+  public void addCommands(BlazeCommand... commands) {
+    this.commands.add(commands);
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/DocumentationTestUtil.java b/src/test/java/com/google/devtools/build/lib/packages/util/DocumentationTestUtil.java
index 0836b5f..2654b45 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/util/DocumentationTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/DocumentationTestUtil.java
@@ -16,18 +16,17 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import com.google.common.collect.Iterables;
 import com.google.devtools.build.docgen.DocCheckerUtils;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.BlazeCommandUtils;
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.BuiltinCommandModule;
+import com.google.devtools.build.lib.runtime.ServerBuilder;
 import com.google.devtools.common.options.Options;
 import com.google.devtools.common.options.OptionsBase;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -47,13 +46,13 @@
           Pattern.CASE_INSENSITIVE);
 
   /**
-   * Validates that a user manual {@code documentationSource} contains only
-   * the flags actually provided by a given set of modules.
+   * Validates that a user manual {@code documentationSource} contains only the flags actually
+   * provided by a given set of modules.
    */
   public static void validateUserManual(
       List<Class<? extends BlazeModule>> modules,
-      ConfiguredRuleClassProvider ruleClassProvider, String documentationSource)
-      throws IOException {
+      ConfiguredRuleClassProvider ruleClassProvider,
+      String documentationSource) {
     // if there is a class missing, one can find it using
     //   find . -name "*.java" -exec grep -Hn "@Option(name = " {} \; | grep "xxx"
     // where 'xxx' is a flag name.
@@ -68,11 +67,12 @@
     }
 
     // collect all command options
-    List<BlazeCommand> blazeCommands = new ArrayList<>();
-    blazeCommands.addAll(BlazeRuntime.getBuiltinCommandList());
+    ServerBuilder serverBuilder = new ServerBuilder();
+    new BuiltinCommandModule().serverInit(null, serverBuilder);
     for (BlazeModule module : blazeModules) {
-      Iterables.addAll(blazeCommands, module.getCommands());
+      module.serverInit(null, serverBuilder);
     }
+    List<BlazeCommand> blazeCommands = serverBuilder.getCommands();
 
     for (BlazeCommand command : blazeCommands) {
       for (Class<? extends OptionsBase> optionClass :
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java
index 8bd8bff..dad3d2d 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java
@@ -25,12 +25,10 @@
 import com.google.devtools.build.lib.analysis.ConfigurationCollectionFactory;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
-import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
 import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.LockingMode;
 import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.ShutdownBlazeServerException;
-import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass;
 import com.google.devtools.build.lib.testutil.Scratch;
 import com.google.devtools.build.lib.testutil.TestConstants;
 import com.google.devtools.build.lib.util.ExitCode;
@@ -140,8 +138,6 @@
             .setDirectories(directories)
             .setStartupOptionsProvider(
                 OptionsParser.newOptionsParser(BlazeServerStartupOptions.class))
-            .setConfigurationFactory(
-                new ConfigurationFactory(Mockito.mock(ConfigurationCollectionFactory.class)))
             .addBlazeModule(
                 new BlazeModule() {
                   @Override
@@ -152,9 +148,10 @@
                     builder.addConfigurationOptions(MockFragmentOptions.class);
                     // The tools repository is needed for createGlobals
                     builder.setToolsRepository(TestConstants.TOOLS_REPOSITORY);
+                    builder.setConfigurationCollectionFactory(
+                        Mockito.mock(ConfigurationCollectionFactory.class));
                   }
                 })
-            .setInvocationPolicy(InvocationPolicyOuterClass.InvocationPolicy.getDefaultInstance())
             .build();
   }