Allow starlark transitions to read starlark-defined options.

Calculate and supply defaults for all read starlark options before transitions are applied. Future work includes:
- combining skyfunction calls for inputs and outputs of starlark transitions
- enforcing that BuildOptions.starlarkOptions will never hold default values (opposite of native options which always hold default values) in order to make BuildConfigurationWithDefault == BuildConfigurationWithNoValue
- Making sure BuildConfiguration properly reflects StarlarkOptions

Work towards #5574, #5577, #5578

RELNOTES: None.
PiperOrigin-RevId: 242681946
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index 0c50d85..6bc26ec 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -1090,13 +1090,14 @@
   private final Supplier<BuildConfigurationEvent> buildEventSupplier;
 
   /**
-   * Returns true if this configuration is semantically equal to the other, with
-   * the possible exception that the other has fewer fragments.
+   * Returns true if this configuration is semantically equal to the other, with the possible
+   * exception that the other has fewer fragments.
    *
    * <p>This is useful for trimming: as the same configuration gets "trimmed" while going down a
    * dependency chain, it's still the same configuration but loses some of its fragments. So we need
    * a more nuanced concept of "equality" than simple reference equality.
    */
+  // TODO(b/121048710): make this reflect starlark options
   public boolean equalsOrIsSupersetOf(BuildConfiguration other) {
     return this.equals(other)
         || (other != null
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
index 6a2bc62..870116a0 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
@@ -496,6 +496,11 @@
       return this;
     }
 
+    /** Returns whether the builder contains a particular Starlark option. */
+    boolean contains(Label key) {
+      return starlarkOptions.containsKey(key);
+    }
+
     /** Removes the value for the Starlark option with the given key. */
     public Builder removeStarlarkOption(Label key) {
       starlarkOptions.remove(key);
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
index af1b663..c155b51 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Verify;
 import com.google.common.base.VerifyException;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
@@ -218,13 +219,21 @@
       FragmentsAndTransition transitionKey = new FragmentsAndTransition(depFragments, transition);
       List<BuildOptions> toOptions = transitionsMap.get(transitionKey);
       if (toOptions == null) {
+        // Default values for all build settings read in {@code transition}
+        ImmutableMap<Label, Object> defaultBuildSettingValues;
+        try {
+          defaultBuildSettingValues = StarlarkTransition.getDefaultInputValues(env, transition);
+        } catch (TransitionException e) {
+          throw new ConfiguredTargetFunction.DependencyEvaluationException(e);
+        }
         toOptions =
             applyTransition(
                 currentConfiguration.getOptions(),
                 transition,
                 depFragments,
                 ruleClassProvider,
-                !sameFragments);
+                !sameFragments,
+                defaultBuildSettingValues);
         transitionsMap.put(transitionKey, toOptions);
       }
 
@@ -233,7 +242,7 @@
       // configured target.
       try {
         ImmutableSet<SkyKey> buildSettingPackageKeys =
-            StarlarkTransition.getBuildSettingPackageKeys(transition);
+            StarlarkTransition.getBuildSettingPackageKeys(transition, "outputs");
         Map<SkyKey, SkyValue> buildSettingPackages = env.getValues(buildSettingPackageKeys);
         if (env.valuesMissing()) {
           return null;
@@ -458,18 +467,22 @@
   /**
    * Applies a configuration transition over a set of build options.
    *
-   * @return the build options for the transitioned configuration. If trimResults is true,
-   *     only options needed by the required fragments are included. Else the same options as the
+   * @return the build options for the transitioned configuration. If trimResults is true, only
+   *     options needed by the required fragments are included. Else the same options as the
    *     original input are included (with different possible values, of course).
    */
   @VisibleForTesting
-  public static List<BuildOptions> applyTransition(BuildOptions fromOptions,
+  public static List<BuildOptions> applyTransition(
+      BuildOptions fromOptions,
       ConfigurationTransition transition,
       Iterable<Class<? extends BuildConfiguration.Fragment>> requiredFragments,
-      RuleClassProvider ruleClassProvider, boolean trimResults) {
+      RuleClassProvider ruleClassProvider,
+      boolean trimResults,
+      ImmutableMap<Label, Object> buildSettingDefaults) {
+    BuildOptions fromOptionsWithDefaults =
+        addDefaultStarlarkOptions(fromOptions, buildSettingDefaults);
     // TODO(bazel-team): safety-check that this never mutates fromOptions.
-    List<BuildOptions> result = transition.apply(fromOptions);
-
+    List<BuildOptions> result = transition.apply(fromOptionsWithDefaults);
     if (!trimResults) {
       return result;
     } else {
@@ -482,6 +495,18 @@
     }
   }
 
+  private static BuildOptions addDefaultStarlarkOptions(
+      BuildOptions fromOptions, ImmutableMap<Label, Object> buildSettingDefaults) {
+    BuildOptions.Builder optionsWithDefaults = fromOptions.toBuilder();
+    for (Map.Entry<Label, Object> buildSettingDefault : buildSettingDefaults.entrySet()) {
+      Label buildSetting = buildSettingDefault.getKey();
+      if (!optionsWithDefaults.contains(buildSetting)) {
+        optionsWithDefaults.addStarlarkOption(buildSetting, buildSettingDefault.getValue());
+      }
+    }
+    return optionsWithDefaults.build();
+  }
+
   /**
    * Checks the config fragments required by a dep against the fragments in its actual
    * configuration. If any are missing, triggers a descriptive "missing fragments" error.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionTransitionUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionTransitionUtil.java
index 0237af7..07f2649 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionTransitionUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionTransitionUtil.java
@@ -160,6 +160,7 @@
     try (Mutability mutability = Mutability.create("build_settings")) {
       SkylarkDict<String, Object> dict = SkylarkDict.withMutability(mutability);
 
+      // Add native options
       for (Map.Entry<String, OptionInfo> entry : optionInfoMap.entrySet()) {
         String optionName = entry.getKey();
         String optionKey = COMMAND_LINE_OPTION_PREFIX + optionName;
@@ -182,7 +183,14 @@
         }
       }
 
-      // TODO(juliexxia): Allowing reading of starlark-defined build settings.
+      // Add Starlark options
+      for (Map.Entry<Label, Object> starlarkOption : buildOptions.getStarlarkOptions().entrySet()) {
+        if (!remainingInputs.remove(starlarkOption.getKey().toString())) {
+          continue;
+        }
+        dict.put(starlarkOption.getKey().toString(), starlarkOption.getValue(), null, mutability);
+      }
+
       if (!remainingInputs.isEmpty()) {
         throw new EvalException(
             starlarkTransition.getLocationForErrorReporting(),
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkTransition.java
index 46974c0..1c8ad1b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkTransition.java
@@ -14,7 +14,10 @@
 package com.google.devtools.build.lib.analysis.skylark;
 
 import static com.google.devtools.build.lib.analysis.skylark.FunctionTransitionUtil.COMMAND_LINE_OPTION_PREFIX;
+import static com.google.devtools.build.lib.packages.RuleClass.Builder.SKYLARK_BUILD_SETTING_DEFAULT_ATTR_NAME;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
@@ -29,6 +32,7 @@
 import com.google.devtools.build.lib.skyframe.PackageValue;
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.syntax.Type.ConversionException;
+import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import java.util.List;
@@ -54,6 +58,10 @@
     return starlarkDefinedConfigTransition.getEventHandler().hasErrors();
   }
 
+  private List<String> getInputs() {
+    return starlarkDefinedConfigTransition.getInputs();
+  }
+
   private List<String> getOutputs() {
     return starlarkDefinedConfigTransition.getOutputs();
   }
@@ -78,23 +86,27 @@
   }
 
   /**
-   * For a given transition, find all Starlark-defined build settings that were set by that
-   * transition. Then return all package keys for those flags.
+   * For a given transition, find all relevant Starlark-defined build settings. Then return all
+   * package keys for those flags.
    *
    * <p>Currently this method does not handle the possibility of aliased build settings. We may not
    * actually load the package that actually contains the build setting but we won't know until we
    * fetch the actual target.
+   *
+   * @param root transition to inspect
+   * @param inputsOrOutputs whether to return inputs or outputs
    */
   // TODO(juliexxia): handle the possibility of aliased build settings.
-  public static ImmutableSet<SkyKey> getBuildSettingPackageKeys(ConfigurationTransition root) {
+  public static ImmutableSet<SkyKey> getBuildSettingPackageKeys(
+      ConfigurationTransition root, String inputsOrOutputs) {
     ImmutableSet.Builder<SkyKey> keyBuilder = new ImmutableSet.Builder<>();
     try {
       root.visit(
           (StarlarkTransitionVisitor)
               transition -> {
-                for (Label setting : getChangedStarlarkSettings(transition)) {
-                  keyBuilder.add(PackageValue.key(setting.getPackageIdentifier()));
-                }
+                keyBuilder.addAll(
+                    getBuildSettingPackageKeys(
+                        getRelevantStarlarkSettingsFromTransition(transition, inputsOrOutputs)));
               });
     } catch (TransitionException e) {
       // Not actually thrown in the visitor, but declared.
@@ -102,6 +114,15 @@
     return keyBuilder.build();
   }
 
+  private static ImmutableSet<SkyKey> getBuildSettingPackageKeys(
+      ImmutableSet<Label> buildSettings) {
+    ImmutableSet.Builder<SkyKey> keyBuilder = new ImmutableSet.Builder<>();
+    for (Label setting : buildSettings) {
+      keyBuilder.add(PackageValue.key(setting.getPackageIdentifier()));
+    }
+    return keyBuilder.build();
+  }
+
   /**
    * Method to be called after Starlark-transitions are applied. Handles events and checks outputs.
    *
@@ -138,27 +159,11 @@
     root.visit(
         (StarlarkTransitionVisitor)
             transition -> {
-              List<Label> changedSettings = getChangedStarlarkSettings(transition);
+              ImmutableSet<Label> changedSettings =
+                  getRelevantStarlarkSettingsFromTransition(transition, "outputs");
               for (Label setting : changedSettings) {
-                Package buildSettingPackage =
-                    ((PackageValue)
-                            buildSettingPackages.get(
-                                PackageValue.key(setting.getPackageIdentifier())))
-                        .getPackage();
-                Target buildSettingTarget;
-                try {
-                  buildSettingTarget = buildSettingPackage.getTarget(setting.getName());
-                } catch (NoSuchTargetException e) {
-                  throw new TransitionException(e);
-                }
-                if (buildSettingTarget.getAssociatedRule() == null
-                    || buildSettingTarget.getAssociatedRule().getRuleClassObject().getBuildSetting()
-                        == null) {
-                  throw new TransitionException(
-                      String.format(
-                          "attempting to transition on '%s' which is not a build setting",
-                          setting));
-                }
+                Target buildSettingTarget =
+                    getAndCheckBuildSettingTarget(buildSettingPackages, setting);
                 changedSettingToType.put(
                     setting,
                     buildSettingTarget
@@ -183,11 +188,79 @@
     }
   }
 
-  private static List<Label> getChangedStarlarkSettings(StarlarkTransition transition) {
-    return transition.getOutputs().stream()
-        .filter(setting -> !setting.startsWith(COMMAND_LINE_OPTION_PREFIX))
-        .map(Label::parseAbsoluteUnchecked)
-        .collect(Collectors.toList());
+  /**
+   * For a given transition, find all Starlark build settings that are read while applying it, then
+   * return a map of their label to their default values.
+   */
+  public static ImmutableMap<Label, Object> getDefaultInputValues(
+      Environment env, ConfigurationTransition root)
+      throws TransitionException, InterruptedException {
+    ImmutableSet<SkyKey> buildSettingInputPackageKeys = getBuildSettingPackageKeys(root, "inputs");
+    Map<SkyKey, SkyValue> buildSettingPackages = env.getValues(buildSettingInputPackageKeys);
+    if (env.valuesMissing()) {
+      return null;
+    }
+    return getDefaultInputValues(buildSettingPackages, root);
+  }
+
+  /**
+   * For a given transition, find all Starlark build settings that are read while applying it, then
+   * return a map of their label to their default values.
+   */
+  public static ImmutableMap<Label, Object> getDefaultInputValues(
+      Map<SkyKey, SkyValue> buildSettingPackages, ConfigurationTransition root)
+      throws TransitionException {
+    ImmutableMap.Builder<Label, Object> defaultValues = new ImmutableMap.Builder<>();
+    root.visit(
+        (StarlarkTransitionVisitor)
+            transition -> {
+              ImmutableSet<Label> settings =
+                  getRelevantStarlarkSettingsFromTransition(transition, "inputs");
+              for (Label setting : settings) {
+                Target buildSettingTarget =
+                    getAndCheckBuildSettingTarget(buildSettingPackages, setting);
+                defaultValues.put(
+                    setting,
+                    buildSettingTarget
+                        .getAssociatedRule()
+                        .getAttributeContainer()
+                        .getAttr(SKYLARK_BUILD_SETTING_DEFAULT_ATTR_NAME));
+              }
+            });
+    return defaultValues.build();
+  }
+
+  private static Target getAndCheckBuildSettingTarget(
+      Map<SkyKey, SkyValue> buildSettingPackages, Label setting) throws TransitionException {
+    Package buildSettingPackage =
+        ((PackageValue) buildSettingPackages.get(PackageValue.key(setting.getPackageIdentifier())))
+            .getPackage();
+    Preconditions.checkNotNull(
+        buildSettingPackage, "Reading build setting for which we don't have a package");
+    Target buildSettingTarget;
+    try {
+      buildSettingTarget = buildSettingPackage.getTarget(setting.getName());
+    } catch (NoSuchTargetException e) {
+      throw new TransitionException(e);
+    }
+    if (buildSettingTarget.getAssociatedRule() == null
+        || buildSettingTarget.getAssociatedRule().getRuleClassObject().getBuildSetting() == null) {
+      throw new TransitionException(
+          String.format("attempting to transition on '%s' which is not a build setting", setting));
+    }
+    return buildSettingTarget;
+  }
+
+  // TODO(juliexxia): use an enum for "inputs"/"outputs" here and elsewhere in starlark transitions.
+  private static ImmutableSet<Label> getRelevantStarlarkSettingsFromTransition(
+      StarlarkTransition transition, String inputOrOutput) {
+    List<String> toGet =
+        inputOrOutput.equals("inputs") ? transition.getInputs() : transition.getOutputs();
+    return ImmutableSet.copyOf(
+        toGet.stream()
+            .filter(setting -> !setting.startsWith(COMMAND_LINE_OPTION_PREFIX))
+            .map(Label::parseAbsoluteUnchecked)
+            .collect(Collectors.toSet()));
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
index c714150..4d7cd1f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
@@ -16,6 +16,7 @@
 import com.google.common.base.Predicates;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Multimap;
@@ -304,23 +305,34 @@
       ImmutableSortedSet<Class<? extends BuildConfiguration.Fragment>> depFragments =
           fragmentsMap.get(key.getLabel());
       if (depFragments != null) {
+        ImmutableMap<Label, Object> defaultBuildSettingValues =
+            StarlarkTransition.getDefaultInputValues(env, key.getTransition());
+        if (env.valuesMissing()) {
+          return null;
+        }
         List<BuildOptions> toOptions =
             ConfigurationResolver.applyTransition(
-                fromOptions, key.getTransition(), depFragments, ruleClassProvider, true);
+                fromOptions,
+                key.getTransition(),
+                depFragments,
+                ruleClassProvider,
+                true,
+                defaultBuildSettingValues);
         for (BuildOptions toOption : toOptions) {
           configSkyKeys.add(
               BuildConfigurationValue.key(
                   depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption)));
         }
         // Post-process transitions on starlark build settings
-        ImmutableSet<SkyKey> buildSettingPackageKeys =
-            StarlarkTransition.getBuildSettingPackageKeys(key.getTransition());
-        Map<SkyKey, SkyValue> buildSettingPackages = env.getValues(buildSettingPackageKeys);
+        ImmutableSet<SkyKey> buildSettingOutputPackageKeys =
+            StarlarkTransition.getBuildSettingPackageKeys(key.getTransition(), "outputs");
+        Map<SkyKey, SkyValue> buildSettingOutputPackages =
+            env.getValues(buildSettingOutputPackageKeys);
         if (env.valuesMissing()) {
           return null;
         }
         StarlarkTransition.validate(
-            key.getTransition(), buildSettingPackages, toOptions, env.getListener());
+            key.getTransition(), buildSettingOutputPackages, toOptions, env.getListener());
       }
     }
     Map<SkyKey, SkyValue> configsResult = env.getValues(configSkyKeys);
@@ -337,11 +349,23 @@
       ImmutableSortedSet<Class<? extends BuildConfiguration.Fragment>> depFragments =
           fragmentsMap.get(key.getLabel());
       if (depFragments != null) {
-        for (BuildOptions toOptions : ConfigurationResolver.applyTransition(
-            fromOptions, key.getTransition(), depFragments, ruleClassProvider, true)) {
+        ImmutableMap<Label, Object> defaultBuildSettingValues =
+            StarlarkTransition.getDefaultInputValues(env, key.getTransition());
+        if (env.valuesMissing()) {
+          return null;
+        }
+        List<BuildOptions> toOptions =
+            ConfigurationResolver.applyTransition(
+                fromOptions,
+                key.getTransition(),
+                depFragments,
+                ruleClassProvider,
+                true,
+                defaultBuildSettingValues);
+        for (BuildOptions toOption : toOptions) {
           SkyKey configKey =
               BuildConfigurationValue.key(
-                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOptions));
+                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption));
           BuildConfigurationValue configValue =
               ((BuildConfigurationValue) configsResult.get(configKey));
           // configValue will be null here if there was an exception thrown during configuration
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index ffdb8f9..c1bfd34 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -1905,23 +1905,36 @@
         if (key.getTransition() == NullTransition.INSTANCE) {
           continue;
         }
+        ImmutableMap<Label, Object> defaultInputValues;
+        try {
+          defaultInputValues =
+              StarlarkTransition.getDefaultInputValues(
+                  collectBuildSettingValues(key.getTransition(), eventHandler, "inputs"),
+                  key.getTransition());
+        } catch (TransitionException e) {
+          eventHandler.handle(Event.error(e.getMessage()));
+          continue;
+        }
+
         List<BuildOptions> toOptions =
             ConfigurationResolver.applyTransition(
-                fromOptions, key.getTransition(), depFragments, ruleClassProvider, true);
+                fromOptions,
+                key.getTransition(),
+                depFragments,
+                ruleClassProvider,
+                true,
+                defaultInputValues);
         for (BuildOptions toOption : toOptions) {
           configSkyKeys.add(
               BuildConfigurationValue.key(
                   depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption)));
         }
-        ImmutableSet<SkyKey> buildSettingPackageKeys =
-            StarlarkTransition.getBuildSettingPackageKeys(key.getTransition());
-        EvaluationResult<SkyValue> buildSettingsResult =
-            evaluateSkyKeys(eventHandler, buildSettingPackageKeys, true);
-        ImmutableMap.Builder<SkyKey, SkyValue> buildSettingValues = new ImmutableMap.Builder<>();
-        buildSettingPackageKeys.forEach(k -> buildSettingValues.put(k, buildSettingsResult.get(k)));
         try {
           StarlarkTransition.validate(
-              key.getTransition(), buildSettingValues.build(), toOptions, eventHandler);
+              key.getTransition(),
+              collectBuildSettingValues(key.getTransition(), eventHandler, "outputs"),
+              toOptions,
+              eventHandler);
         } catch (TransitionException e) {
           eventHandler.handle(Event.error(e.getMessage()));
         }
@@ -1940,13 +1953,28 @@
           builder.put(key, null);
           continue;
         }
-
-        for (BuildOptions toOptions :
+        ImmutableMap<Label, Object> defaultInputValues;
+        try {
+          defaultInputValues =
+              StarlarkTransition.getDefaultInputValues(
+                  collectBuildSettingValues(key.getTransition(), eventHandler, "inputs"),
+                  key.getTransition());
+        } catch (TransitionException e) {
+          eventHandler.handle(Event.error(e.getMessage()));
+          continue;
+        }
+        List<BuildOptions> toOptions =
             ConfigurationResolver.applyTransition(
-                fromOptions, key.getTransition(), depFragments, ruleClassProvider, true)) {
+                fromOptions,
+                key.getTransition(),
+                depFragments,
+                ruleClassProvider,
+                true,
+                defaultInputValues);
+        for (BuildOptions toOption : toOptions) {
           SkyKey configKey =
               BuildConfigurationValue.key(
-                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOptions));
+                  depFragments, BuildOptions.diffForReconstruction(defaultBuildOptions, toOption));
           BuildConfigurationValue configValue =
               ((BuildConfigurationValue) configsResult.get(configKey));
           // configValue will be null here if there was an exception thrown during configuration
@@ -1960,6 +1988,20 @@
     return builder;
   }
 
+  private Map<SkyKey, SkyValue> collectBuildSettingValues(
+      ConfigurationTransition transition,
+      ExtendedEventHandler eventHandler,
+      String inputsOrOutputs) {
+    ImmutableSet<SkyKey> buildSettingInputPackageKeys =
+        StarlarkTransition.getBuildSettingPackageKeys(transition, inputsOrOutputs);
+    EvaluationResult<SkyValue> buildSettingsResult =
+        evaluateSkyKeys(eventHandler, buildSettingInputPackageKeys, true);
+    ImmutableMap.Builder<SkyKey, SkyValue> buildSettingInputValues = new ImmutableMap.Builder<>();
+    buildSettingInputPackageKeys.forEach(
+        k -> buildSettingInputValues.put(k, buildSettingsResult.get(k)));
+    return buildSettingInputValues.build();
+  }
+
   /**
    * Returns whether configurations should trim their fragments to only those needed by targets and
    * their transitive dependencies.
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
index 4fa0585..c5bf9d0 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
@@ -35,9 +35,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/**
- * Tests for StarlarkRuleTransitionProvider.
- */
+/** Tests for StarlarkRuleTransitionProvider. */
 @RunWith(JUnit4.class)
 public class StarlarkRuleTransitionProviderTest extends BuildViewTestCase {
 
@@ -541,26 +539,122 @@
         "attempting to transition on '//test:cute-animal-fact' which is not a build setting");
   }
 
-  // TODO(juliexxia): flip this test when we can read build settings.
   @Test
-  public void testCantReadNonNativeBuildSetting() throws Exception {
+  public void testTransitionReadsBuildSetting_fromDefault() throws Exception {
     setSkylarkSemanticsOptions(
         "--experimental_starlark_config_transitions=true", "--experimental_build_setting_api");
     scratch.file(
         "test/transitions.bzl",
         "def _transition_impl(settings, attr):",
-        "  return {'//test:cute-animal-fact': settings['//test:cute-animal-fact']+' ADDED'}",
+        "  return {'//test:cute-animal-fact': settings['//test:cute-animal-fact']+' <- TRUE'}",
         "my_transition = transition(",
         "  implementation = _transition_impl,",
         "  inputs = ['//test:cute-animal-fact'],",
-        "  outputs = ['//test:i-am-not-real']",
+        "  outputs = ['//test:cute-animal-fact']",
+        ")");
+    writeRulesBuildSettingsAndBUILDforBuildSettingTransitionTests();
+
+    BuildConfiguration configuration = getConfiguration(getConfiguredTarget("//test"));
+    assertThat(
+            configuration
+                .getOptions()
+                .getStarlarkOptions()
+                .get(Label.parseAbsoluteUnchecked("//test:cute-animal-fact")))
+        .isEqualTo("cows produce more milk when they listen to soothing music <- TRUE");
+  }
+
+  @Test
+  public void testTransitionReadsBuildSetting_fromCommandLine() throws Exception {
+    setSkylarkSemanticsOptions(
+        "--experimental_starlark_config_transitions=true", "--experimental_build_setting_api");
+    scratch.file(
+        "test/transitions.bzl",
+        "def _transition_impl(settings, attr):",
+        "  return {'//test:cute-animal-fact': settings['//test:cute-animal-fact']+' <- TRUE'}",
+        "my_transition = transition(",
+        "  implementation = _transition_impl,",
+        "  inputs = ['//test:cute-animal-fact'],",
+        "  outputs = ['//test:cute-animal-fact']",
+        ")");
+    writeRulesBuildSettingsAndBUILDforBuildSettingTransitionTests();
+
+    useConfiguration(ImmutableMap.of("//test:cute-animal-fact", "rats are ticklish"));
+
+    BuildConfiguration configuration = getConfiguration(getConfiguredTarget("//test"));
+    assertThat(
+            configuration
+                .getOptions()
+                .getStarlarkOptions()
+                .get(Label.parseAbsoluteUnchecked("//test:cute-animal-fact")))
+        .isEqualTo("rats are ticklish <- TRUE");
+  }
+
+  @Test
+  public void testTransitionReadsBuildSetting_notABuildSetting() throws Exception {
+    setSkylarkSemanticsOptions(
+        "--experimental_starlark_config_transitions=true", "--experimental_build_setting_api");
+    writeWhitelistFile();
+    scratch.file(
+        "test/transitions.bzl",
+        "def _transition_impl(settings, attr):",
+        "  return {'//test:cute-animal-fact': 'puffins mate for life'}",
+        "my_transition = transition(",
+        "  implementation = _transition_impl,",
+        "  inputs = ['//test:cute-animal-fact'],",
+        "  outputs = ['//test:cute-animal-fact']",
+        ")");
+    scratch.file(
+        "test/rules.bzl",
+        "load('//test:transitions.bzl', 'my_transition')",
+        "def _rule_impl(ctx):",
+        "  return []",
+        "my_rule = rule(",
+        "  implementation = _rule_impl,",
+        "  cfg = my_transition,",
+        "  attrs = {",
+        "    '_whitelist_function_transition': attr.label(",
+        "        default = '//tools/whitelists/function_transition_whitelist',",
+        "    ),",
+        "  },",
+        ")");
+    scratch.file(
+        "test/build_settings.bzl",
+        "def _impl(ctx):",
+        "  return []",
+        "non_build_setting = rule(implementation = _impl)");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:rules.bzl', 'my_rule')",
+        "load('//test:build_settings.bzl', 'non_build_setting')",
+        "my_rule(name = 'test')",
+        "non_build_setting(name = 'cute-animal-fact')");
+
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//test");
+    assertContainsEvent(
+        "attempting to transition on '//test:cute-animal-fact' which is not a build setting");
+  }
+
+  @Test
+  public void testTransitionReadsBuildSetting_noSuchTarget() throws Exception {
+    setSkylarkSemanticsOptions(
+        "--experimental_starlark_config_transitions=true", "--experimental_build_setting_api");
+    scratch.file(
+        "test/transitions.bzl",
+        "def _transition_impl(settings, attr):",
+        "  return {'//test:cute-animal-fact': settings['//test:cute-animal-fact']+' <- TRUE'}",
+        "my_transition = transition(",
+        "  implementation = _transition_impl,",
+        "  inputs = ['//test:i-am-not-real'],",
+        "  outputs = ['//test:cute-animal-fact']",
         ")");
     writeRulesBuildSettingsAndBUILDforBuildSettingTransitionTests();
 
     reporter.removeHandler(failFastHandler);
     getConfiguredTarget("//test");
     assertContainsEvent(
-        "transition inputs [//test:cute-animal-fact] do not correspond to valid settings");
+        "no such target '//test:i-am-not-real': target "
+            + "'i-am-not-real' not declared in package 'test'");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java
index b5d1ec49..dc57cd7 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ConfigurationsForTargetsWithTrimmedConfigurationsTest.java
@@ -23,6 +23,7 @@
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
@@ -579,9 +580,14 @@
   private List<String> getTestFilterOptionValue(ConfigurationTransition transition)
       throws Exception {
     ImmutableList.Builder<String> outValues = ImmutableList.builder();
-    for (BuildOptions toOptions : ConfigurationResolver.applyTransition(
-        getTargetConfiguration().getOptions(), transition,
-        ruleClassProvider.getAllFragments(), ruleClassProvider, false)) {
+    for (BuildOptions toOptions :
+        ConfigurationResolver.applyTransition(
+            getTargetConfiguration().getOptions(),
+            transition,
+            ruleClassProvider.getAllFragments(),
+            ruleClassProvider,
+            false,
+            ImmutableMap.of())) {
       outValues.add(toOptions.get(TestConfiguration.TestOptions.class).testFilter);
     }
     return outValues.build();