Skip to content

Commit 367d09e

Browse files
authored
refactor: make python extension generate platform toolchains (#2875)
This makes the python bzlmod extension handle generating the platform-specific toolchain entries ("python_3_10_{platform}"). This is to eventually allow adding additional toolchains that aren't part of the PLATFORMS mapping in versions.bzl and have their own custom constraints. The main things this refactor does are: 1. The bzlmod phase passes the full list of implementation toolchains to create (previously, it relied on `hub_repo` to generate the implementation names). 2. The name of a toolchain (the toolchain.name arg) and the repo that implements it (the py_toolchain_suite.user_repository_repo arg) are separate. This allows future work to mixin toolchains that point to arbitrary repos. 3. The platform meta data uses a list of target settings instead of dict of flag values. This allows more arbitrary target settings. For now, flag values on the platform metadata is still looked for because it's known that users patch python/versions.bzl. Along the way: * Factor out a platform_info helper in versions.bzl * Factor out a NOT_ACTUALLY_PUBLIC constants to better denote things that are public visibility, but actually internal. * Add some docs to some internals so we don't have to chase down their definitions. Work towards #2081
1 parent a13fcd7 commit 367d09e

8 files changed

+309
-173
lines changed

internal_dev_setup.bzl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ def rules_python_internal_setup():
3434
name = "pythons_hub",
3535
minor_mapping = MINOR_MAPPING,
3636
default_python_version = "",
37-
toolchain_prefixes = [],
38-
toolchain_python_versions = [],
39-
toolchain_set_python_version_constraints = [],
40-
toolchain_user_repository_names = [],
4137
python_versions = sorted(TOOL_VERSIONS.keys()),
38+
toolchain_names = [],
39+
toolchain_repo_names = {},
40+
toolchain_target_compatible_with_map = {},
41+
toolchain_target_settings_map = {},
42+
toolchain_platform_keys = {},
43+
toolchain_python_versions = {},
44+
toolchain_set_python_version_constraints = {},
45+
base_toolchain_repo_names = [],
4246
)
4347

4448
runtime_env_repo(name = "rules_python_runtime_env_tc_info")

python/private/config_settings.bzl

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ If the value is missing, then the default value is being used, see documentation
3131
{docs_url}/python/config_settings
3232
"""
3333

34+
# Indicates something needs public visibility so that other generated code can
35+
# access it, but it's not intended for general public usage.
36+
_NOT_ACTUALLY_PUBLIC = ["//visibility:public"]
37+
3438
def construct_config_settings(*, name, default_version, versions, minor_mapping, documented_flags): # buildifier: disable=function-docstring
3539
"""Create a 'python_version' config flag and construct all config settings used in rules_python.
3640
@@ -128,7 +132,30 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping,
128132
# `whl_library` in the hub repo created by `pip.parse`.
129133
flag_values = {"current_config": "will-never-match"},
130134
# Only public so that PyPI hub repo can access it
131-
visibility = ["//visibility:public"],
135+
visibility = _NOT_ACTUALLY_PUBLIC,
136+
)
137+
138+
libc = Label("//python/config_settings:py_linux_libc")
139+
native.config_setting(
140+
name = "_is_py_linux_libc_glibc",
141+
flag_values = {libc: "glibc"},
142+
visibility = _NOT_ACTUALLY_PUBLIC,
143+
)
144+
native.config_setting(
145+
name = "_is_py_linux_libc_musl",
146+
flag_values = {libc: "glibc"},
147+
visibility = _NOT_ACTUALLY_PUBLIC,
148+
)
149+
freethreaded = Label("//python/config_settings:py_freethreaded")
150+
native.config_setting(
151+
name = "_is_py_freethreaded_yes",
152+
flag_values = {freethreaded: "yes"},
153+
visibility = _NOT_ACTUALLY_PUBLIC,
154+
)
155+
native.config_setting(
156+
name = "_is_py_freethreaded_no",
157+
flag_values = {freethreaded: "no"},
158+
visibility = _NOT_ACTUALLY_PUBLIC,
132159
)
133160

134161
def _python_version_flag_impl(ctx):

python/private/py_repositories.bzl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,15 @@ def py_repositories():
3939
name = "pythons_hub",
4040
minor_mapping = MINOR_MAPPING,
4141
default_python_version = "",
42-
toolchain_prefixes = [],
43-
toolchain_python_versions = [],
44-
toolchain_set_python_version_constraints = [],
45-
toolchain_user_repository_names = [],
4642
python_versions = sorted(TOOL_VERSIONS.keys()),
43+
toolchain_names = [],
44+
toolchain_repo_names = {},
45+
toolchain_target_compatible_with_map = {},
46+
toolchain_target_settings_map = {},
47+
toolchain_platform_keys = {},
48+
toolchain_python_versions = {},
49+
toolchain_set_python_version_constraints = {},
50+
base_toolchain_repo_names = [],
4751
)
4852
http_archive(
4953
name = "bazel_skylib",

python/private/py_toolchain_suite.bzl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,20 @@ def py_toolchain_suite(
3434
python_version,
3535
set_python_version_constraint,
3636
flag_values,
37+
target_settings = [],
3738
target_compatible_with = []):
3839
"""For internal use only.
3940
4041
Args:
4142
prefix: Prefix for toolchain target names.
42-
user_repository_name: The name of the user repository.
43+
user_repository_name: The name of the repository with the toolchain
44+
implementation (it's assumed to have particular target names within
45+
it). Does not include the leading "@".
4346
python_version: The full (X.Y.Z) version of the interpreter.
4447
set_python_version_constraint: True or False as a string.
45-
flag_values: Extra flag values to match for this toolchain.
48+
flag_values: Extra flag values to match for this toolchain. These
49+
are prepended to target_settings.
50+
target_settings: Extra target_settings to match for this toolchain.
4651
target_compatible_with: list constraints the toolchains are compatible with.
4752
"""
4853

@@ -82,7 +87,7 @@ def py_toolchain_suite(
8287
match_any = match_any,
8388
visibility = ["//visibility:private"],
8489
)
85-
target_settings = [name]
90+
target_settings = [name] + target_settings
8691
else:
8792
fail(("Invalid set_python_version_constraint value: got {} {}, wanted " +
8893
"either the string 'True' or the string 'False'; " +

python/private/python.bzl

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,9 @@ load(":python_register_toolchains.bzl", "python_register_toolchains")
2222
load(":pythons_hub.bzl", "hub_repo")
2323
load(":repo_utils.bzl", "repo_utils")
2424
load(":semver.bzl", "semver")
25-
load(":text_util.bzl", "render")
2625
load(":toolchains_repo.bzl", "multi_toolchain_aliases")
2726
load(":util.bzl", "IS_BAZEL_6_4_OR_HIGHER")
2827

29-
# This limit can be increased essentially arbitrarily, but doing so will cause a rebuild of all
30-
# targets using any of these toolchains due to the changed repository name.
31-
_MAX_NUM_TOOLCHAINS = 9999
32-
_TOOLCHAIN_INDEX_PAD_LENGTH = len(str(_MAX_NUM_TOOLCHAINS))
33-
3428
def parse_modules(*, module_ctx, _fail = fail):
3529
"""Parse the modules and return a struct for registrations.
3630
@@ -240,9 +234,6 @@ def parse_modules(*, module_ctx, _fail = fail):
240234
# toolchain. We need the default last.
241235
toolchains.append(default_toolchain)
242236

243-
if len(toolchains) > _MAX_NUM_TOOLCHAINS:
244-
fail("more than {} python versions are not supported".format(_MAX_NUM_TOOLCHAINS))
245-
246237
# sort the toolchains so that the toolchain versions that are in the
247238
# `minor_mapping` are coming first. This ensures that `python_version =
248239
# "3.X"` transitions work as expected.
@@ -275,6 +266,9 @@ def parse_modules(*, module_ctx, _fail = fail):
275266
def _python_impl(module_ctx):
276267
py = parse_modules(module_ctx = module_ctx)
277268

269+
# dict[str version, list[str] platforms]; where version is full
270+
# python version string ("3.4.5"), and platforms are keys from
271+
# the PLATFORMS global.
278272
loaded_platforms = {}
279273
for toolchain_info in py.toolchains:
280274
# Ensure that we pass the full version here.
@@ -297,30 +291,82 @@ def _python_impl(module_ctx):
297291
**kwargs
298292
)
299293

300-
# Create the pythons_hub repo for the interpreter meta data and the
301-
# the various toolchains.
294+
# List of the base names ("python_3_10") for the toolchain repos
295+
base_toolchain_repo_names = []
296+
297+
# list[str] The infix to use for the resulting toolchain() `name` arg.
298+
toolchain_names = []
299+
300+
# dict[str i, str repo]; where repo is the full repo name
301+
# ("python_3_10_unknown-linux-x86_64") for the toolchain
302+
# i corresponds to index `i` in toolchain_names
303+
toolchain_repo_names = {}
304+
305+
# dict[str i, list[str] constraints]; where constraints is a list
306+
# of labels for target_compatible_with
307+
# i corresponds to index `i` in toolchain_names
308+
toolchain_tcw_map = {}
309+
310+
# dict[str i, list[str] settings]; where settings is a list
311+
# of labels for target_settings
312+
# i corresponds to index `i` in toolchain_names
313+
toolchain_ts_map = {}
314+
315+
# dict[str i, str set_constraint]; where set_constraint is the string
316+
# "True" or "False".
317+
# i corresponds to index `i` in toolchain_names
318+
toolchain_set_python_version_constraints = {}
319+
320+
# dict[str i, str python_version]; where python_version is the full
321+
# python version ("3.4.5").
322+
toolchain_python_versions = {}
323+
324+
# dict[str i, str platform_key]; where platform_key is the key within
325+
# the PLATFORMS global for this toolchain
326+
toolchain_platform_keys = {}
327+
328+
# Split the toolchain info into separate objects so they can be passed onto
329+
# the repository rule.
330+
for i, t in enumerate(py.toolchains):
331+
is_last = (i + 1) == len(py.toolchains)
332+
base_name = t.name
333+
base_toolchain_repo_names.append(base_name)
334+
fv = full_version(version = t.python_version, minor_mapping = py.config.minor_mapping)
335+
for platform in loaded_platforms[fv]:
336+
if platform not in PLATFORMS:
337+
continue
338+
key = str(len(toolchain_names))
339+
340+
full_name = "{}_{}".format(base_name, platform)
341+
toolchain_names.append(full_name)
342+
toolchain_repo_names[key] = full_name
343+
toolchain_tcw_map[key] = PLATFORMS[platform].compatible_with
344+
345+
# The target_settings attribute may not be present for users
346+
# patching python/versions.bzl.
347+
toolchain_ts_map[key] = getattr(PLATFORMS[platform], "target_settings", [])
348+
toolchain_platform_keys[key] = platform
349+
toolchain_python_versions[key] = fv
350+
351+
# The last toolchain is the default; it can't have version constraints
352+
# Despite the implication of the arg name, the values are strs, not bools
353+
toolchain_set_python_version_constraints[key] = (
354+
"True" if not is_last else "False"
355+
)
356+
302357
hub_repo(
303358
name = "pythons_hub",
304-
# Last toolchain is default
359+
toolchain_names = toolchain_names,
360+
toolchain_repo_names = toolchain_repo_names,
361+
toolchain_target_compatible_with_map = toolchain_tcw_map,
362+
toolchain_target_settings_map = toolchain_ts_map,
363+
toolchain_platform_keys = toolchain_platform_keys,
364+
toolchain_python_versions = toolchain_python_versions,
365+
toolchain_set_python_version_constraints = toolchain_set_python_version_constraints,
366+
base_toolchain_repo_names = [t.name for t in py.toolchains],
305367
default_python_version = py.default_python_version,
306368
minor_mapping = py.config.minor_mapping,
307369
python_versions = list(py.config.default["tool_versions"].keys()),
308-
toolchain_prefixes = [
309-
render.toolchain_prefix(index, toolchain.name, _TOOLCHAIN_INDEX_PAD_LENGTH)
310-
for index, toolchain in enumerate(py.toolchains)
311-
],
312-
toolchain_python_versions = [
313-
full_version(version = t.python_version, minor_mapping = py.config.minor_mapping)
314-
for t in py.toolchains
315-
],
316-
# The last toolchain is the default; it can't have version constraints
317-
# Despite the implication of the arg name, the values are strs, not bools
318-
toolchain_set_python_version_constraints = [
319-
"True" if i != len(py.toolchains) - 1 else "False"
320-
for i in range(len(py.toolchains))
321-
],
322-
toolchain_user_repository_names = [t.name for t in py.toolchains],
323-
loaded_platforms = loaded_platforms,
324370
)
325371

326372
# This is require in order to support multiple version py_test

0 commit comments

Comments
 (0)