Repository: pantsbuild/pants

Issue:
Builtin goals does not respect `--help`.
Similar to #15660  but for builtin goals. This is inconsistent with regular goals, where `--help` produces the help output.

```
$ ./pants experimental-bsp --help
15:20:02.94 [INFO] Wrote BSP runner script to `/Users/x/src/github/kaos/pants/.pants.d/bsp/scripts/run-bsp.sh`.
15:20:02.95 [INFO] Wrote BSP connection file to `/Users/x/src/github/kaos/pants/.bsp/pants.json`.

$ ./pants help experimental-bsp

`experimental-bsp` goal options
-------------------------------

Setup repository for Build Server Protocol (https://build-server-protocol.github.io/).
[...] 

$ ./pants test --help

`test` goal options
-------------------

Run tests.
[...]
```



Discussion:

Resolving patch:
diff --git a/src/python/pants/goal/builtin_goal.py b/src/python/pants/goal/builtin_goal.py
--- a/src/python/pants/goal/builtin_goal.py
+++ b/src/python/pants/goal/builtin_goal.py
@@ -19,7 +19,12 @@
 class BuiltinGoal(ABC, GoalSubsystem):
     """Builtin goals have precedence over regular goal rules.
 
-    If a builtin goal is invoked, any remaining arguments are passed unaltered to the builtin goal.
+    Only a single builtin goal is executed per run, any remaining goals/arguments are passed
+    unaltered to the builtin goal.
+
+    When multiple builtin goals are presented, the first builtin goal will be used unless there is a
+    builtin goal that begin with a hyphen (`-`), in which case the last such "option goal" will be
+    prioritized. This is to support things like `./pants some-builtin-goal --help`.
     """
 
     # Used by `pants.option.arg_splitter.ArgSplitter()` to optionally allow aliasing builtin goals.
diff --git a/src/python/pants/option/arg_splitter.py b/src/python/pants/option/arg_splitter.py
--- a/src/python/pants/option/arg_splitter.py
+++ b/src/python/pants/option/arg_splitter.py
@@ -151,7 +151,10 @@ def add_goal(scope: str) -> str:
                 return scope
 
             nonlocal builtin_goal
-            if scope_info.is_builtin and not builtin_goal:
+            if scope_info.is_builtin and (not builtin_goal or scope.startswith("-")):
+                if builtin_goal:
+                    goals.add(builtin_goal)
+
                 # Get scope from info in case we hit an aliased builtin goal.
                 builtin_goal = scope_info.scope
             else:


Test patch:
diff --git a/src/python/pants/option/arg_splitter_test.py b/src/python/pants/option/arg_splitter_test.py
--- a/src/python/pants/option/arg_splitter_test.py
+++ b/src/python/pants/option/arg_splitter_test.py
@@ -22,6 +22,7 @@ def known_scope_infos() -> list[ScopeInfo]:
         ScopeInfo("test", is_goal=True),
         ScopeInfo("jvm", is_goal=False),
         ScopeInfo("reporting", is_goal=False),
+        ScopeInfo("bsp", is_goal=True, is_builtin=True),
         # TODO: move help related tests closer to `pants.goal.help` to avoid this cludge.
         *(goal.get_scope_info() for goal in builtin_goals()),
     ]
@@ -311,21 +312,21 @@ def help_no_arguments_test(command_line: str, *scopes: str, **expected):
         help_no_arguments_test("./pants help-all", "help-all", expected_help_all=True),
         help_test(
             "./pants --help-advanced --help",
-            expected_goals=["help"],
+            expected_goals=["help-advanced"],
             expected_scope_to_flags={"": [], "help": [], "help-advanced": []},
             expected_specs=[],
-            expected_help_advanced=True,
+            expected_help_advanced=False,
         ),
         help_test(
             "./pants --help --help-advanced --builtin-option --help-advanced-option",
-            expected_goals=["help-advanced"],
+            expected_goals=["help"],
             expected_scope_to_flags={
                 "": [],
-                "help": ["--builtin-option"],
-                "help-advanced": ["--option"],
+                "help": [],
+                "help-advanced": ["--builtin-option", "--option"],
             },
             expected_specs=[],
-            expected_help_advanced=False,
+            expected_help_advanced=True,
         ),
         help_test(
             "./pants -f",
@@ -403,10 +404,16 @@ def help_no_arguments_test(command_line: str, *scopes: str, **expected):
         ),
         help_test(
             "./pants check help-all test --help",
-            expected_goals=["check", "test", "help"],
+            expected_goals=["check", "test", "help-all"],
             expected_scope_to_flags={"": [], "check": [], "help": [], "help-all": [], "test": []},
             expected_specs=[],
-            expected_help_all=True,
+            expected_help_all=False,
+        ),
+        help_test(
+            "./pants bsp --help",
+            expected_goals=["bsp"],
+            expected_scope_to_flags={"": [], "help": [], "bsp": []},
+            expected_specs=[],
         ),
     ],
 )