Convert ToolchainResolver to ToolchainResolutionFunction.

Part of work on execution transitions, #7935.

Closes #8070.

PiperOrigin-RevId: 244355735
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java
index 0bef37d..9a19b10 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java
@@ -32,6 +32,7 @@
 import com.google.devtools.build.lib.rules.AliasConfiguredTarget;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
 import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
 import com.google.devtools.build.lib.skylarkbuildapi.ToolchainContextApi;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
 import com.google.devtools.build.lib.skylarkinterface.StarlarkContext;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java
index 04e548a..4a92d5b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainResolver.java
@@ -42,6 +42,7 @@
 import com.google.devtools.build.lib.skyframe.SingleToolchainResolutionFunction.NoToolchainFoundException;
 import com.google.devtools.build.lib.skyframe.SingleToolchainResolutionValue;
 import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
 import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.ValueOrException2;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/UnloadedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/UnloadedToolchainContext.java
deleted file mode 100644
index fe71c76..0000000
--- a/src/main/java/com/google/devtools/build/lib/analysis/UnloadedToolchainContext.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 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.analysis;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
-import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
-import com.google.devtools.build.lib.cmdline.Label;
-import java.util.Set;
-
-/**
- * Represents the state of toolchain resolution once the specific required toolchains have been
- * determined, but before the toolchain dependencies have been resolved.
- */
-@AutoValue
-public abstract class UnloadedToolchainContext implements ToolchainContext {
-
-  static Builder builder() {
-    return new AutoValue_UnloadedToolchainContext.Builder();
-  }
-
-  /** Builder class to help create the {@link UnloadedToolchainContext}. */
-  @AutoValue.Builder
-  public interface Builder {
-    /** Sets the selected execution platform that these toolchains use. */
-    Builder setExecutionPlatform(PlatformInfo executionPlatform);
-
-    /** Sets the target platform that these toolchains generate output for. */
-    Builder setTargetPlatform(PlatformInfo targetPlatform);
-
-    /** Sets the toolchain types that were requested. */
-    Builder setRequiredToolchainTypes(Set<ToolchainTypeInfo> requiredToolchainTypes);
-
-    Builder setToolchainTypeToResolved(
-        ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved);
-
-    UnloadedToolchainContext build();
-  }
-
-  /** The map of toolchain type to resolved toolchain to be used. */
-  abstract ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved();
-
-  @Override
-  public ImmutableSet<Label> resolvedToolchainLabels() {
-    return toolchainTypeToResolved().values();
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
index 6dde831..743c7d2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -32,7 +32,6 @@
 import com.google.devtools.build.lib.analysis.ResolvedToolchainContext;
 import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
 import com.google.devtools.build.lib.analysis.ToolchainResolver;
-import com.google.devtools.build.lib.analysis.UnloadedToolchainContext;
 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.ConfigMatchingProvider;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
index 5c6481b..a266129 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
@@ -38,7 +38,6 @@
 import com.google.devtools.build.lib.analysis.ResolvedToolchainContext;
 import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
 import com.google.devtools.build.lib.analysis.ToolchainResolver;
-import com.google.devtools.build.lib.analysis.UnloadedToolchainContext;
 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.ConfigMatchingProvider;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java
index 4bd0451..7b4ddd2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsCycleReporter.java
@@ -14,9 +14,13 @@
 
 package com.google.devtools.build.lib.skyframe;
 
+import static java.util.stream.Collectors.joining;
+
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Event;
@@ -41,6 +45,15 @@
   private static final Predicate<SkyKey> IS_SINGLE_TOOLCHAIN_RESOLUTION_SKY_KEY =
       SkyFunctions.isSkyFunction(SkyFunctions.SINGLE_TOOLCHAIN_RESOLUTION);
 
+  private static final Predicate<SkyKey> IS_TOOLCHAIN_RESOLUTION_SKY_KEY =
+      SkyFunctions.isSkyFunction(SkyFunctions.TOOLCHAIN_RESOLUTION);
+
+  private static final Predicate<SkyKey> IS_TOOLCHAIN_RELATED =
+      Predicates.or(
+          IS_REGISTERED_TOOLCHAINS_SKY_KEY,
+          IS_SINGLE_TOOLCHAIN_RESOLUTION_SKY_KEY,
+          IS_TOOLCHAIN_RESOLUTION_SKY_KEY);
+
   @Override
   public boolean maybeReportCycle(
       SkyKey topLevelKey,
@@ -50,9 +63,7 @@
     ImmutableList<SkyKey> cycle = cycleInfo.getCycle();
     if (alreadyReported) {
       return true;
-    } else if (!Iterables.any(cycle, IS_REGISTERED_TOOLCHAINS_SKY_KEY)
-        || !Iterables.any(cycle, IS_CONFIGURED_TARGET_SKY_KEY)
-        || !Iterables.any(cycle, IS_SINGLE_TOOLCHAIN_RESOLUTION_SKY_KEY)) {
+    } else if (!Iterables.any(cycle, IS_TOOLCHAIN_RELATED)) {
       return false;
     }
 
@@ -63,24 +74,28 @@
     }
 
     Function<SkyKey, String> printer =
-        new Function<SkyKey, String>() {
-          @Override
-          public String apply(SkyKey input) {
-            if (input.argument() instanceof ConfiguredTargetKey) {
-              Label label = ((ConfiguredTargetKey) input.argument()).getLabel();
-              return label.toString();
-            }
-            if (input.argument() instanceof RegisteredToolchainsValue.Key) {
-              return "RegisteredToolchains";
-            }
-            if (input.argument() instanceof SingleToolchainResolutionValue.Key) {
-              Label toolchainType =
-                  ((SingleToolchainResolutionValue.Key) input.argument()).toolchainTypeLabel();
-              return String.format("toolchain type %s", toolchainType.toString());
-            } else {
-              throw new UnsupportedOperationException();
-            }
+        input -> {
+          if (input.argument() instanceof ConfiguredTargetKey) {
+            Label label = ((ConfiguredTargetKey) input.argument()).getLabel();
+            return label.toString();
           }
+          if (input.argument() instanceof RegisteredToolchainsValue.Key) {
+            return "RegisteredToolchains";
+          }
+          if (input.argument() instanceof SingleToolchainResolutionValue.Key) {
+            Label toolchainType =
+                ((SingleToolchainResolutionValue.Key) input.argument()).toolchainTypeLabel();
+            return String.format("toolchain type %s", toolchainType);
+          }
+          if (input.argument() instanceof UnloadedToolchainContext.Key) {
+            ImmutableSet<Label> toolchainTypes =
+                ((UnloadedToolchainContext.Key) input.argument()).requiredToolchainTypeLabels();
+            return String.format(
+                "toolchain types %s",
+                toolchainTypes.stream().map(Label::toString).collect(joining(", ")));
+          }
+
+          throw new UnsupportedOperationException();
         };
 
     StringBuilder cycleMessage =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
index ee43272..da00c9f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
@@ -138,6 +138,8 @@
       SkyFunctionName.createHermetic("REGISTERED_TOOLCHAINS");
   static final SkyFunctionName SINGLE_TOOLCHAIN_RESOLUTION =
       SkyFunctionName.createHermetic("SINGLE_TOOLCHAIN_RESOLUTION");
+  static final SkyFunctionName TOOLCHAIN_RESOLUTION =
+      SkyFunctionName.createHermetic("TOOLCHAIN_RESOLUTION");
   public static final SkyFunctionName REPOSITORY_MAPPING =
       SkyFunctionName.createHermetic("REPOSITORY_MAPPING");
   public static final SkyFunctionName RESOLVED_FILE =
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 eadebad..04b5338 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
@@ -593,6 +593,7 @@
         SkyFunctions.REGISTERED_EXECUTION_PLATFORMS, new RegisteredExecutionPlatformsFunction());
     map.put(SkyFunctions.REGISTERED_TOOLCHAINS, new RegisteredToolchainsFunction());
     map.put(SkyFunctions.SINGLE_TOOLCHAIN_RESOLUTION, new SingleToolchainResolutionFunction());
+    map.put(SkyFunctions.TOOLCHAIN_RESOLUTION, new ToolchainResolutionFunction());
     map.put(SkyFunctions.REPOSITORY_MAPPING, new RepositoryMappingFunction());
     map.put(SkyFunctions.RESOLVED_HASH_VALUES, new ResolvedHashesFunction());
     map.put(SkyFunctions.RESOLVED_FILE, new ResolvedFileFunction());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java
index cc9db65..46a4cf6 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainException.java
@@ -13,6 +13,9 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
+import javax.annotation.Nullable;
+
 /** Base class for exceptions that happen during toolchain resolution. */
 public class ToolchainException extends Exception {
 
@@ -27,4 +30,21 @@
   public ToolchainException(String message, Throwable cause) {
     super(message, cause);
   }
+
+  /**
+   * Attempt to find a {@link ConfiguredValueCreationException} in this exception, or its causes.
+   *
+   * <p>If one cannot be found, a new one will be created.
+   */
+  @Nullable
+  public ConfiguredValueCreationException asConfiguredValueCreationException() {
+    for (Throwable cause = getCause();
+        cause != null && cause != cause.getCause();
+        cause = cause.getCause()) {
+      if (cause instanceof ConfiguredValueCreationException) {
+        return (ConfiguredValueCreationException) cause;
+      }
+    }
+    return null;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java
new file mode 100644
index 0000000..2420696
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java
@@ -0,0 +1,513 @@
+// Copyright 2019 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.skyframe;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Table;
+import com.google.devtools.build.lib.analysis.PlatformConfiguration;
+import com.google.devtools.build.lib.analysis.PlatformOptions;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
+import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
+import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.skyframe.ConstraintValueLookupUtil.InvalidConstraintValueException;
+import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException;
+import com.google.devtools.build.lib.skyframe.RegisteredToolchainsFunction.InvalidToolchainLabelException;
+import com.google.devtools.build.lib.skyframe.SingleToolchainResolutionFunction.NoToolchainFoundException;
+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.ValueOrException2;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/**
+ * Sky function which performs toolchain resolution for multiple toolchain types, including
+ * selecting the execution platform.
+ */
+public class ToolchainResolutionFunction implements SkyFunction {
+
+  @Nullable
+  @Override
+  public UnloadedToolchainContext compute(SkyKey skyKey, Environment env)
+      throws ToolchainResolutionFunctionException, InterruptedException {
+    UnloadedToolchainContext.Key key = (UnloadedToolchainContext.Key) skyKey.argument();
+
+    try {
+      UnloadedToolchainContext.Builder unloadedToolchainContext =
+          UnloadedToolchainContext.builder();
+
+      // Determine the configuration being used.
+      BuildConfigurationValue value =
+          (BuildConfigurationValue) env.getValue(key.configurationKey());
+      if (value == null) {
+        throw new ValueMissingException();
+      }
+      BuildConfiguration configuration = value.getConfiguration();
+      PlatformConfiguration platformConfiguration =
+          configuration.getFragment(PlatformConfiguration.class);
+      if (platformConfiguration == null) {
+        throw new ValueMissingException();
+      }
+
+      // Check if debug output should be generated.
+      boolean debug =
+          configuration.getOptions().get(PlatformOptions.class).toolchainResolutionDebug;
+
+      // Create keys for all platforms that will be used, and validate them early.
+      PlatformKeys platformKeys =
+          loadPlatformKeys(
+              env,
+              debug,
+              key.configurationKey(),
+              configuration,
+              platformConfiguration,
+              key.execConstraintLabels(),
+              key.shouldSanityCheckConfiguration());
+      if (env.valuesMissing()) {
+        return null;
+      }
+
+      // Determine the actual toolchain implementations to use.
+      determineToolchainImplementations(
+          env,
+          debug,
+          key.configurationKey(),
+          key.requiredToolchainTypeLabels(),
+          unloadedToolchainContext,
+          platformKeys,
+          key.shouldSanityCheckConfiguration());
+
+      return unloadedToolchainContext.build();
+    } catch (ToolchainException e) {
+      throw new ToolchainResolutionFunctionException(e);
+    } catch (ValueMissingException e) {
+      return null;
+    }
+  }
+
+  @AutoValue
+  abstract static class PlatformKeys {
+    abstract ConfiguredTargetKey hostPlatformKey();
+
+    abstract ConfiguredTargetKey targetPlatformKey();
+
+    abstract ImmutableList<ConfiguredTargetKey> executionPlatformKeys();
+
+    static PlatformKeys create(
+        ConfiguredTargetKey hostPlatformKey,
+        ConfiguredTargetKey targetPlatformKey,
+        List<ConfiguredTargetKey> executionPlatformKeys) {
+      return new AutoValue_ToolchainResolutionFunction_PlatformKeys(
+          hostPlatformKey, targetPlatformKey, ImmutableList.copyOf(executionPlatformKeys));
+    }
+  }
+
+  private PlatformKeys loadPlatformKeys(
+      SkyFunction.Environment environment,
+      boolean debug,
+      BuildConfigurationValue.Key configurationKey,
+      BuildConfiguration configuration,
+      PlatformConfiguration platformConfiguration,
+      ImmutableSet<Label> execConstraintLabels,
+      boolean shouldSanityCheckConfiguration)
+      throws InterruptedException, ValueMissingException, InvalidConstraintValueException,
+          InvalidPlatformException {
+    // Determine the target and host platform keys.
+    Label hostPlatformLabel = platformConfiguration.getHostPlatform();
+    Label targetPlatformLabel = platformConfiguration.getTargetPlatform();
+
+    ConfiguredTargetKey hostPlatformKey = ConfiguredTargetKey.of(hostPlatformLabel, configuration);
+    ConfiguredTargetKey targetPlatformKey =
+        ConfiguredTargetKey.of(targetPlatformLabel, configuration);
+
+    // Load the host and target platforms early, to check for errors.
+    PlatformLookupUtil.getPlatformInfo(
+        ImmutableList.of(hostPlatformKey, targetPlatformKey),
+        environment,
+        shouldSanityCheckConfiguration);
+    if (environment.valuesMissing()) {
+      throw new ValueMissingException();
+    }
+
+    ImmutableList<ConfiguredTargetKey> executionPlatformKeys =
+        loadExecutionPlatformKeys(
+            environment,
+            debug,
+            configurationKey,
+            configuration,
+            hostPlatformKey,
+            execConstraintLabels,
+            shouldSanityCheckConfiguration);
+
+    return PlatformKeys.create(hostPlatformKey, targetPlatformKey, executionPlatformKeys);
+  }
+
+  private ImmutableList<ConfiguredTargetKey> loadExecutionPlatformKeys(
+      SkyFunction.Environment environment,
+      boolean debug,
+      BuildConfigurationValue.Key configurationKey,
+      BuildConfiguration configuration,
+      ConfiguredTargetKey defaultPlatformKey,
+      ImmutableSet<Label> execConstraintLabels,
+      boolean shouldSanityCheckConfiguration)
+      throws InterruptedException, ValueMissingException, InvalidConstraintValueException,
+          InvalidPlatformException {
+    RegisteredExecutionPlatformsValue registeredExecutionPlatforms =
+        (RegisteredExecutionPlatformsValue)
+            environment.getValueOrThrow(
+                RegisteredExecutionPlatformsValue.key(configurationKey),
+                InvalidPlatformException.class);
+    if (registeredExecutionPlatforms == null) {
+      throw new ValueMissingException();
+    }
+
+    ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys =
+        new ImmutableList.Builder<ConfiguredTargetKey>()
+            .addAll(registeredExecutionPlatforms.registeredExecutionPlatformKeys())
+            .add(defaultPlatformKey)
+            .build();
+
+    // Filter out execution platforms that don't satisfy the extra constraints.
+    ImmutableList<ConfiguredTargetKey> execConstraintKeys =
+        execConstraintLabels.stream()
+            .map(label -> ConfiguredTargetKey.of(label, configuration))
+            .collect(toImmutableList());
+
+    return filterAvailablePlatforms(
+        environment,
+        debug,
+        availableExecutionPlatformKeys,
+        execConstraintKeys,
+        shouldSanityCheckConfiguration);
+  }
+
+  /** Returns only the platform keys that match the given constraints. */
+  private ImmutableList<ConfiguredTargetKey> filterAvailablePlatforms(
+      SkyFunction.Environment environment,
+      boolean debug,
+      ImmutableList<ConfiguredTargetKey> platformKeys,
+      ImmutableList<ConfiguredTargetKey> constraintKeys,
+      boolean shouldSanityCheckConfiguration)
+      throws InterruptedException, ValueMissingException, InvalidConstraintValueException,
+          InvalidPlatformException {
+
+    // Short circuit if not needed.
+    if (constraintKeys.isEmpty()) {
+      return platformKeys;
+    }
+
+    // At this point the host and target platforms have been loaded, but not necessarily the chosen
+    // execution platform (it might be the same as the host platform, and might not).
+    //
+    // It's not worth trying to optimize away this call, since in the optimizable case (the exec
+    // platform is the host platform), Skyframe will return the correct results immediately without
+    // need of a restart.
+    Map<ConfiguredTargetKey, PlatformInfo> platformInfoMap =
+        PlatformLookupUtil.getPlatformInfo(
+            platformKeys, environment, shouldSanityCheckConfiguration);
+    if (platformInfoMap == null) {
+      throw new ValueMissingException();
+    }
+    List<ConstraintValueInfo> constraints =
+        ConstraintValueLookupUtil.getConstraintValueInfo(constraintKeys, environment);
+    if (constraints == null) {
+      throw new ValueMissingException();
+    }
+
+    return platformKeys.stream()
+        .filter(key -> filterPlatform(environment, debug, platformInfoMap.get(key), constraints))
+        .collect(toImmutableList());
+  }
+
+  /** Returns {@code true} if the given platform has all of the constraints. */
+  private boolean filterPlatform(
+      SkyFunction.Environment environment,
+      boolean debug,
+      PlatformInfo platformInfo,
+      List<ConstraintValueInfo> constraints) {
+    ImmutableList<ConstraintValueInfo> missingConstraints =
+        platformInfo.constraints().findMissing(constraints);
+    if (debug) {
+      for (ConstraintValueInfo constraint : missingConstraints) {
+        // The value for this setting is not present in the platform, or doesn't match the expected
+        // value.
+        environment
+            .getListener()
+            .handle(
+                Event.info(
+                    String.format(
+                        "ToolchainResolver: Removed execution platform %s from"
+                            + " available execution platforms, it is missing constraint %s",
+                        platformInfo.label(), constraint.label())));
+      }
+    }
+
+    return missingConstraints.isEmpty();
+  }
+
+  private void determineToolchainImplementations(
+      SkyFunction.Environment environment,
+      boolean debug,
+      BuildConfigurationValue.Key configurationKey,
+      ImmutableSet<Label> requiredToolchainTypeLabels,
+      UnloadedToolchainContext.Builder unloadedToolchainContext,
+      PlatformKeys platformKeys,
+      boolean shouldSanityCheckConfiguration)
+      throws InterruptedException, ValueMissingException, InvalidPlatformException,
+          NoMatchingPlatformException, UnresolvedToolchainsException,
+          InvalidToolchainLabelException {
+
+    // Find the toolchains for the required toolchain types.
+    List<SingleToolchainResolutionValue.Key> registeredToolchainKeys = new ArrayList<>();
+    for (Label toolchainTypeLabel : requiredToolchainTypeLabels) {
+      registeredToolchainKeys.add(
+          SingleToolchainResolutionValue.key(
+              configurationKey,
+              toolchainTypeLabel,
+              platformKeys.targetPlatformKey(),
+              platformKeys.executionPlatformKeys()));
+    }
+
+    Map<SkyKey, ValueOrException2<NoToolchainFoundException, InvalidToolchainLabelException>>
+        results =
+            environment.getValuesOrThrow(
+                registeredToolchainKeys,
+                NoToolchainFoundException.class,
+                InvalidToolchainLabelException.class);
+    boolean valuesMissing = false;
+
+    // Determine the potential set of toolchains.
+    Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> resolvedToolchains =
+        HashBasedTable.create();
+    ImmutableSet.Builder<ToolchainTypeInfo> requiredToolchainTypesBuilder = ImmutableSet.builder();
+    List<Label> missingToolchains = new ArrayList<>();
+    for (Map.Entry<
+            SkyKey, ValueOrException2<NoToolchainFoundException, InvalidToolchainLabelException>>
+        entry : results.entrySet()) {
+      try {
+        ValueOrException2<NoToolchainFoundException, InvalidToolchainLabelException>
+            valueOrException = entry.getValue();
+        SingleToolchainResolutionValue singleToolchainResolutionValue =
+            (SingleToolchainResolutionValue) valueOrException.get();
+        if (singleToolchainResolutionValue == null) {
+          valuesMissing = true;
+          continue;
+        }
+
+        ToolchainTypeInfo requiredToolchainType = singleToolchainResolutionValue.toolchainType();
+        requiredToolchainTypesBuilder.add(requiredToolchainType);
+        resolvedToolchains.putAll(
+            findPlatformsAndLabels(requiredToolchainType, singleToolchainResolutionValue));
+      } catch (NoToolchainFoundException e) {
+        // Save the missing type and continue looping to check for more.
+        missingToolchains.add(e.missingToolchainTypeLabel());
+      }
+    }
+
+    if (!missingToolchains.isEmpty()) {
+      throw new UnresolvedToolchainsException(missingToolchains);
+    }
+
+    if (valuesMissing) {
+      throw new ValueMissingException();
+    }
+
+    ImmutableSet<ToolchainTypeInfo> requiredToolchainTypes = requiredToolchainTypesBuilder.build();
+
+    // Find and return the first execution platform which has all required toolchains.
+    Optional<ConfiguredTargetKey> selectedExecutionPlatformKey;
+    if (!requiredToolchainTypeLabels.isEmpty()) {
+      selectedExecutionPlatformKey =
+          findExecutionPlatformForToolchains(
+              environment,
+              debug,
+              requiredToolchainTypes,
+              platformKeys.executionPlatformKeys(),
+              resolvedToolchains);
+    } else if (platformKeys.executionPlatformKeys().contains(platformKeys.hostPlatformKey())) {
+      // Fall back to the legacy behavior: use the host platform if it's available, otherwise the
+      // first execution platform.
+      selectedExecutionPlatformKey = Optional.of(platformKeys.hostPlatformKey());
+    } else if (!platformKeys.executionPlatformKeys().isEmpty()) {
+      // Just use the first execution platform.
+      selectedExecutionPlatformKey = Optional.of(platformKeys.executionPlatformKeys().get(0));
+    } else {
+      selectedExecutionPlatformKey = Optional.empty();
+    }
+
+    if (!selectedExecutionPlatformKey.isPresent()) {
+      throw new NoMatchingPlatformException(
+          requiredToolchainTypeLabels,
+          platformKeys.executionPlatformKeys(),
+          platformKeys.targetPlatformKey());
+    }
+
+    Map<ConfiguredTargetKey, PlatformInfo> platforms =
+        PlatformLookupUtil.getPlatformInfo(
+            ImmutableList.of(selectedExecutionPlatformKey.get(), platformKeys.targetPlatformKey()),
+            environment,
+            shouldSanityCheckConfiguration);
+    if (platforms == null) {
+      throw new ValueMissingException();
+    }
+
+    unloadedToolchainContext.setRequiredToolchainTypes(requiredToolchainTypes);
+    unloadedToolchainContext.setExecutionPlatform(
+        platforms.get(selectedExecutionPlatformKey.get()));
+    unloadedToolchainContext.setTargetPlatform(platforms.get(platformKeys.targetPlatformKey()));
+
+    Map<ToolchainTypeInfo, Label> toolchains =
+        resolvedToolchains.row(selectedExecutionPlatformKey.get());
+    unloadedToolchainContext.setToolchainTypeToResolved(ImmutableBiMap.copyOf(toolchains));
+  }
+
+  /**
+   * Adds all of toolchain labels from{@code toolchainResolutionValue} to {@code
+   * resolvedToolchains}.
+   */
+  private static Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> findPlatformsAndLabels(
+      ToolchainTypeInfo requiredToolchainType,
+      SingleToolchainResolutionValue singleToolchainResolutionValue) {
+
+    Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> resolvedToolchains =
+        HashBasedTable.create();
+    for (Map.Entry<ConfiguredTargetKey, Label> entry :
+        singleToolchainResolutionValue.availableToolchainLabels().entrySet()) {
+      resolvedToolchains.put(entry.getKey(), requiredToolchainType, entry.getValue());
+    }
+    return resolvedToolchains;
+  }
+
+  /**
+   * Finds the first platform from {@code availableExecutionPlatformKeys} that is present in {@code
+   * resolvedToolchains} and has all required toolchain types.
+   */
+  private static Optional<ConfiguredTargetKey> findExecutionPlatformForToolchains(
+      SkyFunction.Environment environment,
+      boolean debug,
+      ImmutableSet<ToolchainTypeInfo> requiredToolchainTypes,
+      ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys,
+      Table<ConfiguredTargetKey, ToolchainTypeInfo, Label> resolvedToolchains) {
+    for (ConfiguredTargetKey executionPlatformKey : availableExecutionPlatformKeys) {
+      if (!resolvedToolchains.containsRow(executionPlatformKey)) {
+        continue;
+      }
+
+      Map<ToolchainTypeInfo, Label> toolchains = resolvedToolchains.row(executionPlatformKey);
+      if (!toolchains.keySet().containsAll(requiredToolchainTypes)) {
+        // Not all toolchains are present, ignore this execution platform.
+        continue;
+      }
+
+      if (debug) {
+        String selectedToolchains =
+            toolchains.entrySet().stream()
+                .map(
+                    e ->
+                        String.format(
+                            "type %s -> toolchain %s", e.getKey().typeLabel(), e.getValue()))
+                .collect(joining(", "));
+        environment
+            .getListener()
+            .handle(
+                Event.info(
+                    String.format(
+                        "ToolchainResolver: Selected execution platform %s, %s",
+                        executionPlatformKey.getLabel(), selectedToolchains)));
+      }
+      return Optional.of(executionPlatformKey);
+    }
+
+    return Optional.empty();
+  }
+
+  @Nullable
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
+  }
+
+  private static final class ValueMissingException extends Exception {
+    private ValueMissingException() {
+      super();
+    }
+  }
+
+  /** Exception used when no execution platform can be found. */
+  static final class NoMatchingPlatformException extends ToolchainException {
+    NoMatchingPlatformException(
+        Set<Label> requiredToolchainTypeLabels,
+        ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys,
+        ConfiguredTargetKey targetPlatformKey) {
+      super(
+          formatError(
+              requiredToolchainTypeLabels, availableExecutionPlatformKeys, targetPlatformKey));
+    }
+
+    private static String formatError(
+        Set<Label> requiredToolchainTypeLabels,
+        ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys,
+        ConfiguredTargetKey targetPlatformKey) {
+      if (requiredToolchainTypeLabels.isEmpty()) {
+        return String.format(
+            "Unable to find an execution platform for target platform %s"
+                + " from available execution platforms [%s]",
+            targetPlatformKey.getLabel(),
+            availableExecutionPlatformKeys.stream()
+                .map(key -> key.getLabel().toString())
+                .collect(Collectors.joining(", ")));
+      }
+      return String.format(
+          "Unable to find an execution platform for toolchains [%s] and target platform %s"
+              + " from available execution platforms [%s]",
+          requiredToolchainTypeLabels.stream().map(Label::toString).collect(joining(", ")),
+          targetPlatformKey.getLabel(),
+          availableExecutionPlatformKeys.stream()
+              .map(key -> key.getLabel().toString())
+              .collect(Collectors.joining(", ")));
+    }
+  }
+
+  /** Exception used when a toolchain type is required but no matching toolchain is found. */
+  static final class UnresolvedToolchainsException extends ToolchainException {
+    UnresolvedToolchainsException(List<Label> missingToolchainTypes) {
+      super(
+          String.format(
+              "no matching toolchains found for types %s",
+              missingToolchainTypes.stream().map(Label::toString).collect(joining(", "))));
+    }
+  }
+
+  /** Used to indicate errors during the computation of an {@link UnloadedToolchainContext}. */
+  private static final class ToolchainResolutionFunctionException extends SkyFunctionException {
+    public ToolchainResolutionFunctionException(ToolchainException e) {
+      super(e, Transience.PERSISTENT);
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/UnloadedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/skyframe/UnloadedToolchainContext.java
new file mode 100644
index 0000000..cad4345
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/UnloadedToolchainContext.java
@@ -0,0 +1,109 @@
+// Copyright 2019 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.skyframe;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.analysis.ToolchainContext;
+import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
+import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import java.util.Set;
+
+/**
+ * Represents the state of toolchain resolution once the specific required toolchains have been
+ * determined, but before the toolchain dependencies have been resolved.
+ */
+@AutoValue
+public abstract class UnloadedToolchainContext implements ToolchainContext, SkyValue {
+
+  /** Returns a new {@link Key.Builder}. */
+  public static Key.Builder key() {
+    return new AutoValue_UnloadedToolchainContext_Key.Builder()
+        .requiredToolchainTypeLabels(ImmutableSet.of())
+        .execConstraintLabels(ImmutableSet.of())
+        .shouldSanityCheckConfiguration(false);
+  }
+
+  /** {@link SkyKey} implementation used for {@link ToolchainResolutionFunction}. */
+  @AutoValue
+  public abstract static class Key implements SkyKey {
+
+    @Override
+    public SkyFunctionName functionName() {
+      return SkyFunctions.TOOLCHAIN_RESOLUTION;
+    }
+
+    abstract BuildConfigurationValue.Key configurationKey();
+
+    abstract ImmutableSet<Label> requiredToolchainTypeLabels();
+
+    abstract ImmutableSet<Label> execConstraintLabels();
+
+    abstract boolean shouldSanityCheckConfiguration();
+
+    /** Builder for {@link UnloadedToolchainContext.Key}. */
+    @AutoValue.Builder
+    public interface Builder {
+      Builder configurationKey(BuildConfigurationValue.Key key);
+
+      Builder requiredToolchainTypeLabels(ImmutableSet<Label> requiredToolchainTypeLabels);
+
+      Builder requiredToolchainTypeLabels(Label... requiredToolchainTypeLabels);
+
+      Builder execConstraintLabels(ImmutableSet<Label> execConstraintLabels);
+
+      Builder execConstraintLabels(Label... execConstraintLabels);
+
+      Builder shouldSanityCheckConfiguration(boolean shouldSanityCheckConfiguration);
+
+      Key build();
+    }
+  }
+
+  public static Builder builder() {
+    return new AutoValue_UnloadedToolchainContext.Builder();
+  }
+
+  /** Builder class to help create the {@link UnloadedToolchainContext}. */
+  @AutoValue.Builder
+  public interface Builder {
+    /** Sets the selected execution platform that these toolchains use. */
+    Builder setExecutionPlatform(PlatformInfo executionPlatform);
+
+    /** Sets the target platform that these toolchains generate output for. */
+    Builder setTargetPlatform(PlatformInfo targetPlatform);
+
+    /** Sets the toolchain types that were requested. */
+    Builder setRequiredToolchainTypes(Set<ToolchainTypeInfo> requiredToolchainTypes);
+
+    Builder setToolchainTypeToResolved(
+        ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved);
+
+    UnloadedToolchainContext build();
+  }
+
+  /** The map of toolchain type to resolved toolchain to be used. */
+  // TODO(https://github.com/bazelbuild/bazel/issues/7935): Make this package-protected again.
+  public abstract ImmutableBiMap<ToolchainTypeInfo, Label> toolchainTypeToResolved();
+
+  @Override
+  public ImmutableSet<Label> resolvedToolchainLabels() {
+    return toolchainTypeToResolved().values();
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java
index ba4351b..863aa88 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContextTest.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.rules.platform.ToolchainTestCase;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
 import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java
index 6cd63c9..f50a399 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/ToolchainResolverTest.java
@@ -30,6 +30,7 @@
 import com.google.devtools.build.lib.skyframe.ConstraintValueLookupUtil.InvalidConstraintValueException;
 import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException;
 import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
 import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
 import com.google.devtools.build.skyframe.EvaluationResult;
 import com.google.devtools.build.skyframe.SkyFunction;
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 dcf0a3f..849b3502 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
@@ -43,7 +43,6 @@
 import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
 import com.google.devtools.build.lib.analysis.ToolchainResolver;
 import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
-import com.google.devtools.build.lib.analysis.UnloadedToolchainContext;
 import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
 import com.google.devtools.build.lib.analysis.WorkspaceStatusAction;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -83,6 +82,7 @@
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue;
 import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.util.OrderedSetMultimap;
 import com.google.devtools.build.skyframe.SkyKey;
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java
new file mode 100644
index 0000000..84d8777
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java
@@ -0,0 +1,423 @@
+// Copyright 2019 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.skyframe;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.skyframe.EvaluationResultSubjectFactory.assertThatEvaluationResult;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.rules.platform.ToolchainTestCase;
+import com.google.devtools.build.lib.skyframe.ConstraintValueLookupUtil.InvalidConstraintValueException;
+import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException;
+import com.google.devtools.build.lib.skyframe.ToolchainResolutionFunction.NoMatchingPlatformException;
+import com.google.devtools.build.lib.skyframe.ToolchainResolutionFunction.UnresolvedToolchainsException;
+import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
+import com.google.devtools.build.skyframe.EvaluationResult;
+import com.google.devtools.build.skyframe.SkyKey;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link UnloadedToolchainContext} and {@link ToolchainResolutionFunction}. */
+@RunWith(JUnit4.class)
+public class ToolchainResolutionFunctionTest extends ToolchainTestCase {
+
+  private EvaluationResult<UnloadedToolchainContext> invokeToolchainResolution(SkyKey key)
+      throws InterruptedException {
+    try {
+      getSkyframeExecutor().getSkyframeBuildView().enableAnalysis(true);
+      return SkyframeExecutorTestUtils.evaluate(
+          getSkyframeExecutor(), key, /*keepGoing=*/ false, reporter);
+    } finally {
+      getSkyframeExecutor().getSkyframeBuildView().enableAnalysis(false);
+    }
+  }
+
+  @Test
+  public void resolve() throws Exception {
+    // This should select platform mac, toolchain extra_toolchain_mac, because platform
+    // mac is listed first.
+    addToolchain(
+        "extra",
+        "extra_toolchain_linux",
+        ImmutableList.of("//constraints:linux"),
+        ImmutableList.of("//constraints:linux"),
+        "baz");
+    addToolchain(
+        "extra",
+        "extra_toolchain_mac",
+        ImmutableList.of("//constraints:mac"),
+        ImmutableList.of("//constraints:linux"),
+        "baz");
+    rewriteWorkspace(
+        "register_toolchains('//extra:extra_toolchain_linux', '//extra:extra_toolchain_mac')",
+        "register_execution_platforms('//platforms:mac', '//platforms:linux')");
+
+    useConfiguration("--platforms=//platforms:linux");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(testToolchainTypeLabel)
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasNoError();
+    UnloadedToolchainContext unloadedToolchainContext = result.get(key);
+    assertThat(unloadedToolchainContext).isNotNull();
+
+    assertThat(unloadedToolchainContext.requiredToolchainTypes())
+        .containsExactly(testToolchainType);
+    assertThat(unloadedToolchainContext.resolvedToolchainLabels())
+        .containsExactly(Label.parseAbsoluteUnchecked("//extra:extra_toolchain_mac_impl"));
+
+    assertThat(unloadedToolchainContext.executionPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.executionPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:mac"));
+
+    assertThat(unloadedToolchainContext.targetPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.targetPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux"));
+  }
+
+  @Test
+  public void resolve_noToolchainType() throws Exception {
+    scratch.file("host/BUILD", "platform(name = 'host')");
+    rewriteWorkspace("register_execution_platforms('//platforms:mac', '//platforms:linux')");
+
+    useConfiguration("--host_platform=//host:host", "--platforms=//platforms:linux");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key().configurationKey(targetConfigKey).build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasNoError();
+    UnloadedToolchainContext unloadedToolchainContext = result.get(key);
+    assertThat(unloadedToolchainContext).isNotNull();
+
+    assertThat(unloadedToolchainContext.requiredToolchainTypes()).isEmpty();
+
+    // With no toolchains requested, should fall back to the host platform.
+    assertThat(unloadedToolchainContext.executionPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.executionPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//host:host"));
+
+    assertThat(unloadedToolchainContext.targetPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.targetPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux"));
+  }
+
+  @Test
+  public void resolve_noToolchainType_hostNotAvailable() throws Exception {
+    scratch.file("host/BUILD", "platform(name = 'host')");
+    scratch.file(
+        "sample/BUILD",
+        "constraint_setting(name='demo')",
+        "constraint_value(name = 'demo_a', constraint_setting=':demo')",
+        "constraint_value(name = 'demo_b', constraint_setting=':demo')",
+        "platform(name = 'sample_a',",
+        "  constraint_values = [':demo_a'],",
+        ")",
+        "platform(name = 'sample_b',",
+        "  constraint_values = [':demo_b'],",
+        ")");
+    rewriteWorkspace(
+        "register_execution_platforms('//platforms:mac', '//platforms:linux',",
+        "    '//sample:sample_a', '//sample:sample_b')");
+
+    useConfiguration("--host_platform=//host:host", "--platforms=//platforms:linux");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .execConstraintLabels(Label.parseAbsoluteUnchecked("//sample:demo_b"))
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasNoError();
+    UnloadedToolchainContext unloadedToolchainContext = result.get(key);
+    assertThat(unloadedToolchainContext).isNotNull();
+
+    assertThat(unloadedToolchainContext.requiredToolchainTypes()).isEmpty();
+
+    assertThat(unloadedToolchainContext.executionPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.executionPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//sample:sample_b"));
+
+    assertThat(unloadedToolchainContext.targetPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.targetPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux"));
+  }
+
+  @Test
+  public void resolve_unavailableToolchainType_single() throws Exception {
+    useConfiguration("--host_platform=//platforms:linux", "--platforms=//platforms:mac");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(
+                testToolchainTypeLabel, Label.parseAbsoluteUnchecked("//fake/toolchain:type_1"))
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(UnresolvedToolchainsException.class);
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .hasMessageThat()
+        .contains("no matching toolchains found for types //fake/toolchain:type_1");
+  }
+
+  @Test
+  public void resolve_unavailableToolchainType_multiple() throws Exception {
+    useConfiguration("--host_platform=//platforms:linux", "--platforms=//platforms:mac");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(
+                testToolchainTypeLabel,
+                Label.parseAbsoluteUnchecked("//fake/toolchain:type_1"),
+                Label.parseAbsoluteUnchecked("//fake/toolchain:type_2"))
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(UnresolvedToolchainsException.class);
+    // Only one of the missing types will be reported, so do not check the specific error message.
+  }
+
+  @Test
+  public void resolve_invalidTargetPlatform_badTarget() throws Exception {
+    scratch.file("invalid/BUILD", "filegroup(name = 'not_a_platform')");
+    useConfiguration("--platforms=//invalid:not_a_platform");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(testToolchainTypeLabel)
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasError();
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(InvalidPlatformException.class);
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .hasMessageThat()
+        .contains(
+            "//invalid:not_a_platform was referenced as a platform, "
+                + "but does not provide PlatformInfo");
+  }
+
+  @Test
+  public void resolve_invalidTargetPlatform_badPackage() throws Exception {
+    scratch.resolve("invalid").delete();
+    useConfiguration("--platforms=//invalid:not_a_platform");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(testToolchainTypeLabel)
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasError();
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(InvalidPlatformException.class);
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .hasMessageThat()
+        .contains("BUILD file not found");
+  }
+
+  @Test
+  public void resolve_invalidHostPlatform() throws Exception {
+    scratch.file("invalid/BUILD", "filegroup(name = 'not_a_platform')");
+    useConfiguration("--host_platform=//invalid:not_a_platform");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(testToolchainTypeLabel)
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasError();
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(InvalidPlatformException.class);
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .hasMessageThat()
+        .contains("//invalid:not_a_platform");
+  }
+
+  @Test
+  public void resolve_invalidExecutionPlatform() throws Exception {
+    scratch.file("invalid/BUILD", "filegroup(name = 'not_a_platform')");
+    useConfiguration("--extra_execution_platforms=//invalid:not_a_platform");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(testToolchainTypeLabel)
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasError();
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(InvalidPlatformException.class);
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .hasMessageThat()
+        .contains("//invalid:not_a_platform");
+  }
+
+  @Test
+  public void resolve_execConstraints() throws Exception {
+    // This should select platform linux, toolchain extra_toolchain_linux, due to extra constraints,
+    // even though platform mac is registered first.
+    addToolchain(
+        /* packageName= */ "extra",
+        /* toolchainName= */ "extra_toolchain_linux",
+        /* execConstraints= */ ImmutableList.of("//constraints:linux"),
+        /* targetConstraints= */ ImmutableList.of("//constraints:linux"),
+        /* data= */ "baz");
+    addToolchain(
+        /* packageName= */ "extra",
+        /* toolchainName= */ "extra_toolchain_mac",
+        /* execConstraints= */ ImmutableList.of("//constraints:mac"),
+        /* targetConstraints= */ ImmutableList.of("//constraints:linux"),
+        /* data= */ "baz");
+    rewriteWorkspace(
+        "register_toolchains('//extra:extra_toolchain_linux', '//extra:extra_toolchain_mac')",
+        "register_execution_platforms('//platforms:mac', '//platforms:linux')");
+
+    useConfiguration("--platforms=//platforms:linux");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(testToolchainTypeLabel)
+            .execConstraintLabels(Label.parseAbsoluteUnchecked("//constraints:linux"))
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasNoError();
+    UnloadedToolchainContext unloadedToolchainContext = result.get(key);
+    assertThat(unloadedToolchainContext).isNotNull();
+
+    assertThat(unloadedToolchainContext.requiredToolchainTypes())
+        .containsExactly(testToolchainType);
+    assertThat(unloadedToolchainContext.resolvedToolchainLabels())
+        .containsExactly(Label.parseAbsoluteUnchecked("//extra:extra_toolchain_linux_impl"));
+
+    assertThat(unloadedToolchainContext.executionPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.executionPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux"));
+
+    assertThat(unloadedToolchainContext.targetPlatform()).isNotNull();
+    assertThat(unloadedToolchainContext.targetPlatform().label())
+        .isEqualTo(Label.parseAbsoluteUnchecked("//platforms:linux"));
+  }
+
+  @Test
+  public void resolve_execConstraints_invalid() throws Exception {
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(testToolchainTypeLabel)
+            .execConstraintLabels(Label.parseAbsoluteUnchecked("//platforms:linux"))
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+
+    assertThatEvaluationResult(result).hasError();
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(InvalidConstraintValueException.class);
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .hasMessageThat()
+        .contains("//platforms:linux");
+  }
+
+  @Test
+  public void resolve_noMatchingPlatform() throws Exception {
+    // Write toolchain A, and a toolchain implementing it.
+    scratch.appendFile(
+        "a/BUILD",
+        "toolchain_type(name = 'toolchain_type_A')",
+        "toolchain(",
+        "    name = 'toolchain',",
+        "    toolchain_type = ':toolchain_type_A',",
+        "    exec_compatible_with = ['//constraints:mac'],",
+        "    target_compatible_with = [],",
+        "    toolchain = ':toolchain_impl')",
+        "filegroup(name='toolchain_impl')");
+    // Write toolchain B, and a toolchain implementing it.
+    scratch.appendFile(
+        "b/BUILD",
+        "load('//toolchain:toolchain_def.bzl', 'test_toolchain')",
+        "toolchain_type(name = 'toolchain_type_B')",
+        "toolchain(",
+        "    name = 'toolchain',",
+        "    toolchain_type = ':toolchain_type_B',",
+        "    exec_compatible_with = ['//constraints:linux'],",
+        "    target_compatible_with = [],",
+        "    toolchain = ':toolchain_impl')",
+        "filegroup(name='toolchain_impl')");
+
+    rewriteWorkspace(
+        "register_toolchains('//a:toolchain', '//b:toolchain')",
+        "register_execution_platforms('//platforms:mac', '//platforms:linux')");
+
+    useConfiguration("--platforms=//platforms:linux");
+    UnloadedToolchainContext.Key key =
+        UnloadedToolchainContext.key()
+            .configurationKey(targetConfigKey)
+            .requiredToolchainTypeLabels(
+                Label.parseAbsoluteUnchecked("//a:toolchain_type_A"),
+                Label.parseAbsoluteUnchecked("//b:toolchain_type_B"))
+            .build();
+
+    EvaluationResult<UnloadedToolchainContext> result = invokeToolchainResolution(key);
+    assertThatEvaluationResult(result).hasError();
+    assertThatEvaluationResult(result)
+        .hasErrorEntryForKeyThat(key)
+        .hasExceptionThat()
+        .isInstanceOf(NoMatchingPlatformException.class);
+  }
+}