Adds support for multiple top-level dynamic configurations
(e.g. --experimental_multi_cpu).

--
MOS_MIGRATED_REVID=139607063
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
index 1f21e65..b9297a2 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -19,12 +19,13 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
-import com.google.common.base.Verify;
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.common.eventbus.EventBus;
 import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
@@ -839,44 +840,49 @@
   private List<TargetAndConfiguration> getDynamicConfigurations(
       Iterable<TargetAndConfiguration> inputs, EventHandler eventHandler)
       throws InterruptedException {
-    Map<Label, TargetAndConfiguration> labelsToTargets = new LinkedHashMap<>();
-    BuildConfiguration topLevelConfig = null;
-    List<Dependency> asDeps = new ArrayList<Dependency>();
+    Map<Label, Target> labelsToTargets = new LinkedHashMap<>();
+    // We'll get the configs from SkyframeExecutor#getConfigurations, which gets configurations
+    // for deps including transitions. So to satisfy its API we repackage each target as a
+    // Dependency with a NONE transition.
+    Multimap<BuildConfiguration, Dependency> asDeps =
+        ArrayListMultimap.<BuildConfiguration, Dependency>create();
 
     for (TargetAndConfiguration targetAndConfig : inputs) {
-      labelsToTargets.put(targetAndConfig.getLabel(), targetAndConfig);
-      BuildConfiguration targetConfig = targetAndConfig.getConfiguration();
-      if (targetConfig != null) {
-        asDeps.add(Dependency.withTransitionAndAspects(
-            targetAndConfig.getLabel(),
-            Attribute.ConfigurationTransition.NONE,
-            ImmutableSet.<AspectDescriptor>of()));  // TODO(bazel-team): support top-level aspects
-
-        // TODO(bazel-team): support multiple top-level configurations (for, e.g.,
-        // --experimental_multi_cpu). This requires refactoring the getConfigurations() call below.
-        Verify.verify(topLevelConfig == null || topLevelConfig == targetConfig);
-        topLevelConfig = targetConfig;
+      labelsToTargets.put(targetAndConfig.getLabel(), targetAndConfig.getTarget());
+      if (targetAndConfig.getConfiguration() != null) {
+        asDeps.put(targetAndConfig.getConfiguration(),
+            Dependency.withTransitionAndAspects(
+                targetAndConfig.getLabel(),
+                Attribute.ConfigurationTransition.NONE,
+                // TODO(bazel-team): support top-level aspects
+                ImmutableSet.<AspectDescriptor>of()));
       }
     }
 
-    Map<Label, TargetAndConfiguration> successfullyEvaluatedTargets = new LinkedHashMap<>();
+    // Maps <target, originalConfig> pairs to <target, dynamicConfig> pairs for targets that
+    // could be successfully Skyframe-evaluated.
+    Map<TargetAndConfiguration, TargetAndConfiguration> successfullyEvaluatedTargets =
+        new LinkedHashMap<>();
     if (!asDeps.isEmpty()) {
-      Map<Dependency, BuildConfiguration> trimmedTargets =
-          skyframeExecutor.getConfigurations(eventHandler, topLevelConfig.getOptions(), asDeps);
-      for (Map.Entry<Dependency, BuildConfiguration> trimmedTarget : trimmedTargets.entrySet()) {
-        Label targetLabel = trimmedTarget.getKey().getLabel();
-        successfullyEvaluatedTargets.put(targetLabel,
-            new TargetAndConfiguration(
-                labelsToTargets.get(targetLabel).getTarget(), trimmedTarget.getValue()));
+      for (BuildConfiguration fromConfig : asDeps.keySet()) {
+        Map<Dependency, BuildConfiguration> trimmedTargets =
+            skyframeExecutor.getConfigurations(eventHandler, fromConfig.getOptions(),
+                asDeps.get(fromConfig));
+        for (Map.Entry<Dependency, BuildConfiguration> trimmedTarget : trimmedTargets.entrySet()) {
+          Target target = labelsToTargets.get(trimmedTarget.getKey().getLabel());
+          successfullyEvaluatedTargets.put(
+              new TargetAndConfiguration(target, fromConfig),
+              new TargetAndConfiguration(target, trimmedTarget.getValue()));
+        }
       }
     }
 
     ImmutableList.Builder<TargetAndConfiguration> result =
         ImmutableList.<TargetAndConfiguration>builder();
     for (TargetAndConfiguration originalInput : inputs) {
-      if (successfullyEvaluatedTargets.containsKey(originalInput.getLabel())) {
+      if (successfullyEvaluatedTargets.containsKey(originalInput)) {
         // The configuration was successfully trimmed.
-        result.add(successfullyEvaluatedTargets.get(originalInput.getLabel()));
+        result.add(successfullyEvaluatedTargets.get(originalInput));
       } else {
         // Either the configuration couldn't be determined (e.g. loading phase error) or it's null.
         result.add(originalInput);
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
index 2463760..b36cc32 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
@@ -258,8 +258,32 @@
     return masterConfig;
   }
 
-  protected BuildConfiguration getTargetConfiguration() {
-    return Iterables.getOnlyElement(masterConfig.getTargetConfigurations());
+  /**
+   * Returns the target configuration for the most recent build, as created in Blaze's
+   * master configuration creation phase. Most significantly, this is never a dynamic
+   * configuration.
+   */
+  protected BuildConfiguration getTargetConfiguration() throws InterruptedException {
+    return getTargetConfiguration(false);
+  }
+
+  /**
+   * Returns the target configuration for the most recent build. If useDynamicVersionIfEnabled is
+   * true and dynamic configurations are enabled, returns the dynamic version. Else returns the
+   * static version.
+   */
+  // TODO(gregce): force getTargetConfiguration() to getTargetConfiguration(true) once we know
+  //    all callers can handle the dynamic version
+  protected BuildConfiguration getTargetConfiguration(boolean useDynamicVersionIfEnabled)
+    throws InterruptedException {
+    BuildConfiguration targetConfig =
+        Iterables.getOnlyElement(masterConfig.getTargetConfigurations());
+    if (useDynamicVersionIfEnabled && targetConfig.useDynamicConfigurations()) {
+      return skyframeExecutor.getConfigurationForTesting(eventCollector,
+          targetConfig.fragmentClasses(), targetConfig.getOptions());
+    } else {
+      return targetConfig;
+    }
   }
 
   protected BuildConfiguration getHostConfiguration() {
@@ -379,7 +403,7 @@
    * Returns the corresponding configured target, if it exists. Note that this will only return
    * anything useful after a call to update() with the same label.
    */
-  protected ConfiguredTarget getConfiguredTarget(String label) {
+  protected ConfiguredTarget getConfiguredTarget(String label) throws InterruptedException {
     return getConfiguredTarget(label, getTargetConfiguration());
   }
 
@@ -396,7 +420,8 @@
     return buildView.hasErrors(configuredTarget);
   }
 
-  protected Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner) {
+  protected Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner)
+      throws InterruptedException {
     Label label = owner.getLabel();
     return buildView.getArtifactFactory().getDerivedArtifact(
         label.getPackageFragment().getRelative(packageRelativePath),