Simplify split transition semantics: noops now return the input build options.

This results in less special logic in the implementation and a simpler API.

PiperOrigin-RevId: 197772283
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
index dd34f19..a72b346 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
@@ -403,8 +403,15 @@
 
       LateBoundDefault<?, ?> lateBoundDefault = attribute.getLateBoundDefault();
 
-      Collection<BuildOptions> splitOptions = getSplitOptions(attributeMap, attribute, ruleConfig);
-      if (!splitOptions.isEmpty() && !ruleConfig.isHostConfiguration()) {
+      boolean hasSplitTransition = false;
+      List<BuildOptions> splitOptions = null;
+      if (attribute.hasSplitConfigurationTransition()) {
+        splitOptions =
+            attribute.getSplitTransition(attributeMap).checkedSplit(ruleConfig.getOptions());
+        hasSplitTransition = !SplitTransition.equals(ruleConfig.getOptions(), splitOptions);
+      }
+
+      if (hasSplitTransition && !ruleConfig.isHostConfiguration()) {
         // Late-bound attribute with a split transition:
         // Since we want to get the same results as TransitionResolver.evaluateTransition (but
         // skip it since we've already applied the split), we want to make sure this logic
@@ -449,24 +456,6 @@
   }
 
   /**
-   * Returns the BuildOptions if the rule's attribute triggers a split in this configuration, or
-   * the empty collection if the attribute does not trigger a split transition or if the split
-   * transition does not apply.
-   *
-   * <p>Even though the attribute may have a split, splits don't have to apply in every
-   * configuration (see {@link SplitTransition#split}).
-   */
-  private static Collection<BuildOptions> getSplitOptions(ConfiguredAttributeMapper attributeMap,
-      Attribute attribute,
-      BuildConfiguration ruleConfig) {
-    if (!attribute.hasSplitConfigurationTransition()) {
-      return ImmutableList.<BuildOptions>of();
-    }
-    SplitTransition transition = attribute.getSplitTransition(attributeMap);
-    return transition.split(ruleConfig.getOptions());
-  }
-
-  /**
    * Returns the label dependencies for the given late-bound attribute in this rule.
    *
    * @param rule the rule being evaluated
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index adfeb58..fb17814 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -790,10 +790,11 @@
     SplitTransition transition =
         attributeDefinition.getSplitTransition(
             ConfiguredAttributeMapper.of(rule, configConditions));
-    List<BuildOptions> splitOptions = transition.split(getConfiguration().getOptions());
+    BuildOptions fromOptions = getConfiguration().getOptions();
+    List<BuildOptions> splitOptions = transition.checkedSplit(fromOptions);
     List<ConfiguredTargetAndData> deps = getConfiguredTargetAndTargetDeps(attributeName);
 
-    if (splitOptions.isEmpty()) {
+    if (SplitTransition.equals(fromOptions, splitOptions)) {
       // The split transition is not active. Defer the decision on which CPU to use.
       return ImmutableMap.of(Optional.<String>absent(), deps);
     }
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 677c028..531c6f1 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
@@ -261,7 +261,7 @@
     // resolveAspectDependencies don't get a chance to make their own Skyframe requests before
     // bailing out of this ConfiguredTargetFunction call. Ideally we could batch all requests
     // from all methods into a single Skyframe call, but there are enough subtle data flow
-    // dependencies in ConfiguredTargetFucntion to make that impractical.
+    // dependencies in ConfiguredTargetFunction to make that impractical.
     Map<SkyKey, ValueOrException<InvalidConfigurationException>> depConfigValues =
         env.getValuesOrThrow(keysToEntries.keySet(), InvalidConfigurationException.class);
 
@@ -429,14 +429,7 @@
       // TODO(bazel-team): safety-check that this never mutates fromOptions.
       result = ImmutableList.of(((PatchTransition) transition).patch(fromOptions));
     } else if (transition instanceof SplitTransition) {
-      List<BuildOptions> toOptions = ((SplitTransition) transition).split(fromOptions);
-      if (toOptions.isEmpty()) {
-        // When the split returns an empty list, it's signaling it doesn't apply to this instance.
-        // So return the original options.
-        result = ImmutableList.<BuildOptions>of(fromOptions);
-      } else {
-        result = toOptions;
-      }
+      return ((SplitTransition) transition).checkedSplit(fromOptions);
     } else {
       throw new IllegalStateException(String.format(
           "unsupported config transition type: %s", transition.getClass().getName()));
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingSplitTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingSplitTransition.java
index 69970c2..71e3770 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingSplitTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/transitions/ComposingSplitTransition.java
@@ -120,13 +120,7 @@
     if (transition instanceof PatchTransition) {
       return ImmutableList.<BuildOptions>of(((PatchTransition) transition).patch(fromOptions));
     } else if (transition instanceof SplitTransition) {
-      SplitTransition split = (SplitTransition) transition;
-      List<BuildOptions> splitOptions = split.split(fromOptions);
-      if (splitOptions.isEmpty()) {
-        return ImmutableList.<BuildOptions>of(fromOptions);
-      } else {
-        return splitOptions;
-      }
+      return ((SplitTransition) transition).checkedSplit(fromOptions);
     } else {
       throw new IllegalStateException(
           String.format("Unsupported composite transition type: %s",
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 05ecfaa..eb71869 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
@@ -14,6 +14,8 @@
 
 package com.google.devtools.build.lib.analysis.config.transitions;
 
+import com.google.common.base.Verify;
+import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.concurrent.ThreadSafety;
 import java.util.List;
@@ -27,7 +29,29 @@
 @FunctionalInterface
 public interface SplitTransition extends ConfigurationTransition {
   /**
-   * Return the list of {@code BuildOptions} after splitting; empty if not applicable.
+   * Returns the list of {@code BuildOptions} after splitting, or the original options if this
+   * split is a noop.
+   *
+   * <p>Returning an empty or null list triggers a {@link RuntimeException}.
    */
   List<BuildOptions> split(BuildOptions buildOptions);
+
+  /**
+   * Calls {@link #split} and throws a {@link RuntimeException} if the split output is empty.
+   */
+  default List<BuildOptions> checkedSplit(BuildOptions buildOptions) {
+    List<BuildOptions> splitOptions = split(buildOptions);
+    Verify.verifyNotNull(splitOptions, "Split transition output may not be null");
+    Verify.verify(!splitOptions.isEmpty(), "Split transition output may not be empty");
+    return splitOptions;
+  }
+
+  /**
+   * Returns true iff {@code option} and {@splitOptions} are equal.
+   *
+   * <p>This can be used to determine if a split is a noop.
+   */
+  static boolean equals(BuildOptions options, List<BuildOptions> splitOptions) {
+    return splitOptions.size() == 1 && Iterables.getOnlyElement(splitOptions).equals(options);
+  }
 }
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 2035a79..815616d 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
@@ -238,7 +238,7 @@
         if (androidOptions.cpu.isEmpty()
             || androidCrosstoolTop == null
             || androidCrosstoolTop.equals(cppOptions.crosstoolTop)) {
-          return ImmutableList.of();
+          return ImmutableList.of(buildOptions);
 
         } else {