Use platform mappings.

Applying platform mapping to all BuildConfigurationValue.Key instances. This will cause all configurations to apply the platform mapping before being loaded.

Step 7/N towards the platforms mapping functionality for https://github.com/bazelbuild/bazel/issues/6426

RELNOTES: New platform_mappings ability to allow gradual flag to platforms/toolchains migration. See also https://github.com/bazelbuild/bazel/issues/6426
PiperOrigin-RevId: 244731653
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
index e99e9a7..9f224bb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
 import com.google.devtools.build.lib.analysis.config.ConfigurationResolver;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
 import com.google.devtools.build.lib.analysis.config.TransitionResolver;
 import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
@@ -183,7 +184,8 @@
       Collection<Target> targets,
       ExtendedEventHandler eventHandler,
       ConfiguredRuleClassProvider ruleClassProvider,
-      SkyframeExecutor skyframeExecutor) {
+      SkyframeExecutor skyframeExecutor)
+      throws InvalidConfigurationException {
     // We use a hash set here to remove duplicate nodes; this can happen for input files and package
     // groups.
     LinkedHashSet<TargetAndConfiguration> nodes = new LinkedHashSet<>(targets.size());
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
index d71e03c..6adfaae6 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
@@ -30,6 +30,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.Dependency;
 import com.google.devtools.build.lib.analysis.DependencyResolver.DependencyKind;
+import com.google.devtools.build.lib.analysis.PlatformOptions;
 import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
 import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
@@ -47,14 +48,17 @@
 import com.google.devtools.build.lib.skyframe.BuildConfigurationValue;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetValue;
+import com.google.devtools.build.lib.skyframe.PlatformMappingValue;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.skyframe.TransitiveTargetKey;
 import com.google.devtools.build.lib.skyframe.TransitiveTargetValue;
 import com.google.devtools.build.lib.util.OrderedSetMultimap;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.ValueOrException;
+import com.google.devtools.common.options.OptionsParsingException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -261,20 +265,38 @@
       }
 
       // If we get here, we have to get the configuration from Skyframe.
-      for (BuildOptions options : toOptions) {
-        if (sameFragments) {
-          keysToEntries.put(
-              BuildConfigurationValue.key(
-                  currentConfiguration.fragmentClasses(),
-                  BuildOptions.diffForReconstruction(defaultBuildOptions, options)),
-              depsEntry);
+      PathFragment platformMappingPath =
+          currentConfiguration.getOptions().get(PlatformOptions.class).platformMappings;
+      PlatformMappingValue platformMappingValue =
+          (PlatformMappingValue) env.getValue(PlatformMappingValue.Key.create(platformMappingPath));
+      if (platformMappingValue == null) {
+        return null;
+      }
 
-        } else {
-          keysToEntries.put(
-              BuildConfigurationValue.key(
-                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, options)),
-              depsEntry);
+      try {
+        for (BuildOptions options : toOptions) {
+          if (sameFragments) {
+            keysToEntries.put(
+                BuildConfigurationValue.keyWithPlatformMapping(
+                    platformMappingValue,
+                    defaultBuildOptions,
+                    currentConfiguration.fragmentClasses(),
+                    BuildOptions.diffForReconstruction(defaultBuildOptions, options)),
+                depsEntry);
+
+          } else {
+            keysToEntries.put(
+                BuildConfigurationValue.keyWithPlatformMapping(
+                    platformMappingValue,
+                    defaultBuildOptions,
+                    depFragments,
+                    BuildOptions.diffForReconstruction(defaultBuildOptions, options)),
+                depsEntry);
+          }
         }
+      } catch (OptionsParsingException e) {
+        throw new ConfiguredTargetFunction.DependencyEvaluationException(
+            new InvalidConfigurationException(e));
       }
     }
 
@@ -631,7 +653,8 @@
       Iterable<TargetAndConfiguration> defaultContext,
       Multimap<BuildConfiguration, Dependency> targetsToEvaluate,
       ExtendedEventHandler eventHandler,
-      SkyframeExecutor skyframeExecutor) {
+      SkyframeExecutor skyframeExecutor)
+      throws InvalidConfigurationException {
 
     Map<Label, Target> labelsToTargets = new LinkedHashMap<>();
     for (TargetAndConfiguration targetAndConfig : defaultContext) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BuildConfigurationValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/BuildConfigurationValue.java
index 90479a9..924a601 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BuildConfigurationValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BuildConfigurationValue.java
@@ -26,10 +26,10 @@
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.common.options.OptionsParsingException;
 import java.io.Serializable;
 import java.util.Objects;
 import java.util.Set;
-import java.util.logging.Logger;
 
 /** A Skyframe value representing a {@link BuildConfiguration}. */
 // TODO(bazel-team): mark this immutable when BuildConfiguration is immutable.
@@ -37,7 +37,6 @@
 @AutoCodec
 @ThreadSafe
 public class BuildConfigurationValue implements SkyValue {
-  private static final Logger logger = Logger.getLogger(BuildConfigurationValue.class.getName());
   private final BuildConfiguration configuration;
 
   BuildConfigurationValue(BuildConfiguration configuration) {
@@ -49,14 +48,61 @@
   }
 
   /**
+   * Creates a new configuration key based on the given diff, after applying a platform mapping
+   * transformation.
+   *
+   * @param platformMappingValue sky value that can transform a configuration key based on a
+   *     platform mapping
+   * @param defaultBuildOptions set of native build options without modifications based on parsing
+   *     flags
+   * @param fragments set of options fragments this configuration should cover
+   * @param optionsDiff diff between the default options and the desired configuration
+   * @throws OptionsParsingException if the platform mapping cannot be parsed
+   */
+  public static Key keyWithPlatformMapping(
+      PlatformMappingValue platformMappingValue,
+      BuildOptions defaultBuildOptions,
+      FragmentClassSet fragments,
+      BuildOptions.OptionsDiffForReconstruction optionsDiff)
+      throws OptionsParsingException {
+    return platformMappingValue.map(
+        keyWithoutPlatformMapping(fragments, optionsDiff), defaultBuildOptions);
+  }
+
+  /**
+   * Creates a new configuration key based on the given diff, after applying a platform mapping
+   * transformation.
+   *
+   * @param platformMappingValue sky value that can transform a configuration key based on a
+   *     platform mapping
+   * @param defaultBuildOptions set of native build options without modifications based on parsing
+   *     flags
+   * @param fragments set of options fragments this configuration should cover
+   * @param optionsDiff diff between the default options and the desired configuration
+   * @throws OptionsParsingException if the platform mapping cannot be parsed
+   */
+  public static Key keyWithPlatformMapping(
+      PlatformMappingValue platformMappingValue,
+      BuildOptions defaultBuildOptions,
+      Set<Class<? extends BuildConfiguration.Fragment>> fragments,
+      BuildOptions.OptionsDiffForReconstruction optionsDiff)
+      throws OptionsParsingException {
+    return platformMappingValue.map(
+        keyWithoutPlatformMapping(fragments, optionsDiff), defaultBuildOptions);
+  }
+
+  /**
    * Returns the key for a requested configuration.
    *
+   * <p>Callers are responsible for applying the platform mapping or ascertaining that a platform
+   * mapping is not required.
+   *
    * @param fragments the fragments the configuration should contain
    * @param optionsDiff the {@link BuildOptions.OptionsDiffForReconstruction} object the {@link
    *     BuildOptions} should be rebuilt from
    */
   @ThreadSafe
-  public static Key key(
+  static Key keyWithoutPlatformMapping(
       Set<Class<? extends BuildConfiguration.Fragment>> fragments,
       BuildOptions.OptionsDiffForReconstruction optionsDiff) {
     return Key.create(
@@ -65,13 +111,23 @@
         optionsDiff);
   }
 
-  public static Key key(
+  private static Key keyWithoutPlatformMapping(
       FragmentClassSet fragmentClassSet, BuildOptions.OptionsDiffForReconstruction optionsDiff) {
     return Key.create(fragmentClassSet, optionsDiff);
   }
 
+  /**
+   * Returns a configuration key for the given configuration.
+   *
+   * <p>Note that this key creation method does not apply a platform mapping, it is assumed that the
+   * passed configuration was created with one such and thus its key does not need to be mapped
+   * again.
+   *
+   * @param buildConfiguration configuration whose key is requested
+   */
   public static Key key(BuildConfiguration buildConfiguration) {
-    return key(buildConfiguration.fragmentClasses(), buildConfiguration.getBuildOptionsDiff());
+    return keyWithoutPlatformMapping(
+        buildConfiguration.fragmentClasses(), buildConfiguration.getBuildOptionsDiff());
   }
 
   /** {@link SkyKey} for {@link BuildConfigurationValue}. */
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java
index db9fb4c..9f37b18 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java
@@ -228,7 +228,7 @@
       }
     }
 
-    return BuildConfigurationValue.key(
+    return BuildConfigurationValue.keyWithoutPlatformMapping(
         original.getFragments(),
         BuildOptions.diffForReconstruction(defaultBuildOptions, modifiedOptions));
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
index 98850f4..06fed8a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
@@ -23,6 +23,7 @@
 import com.google.devtools.build.lib.analysis.AnalysisUtils;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.Dependency;
+import com.google.devtools.build.lib.analysis.PlatformOptions;
 import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
@@ -40,11 +41,13 @@
 import com.google.devtools.build.lib.packages.NoSuchThingException;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.skyframe.PrepareAnalysisPhaseValue.PrepareAnalysisPhaseKey;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.ValueOrException;
+import com.google.devtools.common.options.OptionsParsingException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -82,19 +85,36 @@
 
     ImmutableSortedSet<Class<? extends BuildConfiguration.Fragment>> allFragments =
         options.getFragments().fragmentClasses();
-    BuildConfigurationValue.Key hostConfigurationKey =
-        BuildConfigurationValue.key(
-            allFragments,
-            BuildOptions.diffForReconstruction(defaultBuildOptions, hostOptions));
-    ImmutableList<BuildConfigurationValue.Key> targetConfigurationKeys =
-        getTopLevelBuildOptions(targetOptions, options.getMultiCpu())
-            .stream()
-            .map(
-                elem ->
-                    BuildConfigurationValue.key(
-                        allFragments,
-                        BuildOptions.diffForReconstruction(defaultBuildOptions, elem)))
-            .collect(ImmutableList.toImmutableList());
+
+    PathFragment platformMappingPath = targetOptions.get(PlatformOptions.class).platformMappings;
+    PlatformMappingValue platformMappingValue =
+        (PlatformMappingValue) env.getValue(PlatformMappingValue.Key.create(platformMappingPath));
+    if (platformMappingValue == null) {
+      return null;
+    }
+
+    BuildConfigurationValue.Key hostConfigurationKey = null;
+    ImmutableList.Builder<BuildConfigurationValue.Key> targetConfigurationKeysBuilder =
+        ImmutableList.builder();
+    try {
+      hostConfigurationKey =
+          BuildConfigurationValue.keyWithPlatformMapping(
+              platformMappingValue,
+              defaultBuildOptions,
+              allFragments,
+              BuildOptions.diffForReconstruction(defaultBuildOptions, hostOptions));
+      for (BuildOptions buildOptions :
+          getTopLevelBuildOptions(targetOptions, options.getMultiCpu())) {
+        targetConfigurationKeysBuilder.add(
+            BuildConfigurationValue.keyWithPlatformMapping(
+                platformMappingValue,
+                defaultBuildOptions,
+                allFragments,
+                BuildOptions.diffForReconstruction(defaultBuildOptions, buildOptions)));
+      }
+    } catch (OptionsParsingException e) {
+      throw new PrepareAnalysisPhaseFunctionException(new InvalidConfigurationException(e));
+    }
 
     // We don't need the host configuration below, but we call this to get the error, if any.
     try {
@@ -103,6 +123,8 @@
       throw new PrepareAnalysisPhaseFunctionException(e);
     }
 
+    ImmutableList<BuildConfigurationValue.Key> targetConfigurationKeys =
+        targetConfigurationKeysBuilder.build();
     Map<SkyKey, SkyValue> configs = env.getValues(targetConfigurationKeys);
 
     // We only report invalid options for the target configurations, and abort if there's an error.
@@ -148,7 +170,7 @@
     LinkedHashSet<TargetAndConfiguration> topLevelTargetsWithConfigs;
     try {
       topLevelTargetsWithConfigs = resolveConfigurations(env, nodes, asDeps);
-    } catch (TransitionException e) {
+    } catch (TransitionException | OptionsParsingException e) {
       throw new PrepareAnalysisPhaseFunctionException(new InvalidConfigurationException(e));
     }
     if (env.valuesMissing()) {
@@ -189,7 +211,7 @@
       SkyFunction.Environment env,
       Iterable<TargetAndConfiguration> nodes,
       Multimap<BuildConfiguration, Dependency> asDeps)
-      throws InterruptedException, TransitionException {
+      throws InterruptedException, TransitionException, OptionsParsingException {
     Map<Label, Target> labelsToTargets = new LinkedHashMap<>();
     for (TargetAndConfiguration node : nodes) {
       labelsToTargets.put(node.getTarget().getLabel(), node.getTarget());
@@ -244,7 +266,7 @@
   // Note: this implementation runs inside Skyframe, so it has access to SkyFunction.Environment.
   private Multimap<Dependency, BuildConfiguration> getConfigurations(
       SkyFunction.Environment env, BuildOptions fromOptions, Iterable<Dependency> keys)
-      throws InterruptedException, TransitionException {
+      throws InterruptedException, TransitionException, OptionsParsingException {
     Multimap<Dependency, BuildConfiguration> builder =
         ArrayListMultimap.<Dependency, BuildConfiguration>create();
     Set<Dependency> depsToEvaluate = new HashSet<>();
@@ -295,6 +317,13 @@
     }
 
     // Now get the configurations.
+    PathFragment platformMappingPath = fromOptions.get(PlatformOptions.class).platformMappings;
+    PlatformMappingValue platformMappingValue =
+        (PlatformMappingValue) env.getValue(PlatformMappingValue.Key.create(platformMappingPath));
+    if (platformMappingValue == null) {
+      return null;
+    }
+
     final List<SkyKey> configSkyKeys = new ArrayList<>();
     for (Dependency key : keys) {
       if (labelsWithErrors.contains(key.getLabel()) || key.hasExplicitConfiguration()) {
@@ -306,6 +335,7 @@
       ConfigurationTransition transition = key.getTransition();
       ImmutableSortedSet<Class<? extends BuildConfiguration.Fragment>> depFragments =
           fragmentsMap.get(key.getLabel());
+
       if (depFragments != null) {
         // TODO(juliexxia): combine these skyframe calls with other skyframe calls for this
         // configured target.
@@ -333,8 +363,11 @@
         StarlarkTransition.replayEvents(env.getListener(), transition);
         for (BuildOptions toOption : toOptions) {
           configSkyKeys.add(
-              BuildConfigurationValue.key(
-                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption)));
+              BuildConfigurationValue.keyWithPlatformMapping(
+                  platformMappingValue,
+                  defaultBuildOptions,
+                  depFragments,
+                  BuildOptions.diffForReconstruction(defaultBuildOptions, toOption)));
         }
       }
     }
@@ -376,8 +409,11 @@
                 buildSettingOutputPackages);
         for (BuildOptions toOption : toOptions) {
           SkyKey configKey =
-              BuildConfigurationValue.key(
-                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption));
+              BuildConfigurationValue.keyWithPlatformMapping(
+                  platformMappingValue,
+                  defaultBuildOptions,
+                  depFragments,
+                  BuildOptions.diffForReconstruction(defaultBuildOptions, toOption));
           BuildConfigurationValue configValue =
               ((BuildConfigurationValue) configsResult.get(configKey));
           // configValue will be null here if there was an exception thrown during configuration
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 04b5338..b6450dc 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -67,6 +67,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.Dependency;
+import com.google.devtools.build.lib.analysis.PlatformOptions;
 import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
 import com.google.devtools.build.lib.analysis.WorkspaceStatusAction;
 import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
@@ -176,6 +177,7 @@
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.WalkableGraph.WalkableGraphFactory;
+import com.google.devtools.common.options.OptionsParsingException;
 import com.google.devtools.common.options.OptionsProvider;
 import java.io.PrintStream;
 import java.math.BigInteger;
@@ -1451,6 +1453,7 @@
         getConfigurations(
             eventHandler,
             PrepareAnalysisPhaseFunction.getTopLevelBuildOptions(buildOptions, multiCpu),
+            buildOptions,
             keepGoing);
 
     BuildConfiguration firstTargetConfig = topLevelTargetConfigs.get(0);
@@ -1603,7 +1606,8 @@
   public ImmutableList<ConfiguredTargetAndData> getConfiguredTargetsForTesting(
       ExtendedEventHandler eventHandler,
       BuildConfiguration originalConfig,
-      Iterable<Dependency> keys) {
+      Iterable<Dependency> keys)
+      throws TransitionException, InvalidConfigurationException {
     return getConfiguredTargetMapForTesting(eventHandler, originalConfig, keys).values().asList();
   }
 
@@ -1619,7 +1623,8 @@
   public ImmutableList<ConfiguredTargetAndData> getConfiguredTargetsForTesting(
       ExtendedEventHandler eventHandler,
       BuildConfigurationValue.Key originalConfig,
-      Iterable<Dependency> keys) {
+      Iterable<Dependency> keys)
+      throws TransitionException, InvalidConfigurationException {
     return getConfiguredTargetMapForTesting(eventHandler, originalConfig, keys).values().asList();
   }
 
@@ -1636,7 +1641,8 @@
   public ImmutableMultimap<Dependency, ConfiguredTargetAndData> getConfiguredTargetMapForTesting(
       ExtendedEventHandler eventHandler,
       BuildConfigurationValue.Key originalConfig,
-      Iterable<Dependency> keys) {
+      Iterable<Dependency> keys)
+      throws TransitionException, InvalidConfigurationException {
     return getConfiguredTargetMapForTesting(
         eventHandler, getConfiguration(eventHandler, originalConfig), keys);
   }
@@ -1654,7 +1660,8 @@
   private ImmutableMultimap<Dependency, ConfiguredTargetAndData> getConfiguredTargetMapForTesting(
       ExtendedEventHandler eventHandler,
       BuildConfiguration originalConfig,
-      Iterable<Dependency> keys) {
+      Iterable<Dependency> keys)
+      throws TransitionException, InvalidConfigurationException {
     checkActive();
 
     Multimap<Dependency, BuildConfiguration> configs;
@@ -1810,7 +1817,7 @@
       ExtendedEventHandler eventHandler, BuildOptions options, boolean keepGoing)
       throws InvalidConfigurationException {
     return Iterables.getOnlyElement(
-        getConfigurations(eventHandler, ImmutableList.of(options), keepGoing));
+        getConfigurations(eventHandler, ImmutableList.of(options), options, keepGoing));
   }
 
   @VisibleForTesting
@@ -1840,8 +1847,11 @@
    * @throws InvalidConfigurationException if any build options produces an invalid configuration
    */
   // TODO(ulfjack): Remove this legacy method after switching to the Skyframe-based implementation.
-  public List<BuildConfiguration> getConfigurations(
-      ExtendedEventHandler eventHandler, List<BuildOptions> optionsList, boolean keepGoing)
+  private List<BuildConfiguration> getConfigurations(
+      ExtendedEventHandler eventHandler,
+      List<BuildOptions> optionsList,
+      BuildOptions referenceBuildOptions,
+      boolean keepGoing)
       throws InvalidConfigurationException {
     Preconditions.checkArgument(!Iterables.isEmpty(optionsList));
 
@@ -1852,14 +1862,16 @@
             .map(factory -> factory.creates())
             .collect(
                 ImmutableSortedSet.toImmutableSortedSet(BuildConfiguration.lexicalFragmentSorter));
-    final ImmutableList<SkyKey> configSkyKeys =
-        optionsList.stream()
-            .map(
-                elem ->
-                    BuildConfigurationValue.key(
-                        allFragments,
-                        BuildOptions.diffForReconstruction(defaultBuildOptions, elem)))
-            .collect(ImmutableList.toImmutableList());
+
+    PlatformMappingValue platformMappingValue =
+        getPlatformMappingValue(eventHandler, referenceBuildOptions);
+
+    ImmutableList.Builder<SkyKey> configSkyKeysBuilder = ImmutableList.builder();
+    for (BuildOptions options : optionsList) {
+      configSkyKeysBuilder.add(toConfigurationKey(platformMappingValue, allFragments, options));
+    }
+
+    ImmutableList<SkyKey> configSkyKeys = configSkyKeysBuilder.build();
 
     // Skyframe-evaluate the configurations and throw errors if any.
     EvaluationResult<SkyValue> evalResult = evaluateSkyKeys(eventHandler, configSkyKeys, keepGoing);
@@ -1898,14 +1910,15 @@
   // Keep this in sync with {@link PrepareAnalysisPhaseFunction#getConfigurations}.
   // TODO(ulfjack): Remove this legacy method after switching to the Skyframe-based implementation.
   public Multimap<Dependency, BuildConfiguration> getConfigurations(
-      ExtendedEventHandler eventHandler, BuildOptions fromOptions, Iterable<Dependency> keys) {
+      ExtendedEventHandler eventHandler, BuildOptions fromOptions, Iterable<Dependency> keys)
+      throws InvalidConfigurationException {
     Multimap<Dependency, BuildConfiguration> builder =
         ArrayListMultimap.<Dependency, BuildConfiguration>create();
     Set<Dependency> depsToEvaluate = new HashSet<>();
 
     ImmutableSortedSet<Class<? extends BuildConfiguration.Fragment>> allFragments = null;
     if (useUntrimmedConfigs(fromOptions)) {
-      allFragments = ((ConfiguredRuleClassProvider) ruleClassProvider).getAllFragments();
+      allFragments = ruleClassProvider.getAllFragments();
     }
 
     // Get the fragments needed for dynamic configuration nodes.
@@ -1944,6 +1957,8 @@
       }
     }
 
+    PlatformMappingValue platformMappingValue = getPlatformMappingValue(eventHandler, fromOptions);
+
     // Now get the configurations.
     final List<SkyKey> configSkyKeys = new ArrayList<>();
     for (Dependency key : keys) {
@@ -1979,9 +1994,7 @@
           eventHandler.handle(Event.error(e.getMessage()));
         }
         for (BuildOptions toOption : toOptions) {
-          configSkyKeys.add(
-              BuildConfigurationValue.key(
-                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption)));
+          configSkyKeys.add(toConfigurationKey(platformMappingValue, depFragments, toOption));
         }
       }
     }
@@ -2018,9 +2031,8 @@
           eventHandler.handle(Event.error(e.getMessage()));
         }
         for (BuildOptions toOption : toOptions) {
-          SkyKey configKey =
-              BuildConfigurationValue.key(
-                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption));
+          BuildConfigurationValue.Key configKey =
+              toConfigurationKey(platformMappingValue, depFragments, toOption);
           BuildConfigurationValue configValue =
               ((BuildConfigurationValue) configsResult.get(configKey));
           // configValue will be null here if there was an exception thrown during configuration
@@ -2034,6 +2046,38 @@
     return builder;
   }
 
+  private PlatformMappingValue getPlatformMappingValue(
+      ExtendedEventHandler eventHandler, BuildOptions referenceBuildOptions)
+      throws InvalidConfigurationException {
+    PathFragment platformMappingPath =
+        referenceBuildOptions.get(PlatformOptions.class).platformMappings;
+
+    PlatformMappingValue.Key platformMappingKey =
+        PlatformMappingValue.Key.create(platformMappingPath);
+    EvaluationResult<SkyValue> evaluationResult =
+        evaluateSkyKeys(eventHandler, ImmutableSet.of(platformMappingKey));
+    if (evaluationResult.hasError()) {
+      throw new InvalidConfigurationException(evaluationResult.getError().getException());
+    }
+    return (PlatformMappingValue) evaluationResult.get(platformMappingKey);
+  }
+
+  private BuildConfigurationValue.Key toConfigurationKey(
+      PlatformMappingValue platformMappingValue,
+      ImmutableSortedSet<Class<? extends BuildConfiguration.Fragment>> depFragments,
+      BuildOptions toOption)
+      throws InvalidConfigurationException {
+    try {
+      return BuildConfigurationValue.keyWithPlatformMapping(
+          platformMappingValue,
+          defaultBuildOptions,
+          depFragments,
+          BuildOptions.diffForReconstruction(defaultBuildOptions, toOption));
+    } catch (OptionsParsingException e) {
+      throw new InvalidConfigurationException(e);
+    }
+  }
+
   private Map<SkyKey, SkyValue> collectBuildSettingValues(
       ConfigurationTransition transition,
       ExtendedEventHandler eventHandler,
@@ -2104,10 +2148,13 @@
   @VisibleForTesting
   public BuildConfiguration getConfigurationForTesting(
       ExtendedEventHandler eventHandler, FragmentClassSet fragments, BuildOptions options)
-      throws InterruptedException {
+      throws InterruptedException, OptionsParsingException, InvalidConfigurationException {
     SkyKey key =
-        BuildConfigurationValue.key(
-            fragments, BuildOptions.diffForReconstruction(defaultBuildOptions, options));
+        BuildConfigurationValue.keyWithPlatformMapping(
+            getPlatformMappingValue(eventHandler, options),
+            defaultBuildOptions,
+            fragments,
+            BuildOptions.diffForReconstruction(defaultBuildOptions, options));
     BuildConfigurationValue result =
         (BuildConfigurationValue)
             evaluate(
@@ -2123,7 +2170,8 @@
   @VisibleForTesting
   @Nullable
   public ConfiguredTarget getConfiguredTargetForTesting(
-      ExtendedEventHandler eventHandler, Label label, BuildConfiguration configuration) {
+      ExtendedEventHandler eventHandler, Label label, BuildConfiguration configuration)
+      throws TransitionException, InvalidConfigurationException {
     return getConfiguredTargetForTesting(eventHandler, label, configuration, NoTransition.INSTANCE);
   }
 
@@ -2134,7 +2182,8 @@
       ExtendedEventHandler eventHandler,
       Label label,
       BuildConfiguration configuration,
-      ConfigurationTransition transition) {
+      ConfigurationTransition transition)
+      throws TransitionException, InvalidConfigurationException {
     ConfiguredTargetAndData configuredTargetAndData =
         getConfiguredTargetAndDataForTesting(eventHandler, label, configuration, transition);
     return configuredTargetAndData == null ? null : configuredTargetAndData.getConfiguredTarget();
@@ -2146,7 +2195,8 @@
       ExtendedEventHandler eventHandler,
       Label label,
       BuildConfiguration configuration,
-      ConfigurationTransition transition) {
+      ConfigurationTransition transition)
+      throws TransitionException, InvalidConfigurationException {
     return Iterables.getFirst(
         getConfiguredTargetsForTesting(
             eventHandler,
@@ -2162,7 +2212,8 @@
   @VisibleForTesting
   @Nullable
   public ConfiguredTargetAndData getConfiguredTargetAndDataForTesting(
-      ExtendedEventHandler eventHandler, Label label, BuildConfiguration configuration) {
+      ExtendedEventHandler eventHandler, Label label, BuildConfiguration configuration)
+      throws TransitionException, InvalidConfigurationException {
     return getConfiguredTargetAndDataForTesting(
         eventHandler, label, configuration, NoTransition.INSTANCE);
   }
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 745c637..5ef6c9f 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
@@ -44,7 +44,9 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
 import com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget;
+import com.google.devtools.build.lib.analysis.skylark.StarlarkTransition;
 import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
 import com.google.devtools.build.lib.clock.BlazeClock;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -449,7 +451,11 @@
     } catch (LabelSyntaxException e) {
       throw new AssertionError(e);
     }
-    return skyframeExecutor.getConfiguredTargetAndDataForTesting(reporter, parsedLabel, config);
+    try {
+      return skyframeExecutor.getConfiguredTargetAndDataForTesting(reporter, parsedLabel, config);
+    } catch (StarlarkTransition.TransitionException | InvalidConfigurationException e) {
+      throw new AssertionError(e);
+    }
   }
 
   protected Target getTarget(String label) throws InterruptedException {
@@ -494,8 +500,12 @@
     } catch (LabelSyntaxException e) {
       throw new AssertionError(e);
     }
-    return skyframeExecutor.getConfiguredTargetAndDataForTesting(
-        reporter, parsedLabel, configuration);
+    try {
+      return skyframeExecutor.getConfiguredTargetAndDataForTesting(
+          reporter, parsedLabel, configuration);
+    } catch (StarlarkTransition.TransitionException | InvalidConfigurationException e) {
+      throw new AssertionError(e);
+    }
   }
 
   protected final BuildConfiguration getConfiguration(TransitiveInfoCollection ct) {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java
index 849b3502..c99c79a 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewForTesting.java
@@ -54,6 +54,7 @@
 import com.google.devtools.build.lib.analysis.config.TransitionResolver;
 import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
+import com.google.devtools.build.lib.analysis.skylark.StarlarkTransition;
 import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory;
 import com.google.devtools.build.lib.causes.Cause;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -188,7 +189,8 @@
    */
   @VisibleForTesting
   public BuildConfiguration getConfigurationForTesting(
-      Target target, BuildConfiguration config, ExtendedEventHandler eventHandler) {
+      Target target, BuildConfiguration config, ExtendedEventHandler eventHandler)
+      throws StarlarkTransition.TransitionException, InvalidConfigurationException {
     List<TargetAndConfiguration> node =
         ImmutableList.<TargetAndConfiguration>of(new TargetAndConfiguration(target, config));
     LinkedHashSet<TargetAndConfiguration> configs =
@@ -216,7 +218,7 @@
       ConfiguredTarget ct,
       BuildConfigurationCollection configurations)
       throws EvalException, InvalidConfigurationException, InterruptedException,
-      InconsistentAspectOrderException {
+          InconsistentAspectOrderException, StarlarkTransition.TransitionException {
     return Collections2.transform(
         getConfiguredTargetAndDataDirectPrerequisitesForTesting(eventHandler, ct, configurations),
         ConfiguredTargetAndData::getConfiguredTarget);
@@ -224,24 +226,24 @@
 
   // TODO(janakr): pass the configuration in as a parameter here and above.
   private Collection<ConfiguredTargetAndData>
-  getConfiguredTargetAndDataDirectPrerequisitesForTesting(
-      ExtendedEventHandler eventHandler,
-      ConfiguredTarget ct,
-      BuildConfigurationCollection configurations)
-      throws EvalException, InvalidConfigurationException, InterruptedException,
-      InconsistentAspectOrderException {
+      getConfiguredTargetAndDataDirectPrerequisitesForTesting(
+          ExtendedEventHandler eventHandler,
+          ConfiguredTarget ct,
+          BuildConfigurationCollection configurations)
+          throws EvalException, InvalidConfigurationException, InterruptedException,
+              InconsistentAspectOrderException, StarlarkTransition.TransitionException {
     return getConfiguredTargetAndDataDirectPrerequisitesForTesting(
         eventHandler, ct, ct.getConfigurationKey(), configurations);
   }
 
   @VisibleForTesting
   public Collection<ConfiguredTargetAndData>
-  getConfiguredTargetAndDataDirectPrerequisitesForTesting(
-      ExtendedEventHandler eventHandler,
-      ConfiguredTargetAndData ct,
-      BuildConfigurationCollection configurations)
-      throws EvalException, InvalidConfigurationException, InterruptedException,
-      InconsistentAspectOrderException {
+      getConfiguredTargetAndDataDirectPrerequisitesForTesting(
+          ExtendedEventHandler eventHandler,
+          ConfiguredTargetAndData ct,
+          BuildConfigurationCollection configurations)
+          throws EvalException, InvalidConfigurationException, InterruptedException,
+              InconsistentAspectOrderException, StarlarkTransition.TransitionException {
     return getConfiguredTargetAndDataDirectPrerequisitesForTesting(
         eventHandler,
         ct.getConfiguredTarget(),
@@ -250,13 +252,13 @@
   }
 
   private Collection<ConfiguredTargetAndData>
-  getConfiguredTargetAndDataDirectPrerequisitesForTesting(
-      ExtendedEventHandler eventHandler,
-      ConfiguredTarget ct,
-      BuildConfigurationValue.Key configuration,
-      BuildConfigurationCollection configurations)
-      throws EvalException, InvalidConfigurationException, InterruptedException,
-      InconsistentAspectOrderException {
+      getConfiguredTargetAndDataDirectPrerequisitesForTesting(
+          ExtendedEventHandler eventHandler,
+          ConfiguredTarget ct,
+          BuildConfigurationValue.Key configuration,
+          BuildConfigurationCollection configurations)
+          throws EvalException, InvalidConfigurationException, InterruptedException,
+              InconsistentAspectOrderException, StarlarkTransition.TransitionException {
     return skyframeExecutor.getConfiguredTargetsForTesting(
         eventHandler,
         configuration,
@@ -272,7 +274,8 @@
       final ConfiguredTarget ct,
       BuildConfigurationCollection configurations,
       ImmutableSet<Label> toolchainLabels)
-      throws EvalException, InterruptedException, InconsistentAspectOrderException {
+      throws EvalException, InterruptedException, InconsistentAspectOrderException,
+          StarlarkTransition.TransitionException, InvalidConfigurationException {
 
     Target target = null;
     try {
@@ -336,7 +339,8 @@
    * present in this rule's attributes.
    */
   private ImmutableMap<Label, ConfigMatchingProvider> getConfigurableAttributeKeysForTesting(
-      ExtendedEventHandler eventHandler, TargetAndConfiguration ctg) {
+      ExtendedEventHandler eventHandler, TargetAndConfiguration ctg)
+      throws StarlarkTransition.TransitionException, InvalidConfigurationException {
     if (!(ctg.getTarget() instanceof Rule)) {
       return ImmutableMap.of();
     }
@@ -362,7 +366,7 @@
       BuildConfigurationCollection configurations,
       ImmutableSet<Label> toolchainLabels)
       throws EvalException, InvalidConfigurationException, InterruptedException,
-          InconsistentAspectOrderException {
+          InconsistentAspectOrderException, StarlarkTransition.TransitionException {
     OrderedSetMultimap<DependencyKind, Dependency> depNodeNames =
         getDirectPrerequisiteDependenciesForTesting(
             eventHandler, target, configurations, toolchainLabels);
@@ -412,7 +416,8 @@
    */
   @VisibleForTesting
   public ConfiguredTarget getConfiguredTargetForTesting(
-      ExtendedEventHandler eventHandler, Label label, BuildConfiguration config) {
+      ExtendedEventHandler eventHandler, Label label, BuildConfiguration config)
+      throws StarlarkTransition.TransitionException, InvalidConfigurationException {
     ConfigurationTransition transition =
         getTopLevelTransitionForTarget(label, config, eventHandler);
     if (transition == null) {
@@ -423,15 +428,15 @@
 
   @VisibleForTesting
   public ConfiguredTargetAndData getConfiguredTargetAndDataForTesting(
-      ExtendedEventHandler eventHandler, Label label, BuildConfiguration config) {
+      ExtendedEventHandler eventHandler, Label label, BuildConfiguration config)
+      throws StarlarkTransition.TransitionException, InvalidConfigurationException {
     ConfigurationTransition transition =
         getTopLevelTransitionForTarget(label, config, eventHandler);
     if (transition == null) {
       return null;
     }
-      return skyframeExecutor.getConfiguredTargetAndDataForTesting(
-          eventHandler, label, config, transition);
-
+    return skyframeExecutor.getConfiguredTargetAndDataForTesting(
+        eventHandler, label, config, transition);
   }
 
   /**
@@ -443,7 +448,8 @@
       StoredEventHandler eventHandler,
       BuildConfigurationCollection configurations)
       throws EvalException, InvalidConfigurationException, InterruptedException,
-      InconsistentAspectOrderException, ToolchainException {
+          InconsistentAspectOrderException, ToolchainException,
+          StarlarkTransition.TransitionException {
     BuildConfiguration targetConfig =
         skyframeExecutor.getConfiguration(eventHandler, target.getConfigurationKey());
     CachingAnalysisEnvironment env =
@@ -470,7 +476,8 @@
       AnalysisEnvironment env,
       BuildConfigurationCollection configurations)
       throws EvalException, InvalidConfigurationException, InterruptedException,
-      InconsistentAspectOrderException, ToolchainException {
+          InconsistentAspectOrderException, ToolchainException,
+          StarlarkTransition.TransitionException {
     BuildConfiguration targetConfig =
         skyframeExecutor.getConfiguration(eventHandler, configuredTarget.getConfigurationKey());
     Target target = null;
@@ -538,7 +545,7 @@
       Label desiredTarget,
       BuildConfigurationCollection configurations)
       throws EvalException, InvalidConfigurationException, InterruptedException,
-      InconsistentAspectOrderException {
+          InconsistentAspectOrderException, StarlarkTransition.TransitionException {
     Collection<ConfiguredTargetAndData> configuredTargets =
         getPrerequisiteMapForTesting(
             eventHandler,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index 58fa36d..a8ee477 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -85,12 +85,14 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Options.ConfigsMode;
 import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
 import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.NullTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
 import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget;
 import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
 import com.google.devtools.build.lib.analysis.extra.ExtraAction;
+import com.google.devtools.build.lib.analysis.skylark.StarlarkTransition;
 import com.google.devtools.build.lib.analysis.test.BaselineCoverageAction;
 import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
 import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
@@ -872,16 +874,24 @@
    * may return null. In that case, use a debugger to inspect the {@link ErrorInfo} for the
    * evaluation, which is produced by the {@link MemoizingEvaluator#getExistingValue} call in {@link
    * SkyframeExecutor#getConfiguredTargetForTesting}. See also b/26382502.
+   *
+   * @throws AssertionError if the target cannot be transitioned into with the given configuration
    */
   protected ConfiguredTarget getConfiguredTarget(Label label, BuildConfiguration config) {
-    return view.getConfiguredTargetForTesting(reporter, BlazeTestUtils.convertLabel(label), config);
+    try {
+      return view.getConfiguredTargetForTesting(
+          reporter, BlazeTestUtils.convertLabel(label), config);
+    } catch (InvalidConfigurationException | StarlarkTransition.TransitionException e) {
+      throw new AssertionError(e);
+    }
   }
 
   /**
    * Returns a ConfiguredTargetAndData for the specified label, using the given build configuration.
    */
   protected ConfiguredTargetAndData getConfiguredTargetAndData(
-      Label label, BuildConfiguration config) {
+      Label label, BuildConfiguration config)
+      throws StarlarkTransition.TransitionException, InvalidConfigurationException {
     return view.getConfiguredTargetAndDataForTesting(reporter, label, config);
   }
 
@@ -891,7 +901,8 @@
    * config in the ConfiguredTargetAndData's ConfiguredTarget.
    */
   public ConfiguredTargetAndData getConfiguredTargetAndData(String label)
-      throws LabelSyntaxException {
+      throws LabelSyntaxException, StarlarkTransition.TransitionException,
+          InvalidConfigurationException {
     return getConfiguredTargetAndData(Label.parseAbsolute(label, ImmutableMap.of()), targetConfig);
   }
 
@@ -1714,16 +1725,22 @@
 
   /**
    * Returns the configuration created by applying the given transition to the source configuration.
+   *
+   * @throws AssertionError if the transition couldn't be evaluated
    */
-  protected BuildConfiguration getConfiguration(BuildConfiguration fromConfig,
-      PatchTransition transition) throws InterruptedException {
+  protected BuildConfiguration getConfiguration(
+      BuildConfiguration fromConfig, PatchTransition transition) throws InterruptedException {
     if (transition == NoTransition.INSTANCE) {
       return fromConfig;
     } else if (transition == NullTransition.INSTANCE) {
       return null;
     } else {
-      return skyframeExecutor.getConfigurationForTesting(
-          reporter, fromConfig.fragmentClasses(), transition.patch(fromConfig.getOptions()));
+      try {
+        return skyframeExecutor.getConfigurationForTesting(
+            reporter, fromConfig.fragmentClasses(), transition.patch(fromConfig.getOptions()));
+      } catch (OptionsParsingException | InvalidConfigurationException e) {
+        throw new AssertionError(e);
+      }
     }
   }
 
@@ -1750,7 +1767,9 @@
     ConfiguredTargetAndData ctad;
     try {
       ctad = getConfiguredTargetAndData(ct.getLabel().toString());
-    } catch (LabelSyntaxException e) {
+    } catch (LabelSyntaxException
+        | StarlarkTransition.TransitionException
+        | InvalidConfigurationException e) {
       throw new RuntimeException(e);
     }
     return getMapperFromConfiguredTargetAndTarget(ctad);
diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcRuleTestCase.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcRuleTestCase.java
index 28e7052..58d5e72 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcRuleTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcRuleTestCase.java
@@ -42,6 +42,7 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
 import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter;
@@ -59,6 +60,7 @@
 import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs;
 import com.google.devtools.build.lib.testutil.TestConstants;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.common.options.OptionsParsingException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -299,8 +301,9 @@
    * Returns all child configurations resulting from a given split transition on a given
    * configuration.
    */
-  protected List<BuildConfiguration> getSplitConfigurations(BuildConfiguration configuration,
-      SplitTransition splitTransition) throws InterruptedException {
+  protected List<BuildConfiguration> getSplitConfigurations(
+      BuildConfiguration configuration, SplitTransition splitTransition)
+      throws InterruptedException, OptionsParsingException, InvalidConfigurationException {
     ImmutableList.Builder<BuildConfiguration> splitConfigs = ImmutableList.builder();
 
     for (BuildOptions splitOptions : splitTransition.split(configuration.getOptions())) {
diff --git a/src/test/java/com/google/devtools/build/lib/rules/platform/PlatformConfigurationApiTest.java b/src/test/java/com/google/devtools/build/lib/rules/platform/PlatformConfigurationApiTest.java
index f35776c..34437c6 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/platform/PlatformConfigurationApiTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/platform/PlatformConfigurationApiTest.java
@@ -74,33 +74,6 @@
   }
 
   @Test
-  public void testTargetPlatform_multiple() throws Exception {
-    scratch.file(
-        "platforms/BUILD",
-        "platform(name = 'test_platform1')",
-        "platform(name = 'test_platform2')");
-
-    useConfiguration("--platforms=//platforms:test_platform1,//platforms:test_platform2");
-    ruleBuilder().build();
-    scratch.file(
-        "foo/BUILD",
-        "load(':extension.bzl', 'my_rule')",
-        "my_rule(",
-        "  name = 'my_skylark_rule',",
-        ")");
-    assertNoEvents();
-
-    PlatformConfigurationApi platformConfiguration = fetchPlatformConfiguration();
-    assertThat(platformConfiguration).isNotNull();
-    // Despite setting two platforms in the flag, only a single platform should be visible to the
-    // target.
-    assertThat(platformConfiguration.getTargetPlatform())
-        .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:test_platform1"));
-    assertThat(platformConfiguration.getTargetPlatforms())
-        .containsExactly(Label.parseAbsoluteUnchecked("//platforms:test_platform1"));
-  }
-
-  @Test
   public void testEnabledToolchainTypes() throws Exception {
     scratch.file(
         "toolchains/BUILD",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingFunctionTest.java
index 5a0867e..c5a962b 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingFunctionTest.java
@@ -83,7 +83,7 @@
         executeFunction(PlatformMappingValue.Key.create(null));
 
     BuildConfigurationValue.Key key =
-        BuildConfigurationValue.key(PLATFORM_FRAGMENT_CLASS, EMPTY_DIFF);
+        BuildConfigurationValue.keyWithoutPlatformMapping(PLATFORM_FRAGMENT_CLASS, EMPTY_DIFF);
 
     BuildConfigurationValue.Key mapped =
         platformMappingValue.map(key, DEFAULT_BUILD_CONFIG_PLATFORM_OPTIONS);
@@ -155,6 +155,6 @@
     BuildOptions.OptionsDiffForReconstruction diff =
         BuildOptions.diffForReconstruction(DEFAULT_BUILD_CONFIG_PLATFORM_OPTIONS, modifiedOptions);
 
-    return BuildConfigurationValue.key(PLATFORM_FRAGMENT_CLASS, diff);
+    return BuildConfigurationValue.keyWithoutPlatformMapping(PLATFORM_FRAGMENT_CLASS, diff);
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingValueTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingValueTest.java
index af8c114..39a2e04 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingValueTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PlatformMappingValueTest.java
@@ -65,7 +65,7 @@
         new PlatformMappingValue(ImmutableMap.of(), ImmutableMap.of());
 
     BuildConfigurationValue.Key key =
-        BuildConfigurationValue.key(PLATFORM_FRAGMENT_CLASS, EMPTY_DIFF);
+        BuildConfigurationValue.keyWithoutPlatformMapping(PLATFORM_FRAGMENT_CLASS, EMPTY_DIFF);
 
     BuildConfigurationValue.Key mapped =
         mappingValue.map(key, DEFAULT_BUILD_CONFIG_PLATFORM_OPTIONS);
@@ -238,6 +238,6 @@
     BuildOptions.OptionsDiffForReconstruction diff =
         BuildOptions.diffForReconstruction(DEFAULT_BUILD_CONFIG_PLATFORM_OPTIONS, modifiedOptions);
 
-    return BuildConfigurationValue.key(PLATFORM_FRAGMENT_CLASS, diff);
+    return BuildConfigurationValue.keyWithoutPlatformMapping(PLATFORM_FRAGMENT_CLASS, diff);
   }
 }
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index 94c7075..2df2986 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -880,3 +880,10 @@
     data = [":test-deps"],
     deps = ["@bazel_tools//tools/bash/runfiles"],
 )
+
+sh_test(
+    name = "platform_mapping_test",
+    srcs = ["platform_mapping_test.sh"],
+    data = [":test-deps"],
+    tags = ["no_windows"],
+)
diff --git a/src/test/shell/bazel/platform_mapping_test.sh b/src/test/shell/bazel/platform_mapping_test.sh
new file mode 100755
index 0000000..9be52af
--- /dev/null
+++ b/src/test/shell/bazel/platform_mapping_test.sh
@@ -0,0 +1,195 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+#
+# Test the providers and rules related to toolchains.
+#
+
+# Load the test setup defined in the parent directory
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "${CURRENT_DIR}/../integration_test_setup.sh" \
+  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+function set_up() {
+  create_new_workspace
+
+  mkdir package
+
+  # Create shared platform definitions
+  mkdir plat
+  cat > plat/BUILD <<EOF
+platform(
+    name = 'platform1',
+    constraint_values = [])
+
+platform(
+    name = 'platform2',
+    constraint_values = [])
+EOF
+
+  # Create shared report rule for printing flag and platform info.
+  mkdir report
+  touch report/BUILD
+  cat > report/report.bzl <<EOF
+def _report_impl(ctx):
+  print('copts: %s' % ctx.fragments.cpp.copts)
+  print('platform: %s' % ctx.fragments.platform.platform)
+
+report_flags = rule(
+    implementation = _report_impl,
+    attrs = {},
+    fragments = ["cpp", "platform"]
+)
+EOF
+}
+
+function test_top_level_flags_to_platform_mapping() {
+  cat > platform_mappings <<EOF
+flags:
+  --cpu=arm64
+    //plat:platform1
+EOF
+
+  cat > package/BUILD <<EOF
+load("//report:report.bzl", "report_flags")
+report_flags(name = "report")
+EOF
+
+  bazel build --cpu=arm64 package:report &> $TEST_log \
+      || fail "Build failed unexpectedly"
+  expect_log "platform: //plat:platform1"
+}
+
+function test_top_level_platform_to_flags_mapping() {
+   cat > platform_mappings <<EOF
+platforms:
+  //plat:platform1
+    --copt=foo
+EOF
+
+  cat > package/BUILD <<EOF
+load("//report:report.bzl", "report_flags")
+report_flags(name = "report")
+EOF
+
+  bazel build --platforms=//plat:platform1 package:report &> $TEST_log \
+      || fail "Build failed unexpectedly"
+  expect_log "copts: \[\"foo\"\]"
+}
+
+function test_custom_platform_mapping_location() {
+  mkdir custom
+  cat > custom/platform_mappings <<EOF
+flags:
+  --cpu=arm64
+    //plat:platform1
+EOF
+
+  cat > package/BUILD <<EOF
+load("//report:report.bzl", "report_flags")
+report_flags(name = "report")
+EOF
+
+  bazel build --cpu=arm64 --platform_mappings=custom/platform_mappings \
+      package:report &> $TEST_log || fail "Build failed unexpectedly"
+  expect_log "platform: //plat:platform1"
+}
+
+function test_top_level_multi_platform_mapping() {
+  cat > platform_mappings <<EOF
+flags:
+  --cpu=k8
+    //plat:platform1
+  --cpu=arm64
+    //plat:platform2
+EOF
+
+  cat > package/BUILD <<EOF
+load("//report:report.bzl", "report_flags")
+report_flags(name = "report")
+EOF
+
+  bazel build --experimental_multi_cpu=k8,arm64 package:report &> $TEST_log \
+      || fail "Build failed unexpectedly"
+  expect_log "platform: //plat:platform1"
+  expect_log "platform: //plat:platform2"
+}
+
+function test_transition_platform_mapping() {
+  cat > platform_mappings <<EOF
+flags:
+  --cpu=k8
+    //plat:platform1
+  --cpu=arm64
+    //plat:platform2
+EOF
+
+  cat > package/rule.bzl <<EOF
+def _my_transition_impl(settings, attrs):
+  return {
+    "//command_line_option:cpu": "arm64",
+    "//command_line_option:copt": ["foo"],
+    # Platforms *must* be wiped for transitions to correctly participate in
+    # platform mapping.
+    "//command_line_option:platforms": [],
+  }
+
+
+my_transition = transition(
+  implementation = _my_transition_impl,
+  inputs = [],
+  outputs = [
+      "//command_line_option:cpu",
+      "//command_line_option:copt",
+      "//command_line_option:platforms",
+  ],
+)
+
+
+def _my_rule_impl(ctx):
+  return []
+
+
+my_rule = rule(
+  implementation = _my_rule_impl,
+  attrs = {
+      "deps": attr.label_list(cfg = my_transition),
+      "_whitelist_function_transition": attr.label(
+          default = "@//tools/whitelists/function_transition_whitelist"),
+  }
+)
+EOF
+
+  cat > package/BUILD <<EOF
+load("//report:report.bzl", "report_flags")
+load("//package:rule.bzl", "my_rule")
+
+my_rule(
+  name = "custom",
+  deps = [ ":report" ]
+)
+
+report_flags(name = "report")
+EOF
+
+  bazel build --cpu=k8 package:custom \
+      --experimental_starlark_config_transitions &> $TEST_log \
+      || fail "Build failed unexpectedly"
+  expect_not_log "platform: //plat:platform1"
+  expect_log "platform: //plat:platform2"
+}
+
+run_suite "platform mapping test"
+