Memoize computation of unloaded toolchain contexts and config conditions.

PiperOrigin-RevId: 526430621
Change-Id: Ia2dd492e5a80488d55a4cc5646c8407b6c76d326
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index d99d23e..54d398d 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -9,6 +9,7 @@
     name = "srcs",
     srcs = glob(["**"]) + [
         "//src/main/java/com/google/devtools/build/lib/analysis/platform:srcs",
+        "//src/main/java/com/google/devtools/build/lib/analysis/producers:srcs",
         "//src/main/java/com/google/devtools/build/lib/analysis/starlark/annotations:srcs",
         "//src/main/java/com/google/devtools/build/lib/analysis/stringtemplate:srcs",
     ],
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/IncompatibleTargetChecker.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/IncompatibleTargetChecker.java
index a740460..4030580 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/IncompatibleTargetChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/IncompatibleTargetChecker.java
@@ -111,12 +111,14 @@
 
     private final ResultSink sink;
 
+    private final StateMachine runAfter;
+
     private final ImmutableList.Builder<ConstraintValueInfo> invalidConstraintValuesBuilder =
         new ImmutableList.Builder<>();
 
     /** Sink for the output of this state machine. */
     public interface ResultSink {
-      void accept(Optional<RuleConfiguredTargetValue> incompatibleTarget);
+      void acceptIncompatibleTarget(Optional<RuleConfiguredTargetValue> incompatibleTarget);
 
       void acceptValidationException(ValidationException e);
     }
@@ -127,29 +129,31 @@
         ConfigConditions configConditions,
         @Nullable PlatformInfo platformInfo,
         @Nullable NestedSetBuilder<Package> transitivePackages,
-        ResultSink sink) {
+        ResultSink sink,
+        StateMachine runAfter) {
       this.target = target;
       this.configuration = configuration;
       this.configConditions = configConditions;
       this.platformInfo = platformInfo;
       this.transitivePackages = transitivePackages;
       this.sink = sink;
+      this.runAfter = runAfter;
     }
 
     @Override
     public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
       Rule rule = target.getAssociatedRule();
       if (rule == null || !rule.useToolchainResolution() || platformInfo == null) {
-        sink.accept(Optional.empty());
-        return DONE;
+        sink.acceptIncompatibleTarget(Optional.empty());
+        return runAfter;
       }
 
       // Retrieves the label list for the target_compatible_with attribute.
       ConfiguredAttributeMapper attrs =
           ConfiguredAttributeMapper.of(rule, configConditions.asProviders(), configuration);
       if (!attrs.has("target_compatible_with", BuildType.LABEL_LIST)) {
-        sink.accept(Optional.empty());
-        return DONE;
+        sink.acceptIncompatibleTarget(Optional.empty());
+        return runAfter;
       }
 
       // Resolves the constraint labels, checking for invalid configured attributes.
@@ -158,7 +162,7 @@
         targetCompatibleWith = attrs.getAndValidate("target_compatible_with", BuildType.LABEL_LIST);
       } catch (ValidationException e) {
         sink.acceptValidationException(e);
-        return DONE;
+        return runAfter;
       }
       for (Label label : targetCompatibleWith) {
         tasks.lookUp(
@@ -180,21 +184,21 @@
 
     private StateMachine processResult(Tasks tasks, ExtendedEventHandler listener) {
       var invalidConstraintValues = invalidConstraintValuesBuilder.build();
-      if (invalidConstraintValues.isEmpty()) {
-        sink.accept(Optional.empty());
-        return DONE;
+      if (!invalidConstraintValues.isEmpty()) {
+        sink.acceptIncompatibleTarget(
+            Optional.of(
+                createIncompatibleRuleConfiguredTarget(
+                    target,
+                    configuration,
+                    configConditions,
+                    IncompatiblePlatformProvider.incompatibleDueToConstraints(
+                        platformInfo.label(), invalidConstraintValues),
+                    target.getAssociatedRule().getRuleClass(),
+                    transitivePackages)));
+        return runAfter;
       }
-      sink.accept(
-          Optional.of(
-              createIncompatibleRuleConfiguredTarget(
-                  target,
-                  configuration,
-                  configConditions,
-                  IncompatiblePlatformProvider.incompatibleDueToConstraints(
-                      platformInfo.label(), invalidConstraintValues),
-                  target.getAssociatedRule().getRuleClass(),
-                  transitivePackages)));
-      return DONE;
+      sink.acceptIncompatibleTarget(Optional.empty());
+      return runAfter;
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/producers/BUILD
new file mode 100644
index 0000000..8eb4648
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/BUILD
@@ -0,0 +1,54 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//src:__subpackages__"],
+)
+
+filegroup(
+    name = "srcs",
+    srcs = glob(["**"]),
+    visibility = ["//src:__subpackages__"],
+)
+
+java_library(
+    name = "producers",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib/analysis:config/build_configuration",
+        "//src/main/java/com/google/devtools/build/lib/analysis:config/config_conditions",
+        "//src/main/java/com/google/devtools/build/lib/analysis:config/config_matching_provider",
+        "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
+        "//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
+        "//src/main/java/com/google/devtools/build/lib/analysis:constraints/incompatible_target_checker",
+        "//src/main/java/com/google/devtools/build/lib/analysis:exec_group_collection",
+        "//src/main/java/com/google/devtools/build/lib/analysis:platform_configuration",
+        "//src/main/java/com/google/devtools/build/lib/analysis:target_and_configuration",
+        "//src/main/java/com/google/devtools/build/lib/analysis:toolchain_collection",
+        "//src/main/java/com/google/devtools/build/lib/analysis/platform",
+        "//src/main/java/com/google/devtools/build/lib/analysis/platform:utils",
+        "//src/main/java/com/google/devtools/build/lib/causes",
+        "//src/main/java/com/google/devtools/build/lib/cmdline",
+        "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
+        "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/packages",
+        "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
+        "//src/main/java/com/google/devtools/build/lib/packages:exec_group",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:configured_value_creation_exception",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:no_matching_platform_exception",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:package_value",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:platform_lookup_util",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:rule_configured_target_value",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:toolchain_context_key",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:toolchain_exception",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:unloaded_toolchain_context",
+        "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
+        "//src/main/java/com/google/devtools/build/skyframe",
+        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+        "//third_party:auto_value",
+        "//third_party:guava",
+        "//third_party:jsr305",
+    ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfigConditionsProducer.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfigConditionsProducer.java
new file mode 100644
index 0000000..bb9c7db
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfigConditionsProducer.java
@@ -0,0 +1,188 @@
+// Copyright 2023 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.producers;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
+import com.google.devtools.build.lib.analysis.config.ConfigConditions;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
+import com.google.devtools.build.lib.util.DetailedExitCode;
+import com.google.devtools.build.lib.util.DetailedExitCode.DetailedExitCodeComparator;
+import com.google.devtools.build.skyframe.state.StateMachine;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Computes the targets that key the configurable attributes used by this rule. */
+final class ConfigConditionsProducer
+    implements StateMachine, ConfiguredTargetAndDataProducer.ResultSink {
+  interface ResultSink {
+    void acceptConfigConditions(ConfigConditions configConditions);
+
+    void acceptConfigConditionsError(ConfiguredValueCreationException error);
+  }
+
+  // -------------------- Input --------------------
+  private final TargetAndConfiguration targetAndConfiguration;
+  @Nullable private final PlatformInfo targetPlatformInfo;
+  private final TransitiveDependencyState transitiveState;
+
+  // -------------------- Output --------------------
+  private final ResultSink sink;
+
+  // -------------------- Sequencing --------------------
+  private final StateMachine runAfter;
+
+  // -------------------- Internal State --------------------
+  @Nullable // Null if there are no config labels.
+  private final List<Label> configLabels;
+  @Nullable // Null if there are no config labels.
+  private final ConfiguredTargetAndData[] prerequisites;
+  @Nullable // Null if there are no dependency errors.
+  private DetailedExitCode mostImportantExitCode;
+
+  ConfigConditionsProducer(
+      TargetAndConfiguration targetAndConfiguration,
+      @Nullable PlatformInfo targetPlatformInfo,
+      TransitiveDependencyState transitiveState,
+      ResultSink sink,
+      StateMachine runAfter) {
+    this.targetAndConfiguration = targetAndConfiguration;
+    this.targetPlatformInfo = targetPlatformInfo;
+    this.transitiveState = transitiveState;
+    this.sink = sink;
+    this.runAfter = runAfter;
+
+    this.configLabels = computeConfigLabels(targetAndConfiguration.getTarget());
+    this.prerequisites =
+        configLabels == null ? null : new ConfiguredTargetAndData[configLabels.size()];
+  }
+
+  @Override
+  public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
+    if (configLabels == null) {
+      sink.acceptConfigConditions(ConfigConditions.EMPTY);
+      return runAfter;
+    }
+
+    // Collect the actual deps without a configuration transition (since by definition config
+    // conditions evaluate over the current target's configuration). If the dependency is
+    // (erroneously) something that needs the null configuration, its analysis will be
+    // short-circuited. That error will be reported later.
+    for (int i = 0; i < configLabels.size(); ++i) {
+      tasks.enqueue(
+          new ConfiguredTargetAndDataProducer(
+              ConfiguredTargetKey.builder()
+                  .setLabel(configLabels.get(i))
+                  .setConfiguration(targetAndConfiguration.getConfiguration())
+                  .build(),
+              /* transitionKey= */ null,
+              transitiveState,
+              (ConfiguredTargetAndDataProducer.ResultSink) this,
+              i));
+    }
+    return this::constructConfigConditions;
+  }
+
+  @Override
+  public void acceptConfiguredTargetAndData(ConfiguredTargetAndData value, int index) {
+    prerequisites[index] = value;
+  }
+
+  @Override
+  public void acceptConfiguredTargetAndDataError(ConfiguredValueCreationException error) {
+    DetailedExitCode newExitCode = error.getDetailedExitCode();
+    mostImportantExitCode =
+        DetailedExitCodeComparator.chooseMoreImportantWithFirstIfTie(
+            newExitCode, mostImportantExitCode);
+    if (newExitCode.equals(mostImportantExitCode)) {
+      sink.acceptConfigConditionsError(
+          // The precise error is reported by the dependency that failed to load.
+          // TODO(gregce): beautify this error: https://github.com/bazelbuild/bazel/issues/11984.
+          new ConfiguredValueCreationException(
+              targetAndConfiguration,
+              "errors encountered resolving select() keys for "
+                  + targetAndConfiguration.getLabel()));
+    }
+  }
+
+  private StateMachine constructConfigConditions(Tasks tasks, ExtendedEventHandler listener) {
+    if (mostImportantExitCode != null) {
+      return runAfter; // There was a previous error.
+    }
+
+    var asConfiguredTargets = new ImmutableMap.Builder<Label, ConfiguredTargetAndData>();
+    var asConfigConditions = new ImmutableMap.Builder<Label, ConfigMatchingProvider>();
+    for (int i = 0; i < configLabels.size(); ++i) {
+      var label = configLabels.get(i);
+      var prerequisite = prerequisites[i];
+      asConfiguredTargets.put(label, prerequisite);
+      try {
+        asConfigConditions.put(
+            label, ConfigConditions.fromConfiguredTarget(prerequisite, targetPlatformInfo));
+      } catch (ConfigConditions.InvalidConditionException e) {
+        var targetLabel = targetAndConfiguration.getLabel();
+        String message =
+            String.format(
+                    "%s is not a valid select() condition for %s.\n",
+                    prerequisite.getTargetLabel(), targetLabel)
+                + String.format(
+                    "To inspect the select(), run: bazel query --output=build %s.\n", targetLabel)
+                + "For more help, see https://bazel.build/reference/be/functions#select.\n\n";
+        sink.acceptConfigConditionsError(
+            new ConfiguredValueCreationException(targetAndConfiguration, message));
+        return runAfter;
+      }
+    }
+    sink.acceptConfigConditions(
+        ConfigConditions.create(
+            asConfiguredTargets.buildOrThrow(), asConfigConditions.buildOrThrow()));
+    return runAfter;
+  }
+
+  /**
+   * Computes the config labels belonging to the given target.
+   *
+   * @return null if there were no config labels, implying a {@link ConfigConditions#EMPTY} result.
+   */
+  @Nullable
+  private static List<Label> computeConfigLabels(Target target) {
+    if (!(target instanceof Rule)) {
+      return null;
+    }
+
+    var attrs = RawAttributeMapper.of(((Rule) target));
+    if (!attrs.has(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE)) {
+      return null;
+    }
+
+    // Collects the labels of the configured targets we need to resolve.
+    List<Label> configLabels =
+        attrs.get(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE, BuildType.LABEL_LIST);
+    if (configLabels.isEmpty()) {
+      return null;
+    }
+    return configLabels;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfiguredTargetAndDataProducer.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfiguredTargetAndDataProducer.java
new file mode 100644
index 0000000..e30a346
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfiguredTargetAndDataProducer.java
@@ -0,0 +1,164 @@
+// Copyright 2023 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.producers;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
+import com.google.devtools.build.lib.skyframe.PackageValue;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.state.StateMachine;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+
+/**
+ * Determines {@link ConfiguredTargetAndData} from {@link ConfiguredTargetKey}.
+ *
+ * <p>The resulting package and configuration are based on the resulting {@link ConfiguredTarget}
+ * and may be different from what is in the key, for example, if there is an alias.
+ */
+final class ConfiguredTargetAndDataProducer
+    implements StateMachine,
+        Consumer<SkyValue>,
+        StateMachine.ValueOrExceptionSink<ConfiguredValueCreationException> {
+  /** Interface for accepting values produced by this class. */
+  interface ResultSink {
+    void acceptConfiguredTargetAndData(ConfiguredTargetAndData value, int index);
+
+    void acceptConfiguredTargetAndDataError(ConfiguredValueCreationException error);
+  }
+
+  // -------------------- Input --------------------
+  private final ConfiguredTargetKey key;
+  @Nullable // Null if no transition key is needed (patch transition or no-op split transition).
+  private final String transitionKey;
+  private final TransitiveDependencyState transitiveState;
+
+  // -------------------- Output --------------------
+  private final ResultSink sink;
+  private final int outputIndex;
+
+  // -------------------- Internal State --------------------
+  private ConfiguredTarget configuredTarget;
+  @Nullable // Null if the configured target key's configuration key is null.
+  private BuildConfigurationValue configurationValue;
+  private Package pkg;
+
+  public ConfiguredTargetAndDataProducer(
+      ConfiguredTargetKey key,
+      @Nullable String transitionKey,
+      TransitiveDependencyState transitiveState,
+      ResultSink sink,
+      int outputIndex) {
+    this.key = key;
+    this.transitionKey = transitionKey;
+    this.transitiveState = transitiveState;
+    this.sink = sink;
+    this.outputIndex = outputIndex;
+  }
+
+  @Override
+  public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
+    tasks.lookUp(
+        key,
+        ConfiguredValueCreationException.class,
+        (ValueOrExceptionSink<ConfiguredValueCreationException>) this);
+    return this::fetchConfigurationAndPackage;
+  }
+
+  @Override
+  public void acceptValueOrException(
+      @Nullable SkyValue value, @Nullable ConfiguredValueCreationException error) {
+    if (value != null) {
+      var configuredTargetValue = (ConfiguredTargetValue) value;
+      this.configuredTarget = configuredTargetValue.getConfiguredTarget();
+      transitiveState.updateTransitivePackages(configuredTargetValue);
+      return;
+    }
+    if (error != null) {
+      transitiveState.addTransitiveCauses(error.getRootCauses());
+      sink.acceptConfiguredTargetAndDataError(error);
+      return;
+    }
+    throw new IllegalArgumentException("both value and error were null");
+  }
+
+  private StateMachine fetchConfigurationAndPackage(Tasks tasks, ExtendedEventHandler listener) {
+    if (configuredTarget == null) {
+      return DONE; // There was a previous error.
+    }
+
+    var configurationKey = configuredTarget.getConfigurationKey();
+    if (configurationKey != null) {
+      tasks.lookUp(configurationKey, (Consumer<SkyValue>) this);
+    }
+
+    // An alternative to this is to optimistically fetch the package using the label of the
+    // configured target key. However, the actual package may differ when this is an
+    // AliasConfiguredTarget and would need to be refetched.
+
+    // TODO(shahan): This lookup should be skipped when the ConfiguredTarget is fetched remotely.
+    var packageId = configuredTarget.getLabel().getPackageIdentifier();
+    this.pkg = transitiveState.getDependencyPackage(packageId);
+    if (pkg == null) {
+      // In incremental builds, it is possible that the package won't be present in the cache. For
+      // example, suppose that a configured target A has two children B and C. If B is dirty, it
+      // causes A's re-evaluation, which causes this fetch to be performed for C. However, C has not
+      // been evaluated this build.
+      tasks.lookUp(PackageValue.key(packageId), (Consumer<SkyValue>) this);
+    }
+
+    return this::constructResult;
+  }
+
+  @Override
+  public void accept(SkyValue value) {
+    if (value instanceof BuildConfigurationValue) {
+      this.configurationValue = (BuildConfigurationValue) value;
+      return;
+    }
+    if (value instanceof PackageValue) {
+      this.pkg = ((PackageValue) value).getPackage();
+      return;
+    }
+    throw new IllegalArgumentException("unexpected value: " + value);
+  }
+
+  private StateMachine constructResult(Tasks tasks, ExtendedEventHandler listener) {
+    Target target;
+    try {
+      target = pkg.getTarget(configuredTarget.getLabel().getName());
+    } catch (NoSuchTargetException e) {
+      // The package was fetched based on the label of the configured target. Since the configured
+      // target exists, it must have existed in the package when it was created.
+      throw new IllegalStateException("Target already verified for " + configuredTarget, e);
+    }
+    sink.acceptConfiguredTargetAndData(
+        new ConfiguredTargetAndData(
+            configuredTarget,
+            target,
+            configurationValue,
+            transitionKey == null ? ImmutableList.of() : ImmutableList.of(transitionKey)),
+        outputIndex);
+    return DONE;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContext.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContext.java
new file mode 100644
index 0000000..c9ad0f2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContext.java
@@ -0,0 +1,39 @@
+// Copyright 2023 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.producers;
+
+import com.google.auto.value.AutoValue;
+import com.google.devtools.build.lib.analysis.ToolchainCollection;
+import com.google.devtools.build.lib.analysis.config.ConfigConditions;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
+import javax.annotation.Nullable;
+
+/**
+ * Groups together unloaded toolchain contexts and config conditions.
+ *
+ * <p>These are used together when computing dependencies.
+ */
+@AutoValue
+public abstract class DependencyContext {
+  @Nullable
+  public abstract ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts();
+
+  public abstract ConfigConditions configConditions();
+
+  static DependencyContext create(
+      @Nullable ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts,
+      ConfigConditions configConditions) {
+    return new AutoValue_DependencyContext(unloadedToolchainContexts, configConditions);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextError.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextError.java
new file mode 100644
index 0000000..13c6d3c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextError.java
@@ -0,0 +1,60 @@
+// Copyright 2023 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.producers;
+
+import com.google.auto.value.AutoOneOf;
+import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetException;
+import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper.ValidationException;
+import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
+import com.google.devtools.build.lib.skyframe.ToolchainException;
+
+/** Tagged union of errors that can be encountered when creating the {@link DependencyContext}. */
+@AutoOneOf(DependencyContextError.Kind.class)
+public abstract class DependencyContextError {
+  /** Tags for errors types that may occur. */
+  public enum Kind {
+    TOOLCHAIN,
+    CONFIGURED_VALUE_CREATION,
+    INCOMPATIBLE_TARGET,
+    VALIDATION
+  }
+
+  public abstract Kind kind();
+
+  public abstract ToolchainException toolchain();
+
+  public abstract ConfiguredValueCreationException configuredValueCreation();
+
+  /** This error is only possible for {@link DependencyContextProducerWithCompatibilityCheck}. */
+  public abstract IncompatibleTargetException incompatibleTarget();
+
+  /** This error is only possible for {@link DependencyContextProducerWithCompatibilityCheck}. */
+  public abstract ValidationException validation();
+
+  static DependencyContextError of(ToolchainException error) {
+    return AutoOneOf_DependencyContextError.toolchain(error);
+  }
+
+  static DependencyContextError of(ConfiguredValueCreationException error) {
+    return AutoOneOf_DependencyContextError.configuredValueCreation(error);
+  }
+
+  static DependencyContextError of(IncompatibleTargetException error) {
+    return AutoOneOf_DependencyContextError.incompatibleTarget(error);
+  }
+
+  static DependencyContextError of(ValidationException error) {
+    return AutoOneOf_DependencyContextError.validation(error);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextProducer.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextProducer.java
new file mode 100644
index 0000000..06b95fa
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextProducer.java
@@ -0,0 +1,125 @@
+// Copyright 2023 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.producers;
+
+import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
+import com.google.devtools.build.lib.analysis.ToolchainCollection;
+import com.google.devtools.build.lib.analysis.config.ConfigConditions;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
+import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
+import com.google.devtools.build.skyframe.state.StateMachine;
+import javax.annotation.Nullable;
+
+/**
+ * This class computes the unloaded toolchain context and {@link ConfigConditions}.
+ *
+ * <p>It uses {@link PlatformInfo} derived from the unloaded toolchain contexts to compute config
+ * conditions, creating a sequential dependency between the two.
+ */
+// TODO(b/278878321): unify this and DependencyContextProducerWithCompatibilityCheck.
+public final class DependencyContextProducer
+    implements StateMachine,
+        UnloadedToolchainContextsProducer.ResultSink,
+        ConfigConditionsProducer.ResultSink {
+  /**
+   * Accepts results for both {@link DependencyContextProducer} and {@link
+   * DependencyContextProducerWithCompatibilityCheck}.
+   */
+  public interface ResultSink {
+    void acceptDependencyContext(DependencyContext value);
+
+    void acceptDependencyContextError(DependencyContextError error);
+  }
+
+  // -------------------- Input --------------------
+  private final UnloadedToolchainContextsInputs unloadedToolchainContextsInputs;
+  private final TargetAndConfiguration targetAndConfiguration;
+  private final TransitiveDependencyState transitiveState;
+
+  // -------------------- Output --------------------
+  private final ResultSink sink;
+
+  // -------------------- Internal State --------------------
+  @Nullable // Will be null if the target doesn't require toolchain resolution.
+  private ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts;
+  private ConfigConditions configConditions;
+  boolean hasError = false;
+
+  public DependencyContextProducer(
+      UnloadedToolchainContextsInputs unloadedToolchainContextsInputs,
+      TargetAndConfiguration targetAndConfiguration,
+      TransitiveDependencyState transitiveState,
+      ResultSink sink) {
+    this.unloadedToolchainContextsInputs = unloadedToolchainContextsInputs;
+    this.targetAndConfiguration = targetAndConfiguration;
+    this.transitiveState = transitiveState;
+    this.sink = sink;
+  }
+
+  @Override
+  public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
+    return new UnloadedToolchainContextsProducer(
+        unloadedToolchainContextsInputs,
+        (UnloadedToolchainContextsProducer.ResultSink) this,
+        /* runAfter= */ this::computeConfigConditions);
+  }
+
+  @Override
+  public void acceptUnloadedToolchainContexts(
+      @Nullable ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts) {
+    this.unloadedToolchainContexts = unloadedToolchainContexts;
+  }
+
+  @Override
+  public void acceptUnloadedToolchainContextsError(ToolchainException error) {
+    this.hasError = true;
+    sink.acceptDependencyContextError(DependencyContextError.of(error));
+  }
+
+  private StateMachine computeConfigConditions(Tasks tasks, ExtendedEventHandler listener) {
+    if (hasError) {
+      return DONE;
+    }
+
+    return new ConfigConditionsProducer(
+        targetAndConfiguration,
+        unloadedToolchainContexts == null ? null : unloadedToolchainContexts.getTargetPlatform(),
+        transitiveState,
+        (ConfigConditionsProducer.ResultSink) this,
+        /* runAfter= */ this::constructResult);
+  }
+
+  @Override
+  public void acceptConfigConditions(ConfigConditions configConditions) {
+    this.configConditions = configConditions;
+  }
+
+  @Override
+  public void acceptConfigConditionsError(ConfiguredValueCreationException error) {
+    this.hasError = true;
+    sink.acceptDependencyContextError(DependencyContextError.of(error));
+  }
+
+  private StateMachine constructResult(Tasks tasks, ExtendedEventHandler listener) {
+    if (hasError) {
+      return DONE;
+    }
+
+    sink.acceptDependencyContext(
+        DependencyContext.create(unloadedToolchainContexts, configConditions));
+    return DONE;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextProducerWithCompatibilityCheck.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextProducerWithCompatibilityCheck.java
new file mode 100644
index 0000000..ce3ae6e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/DependencyContextProducerWithCompatibilityCheck.java
@@ -0,0 +1,202 @@
+// Copyright 2023 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.producers;
+
+import com.google.devtools.build.lib.analysis.PlatformConfiguration;
+import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
+import com.google.devtools.build.lib.analysis.ToolchainCollection;
+import com.google.devtools.build.lib.analysis.config.ConfigConditions;
+import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetException;
+import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetProducer;
+import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper.ValidationException;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
+import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException;
+import com.google.devtools.build.lib.skyframe.RuleConfiguredTargetValue;
+import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
+import com.google.devtools.build.skyframe.state.StateMachine;
+import java.util.Optional;
+import javax.annotation.Nullable;
+
+/**
+ * Computes the {@link DependencyContext} while checking for platform compatibility.
+ *
+ * <p>See <a href="https://bazel.build/extending/platforms#skipping-incompatible-targets">Skipping
+ * Incompatible Targets</a> for more details on platform compatibility.
+ */
+// TODO(b/278878321): unify this and DependencyContextProducer.
+public final class DependencyContextProducerWithCompatibilityCheck
+    implements StateMachine,
+        PlatformInfoProducer.ResultSink,
+        ConfigConditionsProducer.ResultSink,
+        IncompatibleTargetProducer.ResultSink,
+        UnloadedToolchainContextsProducer.ResultSink {
+  // -------------------- Input --------------------
+  private final TargetAndConfiguration targetAndConfiguration;
+  private final UnloadedToolchainContextsInputs unloadedToolchainContextsInputs;
+
+  private final TransitiveDependencyState transitiveState;
+
+  // -------------------- Output --------------------
+  private final DependencyContextProducer.ResultSink sink;
+
+  // -------------------- Internal State --------------------
+  private PlatformInfo targetPlatformInfo;
+  private ConfigConditions configConditions;
+  @Nullable // Will be null if the target doesn't require toolchain resolution.
+  private ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts;
+  private boolean hasError = false;
+
+  public DependencyContextProducerWithCompatibilityCheck(
+      TargetAndConfiguration targetAndConfiguration,
+      UnloadedToolchainContextsInputs unloadedToolchainContextsInputs,
+      TransitiveDependencyState transitiveState,
+      DependencyContextProducer.ResultSink sink) {
+    this.targetAndConfiguration = targetAndConfiguration;
+    this.unloadedToolchainContextsInputs = unloadedToolchainContextsInputs;
+    this.transitiveState = transitiveState;
+    this.sink = sink;
+  }
+
+  @Override
+  public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
+    var defaultToolchainContextKey = unloadedToolchainContextsInputs.targetToolchainContextKey();
+    if (defaultToolchainContextKey == null) {
+      // If `defaultToolchainContextKey` is null, there's no platform info, incompatibility check
+      // or toolchain resolution. Short-circuits and computes only the ConfigConditions.
+      return new ConfigConditionsProducer(
+          targetAndConfiguration,
+          /* targetPlatformInfo= */ null,
+          transitiveState,
+          (ConfigConditionsProducer.ResultSink) this,
+          /* runAfter= */ this::constructResult);
+    }
+
+    // Non-null `defaultToolchainContextKey` guarantees that `platformConfiguration` is non-null.
+    var platformConfiguration =
+        targetAndConfiguration.getConfiguration().getFragment(PlatformConfiguration.class);
+    // Checks for incompatibility before toolchain resolution so that known missing
+    // toolchains mark the target incompatible instead of failing the build.
+    return new PlatformInfoProducer(
+        ConfiguredTargetKey.builder()
+            .setLabel(platformConfiguration.getTargetPlatform())
+            .setConfigurationKey(defaultToolchainContextKey.configurationKey())
+            .build(),
+        (PlatformInfoProducer.ResultSink) this,
+        /* runAfter= */ this::computeConfigConditions);
+  }
+
+  @Override
+  public void acceptPlatformInfo(PlatformInfo info) {
+    this.targetPlatformInfo = info;
+  }
+
+  @Override
+  public void acceptPlatformInfoError(InvalidPlatformException error) {
+    this.hasError = true;
+    sink.acceptDependencyContextError(DependencyContextError.of(error));
+  }
+
+  private StateMachine computeConfigConditions(Tasks tasks, ExtendedEventHandler listener) {
+    if (hasError) {
+      return DONE;
+    }
+
+    return new ConfigConditionsProducer(
+        targetAndConfiguration,
+        targetPlatformInfo,
+        transitiveState,
+        (ConfigConditionsProducer.ResultSink) this,
+        /* runAfter= */ this::checkCompatibility);
+  }
+
+  // -------------------- ConfigConditionsProducer.ResultSink --------------------
+  @Override
+  public void acceptConfigConditions(ConfigConditions configConditions) {
+    this.configConditions = configConditions;
+  }
+
+  @Override
+  public void acceptConfigConditionsError(ConfiguredValueCreationException error) {
+    this.hasError = true;
+    sink.acceptDependencyContextError(DependencyContextError.of(error));
+  }
+
+  private StateMachine checkCompatibility(Tasks tasks, ExtendedEventHandler listener) {
+    if (hasError) {
+      return DONE;
+    }
+
+    return new IncompatibleTargetProducer(
+        targetAndConfiguration.getTarget(),
+        targetAndConfiguration.getConfiguration(),
+        configConditions,
+        targetPlatformInfo,
+        transitiveState.transitivePackages(),
+        (IncompatibleTargetProducer.ResultSink) this,
+        /* runAfter= */ this::computeUnloadedToolchainContexts);
+  }
+
+  @Override
+  public void acceptIncompatibleTarget(Optional<RuleConfiguredTargetValue> incompatibleTarget) {
+    if (!incompatibleTarget.isEmpty()) {
+      this.hasError = true;
+      sink.acceptDependencyContextError(
+          DependencyContextError.of(new IncompatibleTargetException(incompatibleTarget.get())));
+    }
+  }
+
+  @Override
+  public void acceptValidationException(ValidationException e) {
+    this.hasError = true;
+    sink.acceptDependencyContextError(DependencyContextError.of(e));
+  }
+
+  private StateMachine computeUnloadedToolchainContexts(
+      Tasks tasks, ExtendedEventHandler listener) {
+    if (hasError) {
+      return DONE;
+    }
+
+    return new UnloadedToolchainContextsProducer(
+        unloadedToolchainContextsInputs,
+        (UnloadedToolchainContextsProducer.ResultSink) this,
+        /* runAfter= */ this::constructResult);
+  }
+
+  @Override
+  public void acceptUnloadedToolchainContexts(
+      @Nullable ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts) {
+    this.unloadedToolchainContexts = unloadedToolchainContexts;
+  }
+
+  @Override
+  public void acceptUnloadedToolchainContextsError(ToolchainException error) {
+    this.hasError = true;
+    sink.acceptDependencyContextError(DependencyContextError.of(error));
+  }
+
+  private StateMachine constructResult(Tasks tasks, ExtendedEventHandler listener) {
+    if (hasError) {
+      return DONE;
+    }
+
+    sink.acceptDependencyContext(
+        DependencyContext.create(unloadedToolchainContexts, configConditions));
+    return DONE;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/PlatformInfoProducer.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/PlatformInfoProducer.java
new file mode 100644
index 0000000..e3179bf
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/PlatformInfoProducer.java
@@ -0,0 +1,143 @@
+// Copyright 2023 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.producers;
+
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
+import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
+import com.google.devtools.build.lib.analysis.platform.PlatformProviderUtils;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
+import com.google.devtools.build.lib.skyframe.PackageValue;
+import com.google.devtools.build.lib.skyframe.PlatformLookupUtil;
+import com.google.devtools.build.lib.skyframe.PlatformLookupUtil.InvalidPlatformException;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.state.StateMachine;
+import javax.annotation.Nullable;
+
+/**
+ * Retrieves {@link PlatformInfo} for a given platform key.
+ *
+ * <p>This creates an explicit dependency on the {@link Package} to retrieve the associated target,
+ * so it is possible to verify that {@link PlatformInfo} is an advertised provider before
+ * constructing the {@link ConfiguredTarget}.
+ */
+final class PlatformInfoProducer
+    implements StateMachine, StateMachine.ValueOrExceptionSink<NoSuchPackageException> {
+  interface ResultSink {
+    void acceptPlatformInfo(PlatformInfo info);
+
+    void acceptPlatformInfoError(InvalidPlatformException error);
+  }
+
+  // -------------------- Input --------------------
+  private final ConfiguredTargetKey platformKey;
+
+  // -------------------- Output --------------------
+  private final ResultSink sink;
+
+  // -------------------- Sequencing --------------------
+  private final StateMachine runAfter;
+
+  // -------------------- Internal State --------------------
+  private boolean passedValidation = false;
+  private ConfiguredTarget platform;
+
+  PlatformInfoProducer(ConfiguredTargetKey platformKey, ResultSink sink, StateMachine runAfter) {
+    this.platformKey = platformKey;
+    this.sink = sink;
+    this.runAfter = runAfter;
+  }
+
+  @Override
+  public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
+    // Loads the Package first to verify the Target. The ConfiguredTarget should not be loaded
+    // until after verification. See https://github.com/bazelbuild/bazel/pull/10307.
+    //
+    // In distributed analysis, these packages will be duplicated across shards.
+    tasks.lookUp(
+        PackageValue.key(platformKey.getLabel().getPackageIdentifier()),
+        NoSuchPackageException.class,
+        (StateMachine.ValueOrExceptionSink<NoSuchPackageException>) this);
+    return this::lookupPlatform;
+  }
+
+  @Override
+  public void acceptValueOrException(
+      @Nullable SkyValue value, @Nullable NoSuchPackageException error) {
+    if (value != null) {
+      var pkg = ((PackageValue) value).getPackage();
+      try {
+        var label = platformKey.getLabel();
+        var target = pkg.getTarget(label.getName());
+        if (!PlatformLookupUtil.hasPlatformInfo(target)) {
+          sink.acceptPlatformInfoError(new InvalidPlatformException(label)); // validation failure
+          return;
+        }
+      } catch (NoSuchTargetException e) {
+        sink.acceptPlatformInfoError(new InvalidPlatformException(e));
+        return;
+      }
+      passedValidation = true;
+      return;
+    }
+    if (error != null) {
+      sink.acceptPlatformInfoError(new InvalidPlatformException(error));
+      return;
+    }
+    throw new IllegalArgumentException("both value and error were null");
+  }
+
+  private StateMachine lookupPlatform(Tasks tasks, ExtendedEventHandler listener) {
+    if (!passedValidation) {
+      return runAfter;
+    }
+
+    tasks.lookUp(
+        platformKey, ConfiguredValueCreationException.class, this::acceptPlatformValueOrError);
+    return this::retrievePlatformInfo;
+  }
+
+  private void acceptPlatformValueOrError(
+      @Nullable SkyValue value, @Nullable ConfiguredValueCreationException error) {
+    if (value != null) {
+      var configuredTargetValue = (ConfiguredTargetValue) value;
+      this.platform = configuredTargetValue.getConfiguredTarget();
+      return;
+    }
+    if (error != null) {
+      sink.acceptPlatformInfoError(new InvalidPlatformException(platformKey.getLabel(), error));
+      return;
+    }
+    throw new IllegalArgumentException("both value and error were null");
+  }
+
+  private StateMachine retrievePlatformInfo(Tasks tasks, ExtendedEventHandler listener) {
+    if (platform == null) {
+      return runAfter; // An error occurred and was reported.
+    }
+
+    PlatformInfo platformInfo = PlatformProviderUtils.platform(platform);
+    if (platformInfo == null) {
+      sink.acceptPlatformInfoError(new InvalidPlatformException(platform.getLabel()));
+      return runAfter;
+    }
+
+    sink.acceptPlatformInfo(platformInfo);
+    return runAfter;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/README.md b/src/main/java/com/google/devtools/build/lib/analysis/producers/README.md
new file mode 100644
index 0000000..a3f0778
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/README.md
@@ -0,0 +1,74 @@
+## Analysis Producers
+
+Analysis is driven by `StateMachine`[^documentation] implementations that
+memoize computations across Skyframe restarts, and enable extremely efficient
+concurrency. Concurrency is crucial in this context, as analysis involves
+applying a series of processing steps to multiple dependencies. By employing
+concurrency, these steps can proceed independently rather than being delayed by
+stragglers.
+
+`StateMachines` serve as composable building blocks, and this document
+illustrates how the analysis producers fit together.
+
+### `DependencyContextProducer`
+
+The `DependencyContextProducer` is used by `AspectFunction`. It first computes
+the unloaded toolchain contexts, which as a side effect, computes
+`PlatformInfo`, a prerequisite of `ConfigConditionsProducer`.
+
+```none
+                   +---------------------------------+
+                   |    DependencyContextProducer    |
+                   +---------------------------------+
+                                  ^         ^
+                                  |         |
+                                  |         |
++-----------------------------------+     +---------------------------------+
+| UnloadedToolchainContextsProducer |     |    ConfigConditionsProducer     |
++-----------------------------------+     +---------------------------------+
+                                            ^
+                                            |
+                                            |
+                                          +---------------------------------+
+                                          | ConfiguredTargetAndDataProducer |
+                                          +---------------------------------+
+```
+
+### `DependencyContextProducerWithCompatibilityCheck`
+
+The `DependencyContextProducerWithCompatibilityCheck` is used by the
+`ConfiguredTargetFunction`. Here, a check for incompatibility needs to happen
+before the unloaded toolchain contexts are computed to avoid failing the build
+due to incompatibility. So instead of obtaining `PlatformInfo` as a side effect
+of computing the unloaded toolchain contexts, it is first computed directly
+using `PlatformInfoProducer`. The `PlatformInfo` is then used to compute
+`ConfigConditions` via the `ConfigConditionsProducer` and the resulting
+`ConfigConditions` are used in the compatibility check. Only after the check
+passes does it finally compute the unloaded toolchain contexts.
+
+```none
++-----------------------------------++----------------------------+
+| UnloadedToolchainContextsProducer || IncompatibleTargetProducer |
++-----------------------------------++----------------------------+
+  |                                    |
+  |                                    |
+  v                                    v
++-----------------------------------------------------------------+     +----------------------+
+|         DependencyContextProducerWithCompatibilityCheck         | <-- | PlatformInfoProducer |
++-----------------------------------------------------------------+     +----------------------+
+  ^
+  |
+  |
++-----------------------------------+
+|     ConfigConditionsProducer      |
++-----------------------------------+
+  ^
+  |
+  |
++-----------------------------------+
+|  ConfiguredTargetAndDataProducer  |
++-----------------------------------+
+```
+
+[^documentation]: TODO(b/261521010): add link when documentation has been
+    checked in.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/TransitiveDependencyState.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/TransitiveDependencyState.java
new file mode 100644
index 0000000..7030cbb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/TransitiveDependencyState.java
@@ -0,0 +1,87 @@
+// Copyright 2023 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.producers;
+
+import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
+import com.google.devtools.build.lib.causes.Cause;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.Package;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.Nullable;
+
+/** Tuple storing state associated with transitive dependencies. */
+public final class TransitiveDependencyState {
+  private final NestedSetBuilder<Cause> transitiveRootCauses;
+
+  /**
+   * The set of packages transitively loaded for the value being built.
+   *
+   * <p>See {@link
+   * com.google.devtools.build.lib.analysis.ConfiguredObjectValue#getTransitivePackages}.
+   *
+   * <p>Non-null when transitive packages are tracked, determined by {@link
+   * com.google.devtools.build.lib.skyframe.SkyframeExecutor#shouldStoreTransitivePackagesInLoadingAndAnalysis}.
+   */
+  @Nullable private final NestedSetBuilder<Package> transitivePackages;
+
+  /**
+   * Contains packages that were previously computed.
+   *
+   * <p>It's valid to obtain packages of dependencies from this map instead of creating an edge in
+   * {@code Skyframe} due to the transitive dependency through the {@link ConfiguredTarget}. Note
+   * that this is compatible with bottom-up change pruning because {@link ConfiguredTargetValue}
+   * uses identity equals.
+   */
+  @Nullable // TODO(b/261521010): make this non-null.
+  private final ConcurrentHashMap<PackageIdentifier, Package> prerequisitePackages;
+
+  public TransitiveDependencyState(
+      NestedSetBuilder<Cause> transitiveRootCauses,
+      @Nullable NestedSetBuilder<Package> transitivePackages,
+      @Nullable ConcurrentHashMap<PackageIdentifier, Package> prerequisitePackages) {
+    this.transitiveRootCauses = transitiveRootCauses;
+    this.transitivePackages = transitivePackages;
+    this.prerequisitePackages = prerequisitePackages;
+  }
+
+  public NestedSetBuilder<Package> transitivePackages() {
+    return transitivePackages;
+  }
+
+  public void addTransitiveCauses(NestedSet<Cause> transitiveCauses) {
+    transitiveRootCauses.addTransitive(transitiveCauses);
+  }
+
+  /**
+   * Adds to the set of transitive packages if tracked.
+   *
+   * <p>This is a no-op otherwise.
+   */
+  public void updateTransitivePackages(ConfiguredTargetValue configuredTarget) {
+    if (transitivePackages == null) {
+      return;
+    }
+    transitivePackages.addTransitive(configuredTarget.getTransitivePackages());
+  }
+
+  @Nullable
+  public Package getDependencyPackage(PackageIdentifier packageId) {
+    if (prerequisitePackages == null) {
+      return null;
+    }
+    return prerequisitePackages.get(packageId);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/UnloadedToolchainContextsInputs.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/UnloadedToolchainContextsInputs.java
new file mode 100644
index 0000000..90ce841
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/UnloadedToolchainContextsInputs.java
@@ -0,0 +1,40 @@
+// Copyright 2023 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.producers;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.ExecGroupCollection;
+import com.google.devtools.build.lib.packages.ExecGroup;
+import com.google.devtools.build.lib.skyframe.ToolchainContextKey;
+import javax.annotation.Nullable;
+
+/** Collates inputs for the {@link UnloadedToolchainContextsProducer}. */
+@AutoValue
+public abstract class UnloadedToolchainContextsInputs extends ExecGroupCollection.Builder {
+  @Nullable // Null if no toolchain resolution is required.
+  public abstract ToolchainContextKey targetToolchainContextKey();
+
+  public static UnloadedToolchainContextsInputs create(
+      ImmutableMap<String, ExecGroup> processedExecGroups,
+      @Nullable ToolchainContextKey targetToolchainContextKey) {
+    return new AutoValue_UnloadedToolchainContextsInputs(
+        processedExecGroups, targetToolchainContextKey);
+  }
+
+  public static UnloadedToolchainContextsInputs empty() {
+    return new AutoValue_UnloadedToolchainContextsInputs(
+        ImmutableMap.of(), /* targetToolchainContextKey= */ null);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/UnloadedToolchainContextsProducer.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/UnloadedToolchainContextsProducer.java
new file mode 100644
index 0000000..4d618ed
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/UnloadedToolchainContextsProducer.java
@@ -0,0 +1,141 @@
+// Copyright 2023 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.producers;
+
+import static com.google.devtools.build.lib.packages.ExecGroup.DEFAULT_EXEC_GROUP_NAME;
+
+import com.google.devtools.build.lib.analysis.ToolchainCollection;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.packages.ExecGroup;
+import com.google.devtools.build.lib.skyframe.NoMatchingPlatformException;
+import com.google.devtools.build.lib.skyframe.ToolchainContextKey;
+import com.google.devtools.build.lib.skyframe.ToolchainException;
+import com.google.devtools.build.lib.skyframe.UnloadedToolchainContext;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.state.StateMachine;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+final class UnloadedToolchainContextsProducer implements StateMachine {
+  interface ResultSink {
+    void acceptUnloadedToolchainContexts(
+        @Nullable ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts);
+
+    void acceptUnloadedToolchainContextsError(ToolchainException error);
+  }
+
+  // -------------------- Input --------------------
+  private final UnloadedToolchainContextsInputs unloadedToolchainContextsInputs;
+
+  // -------------------- Output --------------------
+  private final ResultSink sink;
+
+  // -------------------- Sequencing --------------------
+  private final StateMachine runAfter;
+
+  // -------------------- Internal State --------------------
+  private ToolchainCollection.Builder<UnloadedToolchainContext> toolchainContextsBuilder;
+  private boolean toolchainContextsHasError = false;
+
+  UnloadedToolchainContextsProducer(
+      UnloadedToolchainContextsInputs unloadedToolchainContextsInputs,
+      ResultSink sink,
+      StateMachine runAfter) {
+    this.unloadedToolchainContextsInputs = unloadedToolchainContextsInputs;
+    this.sink = sink;
+    this.runAfter = runAfter;
+  }
+
+  @Override
+  public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
+    var defaultToolchainContextKey = unloadedToolchainContextsInputs.targetToolchainContextKey();
+    if (defaultToolchainContextKey == null) {
+      // Doesn't use toolchain resolution and short-circuits.
+      sink.acceptUnloadedToolchainContexts(null);
+      return runAfter;
+    }
+
+    this.toolchainContextsBuilder =
+        ToolchainCollection.builderWithExpectedSize(
+            unloadedToolchainContextsInputs.execGroups().size() + 1);
+
+    tasks.lookUp(
+        defaultToolchainContextKey,
+        ToolchainException.class,
+        new ToolchainContextLookupCallback(DEFAULT_EXEC_GROUP_NAME));
+
+    var keyBuilder =
+        ToolchainContextKey.key()
+            .configurationKey(defaultToolchainContextKey.configurationKey())
+            .debugTarget(defaultToolchainContextKey.debugTarget());
+
+    for (Map.Entry<String, ExecGroup> entry :
+        unloadedToolchainContextsInputs.execGroups().entrySet()) {
+      var execGroup = entry.getValue();
+      tasks.lookUp(
+          keyBuilder
+              .toolchainTypes(execGroup.toolchainTypes())
+              .execConstraintLabels(execGroup.execCompatibleWith())
+              .build(),
+          ToolchainException.class,
+          new ToolchainContextLookupCallback(entry.getKey()));
+    }
+
+    return this::buildToolchainContexts;
+  }
+
+  private class ToolchainContextLookupCallback
+      implements StateMachine.ValueOrExceptionSink<ToolchainException> {
+    private final String execGroupName;
+
+    private ToolchainContextLookupCallback(String execGroupName) {
+      this.execGroupName = execGroupName;
+    }
+
+    @Override
+    public void acceptValueOrException(
+        @Nullable SkyValue value, @Nullable ToolchainException error) {
+      if (value != null) {
+        var unloadedToolchainContext = (UnloadedToolchainContext) value;
+        var errorData = unloadedToolchainContext.errorData();
+        if (errorData != null) {
+          handleError(new NoMatchingPlatformException(errorData));
+          return;
+        }
+        toolchainContextsBuilder.addContext(execGroupName, unloadedToolchainContext);
+        return;
+      }
+      if (error != null) {
+        handleError(error);
+        return;
+      }
+      throw new IllegalArgumentException("both inputs were null");
+    }
+  }
+
+  private void handleError(ToolchainException error) {
+    if (!toolchainContextsHasError) { // Only propagates the first error.
+      toolchainContextsHasError = true;
+      sink.acceptUnloadedToolchainContextsError(error);
+    }
+  }
+
+  private StateMachine buildToolchainContexts(Tasks tasks, ExtendedEventHandler listener) {
+    if (toolchainContextsHasError) {
+      return runAfter;
+    }
+    sink.acceptUnloadedToolchainContexts(toolchainContextsBuilder.build());
+    return runAfter;
+  }
+}
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 8f59b09..445ec53 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
@@ -53,6 +53,10 @@
 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.configuredtargets.MergedConfiguredTarget;
+import com.google.devtools.build.lib.analysis.producers.DependencyContext;
+import com.google.devtools.build.lib.analysis.producers.DependencyContextProducer;
+import com.google.devtools.build.lib.analysis.producers.TransitiveDependencyState;
+import com.google.devtools.build.lib.analysis.producers.UnloadedToolchainContextsInputs;
 import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition.TransitionException;
 import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.causes.Cause;
@@ -82,7 +86,6 @@
 import com.google.devtools.build.lib.profiler.memory.CurrentRuleTracker;
 import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
 import com.google.devtools.build.lib.skyframe.BzlLoadFunction.BzlLoadFailedException;
-import com.google.devtools.build.lib.skyframe.PrerequisiteProducer.UnloadedToolchainContextsInputs;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor.BuildViewProvider;
 import com.google.devtools.build.lib.util.OrderedSetMultimap;
 import com.google.devtools.build.skyframe.SkyFunction;
@@ -91,11 +94,11 @@
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.SkyframeLookupResult;
+import com.google.devtools.build.skyframe.state.Driver;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import javax.annotation.Nullable;
 import net.starlark.java.eval.StarlarkSemantics;
 
@@ -206,7 +209,7 @@
     }
 
     if (AliasProvider.isAlias(associatedTarget)) {
-      return createAliasAspect(env, targetAndConfiguration, aspect, key, associatedTarget);
+      return createAliasAspect(env, state, aspect, key, associatedTarget);
     }
     // If we get here, label should match original label, and therefore the target we looked up
     // above indeed corresponds to associatedTarget.getLabel().
@@ -278,33 +281,21 @@
     }
 
     try {
-      var unloadedToolchainContextsInputs =
-          getUnloadedToolchainContextsInputs(
-              aspect.getDefinition(), key.getConfigurationKey(), configuration);
-      Optional<ToolchainCollection<UnloadedToolchainContext>> computedToolchainContexts =
-          PrerequisiteProducer.computeUnloadedToolchainContexts(
-              env, unloadedToolchainContextsInputs);
-      if (computedToolchainContexts == null) {
+      var dependencyContext =
+          getDependencyContext(
+              computeDependenciesState,
+              key,
+              aspect,
+              state.transitiveRootCauses,
+              state.transitivePackages,
+              env);
+      if (dependencyContext == null) {
         return null;
       }
 
       ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts =
-          computedToolchainContexts.orElse(null);
-
-      // Get the configuration targets that trigger this rule's configurable attributes.
-      ConfigConditions configConditions =
-          PrerequisiteProducer.computeConfigConditions(
-              env,
-              targetAndConfiguration,
-              state.transitivePackages,
-              unloadedToolchainContexts == null
-                  ? null
-                  : unloadedToolchainContexts.getTargetPlatform(),
-              state.transitiveRootCauses);
-      if (configConditions == null) {
-        // Those targets haven't yet been resolved.
-        return null;
-      }
+          dependencyContext.unloadedToolchainContexts();
+      ConfigConditions configConditions = dependencyContext.configConditions();
 
       OrderedSetMultimap<DependencyKind, ConfiguredTargetAndData> depValueMap;
       try {
@@ -368,7 +359,7 @@
           configuration,
           configConditions,
           toolchainContexts,
-          unloadedToolchainContextsInputs,
+          computeDependenciesState.execGroupCollectionBuilder,
           depValueMap,
           state.transitivePackages);
     } catch (DependencyEvaluationException e) {
@@ -411,6 +402,59 @@
     }
   }
 
+  /** Populates {@code state.execGroupCollection} as a side effect. */
+  @Nullable // Null if a Skyframe restart is needed.
+  private static DependencyContext getDependencyContext(
+      PrerequisiteProducer.State state,
+      AspectKey key,
+      Aspect aspect,
+      NestedSetBuilder<Cause> transitiveRootCauses,
+      @Nullable NestedSetBuilder<Package> transitivePackages,
+      Environment env)
+      throws InterruptedException, ConfiguredValueCreationException, ToolchainException {
+    if (state.dependencyContext != null) {
+      return state.dependencyContext;
+    }
+    if (state.dependencyContextProducer == null) {
+      TargetAndConfiguration targetAndConfiguration = state.targetAndConfiguration;
+      UnloadedToolchainContextsInputs unloadedToolchainContextsInputs =
+          getUnloadedToolchainContextsInputs(
+              aspect.getDefinition(),
+              key.getConfigurationKey(),
+              targetAndConfiguration.getConfiguration());
+      state.execGroupCollectionBuilder = unloadedToolchainContextsInputs;
+      state.dependencyContextProducer =
+          new Driver(
+              new DependencyContextProducer(
+                  unloadedToolchainContextsInputs,
+                  targetAndConfiguration,
+                  new TransitiveDependencyState(
+                      transitiveRootCauses, transitivePackages, /* prerequisitePackages= */ null),
+                  (DependencyContextProducer.ResultSink) state));
+    }
+    if (state.dependencyContextProducer.drive(env, env.getListener())) {
+      state.dependencyContextProducer = null;
+    }
+
+    // During error bubbling, the state machine might not be done, but still emit an error.
+    var error = state.dependencyContextError;
+    if (error != null) {
+      switch (error.kind()) {
+        case TOOLCHAIN:
+          throw error.toolchain();
+        case CONFIGURED_VALUE_CREATION:
+          throw error.configuredValueCreation();
+        case INCOMPATIBLE_TARGET:
+          throw new IllegalStateException("Unexpected error: " + error.incompatibleTarget());
+        case VALIDATION:
+          throw new IllegalStateException("Unexpected error: " + error.validation());
+      }
+      throw new IllegalStateException("unreachable");
+    }
+
+    return state.dependencyContext; // Null if not yet done.
+  }
+
   static BzlLoadValue.Key bzlLoadKeyForStarlarkAspect(StarlarkAspectClass starlarkAspectClass) {
     Label extensionLabel = starlarkAspectClass.getExtensionLabel();
     return StarlarkBuiltinsValue.isBuiltinsRepo(extensionLabel.getRepository())
@@ -621,7 +665,7 @@
   @Nullable
   private AspectValue createAliasAspect(
       Environment env,
-      TargetAndConfiguration originalTarget,
+      State state,
       Aspect aspect,
       AspectKey originalKey,
       ConfiguredTarget configuredTarget)
@@ -636,36 +680,20 @@
         storeTransitivePackages ? NestedSetBuilder.stableOrder() : null;
     NestedSetBuilder<Cause> transitiveRootCauses = NestedSetBuilder.stableOrder();
 
+    TargetAndConfiguration originalTarget = state.computeDependenciesState.targetAndConfiguration;
+
     // Compute the Dependency from the original target to aliasedLabel.
     Dependency dep;
     try {
-      var configuration = originalTarget.getConfiguration();
-      // Determine what toolchains are needed by this target.
-      var unloadedToolchainContextsInputs =
-          getUnloadedToolchainContextsInputs(
-              aspect.getDefinition(), originalKey.getConfigurationKey(), configuration);
-      Optional<ToolchainCollection<UnloadedToolchainContext>> computedToolchainContexts =
-          PrerequisiteProducer.computeUnloadedToolchainContexts(
-              env, unloadedToolchainContextsInputs);
-      if (computedToolchainContexts == null) {
-        return null;
-      }
-
-      ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts =
-          computedToolchainContexts.orElse(null);
-
-      // Get the configuration targets that trigger this rule's configurable attributes.
-      ConfigConditions configConditions =
-          PrerequisiteProducer.computeConfigConditions(
-              env,
-              originalTarget,
-              transitivePackages,
-              unloadedToolchainContexts == null
-                  ? null
-                  : unloadedToolchainContexts.getTargetPlatform(),
-              transitiveRootCauses);
-      if (configConditions == null) {
-        // Those targets haven't yet been resolved.
+      var dependencyContext =
+          getDependencyContext(
+              state.computeDependenciesState,
+              originalKey,
+              aspect,
+              state.transitiveRootCauses,
+              state.transitivePackages,
+              env);
+      if (dependencyContext == null) {
         return null;
       }
 
@@ -675,24 +703,25 @@
       }
       ConfigurationTransition transition =
           TransitionResolver.evaluateTransition(
-              configuration,
+              originalTarget.getConfiguration(),
               NoTransition.INSTANCE,
               aliasedTarget,
               ((ConfiguredRuleClassProvider) ruleClassProvider).getTrimmingTransitionFactory());
 
       // Use ConfigurationResolver to apply any configuration transitions on the alias edge.
-      // This is a shortened/simplified variant of ConfiguredTargetFunction.computeDependencies
+      // This is a shortened/simplified variant of PrerequisiteProducer.computeDependencies
       // for just the one special attribute we care about here.
       DependencyKey depKey =
           DependencyKey.builder().setLabel(aliasedLabel).setTransition(transition).build();
       DependencyKind depKind =
           DependencyKind.AttributeDependencyKind.forRule(
               getAttributeContainingAlias(originalTarget.getTarget()));
+
       ConfigurationResolver resolver =
           new ConfigurationResolver(
               env,
               originalTarget,
-              configConditions.asProviders(),
+              dependencyContext.configConditions().asProviders(),
               buildViewProvider.getSkyframeBuildView().getStarlarkTransitionCache());
       ImmutableList<Dependency> deps =
           resolver.resolveConfiguration(depKind, depKey, env.getListener());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index c635f15..f772ece 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -47,7 +47,6 @@
         "HighWaterMarkLimiter.java",
         "LocalRepositoryLookupFunction.java",
         "PackageFunction.java",
-        "PlatformLookupUtil.java",
         "PlatformMappingFunction.java",
         "PrepareDepsOfPatternFunction.java",
         "PrerequisiteProducer.java",
@@ -153,7 +152,6 @@
         ":map_as_package_roots",
         ":metadata_consumer_for_metrics",
         ":no_matching_platform_data",
-        ":no_matching_platform_exception",
         ":node_dropping_inconsistency_receiver",
         ":package_error_function",
         ":package_error_message_function",
@@ -164,6 +162,7 @@
         ":package_roots_no_symlink_creation",
         ":package_value",
         ":pattern_expanding_error",
+        ":platform_lookup_util",
         ":precomputed_function",
         ":precomputed_value",
         ":prepare_deps_of_pattern_value",
@@ -287,6 +286,7 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:workspace_status_action",
         "//src/main/java/com/google/devtools/build/lib/analysis/platform",
         "//src/main/java/com/google/devtools/build/lib/analysis/platform:utils",
+        "//src/main/java/com/google/devtools/build/lib/analysis/producers",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:common",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:exception",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:module_extension",
@@ -312,8 +312,6 @@
         "//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
         "//src/main/java/com/google/devtools/build/lib/io:process_package_directory_exception",
         "//src/main/java/com/google/devtools/build/lib/packages",
-        "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
-        "//src/main/java/com/google/devtools/build/lib/packages:exec_group",
         "//src/main/java/com/google/devtools/build/lib/packages:globber",
         "//src/main/java/com/google/devtools/build/lib/packages:globber_utils",
         "//src/main/java/com/google/devtools/build/lib/packages/semantics",
@@ -754,7 +752,6 @@
         "//src/main/java/com/google/devtools/build/lib/actions:action_lookup_key",
         "//src/main/java/com/google/devtools/build/lib/analysis:config/build_configuration",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
-        "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
         "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
@@ -1157,7 +1154,6 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:config/build_configuration",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
-        "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
         "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
         "//third_party:guava",
@@ -1852,6 +1848,29 @@
 )
 
 java_library(
+    name = "platform_lookup_util",
+    srcs = ["PlatformLookupUtil.java"],
+    deps = [
+        ":configured_target_key",
+        ":configured_value_creation_exception",
+        ":package_value",
+        ":toolchain_exception",
+        "//src/main/java/com/google/devtools/build/lib/actions",
+        "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
+        "//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
+        "//src/main/java/com/google/devtools/build/lib/analysis/platform",
+        "//src/main/java/com/google/devtools/build/lib/analysis/platform:utils",
+        "//src/main/java/com/google/devtools/build/lib/bugreport",
+        "//src/main/java/com/google/devtools/build/lib/cmdline",
+        "//src/main/java/com/google/devtools/build/lib/packages",
+        "//src/main/java/com/google/devtools/build/skyframe",
+        "//src/main/protobuf:failure_details_java_proto",
+        "//third_party:guava",
+        "//third_party:jsr305",
+    ],
+)
+
+java_library(
     name = "precomputed_function",
     srcs = ["PrecomputedFunction.java"],
     deps = [
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 23a76b7..3a71a96 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
@@ -254,7 +254,7 @@
               prereqs.getDepValueMap(),
               prereqs.getConfigConditions(),
               toolchainContexts,
-              prereqs.getExecGroupCollectionsBuilder(),
+              state.computeDependenciesState.execGroupCollectionBuilder,
               state.transitivePackages);
       if (ans != null && configuredTargetProgress != null) {
         configuredTargetProgress.doneConfigureTarget();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/NoMatchingPlatformData.java b/src/main/java/com/google/devtools/build/lib/skyframe/NoMatchingPlatformData.java
index d99be9c..12859d5 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/NoMatchingPlatformData.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/NoMatchingPlatformData.java
@@ -23,7 +23,7 @@
 
 /** Contains information related to missing execution platform. */
 @AutoValue
-abstract class NoMatchingPlatformData {
+public abstract class NoMatchingPlatformData {
   abstract ImmutableSet<ToolchainTypeRequirement> toolchainTypes();
 
   abstract ImmutableList<ConfiguredTargetKey> availableExecutionPlatformKeys();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PlatformLookupUtil.java b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformLookupUtil.java
index e2cc40a..d29e871 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PlatformLookupUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformLookupUtil.java
@@ -156,7 +156,7 @@
     }
   }
 
-  static boolean hasPlatformInfo(Target target) {
+  public static boolean hasPlatformInfo(Target target) {
     Rule rule = target.getAssociatedRule();
     // If the rule uses toolchain resolution, it can't be used as a target or exec platform.
     if (rule == null) {
@@ -175,11 +175,11 @@
   public static final class InvalidPlatformException extends ToolchainException {
     private static final String DEFAULT_ERROR = "does not provide PlatformInfo";
 
-    InvalidPlatformException(Label label) {
+    public InvalidPlatformException(Label label) {
       super(formatError(label, DEFAULT_ERROR));
     }
 
-    InvalidPlatformException(Label label, ConfiguredValueCreationException e) {
+    public InvalidPlatformException(Label label, ConfiguredValueCreationException e) {
       super(formatError(label, DEFAULT_ERROR), e);
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrerequisiteProducer.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrerequisiteProducer.java
index b40a494..fa218eb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrerequisiteProducer.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrerequisiteProducer.java
@@ -13,9 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
-import static com.google.devtools.build.lib.packages.ExecGroup.DEFAULT_EXEC_GROUP_NAME;
-
-import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -50,8 +47,13 @@
 import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement;
 import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
 import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetException;
-import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetProducer;
 import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
+import com.google.devtools.build.lib.analysis.producers.DependencyContext;
+import com.google.devtools.build.lib.analysis.producers.DependencyContextError;
+import com.google.devtools.build.lib.analysis.producers.DependencyContextProducer;
+import com.google.devtools.build.lib.analysis.producers.DependencyContextProducerWithCompatibilityCheck;
+import com.google.devtools.build.lib.analysis.producers.TransitiveDependencyState;
+import com.google.devtools.build.lib.analysis.producers.UnloadedToolchainContextsInputs;
 import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.causes.Cause;
 import com.google.devtools.build.lib.causes.LoadingFailedCause;
@@ -63,12 +65,9 @@
 import com.google.devtools.build.lib.events.StoredEventHandler;
 import com.google.devtools.build.lib.packages.Aspect;
 import com.google.devtools.build.lib.packages.BuildType;
-import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper.ValidationException;
-import com.google.devtools.build.lib.packages.ExecGroup;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
 import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
 import com.google.devtools.build.lib.packages.Package;
-import com.google.devtools.build.lib.packages.RawAttributeMapper;
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
@@ -87,64 +86,66 @@
 import com.google.devtools.build.skyframe.state.Driver;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import javax.annotation.Nullable;
-import net.starlark.java.syntax.Location;
 
 /**
- * Helper logic for {@link ConfiguredTargetFunction}: performs the analysis phase through
- * computation of prerequisites.
+ * Helper logic for {@link ConfiguredTargetFunction} and {@link AspectFunction}: performs the
+ * analysis phase through computation of prerequisites.
  *
- * <p>This includes:
+ * <p>For the {@link ConfiguredTargetFunction} this includes:
  *
  * <ul>
  *   <li>getting this target's {@link Target} and {@link BuildConfigurationValue}
- *   <li>getting this target's {@code select()} keys (config conditions), which are used to evaluate
- *       all rule attributes with {@code select()} and determine exact dependencies
+ *   <li>getting this target's {@code select()} keys ({@link ConfigConditions}), which are used to
+ *       evaluate all rule attributes with {@code select()} and determine exact dependencies
  *   <li>figuring out which toolchains this target needs
  *   <li>getting the {@link ConfiguredTargetValue}s of this target's prerequisites (through
  *       recursive calls to {@link ConfiguredTargetFunction}
  * </ul>
  *
+ * <p>Figuring out which toolchains are needed and computing the {@link ConfigConditions} is
+ * performed by the {@link DependencyContextProducerWithCompatibilityCheck}, which additionally
+ * checks for directly incompatible targets using the {@link
+ * IncompatibleTargetChecker.IncompatibleTargetProducer}.
+ *
  * <p>Cumulatively, this is enough information to run the target's rule logic.
  *
  * <p>This class also provides getters for the above data for subsequent analysis logic to use.
  *
  * <p>See {@link ConfiguredTargetFunction} for more review on analysis implementation.
+ *
+ * <p>{@link AspectFunction} shares the logic computing a target's prerequisites via the {@link
+ * PrerequisiteProducer#computeDependencies}.
  */
 public final class PrerequisiteProducer {
-  static class State implements SkyKeyComputeState, IncompatibleTargetProducer.ResultSink {
-    @Nullable TargetAndConfiguration targetAndConfiguration;
+  /**
+   * Memoizies computation steps of {@link #evaluate} so they do not need to be repeated on {@code
+   * Skyframe} restart.
+   */
+  @VisibleForTesting
+  public static class State implements SkyKeyComputeState, DependencyContextProducer.ResultSink {
+    @VisibleForTesting @Nullable public TargetAndConfiguration targetAndConfiguration;
+
+    /** Set once {@link #dependencyContextProducer} starts. */
+    @VisibleForTesting public ExecGroupCollection.Builder execGroupCollectionBuilder;
 
     /**
-     * Drives the stateful computation of {@link #incompatibleTarget}.
+     * Computes the dependency context, comprised of the unloaded toolchain contexts and the config
+     * conditions.
      *
-     * <p>Non-null only while the computation is in-flight.
+     * <p>One of {@link #dependencyContext} or {@link #dependencyContextError} will be set upon
+     * completion.
      */
-    @Nullable private Driver incompatibleTargetProducer;
+    @Nullable // Non-null when in-flight.
+    Driver dependencyContextProducer;
 
-    /**
-     * If a value is present, it means the target was directly incompatible.
-     *
-     * <p>Either this or {@link #validationException} will be non-null after the {@link
-     * #incompatibleTargetProducer} completes.
-     */
-    @Nullable private Optional<RuleConfiguredTargetValue> incompatibleTarget;
-
-    /**
-     * If this is set, an exception occurred during validation of the {@code target_compatible_with}
-     * attribute.
-     *
-     * <p>Either this or {@link #incompatibleTarget} will be non-null after the {@link
-     * #incompatibleTargetProducer} completes.
-     */
-    @Nullable private ValidationException validationException;
+    @Nullable DependencyContext dependencyContext;
+    @Nullable DependencyContextError dependencyContextError;
 
     /** Null if not yet computed or if {@link #resolveConfigurationsResult} is non-null. */
     @Nullable private OrderedSetMultimap<DependencyKind, DependencyKey> dependentNodeMapResult;
@@ -182,13 +183,13 @@
     @Nullable private StoredEventHandler storedEventHandlerFromResolveConfigurations;
 
     @Override
-    public void accept(Optional<RuleConfiguredTargetValue> incompatibleTarget) {
-      this.incompatibleTarget = incompatibleTarget;
+    public void acceptDependencyContext(DependencyContext value) {
+      this.dependencyContext = value;
     }
 
     @Override
-    public void acceptValidationException(ValidationException e) {
-      this.validationException = e;
+    public void acceptDependencyContextError(DependencyContextError error) {
+      this.dependencyContextError = error;
     }
   }
 
@@ -207,8 +208,7 @@
   private OrderedSetMultimap<DependencyKind, ConfiguredTargetAndData> depValueMap = null;
   private ConfigConditions configConditions = null;
   private PlatformInfo platformInfo = null;
-  private ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts = null;
-  private ExecGroupCollection.Builder execGroupCollectionBuilder = null;
+  @Nullable private ToolchainCollection<UnloadedToolchainContext> unloadedToolchainContexts = null;
 
   /**
    * Return this target's {@link TargetAndConfiguration}.
@@ -258,15 +258,6 @@
   }
 
   /**
-   * Return this target's {@link ExecGroupCollection}, as far as this phase of analysis computes.
-   *
-   * <p>{@link #evaluate} must be called before this info is available.
-   */
-  ExecGroupCollection.Builder getExecGroupCollectionsBuilder() {
-    return Preconditions.checkNotNull(execGroupCollectionBuilder);
-  }
-
-  /**
    * Run's the analysis phase for this target through prerequisite evaluation.
    *
    * <p>See {@link PrerequisiteProducer} javadoc for details.
@@ -315,72 +306,22 @@
     //  would exit this SkyFunction and restart it when permits were available.
     semaphoreLocker.acquireSemaphore();
     try {
-      // Check target compatibility before requesting toolchains - known missing toolchains are a
-      // valid reason for declaring a target incompatible and should not result in the target
-      // failing to build rather than being skipped as incompatible.
-      // Non-rule targets and those that are part of the toolchain resolution system do not support
-      // target compatibility checking anyway.
-      if (targetAndConfiguration.getTarget() instanceof Rule
-          && ((Rule) targetAndConfiguration.getTarget()).useToolchainResolution()) {
-        platformInfo = loadTargetPlatformInfo(env);
-        // loadTargetPlatformInfo may return null even when no deps are missing.
-        if (env.valuesMissing()) {
-          return false;
-        }
-
-        configConditions =
-            computeConfigConditions(
-                env,
-                targetAndConfiguration,
-                transitivePackages,
-                platformInfo,
-                transitiveRootCauses);
-        if (configConditions == null) {
-          return false;
-        }
-
-        if (!checkForIncompatibleTarget(
-            env,
-            state,
-            targetAndConfiguration,
-            configConditions,
-            platformInfo,
-            transitivePackages)) {
-          return false;
-        }
-      }
-      var unloadedToolchainContextsInputs =
-          getUnloadedToolchainContextsInputs(
-              targetAndConfiguration,
+      var dependencyContext =
+          getDependencyContext(
+              state,
               configuredTargetKey.getExecutionPlatformLabel(),
+              transitiveRootCauses,
+              transitivePackages,
               ruleClassProvider,
-              env.getListener());
-      // Determine what toolchains are needed by this target.
-      Optional<ToolchainCollection<UnloadedToolchainContext>> result =
-          computeUnloadedToolchainContexts(env, unloadedToolchainContextsInputs);
-      if (result == null) {
+              env);
+      if (dependencyContext == null) {
         return false;
       }
-      this.unloadedToolchainContexts = result.orElse(null);
-      this.execGroupCollectionBuilder = unloadedToolchainContextsInputs;
+      this.unloadedToolchainContexts = dependencyContext.unloadedToolchainContexts();
       this.platformInfo =
           unloadedToolchainContexts != null ? unloadedToolchainContexts.getTargetPlatform() : null;
+      this.configConditions = dependencyContext.configConditions();
 
-      // Get the configuration targets that trigger this rule's configurable attributes.
-      // Has been computed as part of checking for incompatible targets at the beginning of this
-      // function unless the current target is not a rule participating in toolchain resolution.
-      if (configConditions == null) {
-        configConditions =
-            computeConfigConditions(
-                env,
-                targetAndConfiguration,
-                transitivePackages,
-                platformInfo,
-                transitiveRootCauses);
-      }
-      if (configConditions == null) {
-        return false;
-      }
       // TODO(ulfjack): ConfiguredAttributeMapper (indirectly used from computeDependencies) isn't
       // safe to use if there are missing config conditions, so we stop here, but only if there are
       // config conditions - though note that we can't check if configConditions is non-empty - it
@@ -439,87 +380,81 @@
     return true;
   }
 
-  // May return null even when no deps are missing, use env.valuesMissing() to check.
-  @Nullable
-  private PlatformInfo loadTargetPlatformInfo(Environment env)
-      throws InterruptedException, ToolchainException {
-    PlatformConfiguration platformConfiguration =
-        targetAndConfiguration.getConfiguration().getFragment(PlatformConfiguration.class);
-    if (platformConfiguration == null) {
-      // No restart required in this case.
-      return null;
-    }
-    Label targetPlatformLabel = platformConfiguration.getTargetPlatform();
-    ConfiguredTargetKey targetPlatformKey =
-        ConfiguredTargetKey.builder()
-            .setLabel(targetPlatformLabel)
-            .setConfiguration(targetAndConfiguration.getConfiguration())
-            .build();
-
-    Map<ConfiguredTargetKey, PlatformInfo> platformInfoMap =
-        PlatformLookupUtil.getPlatformInfo(ImmutableList.of(targetPlatformKey), env);
-    if (platformInfoMap == null) {
-      return null;
-    }
-
-    return platformInfoMap.get(targetPlatformKey);
-  }
-
-  /**
-   * Checks if a target is incompatible because of its "target_compatible_with" attribute.
-   *
-   * @return false if a {@code Skyframe} restart is needed.
-   */
-  private static boolean checkForIncompatibleTarget(
-      Environment env,
+  @VisibleForTesting
+  @Nullable // Null when a Skyframe restart is needed.
+  public static DependencyContext getDependencyContext(
       State state,
-      TargetAndConfiguration targetAndConfiguration,
-      @Nullable ConfigConditions configConditions,
-      @Nullable PlatformInfo targetPlatformInfo,
-      @Nullable NestedSetBuilder<Package> transitivePackages)
-      throws InterruptedException, IncompatibleTargetException, DependencyEvaluationException {
-    if (state.incompatibleTarget == null) {
-      BuildConfigurationValue configuration = targetAndConfiguration.getConfiguration();
-      if (state.incompatibleTargetProducer == null) {
-        state.incompatibleTargetProducer =
-            new Driver(
-                new IncompatibleTargetProducer(
-                    targetAndConfiguration.getTarget(),
-                    configuration,
-                    configConditions,
-                    targetPlatformInfo,
-                    transitivePackages,
-                    state));
-      }
-      if (!state.incompatibleTargetProducer.drive(env, env.getListener())) {
-        return false;
-      }
-      state.incompatibleTargetProducer = null;
-      if (state.validationException != null) {
-        Label label = targetAndConfiguration.getLabel();
-        Location location = targetAndConfiguration.getTarget().getLocation();
-        env.getListener()
-            .post(
-                new AnalysisRootCauseEvent(
-                    configuration, label, state.validationException.getMessage()));
-        throw new DependencyEvaluationException(
-            new ConfiguredValueCreationException(
-                location,
-                state.validationException.getMessage(),
-                label,
-                configuration.getEventId(),
-                null,
-                null),
-            // These errors occur within DependencyResolver, which is attached to the current
-            // target. i.e. no dependent ConfiguredTargetFunction call happens to report its own
-            // error.
-            /* depReportedOwnError= */ false);
-      }
-      if (state.incompatibleTarget.isPresent()) {
-        throw new IncompatibleTargetException(state.incompatibleTarget.get());
-      }
+      @Nullable Label parentExecutionPlatformLabel,
+      NestedSetBuilder<Cause> transitiveRootCauses,
+      @Nullable NestedSetBuilder<Package> transitivePackages,
+      RuleClassProvider ruleClassProvider,
+      Environment env)
+      throws InterruptedException,
+          ToolchainException,
+          ConfiguredValueCreationException,
+          IncompatibleTargetException,
+          DependencyEvaluationException {
+    if (state.dependencyContext != null) {
+      return state.dependencyContext;
     }
-    return true;
+    if (state.dependencyContextProducer == null) {
+      var targetAndConfiguration = state.targetAndConfiguration;
+      var unloadedToolchainContextsInputs =
+          getUnloadedToolchainContextsInputs(
+              targetAndConfiguration,
+              parentExecutionPlatformLabel,
+              ruleClassProvider,
+              env.getListener());
+      state.execGroupCollectionBuilder = unloadedToolchainContextsInputs;
+      state.dependencyContextProducer =
+          new Driver(
+              new DependencyContextProducerWithCompatibilityCheck(
+                  targetAndConfiguration,
+                  unloadedToolchainContextsInputs,
+                  new TransitiveDependencyState(
+                      transitiveRootCauses, transitivePackages, /* prerequisitePackages= */ null),
+                  (DependencyContextProducer.ResultSink) state));
+    }
+    if (state.dependencyContextProducer.drive(env, env.getListener())) {
+      state.dependencyContextProducer = null;
+    }
+
+    // During error bubbling, the state machine might not be done, but still emit an error.
+    var error = state.dependencyContextError;
+    if (error != null) {
+      switch (error.kind()) {
+        case TOOLCHAIN:
+          throw error.toolchain();
+        case CONFIGURED_VALUE_CREATION:
+          throw error.configuredValueCreation();
+        case INCOMPATIBLE_TARGET:
+          throw error.incompatibleTarget();
+        case VALIDATION:
+          var targetAndConfiguration = state.targetAndConfiguration;
+          var configuration = targetAndConfiguration.getConfiguration();
+          Label label = targetAndConfiguration.getLabel();
+          var validationException = error.validation();
+          env.getListener()
+              .post(
+                  new AnalysisRootCauseEvent(
+                      configuration, label, validationException.getMessage()));
+          throw new DependencyEvaluationException(
+              new ConfiguredValueCreationException(
+                  targetAndConfiguration.getTarget().getLocation(),
+                  validationException.getMessage(),
+                  label,
+                  configuration.getEventId(),
+                  null,
+                  null),
+              // These errors occur within DependencyResolver, which is attached to the current
+              // target. i.e. no dependent ConfiguredTargetFunction call happens to report its own
+              // error.
+              /* depReportedOwnError= */ false);
+      }
+      throw new IllegalStateException("unreachable");
+    }
+
+    return state.dependencyContext; // Null if not yet done.
   }
 
   /**
@@ -655,92 +590,6 @@
     return state.targetAndConfiguration;
   }
 
-  @AutoValue
-  abstract static class UnloadedToolchainContextsInputs extends ExecGroupCollection.Builder {
-    @Nullable // Null if no toolchain resolution is required.
-    abstract ToolchainContextKey targetToolchainContextKey();
-
-    static UnloadedToolchainContextsInputs create(
-        ImmutableMap<String, ExecGroup> processedExecGroups,
-        @Nullable ToolchainContextKey targetToolchainContextKey) {
-      return new AutoValue_PrerequisiteProducer_UnloadedToolchainContextsInputs(
-          processedExecGroups, targetToolchainContextKey);
-    }
-
-    static UnloadedToolchainContextsInputs empty() {
-      return new AutoValue_PrerequisiteProducer_UnloadedToolchainContextsInputs(
-          ImmutableMap.of(), /* targetToolchainContextKey= */ null);
-    }
-  }
-
-  /**
-   * Computes the unloaded toolchain contexts.
-   *
-   * @return null if a Skyframe restart is needed, {@link Optional#empty} if there is no toolchain
-   *     resolution and the unloaded toolchain contexts otherwise.
-   */
-  @VisibleForTesting // package private
-  @Nullable
-  public static Optional<ToolchainCollection<UnloadedToolchainContext>>
-      computeUnloadedToolchainContexts(Environment env, UnloadedToolchainContextsInputs inputs)
-          throws InterruptedException, ToolchainException {
-    var targetToolchainContextKey = inputs.targetToolchainContextKey();
-    // Short circuit and end now if this target doesn't require toolchain resolution.
-    if (targetToolchainContextKey == null) {
-      return Optional.empty();
-    }
-
-    var toolchainContextKeys = new HashMap<String, ToolchainContextKey>();
-    toolchainContextKeys.put(DEFAULT_EXEC_GROUP_NAME, targetToolchainContextKey);
-
-    var keyBuilder =
-        ToolchainContextKey.key()
-            .configurationKey(targetToolchainContextKey.configurationKey())
-            .debugTarget(targetToolchainContextKey.debugTarget());
-    for (Map.Entry<String, ExecGroup> entry : inputs.execGroups().entrySet()) {
-      var execGroup = entry.getValue();
-      toolchainContextKeys.put(
-          entry.getKey(),
-          keyBuilder
-              .toolchainTypes(execGroup.toolchainTypes())
-              .execConstraintLabels(execGroup.execCompatibleWith())
-              .build());
-    }
-
-    SkyframeLookupResult values = env.getValuesAndExceptions(toolchainContextKeys.values());
-    boolean valuesMissing = env.valuesMissing();
-
-    ToolchainCollection.Builder<UnloadedToolchainContext> toolchainContexts =
-        valuesMissing
-            ? null
-            : ToolchainCollection.builderWithExpectedSize(toolchainContextKeys.size());
-    for (Map.Entry<String, ToolchainContextKey> unloadedToolchainContextKey :
-        toolchainContextKeys.entrySet()) {
-      UnloadedToolchainContext unloadedToolchainContext =
-          (UnloadedToolchainContext)
-              values.getOrThrow(unloadedToolchainContextKey.getValue(), ToolchainException.class);
-      if (valuesMissing != env.valuesMissing()) {
-        BugReport.logUnexpected(
-            "Value for: '%s' was missing, this should never happen",
-            unloadedToolchainContextKey.getValue());
-        break;
-      }
-      if (unloadedToolchainContext != null && unloadedToolchainContext.errorData() != null) {
-        throw new NoMatchingPlatformException(unloadedToolchainContext.errorData());
-      }
-      if (!valuesMissing) {
-        toolchainContexts.addContext(
-            unloadedToolchainContextKey.getKey(), unloadedToolchainContext);
-      }
-    }
-
-    if (valuesMissing) {
-      return null;
-    }
-
-    return Optional.of(toolchainContexts.build());
-  }
-
   /**
    * Returns the target-specific execution platform constraints, based on the rule definition and
    * any constraints added by the target, including those added for the target on the command line.
@@ -927,99 +776,6 @@
     }
   }
 
-  /**
-   * Returns the targets that key the configurable attributes used by this rule.
-   *
-   * <p>If the configured targets supplying those providers aren't yet resolved by the dependency
-   * resolver, returns null.
-   */
-  @Nullable
-  static ConfigConditions computeConfigConditions(
-      Environment env,
-      TargetAndConfiguration ctgValue,
-      @Nullable NestedSetBuilder<Package> transitivePackages,
-      @Nullable PlatformInfo platformInfo,
-      NestedSetBuilder<Cause> transitiveRootCauses)
-      throws ConfiguredValueCreationException, InterruptedException {
-    Target target = ctgValue.getTarget();
-    if (!(target instanceof Rule)) {
-      return ConfigConditions.EMPTY;
-    }
-    RawAttributeMapper attrs = RawAttributeMapper.of(((Rule) target));
-    if (!attrs.has(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE)) {
-      return ConfigConditions.EMPTY;
-    }
-
-    // Collect the labels of the configured targets we need to resolve.
-    List<Label> configLabels =
-        attrs.get(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE, BuildType.LABEL_LIST);
-    if (configLabels.isEmpty()) {
-      return ConfigConditions.EMPTY;
-    }
-
-    // Collect the actual deps without a configuration transition (since by definition config
-    // conditions evaluate over the current target's configuration). If the dependency is
-    // (erroneously) something that needs the null configuration, its analysis will be
-    // short-circuited. That error will be reported later.
-    ImmutableList.Builder<Dependency> depsBuilder = ImmutableList.builder();
-    for (Label configurabilityLabel : configLabels) {
-      Dependency configurabilityDependency =
-          Dependency.builder()
-              .setLabel(configurabilityLabel)
-              .setConfiguration(ctgValue.getConfiguration())
-              .build();
-      depsBuilder.add(configurabilityDependency);
-    }
-
-    ImmutableList<Dependency> configConditionDeps = depsBuilder.build();
-
-    Map<SkyKey, ConfiguredTargetAndData> configValues;
-    try {
-      configValues =
-          resolveConfiguredTargetDependencies(
-              env, ctgValue, configConditionDeps, transitivePackages, transitiveRootCauses);
-      if (configValues == null) {
-        return null;
-      }
-    } catch (DependencyEvaluationException e) {
-      // One of the config dependencies doesn't exist, and we need to report that. Unfortunately,
-      // there's not enough information to know which configurable attribute has the problem.
-      throw new ConfiguredValueCreationException(
-          // The precise error is reported by the dependency that failed to load.
-          // TODO(gregce): beautify this error: https://github.com/bazelbuild/bazel/issues/11984.
-          ctgValue, "errors encountered resolving select() keys for " + target.getLabel());
-    }
-
-    ImmutableMap.Builder<Label, ConfiguredTargetAndData> asConfiguredTargets =
-        ImmutableMap.builder();
-    ImmutableMap.Builder<Label, ConfigMatchingProvider> asConfigConditions = ImmutableMap.builder();
-
-    // Get the configured targets as ConfigMatchingProvider interfaces.
-    for (Dependency entry : configConditionDeps) {
-      SkyKey baseKey = entry.getConfiguredTargetKey();
-      // The code above guarantees that selectKeyTarget is non-null here.
-      ConfiguredTargetAndData selectKeyTarget = configValues.get(baseKey);
-      asConfiguredTargets.put(entry.getLabel(), selectKeyTarget);
-      try {
-        asConfigConditions.put(
-            entry.getLabel(), ConfigConditions.fromConfiguredTarget(selectKeyTarget, platformInfo));
-      } catch (ConfigConditions.InvalidConditionException e) {
-        String message =
-            String.format(
-                    "%s is not a valid select() condition for %s.\n",
-                    selectKeyTarget.getTargetLabel(), target.getLabel())
-                + String.format(
-                    "To inspect the select(), run: bazel query --output=build %s.\n",
-                    target.getLabel())
-                + "For more help, see https://bazel.build/reference/be/functions#select.\n\n";
-        throw new ConfiguredValueCreationException(ctgValue, message);
-      }
-    }
-
-    return ConfigConditions.create(
-        asConfiguredTargets.buildOrThrow(), asConfigConditions.buildOrThrow());
-  }
-
   static ToolchainContextKey createDefaultToolchainContextKey(
       BuildConfigurationKey configurationKey,
       ImmutableSet<Label> defaultExecConstraintLabels,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainContextKey.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainContextKey.java
index f5d4ad8..c9551c9 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainContextKey.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainContextKey.java
@@ -49,7 +49,7 @@
     return interner;
   }
 
-  abstract BuildConfigurationKey configurationKey();
+  public abstract BuildConfigurationKey configurationKey();
 
   abstract ImmutableSet<ToolchainTypeRequirement> toolchainTypes();
 
@@ -57,7 +57,7 @@
 
   abstract Optional<Label> forceExecutionPlatform();
 
-  abstract boolean debugTarget();
+  public abstract boolean debugTarget();
 
   /** Builder for {@link ToolchainContextKey}. */
   @AutoValue.Builder
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD b/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
index 30fe172..2b09e71 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
@@ -62,6 +62,7 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/patch_transition",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
+        "//src/main/java/com/google/devtools/build/lib/analysis:constraints/incompatible_target_checker",
         "//src/main/java/com/google/devtools/build/lib/analysis:dependency_key",
         "//src/main/java/com/google/devtools/build/lib/analysis:dependency_kind",
         "//src/main/java/com/google/devtools/build/lib/analysis:duplicate_exception",
@@ -84,6 +85,7 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:view_creation_failed_exception",
         "//src/main/java/com/google/devtools/build/lib/analysis:workspace_status_action",
         "//src/main/java/com/google/devtools/build/lib/analysis/platform",
+        "//src/main/java/com/google/devtools/build/lib/analysis/producers",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution_impl",
         "//src/main/java/com/google/devtools/build/lib/bazel/repository:repository_options",
@@ -110,6 +112,7 @@
         "//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:configured_value_creation_exception",
         "//src/main/java/com/google/devtools/build/lib/skyframe:diff_awareness",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_roots_no_symlink_creation",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_value",
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 39d184e..8404f8e 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
@@ -17,6 +17,7 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.collect.Streams.stream;
+import static com.google.devtools.build.lib.skyframe.PrerequisiteProducer.getDependencyContext;
 
 import com.google.common.base.Functions;
 import com.google.common.base.Preconditions;
@@ -68,13 +69,16 @@
 import com.google.devtools.build.lib.analysis.config.ConfigConditions;
 import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
 import com.google.devtools.build.lib.analysis.config.ConfigurationResolver;
+import com.google.devtools.build.lib.analysis.config.DependencyEvaluationException;
 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;
 import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget;
+import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetException;
 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.producers.DependencyContext;
 import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition;
 import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory;
 import com.google.devtools.build.lib.bugreport.BugReporter;
@@ -101,6 +105,7 @@
 import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
 import com.google.devtools.build.lib.skyframe.PackageValue;
 import com.google.devtools.build.lib.skyframe.PrerequisiteProducer;
 import com.google.devtools.build.lib.skyframe.SkyFunctionEnvironmentForTesting;
@@ -123,7 +128,6 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -636,18 +640,31 @@
 
     SkyFunctionEnvironmentForTesting skyfunctionEnvironment =
         new SkyFunctionEnvironmentForTesting(eventHandler, skyframeExecutor);
+    var state = new PrerequisiteProducer.State();
+    state.targetAndConfiguration =
+        new TargetAndConfiguration(target.getAssociatedRule(), targetConfig);
+    NestedSetBuilder<Cause> transitiveRootCauses = NestedSetBuilder.stableOrder();
 
-    var unloadedToolchainContextsInputs =
-        PrerequisiteProducer.getUnloadedToolchainContextsInputs(
-            new TargetAndConfiguration(target.getAssociatedRule(), targetConfig),
-            /* parentExecutionPlatformLabel= */ null,
-            ruleClassProvider,
-            eventHandler);
-    Optional<ToolchainCollection<UnloadedToolchainContext>> result =
-        PrerequisiteProducer.computeUnloadedToolchainContexts(
-            skyfunctionEnvironment, unloadedToolchainContextsInputs);
-
-    ToolchainCollection<UnloadedToolchainContext> unloadedToolchainCollection = result.orElse(null);
+    DependencyContext result;
+    try {
+      result =
+          getDependencyContext(
+              state,
+              /* parentExecutionPlatformLabel= */ null,
+              transitiveRootCauses,
+              /* transitivePackages= */ null,
+              ruleClassProvider,
+              skyfunctionEnvironment);
+    } catch (ConfiguredValueCreationException
+        | IncompatibleTargetException
+        | DependencyEvaluationException e) {
+      throw new IllegalStateException(e);
+    }
+    if (!transitiveRootCauses.isEmpty()) {
+      throw new IllegalStateException("expected empty: " + transitiveRootCauses.build().toList());
+    }
+    ToolchainCollection<UnloadedToolchainContext> unloadedToolchainCollection =
+        result.unloadedToolchainContexts();
 
     OrderedSetMultimap<DependencyKind, ConfiguredTargetAndData> prerequisiteMap =
         getPrerequisiteMapForTesting(
@@ -681,7 +698,7 @@
         .setPrerequisites(ConfiguredTargetFactory.transformPrerequisiteMap(prerequisiteMap))
         .setConfigConditions(ConfigConditions.EMPTY)
         .setToolchainContexts(resolvedToolchainContext.build())
-        .setExecGroupCollectionBuilder(unloadedToolchainContextsInputs)
+        .setExecGroupCollectionBuilder(state.execGroupCollectionBuilder)
         .unsafeBuild();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
index d2905e2..c46ff90 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -137,6 +137,7 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/transition_factory",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
+        "//src/main/java/com/google/devtools/build/lib/analysis:constraints/incompatible_target_checker",
         "//src/main/java/com/google/devtools/build/lib/analysis:dependency",
         "//src/main/java/com/google/devtools/build/lib/analysis:dependency_kind",
         "//src/main/java/com/google/devtools/build/lib/analysis:file_provider",
@@ -151,6 +152,7 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:transitive_info_provider",
         "//src/main/java/com/google/devtools/build/lib/analysis:view_creation_failed_exception",
         "//src/main/java/com/google/devtools/build/lib/analysis/platform",
+        "//src/main/java/com/google/devtools/build/lib/analysis/producers",
         "//src/main/java/com/google/devtools/build/lib/bazel:main",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:common",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution_impl",
@@ -158,6 +160,7 @@
         "//src/main/java/com/google/devtools/build/lib/bazel/repository:repository_options",
         "//src/main/java/com/google/devtools/build/lib/bazel/rules",
         "//src/main/java/com/google/devtools/build/lib/bugreport",
+        "//src/main/java/com/google/devtools/build/lib/causes",
         "//src/main/java/com/google/devtools/build/lib/clock",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/collect",
@@ -201,6 +204,7 @@
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_progress_receiver",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:configured_value_creation_exception",
         "//src/main/java/com/google/devtools/build/lib/skyframe:containing_package_lookup_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:containing_package_lookup_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:default_syscall_cache",
@@ -229,6 +233,7 @@
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_lookup_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_progress_receiver",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_value",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:platform_lookup_util",
         "//src/main/java/com/google/devtools/build/lib/skyframe:precomputed_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:prepare_deps_of_pattern_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:prepare_deps_of_patterns_value",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainsForTargetsTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainsForTargetsTest.java
index ac28444..b06b974 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainsForTargetsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainsForTargetsTest.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.devtools.build.lib.analysis.testing.ToolchainCollectionSubject.assertThat;
 import static com.google.devtools.build.lib.analysis.testing.ToolchainContextSubject.assertThat;
+import static com.google.devtools.build.lib.skyframe.PrerequisiteProducer.getDependencyContext;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
@@ -31,11 +32,17 @@
 import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
 import com.google.devtools.build.lib.analysis.ToolchainCollection;
 import com.google.devtools.build.lib.analysis.ToolchainContext;
+import com.google.devtools.build.lib.analysis.config.DependencyEvaluationException;
 import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
+import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetException;
+import com.google.devtools.build.lib.analysis.producers.DependencyContext;
+import com.google.devtools.build.lib.analysis.producers.DependencyContextProducer;
 import com.google.devtools.build.lib.analysis.test.BaselineCoverageAction;
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
 import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
+import com.google.devtools.build.lib.causes.Cause;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
 import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
 import com.google.devtools.build.skyframe.EvaluationResult;
@@ -45,7 +52,6 @@
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import java.util.Optional;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,19 +60,19 @@
 /**
  * Tests {@link ConfiguredTargetFunction}'s logic for determining each target toolchain context.
  *
- * <p>This is essentially an integration test for {@link
- * PrerequisiteProducer#computeUnloadedToolchainContexts}. These methods form the core logic that
- * figures out what a target's toolchain dependencies are.
+ * <p>This is essentially an integration test for the toolchain part of {@link
+ * DependencyContextProducer}. These methods form the core logic that figures out what a target's
+ * toolchain dependencies are.
  *
  * <p>{@link ConfiguredTargetFunction} is a complicated class that does a lot of things. This test
  * focuses purely on the task of toolchain resolution. So instead of evaluating full {@link
  * ConfiguredTargetFunction} instances, it evaluates a mock {@link SkyFunction} that just wraps the
- * {@link PrerequisiteProducer#computeUnloadedToolchainContexts} part. This keeps focus tight and
- * integration dependencies narrow.
+ * {@link PrerequisiteProducer#getDependencyContext} part. This keeps focus tight and integration
+ * dependencies narrow.
  *
- * <p>We can't just call {@link PrerequisiteProducer#computeUnloadedToolchainContexts} directly
- * because that method needs a {@link SkyFunction.Environment} and Blaze's test infrastructure
- * doesn't support direct access to environments.
+ * <p>We can't just call {@link ToolchainContextProducer} directly because that method needs a
+ * {@link SkyFunction.Environment} and Blaze's test infrastructure doesn't support direct access to
+ * environments.
  */
 @RunWith(JUnit4.class)
 public final class ToolchainsForTargetsTest extends AnalysisTestCase {
@@ -91,7 +97,7 @@
 
   /**
    * Returns a {@link ToolchainCollection<UnloadedToolchainContext>} as the result of {@link
-   * PrerequisiteProducer#computeUnloadedToolchainContexts}.
+   * PrerequisiteProducer#getDependencyContext}.
    */
   @AutoValue
   abstract static class Value implements SkyValue {
@@ -103,8 +109,8 @@
   }
 
   /**
-   * A mock {@link SkyFunction} that just calls {@link
-   * PrerequisiteProducer#computeUnloadedToolchainContexts} and returns its results.
+   * A mock {@link SkyFunction} that just calls {@link PrerequisiteProducer#getDependencyContext}
+   * and returns its results.
    */
   static class ComputeUnloadedToolchainContextsFunction implements SkyFunction {
     static final SkyFunctionName SKYFUNCTION_NAME =
@@ -120,25 +126,33 @@
     @Override
     public SkyValue compute(SkyKey skyKey, Environment env)
         throws ComputeUnloadedToolchainContextsException, InterruptedException {
+      Key key = (Key) skyKey.argument();
+      var state = env.getState(PrerequisiteProducer.State::new);
+      state.targetAndConfiguration = key.targetAndConfiguration();
+      NestedSetBuilder<Cause> transitiveRootCauses = NestedSetBuilder.stableOrder();
+      DependencyContext result;
       try {
-        Key key = (Key) skyKey.argument();
-        var unloadedToolchainContextsInputs =
-            PrerequisiteProducer.getUnloadedToolchainContextsInputs(
-                key.targetAndConfiguration(),
+        result =
+            getDependencyContext(
+                state,
                 key.configuredTargetKey().getExecutionPlatformLabel(),
+                transitiveRootCauses,
+                /* transitivePackages= */ null,
                 stateProvider.lateBoundRuleClassProvider(),
-                env.getListener());
-        Optional<ToolchainCollection<UnloadedToolchainContext>> result =
-            PrerequisiteProducer.computeUnloadedToolchainContexts(
-                env, unloadedToolchainContextsInputs);
-        if (result == null) {
-          return null;
-        }
-        ToolchainCollection<UnloadedToolchainContext> toolchainCollection = result.orElse(null);
-        return Value.create(toolchainCollection);
-      } catch (ToolchainException e) {
+                env);
+      } catch (ToolchainException
+          | ConfiguredValueCreationException
+          | IncompatibleTargetException
+          | DependencyEvaluationException e) {
         throw new ComputeUnloadedToolchainContextsException(e);
       }
+      if (!transitiveRootCauses.isEmpty()) {
+        throw new IllegalStateException("expected empty: " + transitiveRootCauses.build().toList());
+      }
+      if (result == null) {
+        return null;
+      }
+      return Value.create(result.unloadedToolchainContexts());
     }
 
     private static class ComputeUnloadedToolchainContextsException extends SkyFunctionException {