Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ sphinxdocs
tests/integration/compile_pip_requirements/bazel-compile_pip_requirements
tests/integration/local_toolchains/bazel-local_toolchains
tests/integration/py_cc_toolchain_registered/bazel-py_cc_toolchain_registered
tests/integration/toolchain_target_settings/bazel-module_under_test
1 change: 1 addition & 0 deletions .bazelrc.deleted_packages
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ common --deleted_packages=tests/integration/local_toolchains
common --deleted_packages=tests/integration/pip_parse
common --deleted_packages=tests/integration/pip_parse/empty
common --deleted_packages=tests/integration/py_cc_toolchain_registered
common --deleted_packages=tests/integration/toolchain_target_settings
common --deleted_packages=tests/modules/another_module
common --deleted_packages=tests/modules/other
common --deleted_packages=tests/modules/other/nspkg_delta
Expand Down
35 changes: 34 additions & 1 deletion python/private/python.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ def _python_impl(module_ctx):
# the PLATFORMS global for this toolchain
toolchain_platform_keys = {}

# Extra target_settings to add to every registered toolchain, e.g. for
# gating the default toolchains behind a custom config_setting.
global_toolchain_target_settings = py.config.toolchain_target_settings

# Split the toolchain info into separate objects so they can be passed onto
# the repository rule.
for entry in toolchain_impls:
Expand All @@ -414,7 +418,7 @@ def _python_impl(module_ctx):

# The target_settings attribute may not be present for users
# patching python/versions.bzl.
toolchain_ts_map[key] = getattr(entry.platform, "target_settings", [])
toolchain_ts_map[key] = getattr(entry.platform, "target_settings", []) + global_toolchain_target_settings
toolchain_platform_keys[key] = entry.platform_name
toolchain_python_versions[key] = entry.full_python_version

Expand Down Expand Up @@ -702,6 +706,9 @@ def _process_global_overrides(*, tag, default, _fail = fail):

default["minor_mapping"] = tag.minor_mapping

if tag.toolchain_target_settings:
default["toolchain_target_settings"] = list(tag.toolchain_target_settings)

forwarded_attrs = sorted(AUTH_ATTRS) + [
"base_url",
"register_all_versions",
Expand Down Expand Up @@ -809,6 +816,7 @@ def _get_toolchain_config(*, modules, _fail = fail):
)

register_all_versions = default.pop("register_all_versions", False)
toolchain_target_settings = default.pop("toolchain_target_settings", [])
kwargs = default.pop("kwargs", {})

versions = {}
Expand All @@ -834,6 +842,7 @@ def _get_toolchain_config(*, modules, _fail = fail):
minor_mapping = minor_mapping,
default = default,
register_all_versions = register_all_versions,
toolchain_target_settings = toolchain_target_settings,
)

def _compute_default_python_version(mctx):
Expand Down Expand Up @@ -1144,6 +1153,30 @@ The values in this mapping override the default values and do not replace them.
default = {},
),
"register_all_versions": attr.bool(default = False, doc = "Add all versions"),
"toolchain_target_settings": attr.string_list(
mandatory = False,
doc = """\
A list of `config_setting` labels to add to the `target_settings` of every
toolchain registered by this module extension. This is useful for creating
separate "families" of toolchains gated behind custom build settings.
Comment thread
rickeylev marked this conversation as resolved.
Outdated

For example, to ensure the default prebuilt toolchains are only resolved when
a `prebuilt` config setting is active:

```starlark
python.override(
toolchain_target_settings = ["@@//:python_toolchain_family_prebuilt"],
)
```

These settings are appended to the `target_settings` of all toolchains
registered by the extension, including any that already have settings
from `python.single_version_platform_override`.

:::{versionadded} VERSION_NEXT
:::
""",
),
} | AUTH_ATTRS,
)

Expand Down
5 changes: 5 additions & 0 deletions tests/integration/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ rules_python_integration_test(
py_main = "custom_commands_test.py",
)

rules_python_integration_test(
name = "toolchain_target_settings_test",
py_main = "toolchain_target_settings_test.py",
)

py_library(
name = "runner_lib",
srcs = ["runner.py"],
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/toolchain_target_settings/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
common --lockfile_mode=off
test --test_output=errors
build --enable_runfiles
70 changes: 70 additions & 0 deletions tests/integration/toolchain_target_settings/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright 2025 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
load("@rules_python//python:py_test.bzl", "py_test")

# A flag to select which "family" of toolchains to use.
string_flag(
name = "family",
build_setting_default = "prebuilt",
values = [
"prebuilt",
"custom",
],
)

# Matches when the "prebuilt" family is selected.
# This is referenced in MODULE.bazel's python.override(toolchain_target_settings=...).
config_setting(
name = "is_prebuilt",
flag_values = {
":family": "prebuilt",
},
)

# Matches when the "custom" family is selected.
# No toolchains use this setting, so selecting it should produce an error.
config_setting(
name = "is_custom",
flag_values = {
":family": "custom",
},
)

# This target selects the "prebuilt" family via config_settings transition.
# Since python.override(toolchain_target_settings = ["@@//:is_prebuilt"]) gates
# the default toolchains, this should succeed: the flag matches, the config_setting
# is satisfied, and the default 3.13 toolchain resolves.
py_test(
name = "prebuilt_test",
srcs = ["main.py"],
config_settings = {
"//:family": "prebuilt",
},
main = "main.py",
)

# This target selects the "custom" family via config_settings transition.
# No toolchains have target_settings = [":is_custom"], so toolchain resolution
# should fail -- the default toolchains are gated behind ":is_prebuilt" and
# won't match.
py_test(
name = "custom_no_toolchain_test",
srcs = ["main.py"],
config_settings = {
"//:family": "custom",
},
main = "main.py",
)
37 changes: 37 additions & 0 deletions tests/integration/toolchain_target_settings/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2025 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module(name = "module_under_test")

bazel_dep(name = "rules_python", version = "0.0.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")

local_path_override(
module_name = "rules_python",
path = "../../..",
)

python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(python_version = "3.13")

# Gate ALL default-registered toolchains behind the "prebuilt" config setting.
# This prevents them from being a silent fallback when a different toolchain
# family is requested.
python.override(
toolchain_target_settings = ["@@//:is_prebuilt"],
Comment thread
rickeylev marked this conversation as resolved.
Outdated
)

# Register //:family as a transition setting so py_binary/py_test
# config_settings can set it.
config = use_extension("@rules_python//python/extensions:config.bzl", "config")
config.add_transition_setting(setting = "//:family")
Empty file.
1 change: 1 addition & 0 deletions tests/integration/toolchain_target_settings/WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Intentionally blank; bzlmod is used.
Empty file.
2 changes: 2 additions & 0 deletions tests/integration/toolchain_target_settings/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import sys
print(f"Python {sys.version}")
55 changes: 55 additions & 0 deletions tests/integration/toolchain_target_settings_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2025 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Integration test for python.override(toolchain_target_settings=...).

Verifies that when all default toolchains are gated behind a config_setting,
requesting a different (unregistered) toolchain family produces a toolchain
resolution error instead of silently falling back to the default toolchains.
"""

import unittest

from tests.integration import runner


class ToolchainTargetSettingsTest(runner.TestCase):
def test_prebuilt_family_resolves(self):
"""Building with the 'prebuilt' family should succeed.

The default toolchains have target_settings = [":is_prebuilt"],
and the transition sets //:family=prebuilt, so the config_setting
matches and toolchain resolution finds the default 3.13 toolchain.
"""
self.run_bazel("test", "//:prebuilt_test")

def test_custom_family_without_toolchain_fails(self):
"""Building with the 'custom' family should fail.

No toolchains have target_settings = [":is_custom"], and the default
toolchains are gated behind ":is_prebuilt" (via toolchain_target_settings),
so toolchain resolution should fail with no matching toolchain.
"""
result = self.run_bazel(
"build", "//:custom_no_toolchain_test", check=False
)
self.assertNotEqual(result.exit_code, 0, "Expected build to fail")
self.assert_result_matches(
result,
r"No matching toolchains found for types",
)


if __name__ == "__main__":
unittest.main()
30 changes: 29 additions & 1 deletion tests/python/python_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,16 @@ def _override(
base_url = "",
minor_mapping = {},
netrc = "",
register_all_versions = False):
register_all_versions = False,
toolchain_target_settings = []):
return struct(
auth_patterns = auth_patterns,
available_python_versions = available_python_versions,
base_url = base_url,
minor_mapping = minor_mapping,
netrc = netrc,
register_all_versions = register_all_versions,
toolchain_target_settings = toolchain_target_settings,
)

def _rules_python_module(is_root = False):
Expand Down Expand Up @@ -465,6 +467,32 @@ def _test_auth_overrides(env):

_tests.append(_test_auth_overrides)

def _test_toolchain_target_settings(env):
py = parse_modules(
module_ctx = mocks.mctx(
_mod(
name = "my_module",
toolchain = [_toolchain("3.12")],
override = [
_override(
toolchain_target_settings = [
"@@//my:custom_setting",
],
),
],
is_root = True,
),
_rules_python_module(),
),
logger = repo_utils.logger(verbosity_level = 0, name = "python"),
)

env.expect.that_collection(
py.config.toolchain_target_settings,
).contains_exactly(["@@//my:custom_setting"])

_tests.append(_test_toolchain_target_settings)

def _test_add_new_version(env):
py = parse_modules(
module_ctx = mocks.mctx(
Expand Down