Report fragments Starlark transitions use.

Also add some straggler native transitions.

With all transitions converted, also remove legacy
PatchTransition.patch(BuildOptions) and
SplitTransition.split(BuildOptions).

In service of https://github.com/bazelbuild/bazel/issues/11258.

PiperOrigin-RevId: 317383960
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ConfigurationTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ConfigurationTransition.java
index 6232b13..4b62ec5 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ConfigurationTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ConfigurationTransition.java
@@ -42,6 +42,19 @@
   }
 
   /**
+   * {@link #requiresOptionFragments()} variation for Starlark transitions, which need a {@link
+   * BuildOptions} instance to map required options to their {@link FragmentOptions}.
+   *
+   * <p>Non-Starlark transitions should override {@link #requiresOptionFragments()} and ignore this.
+   *
+   * <p>Callers may also ignore this if they know they're not calling into a Starlark transition.
+   */
+  default ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments(
+      BuildOptions options) {
+    return requiresOptionFragments();
+  }
+
+  /**
    * Returns the map of {@code BuildOptions} after applying this transition. The returned map keys
    * are only used for dealing with split transitions. Patch transitions, including internal, native
    * Patch transitions, should return a single entry map with key {@code PATCH_TRANSITION_KEY}.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/PatchTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/PatchTransition.java
index 99bff12..79341ba 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/PatchTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/PatchTransition.java
@@ -58,22 +58,6 @@
   /**
    * Applies the transition.
    *
-   * <p>This method is being deprecated (https://github.com/bazelbuild/bazel/issues/11258). Please
-   * use {@link #patch(BuildOptionsView, EventHandler)} for new uses.
-   *
-   * @param options the options representing the input configuration to this transition. <b>DO NOT
-   *     MODIFY THIS VARIABLE WITHOUT CLONING IT FIRST!</b>
-   * @param eventHandler
-   * @return the options representing the desired post-transition configuration
-   */
-  default BuildOptions patch(BuildOptions options, EventHandler eventHandler) {
-    throw new UnsupportedOperationException(
-        "Either this or patch(BuildOptionsView) must be overridden");
-  }
-
-  /**
-   * Applies the transition.
-   *
    * <p>Blaze throws an {@link IllegalArgumentException} if this method reads any options fragment
    * not declared in {@link ConfigurationTransition#requiresOptionFragments}.
    *
@@ -82,11 +66,7 @@
    * @param eventHandler
    * @return the options representing the desired post-transition configuration
    */
-  default BuildOptions patch(BuildOptionsView options, EventHandler eventHandler) {
-    // Escape hatch for implementers of the BuildOptions method: provide uninhibited access. When
-    // all implementers use this variation we'll remove this default implementation.
-    return patch(options.underlying(), eventHandler);
-  }
+  BuildOptions patch(BuildOptionsView options, EventHandler eventHandler);
 
   @Override
   default Map<String, BuildOptions> apply(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/SplitTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/SplitTransition.java
index d3b7d0b..c39e7c1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/SplitTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/SplitTransition.java
@@ -42,32 +42,12 @@
    * is a noop. The key values are used as dict keys in ctx.split_attr, so human-readable strings
    * are recommended.
    *
-   * <p>This method is being deprecated (https://github.com/bazelbuild/bazel/issues/11258). Please
-   * use {@link #split(BuildOptionsView, EventHandler)} for new uses.
-   *
-   * <p>Returning an empty or null list triggers a {@link RuntimeException}.
-   */
-  default Map<String, BuildOptions> split(BuildOptions buildOptions, EventHandler eventHandler) {
-    throw new UnsupportedOperationException(
-        "Either this or patch(BuildOptionsView) must be overridden");
-  }
-
-  /**
-   * Returns the map of {@code BuildOptions} after splitting, or the original options if this split
-   * is a noop. The key values are used as dict keys in ctx.split_attr, so human-readable strings
-   * are recommended.
-   *
    * <p>Blaze throws an {@link IllegalArgumentException} if this method reads any options fragment
    * not declared in {@link ConfigurationTransition#requiresOptionFragments}.
    *
    * <p>Returning an empty or null list triggers a {@link RuntimeException}.
    */
-  default Map<String, BuildOptions> split(
-      BuildOptionsView buildOptions, EventHandler eventHandler) {
-    // Escape hatch for implementers of the BuildOptions method: provide uninhibited access. When
-    // all implementers use this variation we'll remove this default implementation.
-    return split(buildOptions.underlying(), eventHandler);
-  }
+  Map<String, BuildOptions> split(BuildOptionsView buildOptions, EventHandler eventHandler);
 
   /**
    * Returns true iff {@code option} and {@code splitOptions} are equal.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/TransitionUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/TransitionUtil.java
index 60a20ea..059e53c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/TransitionUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/TransitionUtil.java
@@ -26,6 +26,6 @@
    */
   public static BuildOptionsView restrict(
       ConfigurationTransition transition, BuildOptions options) {
-    return new BuildOptionsView(options, transition.requiresOptionFragments());
+    return new BuildOptionsView(options, transition.requiresOptionFragments(options));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java
index f089496..b99c527 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java
@@ -135,7 +135,7 @@
   }
 
   /** For all the options in the BuildOptions, build a map from option name to its information. */
-  private static Map<String, OptionInfo> buildOptionInfo(BuildOptions buildOptions) {
+  static ImmutableMap<String, OptionInfo> buildOptionInfo(BuildOptions buildOptions) {
     ImmutableMap.Builder<String, OptionInfo> builder = new ImmutableMap.Builder<>();
 
     ImmutableSet<Class<? extends FragmentOptions>> optionClasses =
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributeTransitionProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributeTransitionProvider.java
index 78a6fd2..ebb6cc6 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributeTransitionProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributeTransitionProvider.java
@@ -21,6 +21,7 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
 import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
@@ -104,7 +105,11 @@
      */
     @Override
     public final Map<String, BuildOptions> split(
-        BuildOptions buildOptions, EventHandler eventHandler) {
+        BuildOptionsView buildOptionsView, EventHandler eventHandler) {
+      // Starlark transitions already have logic to enforce they only access declared inputs and
+      // outputs. Rather than complicate BuildOptionsView with more access points to BuildOptions,
+      // we just use the original BuildOptions and trust the transition's enforcement logic.
+      BuildOptions buildOptions = buildOptionsView.underlying();
       try {
         return applyAndValidate(
             buildOptions, starlarkDefinedConfigTransition, attrObject, eventHandler);
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleTransitionProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleTransitionProvider.java
index 34d2a26..6e0ae68 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleTransitionProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleTransitionProvider.java
@@ -19,6 +19,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
 import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
@@ -106,8 +107,12 @@
     // TODO(b/121134880): validate that the targets these transitions are applied on don't read any
     // attributes that are then configured by the outputs of these transitions.
     @Override
-    public BuildOptions patch(BuildOptions buildOptions, EventHandler eventHandler) {
+    public BuildOptions patch(BuildOptionsView buildOptionsView, EventHandler eventHandler) {
       Map<String, BuildOptions> result;
+      // Starlark transitions already have logic to enforce they only access declared inputs and
+      // outputs. Rather than complicate BuildOptionsView with more access points to BuildOptions,
+      // we just use the original BuildOptions and trust the transition's enforcement logic.
+      BuildOptions buildOptions = buildOptionsView.underlying();
       try {
         result =
             applyAndValidate(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkTransition.java
index 2456136..9a8d47c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkTransition.java
@@ -19,11 +19,14 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
+import com.google.devtools.build.lib.analysis.starlark.FunctionTransitionUtil.OptionInfo;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.packages.BuildType.SelectorList;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
@@ -77,6 +80,28 @@
     return starlarkDefinedConfigTransition.getOutputs();
   }
 
+  @Override
+  public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments(
+      BuildOptions buildOptions) {
+    // TODO(bazel-team): complexity cleanup: merge buildOptionInfo with TransitiveOptionDetails.
+    Map<String, OptionInfo> optionToFragment = FunctionTransitionUtil.buildOptionInfo(buildOptions);
+    ImmutableSet.Builder<Class<? extends FragmentOptions>> fragments = ImmutableSet.builder();
+    for (String optionStarlarkName : Iterables.concat(getInputs(), getOutputs())) {
+      // TODO(bazel-team): support Starlark flags.
+      if (!optionStarlarkName.startsWith(COMMAND_LINE_OPTION_PREFIX)) {
+        continue;
+      }
+      String optionNativeName = optionStarlarkName.substring(COMMAND_LINE_OPTION_PREFIX.length());
+      OptionInfo optionInfo = optionToFragment.get(optionNativeName);
+      // A null optionInfo means the flag is invalid. Starlark transitions independently catch and
+      // report that (search the code for "do not correspond to valid settings").
+      if (optionInfo != null) {
+        fragments.add(optionInfo.getOptionClass());
+      }
+    }
+    return fragments.build();
+  }
+
   /** Exception class for exceptions thrown during application of a starlark-defined transition */
   // TODO(juliexxia): add more information to this exception e.g. originating target of transition
   public static class TransitionException extends Exception {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
index 57ed3fb..31ecaf9 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -28,6 +28,7 @@
 
 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.Lists;
 import com.google.devtools.build.lib.analysis.BaseRuleClasses;
@@ -35,7 +36,9 @@
 import com.google.devtools.build.lib.analysis.RuleDefinition;
 import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
 import com.google.devtools.build.lib.analysis.config.CoreOptions;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.analysis.config.HostTransition;
 import com.google.devtools.build.lib.analysis.config.TransitionFactories;
 import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
@@ -231,7 +234,7 @@
   /** Android Split configuration transition for properly handling native dependencies */
   public static final class AndroidSplitTransition
       implements SplitTransition, AndroidSplitTransititionApi {
-    private static void setCrosstoolToAndroid(BuildOptions options) {
+    private static void setCrosstoolToAndroid(BuildOptionsView options) {
       AndroidConfiguration.Options androidOptions = options.get(AndroidConfiguration.Options.class);
 
       CppOptions cppOptions = options.get(CppOptions.class);
@@ -243,8 +246,17 @@
     }
 
     @Override
+    public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments() {
+      return ImmutableSet.of(
+          AndroidConfiguration.Options.class,
+          CoreOptions.class,
+          CppOptions.class,
+          PlatformOptions.class);
+    }
+
+    @Override
     public ImmutableMap<String, BuildOptions> split(
-        BuildOptions buildOptions, EventHandler eventHandler) {
+        BuildOptionsView buildOptions, EventHandler eventHandler) {
 
       AndroidConfiguration.Options androidOptions =
           buildOptions.get(AndroidConfiguration.Options.class);
@@ -256,21 +268,22 @@
         if (androidOptions.cpu.isEmpty()
             || androidCrosstoolTop == null
             || androidCrosstoolTop.equals(cppOptions.crosstoolTop)) {
-          return ImmutableMap.of(buildOptions.get(CoreOptions.class).cpu, buildOptions);
+          return ImmutableMap.of(
+              buildOptions.get(CoreOptions.class).cpu, buildOptions.underlying());
 
         } else {
 
-          BuildOptions splitOptions = buildOptions.clone();
+          BuildOptionsView splitOptions = buildOptions.clone();
           splitOptions.get(CoreOptions.class).cpu = androidOptions.cpu;
           setCommonAndroidOptions(androidOptions, splitOptions);
-          return ImmutableMap.of(androidOptions.cpu, splitOptions);
+          return ImmutableMap.of(androidOptions.cpu, splitOptions.underlying());
         }
 
       } else {
 
         ImmutableMap.Builder<String, BuildOptions> result = ImmutableMap.builder();
         for (String cpu : ImmutableSortedSet.copyOf(androidOptions.fatApkCpus)) {
-          BuildOptions splitOptions = buildOptions.clone();
+          BuildOptionsView splitOptions = buildOptions.clone();
           // Disable fat APKs for the child configurations.
           splitOptions.get(AndroidConfiguration.Options.class).fatApkCpus = ImmutableList.of();
 
@@ -279,14 +292,14 @@
           splitOptions.get(AndroidConfiguration.Options.class).cpu = cpu;
           splitOptions.get(CoreOptions.class).cpu = cpu;
           setCommonAndroidOptions(androidOptions, splitOptions);
-          result.put(cpu, splitOptions);
+          result.put(cpu, splitOptions.underlying());
         }
         return result.build();
       }
     }
 
     private void setCommonAndroidOptions(
-        AndroidConfiguration.Options androidOptions, BuildOptions newOptions) {
+        AndroidConfiguration.Options androidOptions, BuildOptionsView newOptions) {
       newOptions.get(CppOptions.class).cppCompiler = androidOptions.cppCompiler;
       newOptions.get(CppOptions.class).libcTopLabel = androidOptions.androidLibcTopLabel;
       newOptions.get(CppOptions.class).dynamicMode = androidOptions.dynamicMode;
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/CircularDependencyTest.java b/src/test/java/com/google/devtools/build/lib/analysis/CircularDependencyTest.java
index 47f1fcb..077af5b 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/CircularDependencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/CircularDependencyTest.java
@@ -23,9 +23,12 @@
 import static org.junit.Assert.assertThrows;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
 import com.google.devtools.build.lib.analysis.config.CoreOptions;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
@@ -268,17 +271,24 @@
                         @Override
                         public SplitTransition create(AttributeTransitionData data) {
                           return new SplitTransition() {
+
+                            @Override
+                            public ImmutableSet<Class<? extends FragmentOptions>>
+                                requiresOptionFragments() {
+                              return ImmutableSet.of(CoreOptions.class);
+                            }
+
                             @Override
                             public Map<String, BuildOptions> split(
-                                BuildOptions options, EventHandler eventHandler) {
+                                BuildOptionsView options, EventHandler eventHandler) {
                               String define = data.attributes().get("define", STRING);
-                              BuildOptions newOptions = options.clone();
+                              BuildOptionsView newOptions = options.clone();
                               CoreOptions optionsFragment = newOptions.get(CoreOptions.class);
                               optionsFragment.commandLineBuildVariables =
                                   optionsFragment.commandLineBuildVariables.stream()
                                       .filter((pair) -> !pair.getKey().equals(define))
                                       .collect(toImmutableList());
-                              return ImmutableMap.of("define_cleaner", newOptions);
+                              return ImmutableMap.of("define_cleaner", newOptions.underlying());
                             }
                           };
                         }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
index 7206364..012886b 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
@@ -25,13 +25,19 @@
 import com.google.common.eventbus.EventBus;
 import com.google.devtools.build.lib.analysis.StarlarkRuleTransitionProviderTest.DummyTestLoader;
 import com.google.devtools.build.lib.analysis.config.CoreOptions;
+import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
 import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.AttributeTransitionData;
+import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper;
 import com.google.devtools.build.lib.packages.Provider;
+import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.StarlarkProvider;
 import com.google.devtools.build.lib.packages.StructImpl;
 import com.google.devtools.build.lib.packages.util.BazelMockAndroidSupport;
+import com.google.devtools.build.lib.rules.cpp.CppOptions;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 import com.google.devtools.build.lib.util.Fingerprint;
@@ -1795,6 +1801,52 @@
   }
 
   @Test
+  public void starlarkSplitTransitionRequiredFragments() throws Exception {
+    // All Starlark rule transitions are patch transitions, while all Starlark attribute transitions
+    // are split transitions.
+    writeAllowlistFile();
+    scratch.file(
+        "test/my_rule.bzl",
+        "load('//myinfo:myinfo.bzl', 'MyInfo')",
+        "def transition_func(settings, attr):",
+        "  return [",
+        "    {'//command_line_option:copt': []}", // --copt is a C++ option.
+        "  ]",
+        "my_transition = transition(",
+        "  implementation = transition_func,",
+        "  inputs = [],",
+        "  outputs = ['//command_line_option:copt'])",
+        "def impl(ctx): ",
+        "  return []",
+        "my_rule = rule(",
+        "  implementation = impl,",
+        "  attrs = {",
+        "    'dep': attr.label(cfg = my_transition),",
+        "    '_whitelist_function_transition': attr.label(",
+        "        default = '//tools/whitelists/function_transition_whitelist',",
+        "    ),",
+        "  })");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:my_rule.bzl', 'my_rule')",
+        "my_rule(name = 'test', dep = ':dep')",
+        "cc_library(name = 'dep', srcs = ['dep.c'])");
+
+    ConfiguredTargetAndData ct = getConfiguredTargetAndData("//test");
+    assertNoEvents();
+    Rule testTarget = (Rule) ct.getTarget();
+    ConfiguredAttributeMapper attributes =
+        ConfiguredAttributeMapper.of(testTarget, ImmutableMap.of());
+    ConfigurationTransition attrTransition =
+        attributes
+            .getAttributeDefinition("dep")
+            .getTransitionFactory()
+            .create(AttributeTransitionData.builder().attributes(attributes).build());
+    assertThat(attrTransition.requiresOptionFragments(ct.getConfiguration().getOptions()))
+        .containsExactly(CppOptions.class);
+  }
+
+  @Test
   public void testOptionConversionDynamicMode() throws Exception {
     // TODO(waltl): check that dynamic_mode is parsed properly.
   }
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 6f00086..e84713d 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
@@ -24,10 +24,13 @@
 import com.google.devtools.build.lib.analysis.config.Fragment;
 import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
 import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.rules.cpp.CppOptions;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
@@ -1366,4 +1369,43 @@
     assertNoEvents();
     assertThat(getConfiguration(ct).getOptions().get(CppOptions.class).fissionModes).isEmpty();
   }
+
+  @Test
+  public void starlarkPatchTransitionRequiredFragments() throws Exception {
+    // All Starlark rule transitions are patch transitions, while all Starlark attribute transitions
+    // are split transitions.
+    writeAllowlistFile();
+    scratch.file(
+        "test/transitions.bzl",
+        "def _impl(settings, attr):",
+        "  return {'//command_line_option:copt': []}", // --copt is a C++ option.
+        "my_transition = transition(implementation = _impl, inputs = [],",
+        "  outputs = ['//command_line_option:copt'])");
+    scratch.file(
+        "test/rules.bzl",
+        "load('//test:transitions.bzl', 'my_transition')",
+        "def _impl(ctx):",
+        "  return []",
+        "my_rule = rule(",
+        "  implementation = _impl,",
+        "  cfg = my_transition,",
+        "  attrs = {",
+        "    '_whitelist_function_transition': attr.label(",
+        "        default = '//tools/whitelists/function_transition_whitelist',",
+        "    ),",
+        "  })");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:rules.bzl', 'my_rule')",
+        "platform(name = 'my_platform')",
+        "my_rule(name = 'test')");
+
+    ConfiguredTargetAndData ct = getConfiguredTargetAndData("//test");
+    assertNoEvents();
+    Rule testTarget = (Rule) ct.getTarget();
+    ConfigurationTransition ruleTransition =
+        testTarget.getRuleClassObject().getTransitionFactory().create(testTarget);
+    assertThat(ruleTransition.requiresOptionFragments(ct.getConfiguration().getOptions()))
+        .containsExactly(CppOptions.class);
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/TransitionFactoriesTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/TransitionFactoriesTest.java
index 67dcf08..3c00b5b 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/config/TransitionFactoriesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/TransitionFactoriesTest.java
@@ -20,8 +20,6 @@
 import com.google.devtools.build.lib.analysis.config.transitions.NullTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
-import com.google.devtools.build.lib.events.EventHandler;
-import java.util.Map;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -61,13 +59,9 @@
   public void splitTransition() {
     TransitionFactory<Object> factory =
         TransitionFactories.of(
-            new SplitTransition() {
-              @Override
-              public Map<String, BuildOptions> split(
-                  BuildOptions buildOptions, EventHandler eventHandler) {
-                return ImmutableMap.of("test0", buildOptions.clone());
-              }
-            });
+            (SplitTransition)
+                (buildOptions, eventHandler) ->
+                    ImmutableMap.of("test0", buildOptions.clone().underlying()));
     assertThat(factory).isNotNull();
     assertThat(factory.isHost()).isFalse();
     assertThat(factory.isSplit()).isTrue();