fix(toolchain): disable exec toolchain by default (#1968)

This makes the exec tools toolchain disabled by default to prevent
toolchain resolution
from matching it and inadvertently pulling in a dependency on the
hermetic runtimes.
While the hermetic runtime wouldn't actually be used (precompiling is
disabled
by default), the dependency triggered downloading of the runtimes, which
breaks
environments which forbid remote downloads they haven't vetted (such a
case is
Bazel's own build process).

To fix this, a flag is added to control if the exec tools toolchain is
enabled or not.
When disabled (the default), the toolchain won't match, and the remote
dependency isn't
triggered.

Fixes #1967.

Cherry-pick of cf1f36dd.

---------

Co-authored-by: Richard Levasseur <rlevasseur@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9abbd44..28ab14d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,17 @@
 ### Removed
 * Nothing yet
 
+## [0.33.2] - 2024-06-13
+
+[0.33.2]: https://github.com/bazelbuild/rules_python/releases/tag/0.33.2
+
+### Fixed
+* (toolchains) The {obj}`exec_tools_toolchain_type` is disabled by default.
+  To enable it, set {obj}`--//python/config_settings:exec_tools_toolchain=enabled`.
+  This toolchain must be enabled for precompilation to work. This toolchain will
+  be enabled by default in a future release.
+  Fixes [1967](https://github.com/bazelbuild/rules_python/issues/1967).
+
 ## [0.33.1] - 2024-06-13
 
 [0.33.1]: https://github.com/bazelbuild/rules_python/releases/tag/0.33.1
diff --git a/docs/sphinx/api/python/config_settings/index.md b/docs/sphinx/api/python/config_settings/index.md
index 8c23bf6..50647ab 100644
--- a/docs/sphinx/api/python/config_settings/index.md
+++ b/docs/sphinx/api/python/config_settings/index.md
@@ -10,6 +10,22 @@
 one of the values that `rules_python` maintains.
 :::
 
+::::{bzl:flag} exec_tools_toolchain
+Determines if the {obj}`exec_tools_toolchain_type` toolchain is enabled.
+
+:::{note}
+* Note that this only affects the rules_python generated toolchains.
+:::
+
+Values:
+
+* `enabled`: Allow matching of the registered toolchains at build time.
+* `disabled`: Prevent the toolchain from being matched at build time.
+
+:::{versionadded} 0.33.2
+:::
+::::
+
 ::::{bzl:flag} precompile
 Determines if Python source files should be compiled at build time.
 
@@ -34,6 +50,8 @@
 * `force_disabled`: Like `disabled`, except overrides target-level setting. This
   is useful useful for development, testing enabling precompilation more
   broadly, or as an escape hatch if build-time compiling is not available.
+:::{versionadded} 0.33.0
+:::
 ::::
 
 ::::{bzl:flag} precompile_source_retention
@@ -51,9 +69,11 @@
 * `omit_source`: Don't include the orignal py source.
 * `omit_if_generated_source`: Keep the original source if it's a regular source
   file, but omit it if it's a generated file.
+:::{versionadded} 0.33.0
+:::
 ::::
 
-:::{bzl:flag} precompile_add_to_runfiles
+::::{bzl:flag} precompile_add_to_runfiles
 Determines if a target adds its compiled files to its runfiles.
 
 When a target compiles its files, but doesn't add them to its own runfiles, it
@@ -66,7 +86,9 @@
   runfiles; they are still added to {bzl:obj}`PyInfo.transitive_pyc_files`. See
   also: {bzl:obj}`py_binary.pyc_collection` attribute. This is useful for allowing
   incrementally enabling precompilation on a per-binary basis.
+:::{versionadded} 0.33.0
 :::
+::::
 
 ::::{bzl:flag} pyc_collection
 Determine if `py_binary` collects transitive pyc files.
@@ -78,6 +100,8 @@
 Values:
 * `include_pyc`: Include `PyInfo.transitive_pyc_files` as part of the binary.
 * `disabled`: Don't include `PyInfo.transitive_pyc_files` as part of the binary.
+:::{versionadded} 0.33.0
+:::
 ::::
 
 ::::{bzl:flag} py_linux_libc
diff --git a/docs/sphinx/toolchains.md b/docs/sphinx/toolchains.md
index e3be22f..26557ca 100644
--- a/docs/sphinx/toolchains.md
+++ b/docs/sphinx/toolchains.md
@@ -233,7 +233,7 @@
 limited functionality to the rules (at build time, nothing is knowable about
 the Python runtime).
 
-Bazel itself automatically registers `@bazel_tools//python:autodetecting_toolchain`
+Bazel itself automatically registers `@bazel_tools//tools/python:autodetecting_toolchain`
 as the lowest priority toolchain. For WORKSPACE builds, if no other toolchain
 is registered, that toolchain will be used. For bzlmod builds, rules_python
 automatically registers a higher-priority toolchain; it won't be used unless
diff --git a/python/config_settings/BUILD.bazel b/python/config_settings/BUILD.bazel
index e2d2608..cacfb81 100644
--- a/python/config_settings/BUILD.bazel
+++ b/python/config_settings/BUILD.bazel
@@ -2,6 +2,7 @@
 load(
     "//python/private:flags.bzl",
     "BootstrapImplFlag",
+    "ExecToolsToolchainFlag",
     "PrecompileAddToRunfilesFlag",
     "PrecompileFlag",
     "PrecompileSourceRetentionFlag",
@@ -30,6 +31,25 @@
 )
 
 string_flag(
+    name = "exec_tools_toolchain",
+    build_setting_default = ExecToolsToolchainFlag.DISABLED,
+    values = sorted(ExecToolsToolchainFlag.__members__.values()),
+    # NOTE: Only public because it is used in py_toolchain_suite from toolchain
+    # repositories
+    visibility = ["//visibility:private"],
+)
+
+config_setting(
+    name = "is_exec_tools_toolchain_enabled",
+    flag_values = {
+        "exec_tools_toolchain": ExecToolsToolchainFlag.ENABLED,
+    },
+    # NOTE: Only public because it is used in py_toolchain_suite from toolchain
+    # repositories
+    visibility = ["//visibility:public"],
+)
+
+string_flag(
     name = "precompile",
     build_setting_default = PrecompileFlag.AUTO,
     values = sorted(PrecompileFlag.__members__.values()),
diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel
index 422ed9c..e2a2bc0 100644
--- a/python/private/BUILD.bazel
+++ b/python/private/BUILD.bazel
@@ -59,6 +59,7 @@
     name = "autodetecting_toolchain_bzl",
     srcs = ["autodetecting_toolchain.bzl"],
     deps = [
+        ":toolchain_types_bzl",
         "//python:py_runtime_bzl",
         "//python:py_runtime_pair_bzl",
     ],
diff --git a/python/private/autodetecting_toolchain.bzl b/python/private/autodetecting_toolchain.bzl
index 55c9569..174136e 100644
--- a/python/private/autodetecting_toolchain.bzl
+++ b/python/private/autodetecting_toolchain.bzl
@@ -16,6 +16,7 @@
 
 load("//python:py_runtime.bzl", "py_runtime")
 load("//python:py_runtime_pair.bzl", "py_runtime_pair")
+load(":toolchain_types.bzl", "TARGET_TOOLCHAIN_TYPE")
 
 def define_autodetecting_toolchain(name):
     """Defines the autodetecting Python toolchain.
@@ -65,6 +66,6 @@
     native.toolchain(
         name = name,
         toolchain = ":_autodetecting_py_runtime_pair",
-        toolchain_type = ":toolchain_type",
+        toolchain_type = TARGET_TOOLCHAIN_TYPE,
         visibility = ["//visibility:public"],
     )
diff --git a/python/private/flags.bzl b/python/private/flags.bzl
index d141f72..fa31262 100644
--- a/python/private/flags.bzl
+++ b/python/private/flags.bzl
@@ -37,6 +37,15 @@
         value = PrecompileFlag.DISABLED
     return value
 
+# Determines if the Python exec tools toolchain should be registered.
+# buildifier: disable=name-conventions
+ExecToolsToolchainFlag = enum(
+    # Enable registering the exec tools toolchain using the hermetic toolchain.
+    ENABLED = "enabled",
+    # Disable registering the exec tools toolchain using the hermetic toolchain.
+    DISABLED = "disabled",
+)
+
 # Determines if Python source files should be compiled at build time.
 #
 # NOTE: The flag value is overridden by the target-level attribute, except
diff --git a/python/private/py_toolchain_suite.bzl b/python/private/py_toolchain_suite.bzl
index 174c36f..ed562c9 100644
--- a/python/private/py_toolchain_suite.bzl
+++ b/python/private/py_toolchain_suite.bzl
@@ -22,6 +22,8 @@
     "TARGET_TOOLCHAIN_TYPE",
 )
 
+_IS_EXEC_TOOLCHAIN_ENABLED = Label("//python/config_settings:is_exec_tools_toolchain_enabled")
+
 def py_toolchain_suite(*, prefix, user_repository_name, python_version, set_python_version_constraint, flag_values, **kwargs):
     """For internal use only.
 
@@ -106,8 +108,16 @@
             user_repository_name = user_repository_name,
         ),
         toolchain_type = EXEC_TOOLS_TOOLCHAIN_TYPE,
-        # The target settings capture the Python version
-        target_settings = target_settings,
+        target_settings = select({
+            _IS_EXEC_TOOLCHAIN_ENABLED: target_settings,
+            # Whatever the default is, it has to map to a `config_setting`
+            # that will never match. Since the default branch is only taken if
+            # _IS_EXEC_TOOLCHAIN_ENABLED is false, then it will never match
+            # when later evaluated during toolchain resolution.
+            # Note that @platforms//:incompatible can't be used here because
+            # the RHS must be a `config_setting`.
+            "//conditions:default": [_IS_EXEC_TOOLCHAIN_ENABLED],
+        }),
         exec_compatible_with = kwargs.get("target_compatible_with"),
     )
 
diff --git a/tests/base_rules/precompile/precompile_tests.bzl b/tests/base_rules/precompile/precompile_tests.bzl
index 02ab4ab..58bdafe 100644
--- a/tests/base_rules/precompile/precompile_tests.bzl
+++ b/tests/base_rules/precompile/precompile_tests.bzl
@@ -27,6 +27,7 @@
 load(
     "//tests/support:support.bzl",
     "CC_TOOLCHAIN",
+    "EXEC_TOOLS_TOOLCHAIN",
     "PLATFORM_TOOLCHAIN",
     "PRECOMPILE",
     "PRECOMPILE_ADD_TO_RUNFILES",
@@ -61,6 +62,7 @@
         target = name + "_subject",
         config_settings = {
             "//command_line_option:extra_toolchains": _TEST_TOOLCHAINS,
+            EXEC_TOOLS_TOOLCHAIN: "enabled",
         },
     )
 
@@ -119,6 +121,7 @@
         config_settings = {
             "//command_line_option:extra_toolchains": _TEST_TOOLCHAINS,
             ##PRECOMPILE_SOURCE_RETENTION: "omit_source",
+            EXEC_TOOLS_TOOLCHAIN: "enabled",
         },
         target = name + "_subject",
     )
@@ -161,6 +164,7 @@
         target = name + "_subject",
         config_settings = {
             "//command_line_option:extra_toolchains": _TEST_TOOLCHAINS,
+            EXEC_TOOLS_TOOLCHAIN: "enabled",
         },
     )
 
@@ -203,6 +207,7 @@
         config_settings = {
             "//command_line_option:extra_toolchains": _TEST_TOOLCHAINS,
             PRECOMPILE_SOURCE_RETENTION: "omit_if_generated_source",
+            EXEC_TOOLS_TOOLCHAIN: "enabled",
         },
     )
 
@@ -252,6 +257,7 @@
             "//command_line_option:extra_toolchains": _TEST_TOOLCHAINS,
             PRECOMPILE_ADD_TO_RUNFILES: "decided_elsewhere",
             PRECOMPILE: "enabled",
+            EXEC_TOOLS_TOOLCHAIN: "enabled",
         },
     )
 
@@ -288,6 +294,7 @@
         target = name + "_subject",
         config_settings = {
             "//command_line_option:extra_toolchains": _TEST_TOOLCHAINS,
+            EXEC_TOOLS_TOOLCHAIN: "enabled",
         },
     )
 
diff --git a/tests/support/support.bzl b/tests/support/support.bzl
index 4bcc554..efcc43a 100644
--- a/tests/support/support.bzl
+++ b/tests/support/support.bzl
@@ -31,6 +31,7 @@
 
 # str() around Label() is necessary because rules_testing's config_settings
 # doesn't accept yet Label objects.
+EXEC_TOOLS_TOOLCHAIN = str(Label("//python/config_settings:exec_tools_toolchain"))
 PRECOMPILE = str(Label("//python/config_settings:precompile"))
 PYC_COLLECTION = str(Label("//python/config_settings:pyc_collection"))
 PRECOMPILE_SOURCE_RETENTION = str(Label("//python/config_settings:precompile_source_retention"))