feat: add //command_line_option:extra_toolchains pseudo-flag (#3810)

Add support for transitioning on the
//command_line_option:extra_toolchains built-in flag using
py_binary.config_settings.

Note that, unlike the normal Bazel built-in flag, it must be specified
as a simple comma-separated string when set using config_settings, which
is then parsed as a CSV string.

This is to make it easier to, on a per-target basis, such as with a
zipapp or wheels,
change the Python toolchains used.
diff --git a/command_line_option/BUILD.bazel b/command_line_option/BUILD.bazel
index a4d5f0f..15c22f3 100644
--- a/command_line_option/BUILD.bazel
+++ b/command_line_option/BUILD.bazel
@@ -18,6 +18,11 @@
     actual = "//python:none",
 )
 
+alias(
+    name = "extra_toolchains",
+    actual = "//python:none",
+)
+
 filegroup(
     name = "distribution",
     srcs = glob(["**"]),
diff --git a/docs/api/rules_python/command_line_option/index.md b/docs/api/rules_python/command_line_option/index.md
index a7dfa34..02d2920 100644
--- a/docs/api/rules_python/command_line_option/index.md
+++ b/docs/api/rules_python/command_line_option/index.md
@@ -51,3 +51,26 @@
 
 The special value `INHERIT` can be specified to use the existing flag value.
 :::
+
+## extra_toolchains
+
+:::{bzl:target} extra_toolchains
+
+Special target for the Bazel-builtin `//command_line_option:extra_toolchains`
+flag.
+
+See the [Bazel documentation for --extra_toolchains](https://bazel.build/reference/command-line-reference#flag--extra_toolchains).
+
+The special value `INHERIT` can be specified to use the existing flag value.
+
+:::{note}
+Unlike the normal Bazel built-in flag, which accepts a list of labels, this
+pseudo-flag must be specified as a single, comma-separated string when set
+using the `config_settings` attribute. For example:
+
+```python
+"//command_line_option:extra_toolchains": "//my/tc1,//my/tc2"
+```
+:::
+:::
+
diff --git a/python/features.bzl b/python/features.bzl
index 26acc8c..a2cb95f 100644
--- a/python/features.bzl
+++ b/python/features.bzl
@@ -86,6 +86,7 @@
 _TARGETS = {
     "//command_line_option:build_runfile_links": True,
     "//command_line_option:enable_runfiles": True,
+    "//command_line_option:extra_toolchains": True,
     "//python/cc:current_py_cc_headers_abi3": True,
 }
 
diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl
index beaa7a6..ad741d0 100644
--- a/python/private/attributes.bzl
+++ b/python/private/attributes.bzl
@@ -446,10 +446,15 @@
         if key.package == "command_line_option":
             if value == "INHERIT":
                 continue
-            else:
-                str_key = "//command_line_option:" + key.name
+            str_key = "//command_line_option:" + key.name
+            if key.name == "extra_toolchains":
+                if value == "":
+                    value = []
+                else:
+                    value = [v.strip() for v in value.split(",") if v.strip()]
         else:
             str_key = str(key)
+
         settings[str_key] = value
     return settings
 
diff --git a/python/private/common_labels.bzl b/python/private/common_labels.bzl
index ff4e7e1..a83ba2b 100644
--- a/python/private/common_labels.bzl
+++ b/python/private/common_labels.bzl
@@ -14,6 +14,7 @@
     # NOTE: Special target; see definition for details.
     ENABLE_RUNFILES = str(Label("//command_line_option:enable_runfiles")),
     EXEC_TOOLS_TOOLCHAIN = str(Label("//python/config_settings:exec_tools_toolchain")),
+    EXTRA_TOOLCHAINS = str(Label("//command_line_option:extra_toolchains")),
     NONE = str(Label("//python:none")),
     PIP_ENV_MARKER_CONFIG = str(Label("//python/config_settings:pip_env_marker_config")),
     PIP_WHL_OSX_VERSION = str(Label("//python/config_settings:pip_whl_osx_version")),
diff --git a/python/private/transition_labels.bzl b/python/private/transition_labels.bzl
index 5f0aa69..7a6531e 100644
--- a/python/private/transition_labels.bzl
+++ b/python/private/transition_labels.bzl
@@ -12,6 +12,7 @@
     labels.BOOTSTRAP_IMPL,
     labels.DEBUGGER,
     labels.EXEC_TOOLS_TOOLCHAIN,
+    "//command_line_option:extra_toolchains",
     labels.PIP_ENV_MARKER_CONFIG,
     labels.PIP_WHL_OSX_VERSION,
     labels.PRECOMPILE,
diff --git a/tests/base_rules/py_executable_base_tests.bzl b/tests/base_rules/py_executable_base_tests.bzl
index dff0399..7531de4 100644
--- a/tests/base_rules/py_executable_base_tests.bzl
+++ b/tests/base_rules/py_executable_base_tests.bzl
@@ -109,6 +109,29 @@
 
 _tests.append(_test_basic_zip)
 
+def _test_config_settings_extra_toolchains(name, config):
+    rt_util.helper_target(
+        config.rule,
+        name = name + "_subject",
+        srcs = ["main.py"],
+        main = "main.py",
+        config_settings = {
+            "//command_line_option:extra_toolchains": "{tc},{tc}".format(tc = CC_TOOLCHAIN),
+        },
+    )
+    analysis_test(
+        name = name,
+        impl = _test_config_settings_extra_toolchains_impl,
+        target = name + "_subject",
+    )
+
+def _test_config_settings_extra_toolchains_impl(env, target):
+    # If we got here, it means analysis succeeded, which implies the transition
+    # successfully parsed the CSV string into a list.
+    env.expect.that_target(target).has_provider(PyInfo)
+
+_tests.append(_test_config_settings_extra_toolchains)
+
 def _test_cross_compile_to_unix(name, config):
     rt_util.helper_target(
         config.rule,
diff --git a/tests/integration/bzlmod_lockfile/MODULE.bazel.lock b/tests/integration/bzlmod_lockfile/MODULE.bazel.lock
index 0dcc4b2..d21fec2 100644
--- a/tests/integration/bzlmod_lockfile/MODULE.bazel.lock
+++ b/tests/integration/bzlmod_lockfile/MODULE.bazel.lock
@@ -250,7 +250,7 @@
     },
     "@@rules_python+//python/uv:uv.bzl%uv": {
       "general": {
-        "bzlTransitiveDigest": "yrEbeCJlv5gbdoRjwwR/EDEkc3pfuQy/FTYKMNoK5Wc=",
+        "bzlTransitiveDigest": "Z5ZPR9z4PkJRXSyJ4KQEqM4kwiqWBCn8Ajzxy9YlS/g=",
         "usagesDigest": "6yXGw7XDyXjOfqBL0SBu1YBEMMYPQzCE3jTzUCkxPgg=",
         "recordedInputs": [
           "REPO_MAPPING:rules_python+,bazel_tools bazel_tools",