Don't filter starlark flag resolution based on --build_tests_only and friends

Previously when --build_tests_only was passed alongside any starlark flag a crash would occur because the starlark flag is not a test and therefore would be filtered from the targets available at the top level.

With this change no flags that affect top-level target availability should impact the loading of starlark flags.

RELNOTES: None
PiperOrigin-RevId: 281397052
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java b/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java
index 89af14f..30cf557 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java
@@ -167,7 +167,7 @@
     boolean keepGoing = request.getKeepGoing();
     TargetPatternPhaseValue result =
         env.getSkyframeExecutor()
-            .loadTargetPatterns(
+            .loadTargetPatternsWithFilters(
                 env.getReporter(),
                 request.getTargets(),
                 env.getRelativeWorkingDirectory(),
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
index 2584bfb..79d4eb7 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
@@ -246,8 +246,7 @@
       return ExitCode.SUCCESS;
     }
     try {
-      StarlarkOptionsParser.newStarlarkOptionsParser(env, optionsParser)
-          .parse(commandAnnotation, eventHandler);
+      StarlarkOptionsParser.newStarlarkOptionsParser(env, optionsParser).parse(eventHandler);
     } catch (OptionsParsingException e) {
       env.getReporter().handle(Event.error(e.getMessage()));
       return ExitCode.PARSING_FAILURE;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
index 624c5d5..cbf611a 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
@@ -30,7 +30,6 @@
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.Type;
-import com.google.devtools.build.lib.pkgcache.LoadingOptions;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue;
 import com.google.devtools.build.lib.util.Pair;
@@ -80,8 +79,7 @@
   // OptionsParserImpl.identifyOptionAndPossibleArgument. Consider combining. This would probably
   // require multiple rounds of parsing to fit starlark-defined options into native option format.
   @VisibleForTesting
-  public void parse(Command command, ExtendedEventHandler eventHandler)
-      throws OptionsParsingException {
+  public void parse(ExtendedEventHandler eventHandler) throws OptionsParsingException {
     ImmutableList.Builder<String> residue = new ImmutableList.Builder<>();
     // Map of <option name (label), <unparsed option value, loaded option>>.
     Map<String, Pair<String, Target>> unparsedOptions =
@@ -98,7 +96,7 @@
         continue;
       }
 
-      parseArg(arg, unparsedArgs, unparsedOptions, command, eventHandler);
+      parseArg(arg, unparsedArgs, unparsedOptions, eventHandler);
     }
     residue.addAll(nativeOptionsParser.getPostDoubleDashResidue());
     nativeOptionsParser.setResidue(residue.build());
@@ -146,7 +144,6 @@
       String arg,
       Iterator<String> unparsedArgs,
       Map<String, Pair<String, Target>> unparsedOptions,
-      Command command,
       ExtendedEventHandler eventHandler)
       throws OptionsParsingException {
     int equalsAt = arg.indexOf('=');
@@ -158,8 +155,7 @@
 
     if (value != null) {
       // --flag=value or -flag=value form
-      Target buildSettingTarget =
-          loadBuildSetting(name, nativeOptionsParser, command, eventHandler);
+      Target buildSettingTarget = loadBuildSetting(name, nativeOptionsParser, eventHandler);
       unparsedOptions.put(name, new Pair<>(value, buildSettingTarget));
     } else {
       boolean booleanValue = true;
@@ -168,8 +164,7 @@
         booleanValue = false;
         name = name.substring(2);
       }
-      Target buildSettingTarget =
-          loadBuildSetting(name, nativeOptionsParser, command, eventHandler);
+      Target buildSettingTarget = loadBuildSetting(name, nativeOptionsParser, eventHandler);
       BuildSetting current =
           buildSettingTarget.getAssociatedRule().getRuleClassObject().getBuildSetting();
       if (current.getType().equals(BOOLEAN)) {
@@ -194,20 +189,17 @@
   private Target loadBuildSetting(
       String targetToBuild,
       OptionsParser optionsParser,
-      Command command,
       ExtendedEventHandler eventHandler)
       throws OptionsParsingException {
     Target buildSetting;
     try {
       TargetPatternPhaseValue result =
-          skyframeExecutor.loadTargetPatterns(
+          skyframeExecutor.loadTargetPatternsWithoutFilters(
               reporter,
               Collections.singletonList(targetToBuild),
               relativeWorkingDirectory,
-              optionsParser.getOptions(LoadingOptions.class),
               SkyframeExecutor.DEFAULT_THREAD_COUNT,
-              optionsParser.getOptions(KeepGoingOption.class).keepGoing,
-              command.name().equals("test"));
+              optionsParser.getOptions(KeepGoingOption.class).keepGoing);
       buildSetting =
           Iterables.getOnlyElement(
               result.getTargets(eventHandler, skyframeExecutor.getPackageManager()));
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 439c01c..dd7a1a2 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
@@ -203,7 +203,6 @@
 import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -2808,7 +2807,39 @@
     return packageProgress;
   }
 
-  public TargetPatternPhaseValue loadTargetPatterns(
+  /**
+   * Loads the given target patterns without applying any filters (such as removing non-test targets
+   * if {@code --build_tests_only} is set).
+   *
+   * @param eventHandler handler which accepts update events
+   * @param targetPatterns patterns to be loaded
+   * @param threadCount number of threads to use for this skyframe evaluation
+   * @param keepGoing whether to attempt to ignore errors. See also {@link KeepGoingOption}
+   */
+  public TargetPatternPhaseValue loadTargetPatternsWithoutFilters(
+      ExtendedEventHandler eventHandler,
+      List<String> targetPatterns,
+      PathFragment relativeWorkingDirectory,
+      int threadCount,
+      boolean keepGoing)
+      throws TargetParsingException, InterruptedException {
+    SkyKey key =
+        TargetPatternPhaseValue.keyWithoutFilters(
+            ImmutableList.copyOf(targetPatterns), relativeWorkingDirectory.getPathString());
+    return getTargetPatternPhaseValue(eventHandler, targetPatterns, threadCount, keepGoing, key);
+  }
+
+  /**
+   * Loads the given target patterns after applying filters configured through parameters and
+   * options (such as removing non-test targets if {@code --build_tests_only} is set).
+   *
+   * @param eventHandler handler which accepts update events
+   * @param targetPatterns patterns to be loaded
+   * @param threadCount number of threads to use for this skyframe evaluation
+   * @param keepGoing whether to attempt to ignore errors. See also {@link KeepGoingOption}
+   * @param determineTests whether to ignore any targets that aren't tests or test suites
+   */
+  public TargetPatternPhaseValue loadTargetPatternsWithFilters(
       ExtendedEventHandler eventHandler,
       List<String> targetPatterns,
       PathFragment relativeWorkingDirectory,
@@ -2817,7 +2848,6 @@
       boolean keepGoing,
       boolean determineTests)
       throws TargetParsingException, InterruptedException {
-    Stopwatch timer = Stopwatch.createStarted();
     SkyKey key =
         TargetPatternPhaseValue.key(
             ImmutableList.copyOf(targetPatterns),
@@ -2829,9 +2859,20 @@
             options.buildManualTests,
             options.expandTestSuites,
             TestFilter.forOptions(options, eventHandler, pkgFactory.getRuleClassNames()));
-    EvaluationResult<TargetPatternPhaseValue> evalResult;
+    return getTargetPatternPhaseValue(eventHandler, targetPatterns, threadCount, keepGoing, key);
+  }
+
+  private TargetPatternPhaseValue getTargetPatternPhaseValue(
+      ExtendedEventHandler eventHandler,
+      List<String> targetPatterns,
+      int threadCount,
+      boolean keepGoing,
+      SkyKey key)
+      throws InterruptedException, TargetParsingException {
+    Stopwatch timer = Stopwatch.createStarted();
     eventHandler.post(new LoadingPhaseStartedEvent(packageProgress));
-    evalResult = evaluate(ImmutableList.of(key), keepGoing, threadCount, eventHandler);
+    EvaluationResult<TargetPatternPhaseValue> evalResult =
+        evaluate(ImmutableList.of(key), keepGoing, threadCount, eventHandler);
     if (evalResult.hasError()) {
       ErrorInfo errorInfo = evalResult.getError(key);
       TargetParsingException exc;
@@ -2863,11 +2904,8 @@
       }
       throw exc;
     }
-    long timeMillis = timer.stop().elapsed(TimeUnit.MILLISECONDS);
-    eventHandler.post(new TargetParsingPhaseTimeEvent(timeMillis));
-
-    TargetPatternPhaseValue patternParsingValue = evalResult.get(key);
-    return patternParsingValue;
+    eventHandler.post(new TargetParsingPhaseTimeEvent(timer.stop().elapsed().toMillis()));
+    return evalResult.get(key);
   }
 
   public PrepareAnalysisPhaseValue prepareAnalysisPhase(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
index bf6337c..a5998ae 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
@@ -166,6 +166,19 @@
         testFilter);
   }
 
+  /**
+   * Creates a new target pattern sky key which represents the given target patterns without
+   * attempting to filter them in any way (for example, ignores options such as only loading tests).
+   *
+   * @param targetPatterns list of targets to evaluate
+   * @param offset relative path to the working directory
+   */
+  @ThreadSafe
+  public static SkyKey keyWithoutFilters(ImmutableList<String> targetPatterns, String offset) {
+    return new TargetPatternPhaseKey(
+        targetPatterns, offset, false, false, false, ImmutableList.of(), false, false, null);
+  }
+
   /** The configuration needed to run the target pattern evaluation phase. */
   @ThreadSafe
   @VisibleForSerialization
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
index 614cfb0..a7ca224 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
@@ -379,7 +379,7 @@
         reporter, ModifiedFileSet.EVERYTHING_MODIFIED, Root.fromPath(rootDirectory));
 
     TargetPatternPhaseValue loadingResult =
-        skyframeExecutor.loadTargetPatterns(
+        skyframeExecutor.loadTargetPatternsWithFilters(
             reporter,
             ImmutableList.copyOf(labels),
             PathFragment.EMPTY_FRAGMENT,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index 714a6e6..6ded04e 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -1818,7 +1818,7 @@
     AnalysisOptions viewOptions = Options.getDefaults(AnalysisOptions.class);
 
     TargetPatternPhaseValue loadingResult =
-        skyframeExecutor.loadTargetPatterns(
+        skyframeExecutor.loadTargetPatternsWithFilters(
             reporter,
             targets,
             PathFragment.EMPTY_FRAGMENT,
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java
index 5c2d84a..1ab99d4 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java
@@ -82,7 +82,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/** Tests for {@link SkyframeExecutor#loadTargetPatterns}. */
+/** Tests for {@link SkyframeExecutor#loadTargetPatternsWithFilters}. */
 @RunWith(JUnit4.class)
 public class LoadingPhaseRunnerTest {
 
@@ -1308,7 +1308,7 @@
       sync();
       storedErrors.clear();
       TargetPatternPhaseValue result =
-          skyframeExecutor.loadTargetPatterns(
+          skyframeExecutor.loadTargetPatternsWithFilters(
               storedErrors,
               ImmutableList.copyOf(patterns),
               relativeWorkingDirectory,
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java b/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java
index 9d0b565..fbcd510 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java
@@ -23,17 +23,12 @@
 import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
 import com.google.devtools.build.lib.pkgcache.LoadingOptions;
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
-import com.google.devtools.build.lib.runtime.BlazeCommand;
-import com.google.devtools.build.lib.runtime.BlazeCommandResult;
 import com.google.devtools.build.lib.runtime.ClientOptions;
-import com.google.devtools.build.lib.runtime.Command;
-import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.CommonCommandOptions;
 import com.google.devtools.build.lib.runtime.KeepGoingOption;
 import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
 import com.google.devtools.build.lib.runtime.UiOptions;
 import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
-import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsParser;
@@ -75,36 +70,9 @@
             skyframeExecutor, reporter, PathFragment.EMPTY_FRAGMENT, optionsParser);
   }
 
-  @Command(
-      name = "residue",
-      builds = true,
-      options = {
-        PackageCacheOptions.class,
-        StarlarkSemanticsOptions.class,
-        KeepGoingOption.class,
-        LoadingOptions.class,
-        ClientOptions.class,
-        UiOptions.class,
-      },
-      allowResidue = true,
-      shortDescription =
-          "a dummy command for testing that allows residue and recognizes all"
-              + " relevant options for starlark options parsing.",
-      help = "")
-  private static class ResidueCommand implements BlazeCommand {
-    @Override
-    public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
-      return BlazeCommandResult.exitCode(ExitCode.SUCCESS);
-    }
-
-    @Override
-    public void editOptions(OptionsParser optionsParser) {}
-  }
-
   private OptionsParsingResult parseStarlarkOptions(String options) throws Exception {
     starlarkOptionsParser.setResidueForTesting(Arrays.asList(options.split(" ")));
-    starlarkOptionsParser.parse(
-        ResidueCommand.class.getAnnotation(Command.class), new StoredEventHandler());
+    starlarkOptionsParser.parse(new StoredEventHandler());
     return starlarkOptionsParser.getNativeOptionsParserFortesting();
   }
 
@@ -421,4 +389,14 @@
 
     assertThat(result.getStarlarkOptions()).isEmpty();
   }
+
+  @Test
+  public void testOptionsAreParsedWithBuildTestsOnly() throws Exception {
+    writeBasicIntFlag();
+    optionsParser.parse("--build_tests_only");
+
+    OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting=15");
+
+    assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(15);
+  }
 }