Introduce configuration transition fragment declarations.

See https://github.com/bazelbuild/bazel/issues/11258 for context.

This essentially converts ConfigurationTransition.apply(BuildOptions...)
to ConfigurationTransition.apply(RestrictedBuildOptions...). The new
BuildOptionsView wraps BuildOptions while only allowing access
to options fragments the transitions declare.

Because there are a *lot* of uses of PatchTransition and SplitTransition,
this change only adds a parallel interface to PatchTransition.patch and
SplitTransition.split. Implementers continue to work as-is with the
BuildOptions variant, while we can incrementally switch them over to
the RestrictedBuildOptions variant at whatever pace we desire.

This helps decouple risk of failing CI if/when we accidentally forget
to migrate a few implementers or get their set of declared fragments wrong.

This implements step 1 of https://github.com/bazelbuild/bazel/issues/11258.

PiperOrigin-RevId: 312311815
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java b/src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java
index 045500e..8e4f081 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java
@@ -30,6 +30,7 @@
 import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
 import com.google.devtools.build.lib.analysis.util.AnalysisCachingTestBase;
 import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.rules.java.JavaInfo;
 import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
 import com.google.devtools.build.lib.syntax.StarlarkValue;
@@ -544,11 +545,14 @@
     public static final OptionDefinition ALSO_IRRELEVANT_OPTION =
         OptionsParser.getOptionDefinitionByName(DiffResetOptions.class, "also_irrelevant");
     public static final PatchTransition CLEAR_IRRELEVANT =
-        (options, eventHandler) -> {
-          BuildOptions cloned = options.clone();
-          cloned.get(DiffResetOptions.class).probablyIrrelevantOption = "(cleared)";
-          cloned.get(DiffResetOptions.class).alsoIrrelevantOption = "(cleared)";
-          return cloned;
+        new PatchTransition() {
+          @Override
+          public BuildOptions patch(BuildOptions options, EventHandler eventHandler) {
+            BuildOptions cloned = options.clone();
+            cloned.get(DiffResetOptions.class).probablyIrrelevantOption = "(cleared)";
+            cloned.get(DiffResetOptions.class).alsoIrrelevantOption = "(cleared)";
+            return cloned;
+          }
         };
 
     @Option(
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 77fdbda..b49af3d 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
@@ -37,6 +37,7 @@
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
 import com.google.devtools.build.lib.packages.Package;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import java.util.Map;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -266,15 +267,19 @@
                       new TransitionFactory<AttributeTransitionData>() {
                         @Override
                         public SplitTransition create(AttributeTransitionData data) {
-                          return (BuildOptions options, EventHandler eventHandler) -> {
-                            String define = data.attributes().get("define", STRING);
-                            BuildOptions 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 new SplitTransition() {
+                            @Override
+                            public Map<String, BuildOptions> split(
+                                BuildOptions options, EventHandler eventHandler) {
+                              String define = data.attributes().get("define", STRING);
+                              BuildOptions 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);
+                            }
                           };
                         }
 
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ConfigurationsForLateBoundTargetsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ConfigurationsForLateBoundTargetsTest.java
index e2f3cb6..150bdae 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/ConfigurationsForLateBoundTargetsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/ConfigurationsForLateBoundTargetsTest.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
 import com.google.devtools.build.lib.analysis.util.MockRule;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
 import com.google.devtools.build.lib.testutil.Suite;
@@ -48,10 +49,13 @@
 @RunWith(JUnit4.class)
 public class ConfigurationsForLateBoundTargetsTest extends AnalysisTestCase {
   private static final PatchTransition CHANGE_FOO_FLAG_TRANSITION =
-      (options, eventHandler) -> {
-        BuildOptions toOptions = options.clone();
-        toOptions.get(LateBoundSplitUtil.TestOptions.class).fooFlag = "PATCHED!";
-        return toOptions;
+      new PatchTransition() {
+        @Override
+        public BuildOptions patch(BuildOptions options, EventHandler eventHandler) {
+          BuildOptions toOptions = options.clone();
+          toOptions.get(LateBoundSplitUtil.TestOptions.class).fooFlag = "PATCHED!";
+          return toOptions;
+        }
       };
 
   /** Rule definition with a latebound dependency. */
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsViewTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsViewTest.java
new file mode 100644
index 0000000..6f9a723
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsViewTest.java
@@ -0,0 +1,86 @@
+// Copyright 2020 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.config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.rules.cpp.CppOptions;
+import com.google.devtools.common.options.OptionsParser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BuildOptionsView}. */
+@RunWith(JUnit4.class)
+public final class BuildOptionsViewTest {
+
+  private static final ImmutableList<Class<? extends FragmentOptions>> BUILD_CONFIG_OPTIONS =
+      ImmutableList.of(CoreOptions.class, CppOptions.class);
+
+  private BuildOptions options;
+
+  @Before
+  public void constructBuildOptions() {
+    options =
+        BuildOptions.of(
+            BUILD_CONFIG_OPTIONS,
+            OptionsParser.builder().optionsClasses(BUILD_CONFIG_OPTIONS).build());
+  }
+
+  @Test
+  public void allowedGet() throws Exception {
+    BuildOptionsView restrictedOptions =
+        new BuildOptionsView(options, ImmutableSet.of(CoreOptions.class));
+    assertThat(restrictedOptions.get(CoreOptions.class))
+        .isSameInstanceAs(options.get(CoreOptions.class));
+    ;
+  }
+
+  @Test
+  public void prohibitedGet() throws Exception {
+    BuildOptionsView restrictedOptions =
+        new BuildOptionsView(options, ImmutableSet.of(CoreOptions.class));
+    assertThrows(IllegalArgumentException.class, () -> restrictedOptions.get(CppOptions.class));
+  }
+
+  @Test
+  public void allowedContains() throws Exception {
+    BuildOptionsView restrictedOptions =
+        new BuildOptionsView(options, ImmutableSet.of(CoreOptions.class));
+    assertThat(restrictedOptions.contains(CoreOptions.class)).isTrue();
+  }
+
+  @Test
+  public void prohibitedContains() throws Exception {
+    BuildOptionsView restrictedOptions =
+        new BuildOptionsView(options, ImmutableSet.of(CoreOptions.class));
+    assertThrows(
+        IllegalArgumentException.class, () -> restrictedOptions.contains(CppOptions.class));
+  }
+
+  @Test
+  public void cloneTest() throws Exception {
+    BuildOptionsView restrictedOptions =
+        new BuildOptionsView(options, ImmutableSet.of(CoreOptions.class));
+    BuildOptionsView clone = restrictedOptions.clone();
+    assertThat(clone).isNotSameInstanceAs(restrictedOptions);
+    assertThat(restrictedOptions.underlying()).isSameInstanceAs(options);
+    assertThat(clone.underlying()).isNotSameInstanceAs(options);
+    assertThat(clone.underlying()).isEqualTo(options);
+  }
+}
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 aee0d37..67dcf08 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,6 +20,8 @@
 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;
@@ -59,8 +61,13 @@
   public void splitTransition() {
     TransitionFactory<Object> factory =
         TransitionFactories.of(
-            (SplitTransition)
-                (buildOptions, eventHandler) -> ImmutableMap.of("test0", buildOptions.clone()));
+            new SplitTransition() {
+              @Override
+              public Map<String, BuildOptions> split(
+                  BuildOptions buildOptions, EventHandler eventHandler) {
+                return ImmutableMap.of("test0", buildOptions.clone());
+              }
+            });
     assertThat(factory).isNotNull();
     assertThat(factory.isHost()).isFalse();
     assertThat(factory.isSplit()).isTrue();
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionFactoryTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionFactoryTest.java
index 85855b5..fb31d78 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionFactoryTest.java
@@ -58,7 +58,10 @@
     assertThat(composed.isSplit()).isFalse();
     ConfigurationTransition transition = composed.create(new StubData());
     Collection<BuildOptions> results =
-        transition.apply(BuildOptions.builder().build(), eventHandler).values();
+        transition
+            .apply(
+                TransitionUtil.restrict(transition, BuildOptions.builder().build()), eventHandler)
+            .values();
     assertThat(results).isNotNull();
     assertThat(results).hasSize(1);
     BuildOptions result = Iterables.getOnlyElement(results);
@@ -78,7 +81,8 @@
     assertThat(composed.isSplit()).isTrue();
     ConfigurationTransition transition = composed.create(new StubData());
     Map<String, BuildOptions> results =
-        transition.apply(BuildOptions.builder().build(), eventHandler);
+        transition.apply(
+            TransitionUtil.restrict(transition, BuildOptions.builder().build()), eventHandler);
     assertThat(results).isNotNull();
     assertThat(results).hasSize(2);
 
@@ -105,7 +109,8 @@
     assertThat(composed.isSplit()).isTrue();
     ConfigurationTransition transition = composed.create(new StubData());
     Map<String, BuildOptions> results =
-        transition.apply(BuildOptions.builder().build(), eventHandler);
+        transition.apply(
+            TransitionUtil.restrict(transition, BuildOptions.builder().build()), eventHandler);
     assertThat(results).isNotNull();
     assertThat(results).hasSize(2);
 
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionTest.java
index acc758e..a3375b3 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingTransitionTest.java
@@ -18,13 +18,19 @@
 import static org.junit.Assert.assertThrows;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 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.FragmentOptions;
 import com.google.devtools.build.lib.analysis.config.HostTransition;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.rules.cpp.CppOptions;
+import com.google.devtools.build.lib.rules.java.JavaOptions;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.IntStream;
 import org.junit.Before;
 import org.junit.Test;
@@ -52,7 +58,8 @@
 
     assertThat(composed).isNotNull();
     Map<String, BuildOptions> results =
-        composed.apply(BuildOptions.builder().build(), eventHandler);
+        composed.apply(
+            TransitionUtil.restrict(composed, BuildOptions.builder().build()), eventHandler);
     assertThat(results).isNotNull();
     assertThat(results).hasSize(1);
     BuildOptions result = Iterables.getOnlyElement(results.values());
@@ -69,7 +76,8 @@
 
     assertThat(composed).isNotNull();
     Map<String, BuildOptions> results =
-        composed.apply(BuildOptions.builder().build(), eventHandler);
+        composed.apply(
+            TransitionUtil.restrict(composed, BuildOptions.builder().build()), eventHandler);
     assertThat(results).isNotNull();
     assertThat(results).hasSize(2);
 
@@ -93,7 +101,8 @@
 
     assertThat(composed).isNotNull();
     Map<String, BuildOptions> results =
-        composed.apply(BuildOptions.builder().build(), eventHandler);
+        composed.apply(
+            TransitionUtil.restrict(composed, BuildOptions.builder().build()), eventHandler);
     assertThat(results).isNotNull();
     assertThat(results).hasSize(2);
 
@@ -119,7 +128,9 @@
     assertThat(composed).isNotNull();
     assertThrows(
         IllegalStateException.class,
-        () -> composed.apply(BuildOptions.builder().build(), eventHandler));
+        () ->
+            composed.apply(
+                TransitionUtil.restrict(composed, BuildOptions.builder().build()), eventHandler));
   }
 
   @Test
@@ -197,4 +208,32 @@
                   i -> updateOptions(options, flagLabel, flagValues.get(i))));
     }
   }
+
+  private static final class TransitionWithCustomFragments implements PatchTransition {
+    private final Set<Class<? extends FragmentOptions>> fragments;
+
+    TransitionWithCustomFragments(Set<Class<? extends FragmentOptions>> fragments) {
+      this.fragments = fragments;
+    }
+
+    @Override
+    public Set<Class<? extends FragmentOptions>> requiresOptionFragments() {
+      return fragments;
+    }
+
+    @Override
+    public BuildOptions patch(BuildOptionsView options, EventHandler eventHandler) {
+      return options.underlying();
+    }
+  }
+
+  @Test
+  public void composed_required_fragments() throws Exception {
+    ConfigurationTransition composed =
+        ComposingTransition.of(
+            new TransitionWithCustomFragments(ImmutableSet.of(CppOptions.class)),
+            new TransitionWithCustomFragments(ImmutableSet.of(JavaOptions.class)));
+    assertThat(composed.requiresOptionFragments())
+        .containsExactly(CppOptions.class, JavaOptions.class);
+  }
 }