Skip to content

Commit 5a1d250

Browse files
committed
feat: freethreaded support for the builder API
Stacked on bazel-contrib#3058 This is a continuation of bazel-contrib#3058 where we define freethreaded platforms. They need to be used only for particular python versions so I included an extra marker configuration attribute where we are using pipstar marker evaluation before using the platform. I think this in general will be a useful tool to configure only particular platforms for particular python versions Work towards bazel-contrib#2548, since this shows how we can define custom platforms Work towards bazel-contrib#2747 TODO: - [ ] Fix the remaining expectations in the unit tests. Maybe make the tests less brittle and define platforms for unit testing.
1 parent 855a673 commit 5a1d250

File tree

5 files changed

+112
-41
lines changed

5 files changed

+112
-41
lines changed

MODULE.bazel

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,22 @@ pip = use_extension("//python/extensions:pip.bzl", "pip")
7070
config_settings = [
7171
"@platforms//cpu:{}".format(cpu),
7272
"@platforms//os:linux",
73+
"//python/config_settings:_is_py_freethreaded_{}".format(
74+
"yes" if freethreaded else "no",
75+
),
7376
],
7477
env = {"platform_version": "0"},
78+
marker = "python_version ~= \"3.13\"" if freethreaded else "",
7579
os_name = "linux",
76-
platform = "linux_{}".format(cpu),
80+
platform = "linux_{}{}".format(cpu, freethreaded),
7781
platform_tags = [
7882
"linux_*_{}".format(cpu),
7983
"manylinux_*_{}".format(cpu),
8084
],
85+
want_abis = [
86+
"cp{0}{1}t",
87+
"none",
88+
] if freethreaded else [],
8189
)
8290
for cpu in [
8391
"x86_64",
@@ -89,6 +97,10 @@ pip = use_extension("//python/extensions:pip.bzl", "pip")
8997
"ppc",
9098
"s390x",
9199
]
100+
for freethreaded in [
101+
"",
102+
"_freethreaded",
103+
]
92104
]
93105

94106
[
@@ -97,16 +109,24 @@ pip = use_extension("//python/extensions:pip.bzl", "pip")
97109
config_settings = [
98110
"@platforms//cpu:{}".format(cpu),
99111
"@platforms//os:osx",
112+
"//python/config_settings:_is_py_freethreaded_{}".format(
113+
"yes" if freethreaded else "no",
114+
),
100115
],
101116
# We choose the oldest non-EOL version at the time when we release `rules_python`.
102117
# See https://endoflife.date/macos
103118
env = {"platform_version": "14.0"},
119+
marker = "python_version ~= \"3.13\"" if freethreaded else "",
104120
os_name = "osx",
105-
platform = "osx_{}".format(cpu),
121+
platform = "osx_{}{}".format(cpu, freethreaded),
106122
platform_tags = [
107123
"macosx_*_{}".format(suffix)
108124
for suffix in platform_tag_cpus
109125
],
126+
want_abis = [
127+
"cp{0}{1}t",
128+
"none",
129+
] if freethreaded else [],
110130
)
111131
for cpu, platform_tag_cpus in {
112132
"aarch64": [
@@ -118,19 +138,38 @@ pip = use_extension("//python/extensions:pip.bzl", "pip")
118138
"x86_64",
119139
],
120140
}.items()
141+
for freethreaded in [
142+
"",
143+
"_freethreaded",
144+
]
145+
]
146+
147+
[
148+
pip.default(
149+
arch_name = "x86_64",
150+
config_settings = [
151+
"@platforms//cpu:x86_64",
152+
"@platforms//os:windows",
153+
"//python/config_settings:_is_py_freethreaded_{}".format(
154+
"yes" if freethreaded else "no",
155+
),
156+
],
157+
env = {"platform_version": "0"},
158+
marker = "python_version ~= \"3.13\"" if freethreaded else "",
159+
os_name = "windows",
160+
platform = "windows_x86_64{}".format(freethreaded),
161+
platform_tags = ["win_amd64"],
162+
want_abis = [
163+
"cp{0}{1}t",
164+
"none",
165+
] if freethreaded else [],
166+
)
167+
for freethreaded in [
168+
"",
169+
"_freethreaded",
170+
]
121171
]
122172

123-
pip.default(
124-
arch_name = "x86_64",
125-
config_settings = [
126-
"@platforms//cpu:x86_64",
127-
"@platforms//os:windows",
128-
],
129-
env = {"platform_version": "0"},
130-
os_name = "windows",
131-
platform = "windows_x86_64",
132-
platform_tags = ["win_amd64"],
133-
)
134173
pip.parse(
135174
# NOTE @aignas 2024-10-26: We have an integration test that depends on us
136175
# being able to build sdists for this hub, so explicitly set this to False.

python/private/pypi/extension.bzl

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ load(":hub_repository.bzl", "hub_repository", "whl_config_settings_to_json")
3030
load(":parse_requirements.bzl", "parse_requirements")
3131
load(":parse_whl_name.bzl", "parse_whl_name")
3232
load(":pep508_env.bzl", "env")
33+
load(":pep508_evaluate.bzl", "evaluate")
3334
load(":pip_repository_attrs.bzl", "ATTRS")
3435
load(":requirements_files_by_platform.bzl", "requirements_files_by_platform")
3536
load(":simpleapi_download.bzl", "simpleapi_download")
@@ -83,8 +84,15 @@ def _platforms(*, python_version, minor_mapping, config):
8384
os = values.os_name,
8485
arch = values.arch_name,
8586
)) | values.env
87+
88+
if values.marker and not evaluate(values.marker, env = env_):
89+
continue
90+
8691
platforms[key] = struct(
8792
env = env_,
93+
abi = abi,
94+
os_name = values.os_name,
95+
arch_name = values.arch_name,
8896
want_abis = [
8997
v.format(*python_version.split("."))
9098
for v in values.want_abis
@@ -190,17 +198,19 @@ def _create_whl_repos(
190198
whl_group_mapping = {}
191199
requirement_cycles = {}
192200

201+
platforms = _platforms(
202+
python_version = pip_attr.python_version,
203+
minor_mapping = minor_mapping,
204+
config = config,
205+
)
206+
193207
if evaluate_markers:
194208
# This is most likely unit tests
195209
pass
196210
elif config.enable_pipstar:
197211
evaluate_markers = lambda _, requirements: evaluate_markers_star(
198212
requirements = requirements,
199-
platforms = _platforms(
200-
python_version = pip_attr.python_version,
201-
minor_mapping = minor_mapping,
202-
config = config,
203-
),
213+
platforms = platforms,
204214
)
205215
else:
206216
# NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either
@@ -219,7 +229,18 @@ def _create_whl_repos(
219229
# spin up a Python interpreter.
220230
evaluate_markers = lambda module_ctx, requirements: evaluate_markers_py(
221231
module_ctx,
222-
requirements = requirements,
232+
requirements = {
233+
k: {
234+
# TODO @aignas 2025-07-06: should we leave this as is?
235+
p: "{abi}_{os}_{cpu}".format(
236+
abi = platforms[p].abi,
237+
os = platforms[p].os_name,
238+
cpu = platforms[p].arch_name,
239+
)
240+
for p in plats
241+
}
242+
for k, plats in requirements.items()
243+
},
223244
python_interpreter = pip_attr.python_interpreter,
224245
python_interpreter_target = python_interpreter_target,
225246
srcs = pip_attr._evaluate_markers_srcs,
@@ -235,18 +256,14 @@ def _create_whl_repos(
235256
requirements_osx = pip_attr.requirements_darwin,
236257
requirements_windows = pip_attr.requirements_windows,
237258
extra_pip_args = pip_attr.extra_pip_args,
238-
platforms = sorted(config.platforms), # here we only need keys
259+
platforms = sorted(platforms), # here we only need keys
239260
python_version = full_version(
240261
version = pip_attr.python_version,
241262
minor_mapping = minor_mapping,
242263
),
243264
logger = logger,
244265
),
245-
platforms = _platforms(
246-
python_version = pip_attr.python_version,
247-
minor_mapping = minor_mapping,
248-
config = config,
249-
),
266+
platforms = platforms,
250267
extra_pip_args = pip_attr.extra_pip_args,
251268
get_index_urls = get_index_urls,
252269
evaluate_markers = evaluate_markers,
@@ -385,7 +402,7 @@ def _whl_repo(*, src, whl_library_args, is_multiple_versions, download_only, net
385402
),
386403
)
387404

388-
def _configure(config, *, platform, os_name, arch_name, config_settings, env = {}, want_abis, platform_tags, override = False):
405+
def _configure(config, *, platform, os_name, arch_name, config_settings, env = {}, want_abis, platform_tags, marker, override = False):
389406
"""Set the value in the config if the value is provided"""
390407
config.setdefault("platforms", {})
391408
if platform and (os_name or arch_name or config_settings or platform_tags or env):
@@ -406,21 +423,25 @@ def _configure(config, *, platform, os_name, arch_name, config_settings, env = {
406423
# the lowest priority one needs to be the first one
407424
platform_tags = ["any"] + platform_tags
408425

426+
want_abis = want_abis or [
427+
"cp{0}{1}",
428+
"abi3",
429+
"none",
430+
]
431+
env = {
432+
# default to this
433+
"implementation_name": "cpython",
434+
} | env
435+
409436
config["platforms"][platform] = struct(
410437
name = platform.replace("-", "_").lower(),
411-
os_name = os_name,
412438
arch_name = arch_name,
413439
config_settings = config_settings,
414-
want_abis = want_abis or [
415-
"cp{0}{1}",
416-
"abi3",
417-
"none",
418-
],
440+
env = env,
441+
marker = marker,
442+
os_name = os_name,
419443
platform_tags = platform_tags,
420-
env = {
421-
# default to this
422-
"implementation_name": "cpython",
423-
} | env,
444+
want_abis = want_abis,
424445
)
425446
else:
426447
config["platforms"].pop(platform)
@@ -491,6 +512,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
491512
env = tag.env,
492513
os_name = tag.os_name,
493514
platform = tag.platform,
515+
marker = tag.marker,
494516
platform_tags = tag.platform_tags,
495517
want_abis = tag.want_abis,
496518
override = mod.is_root,
@@ -823,6 +845,12 @@ Supported keys:
823845
::::{note}
824846
This is only used if the {envvar}`RULES_PYTHON_ENABLE_PIPSTAR` is enabled.
825847
::::
848+
""",
849+
),
850+
"marker": attr.string(
851+
doc = """\
852+
A marker which will be evaluated to disable the target platform for certain python versions. This
853+
is especially useful when defining freethreaded platform variants.
826854
""",
827855
),
828856
# The values for PEP508 env marker evaluation during the lock file parsing

python/private/pypi/requirements_files_by_platform.bzl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ def _default_platforms(*, filter, platforms):
3737
if not prefix:
3838
return platforms
3939

40-
match = [p for p in platforms if p.startswith(prefix)]
40+
match = [p for p in platforms if p.startswith(prefix) or (
41+
p.startswith("cp") and p.partition("_")[-1].startswith(prefix)
42+
)]
4143
else:
4244
match = [p for p in platforms if filter in p]
4345

@@ -140,7 +142,7 @@ def requirements_files_by_platform(
140142
if logger:
141143
logger.debug(lambda: "Platforms from pip args: {}".format(platforms_from_args))
142144

143-
default_platforms = [_platform(p, python_version) for p in platforms]
145+
default_platforms = platforms
144146

145147
if platforms_from_args:
146148
lock_files = [
@@ -252,6 +254,6 @@ def requirements_files_by_platform(
252254

253255
ret = {}
254256
for plat, file in requirements.items():
255-
ret.setdefault(file, []).append(plat)
257+
ret.setdefault(file, []).append(_platform(plat, python_version = python_version))
256258

257259
return ret

python/private/pypi/requirements_parser/resolve_target_platforms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def main():
5050
hashes = prefix + hashes
5151

5252
req = Requirement(entry)
53-
for p in target_platforms:
54-
(platform,) = Platform.from_string(p)
53+
for p, tripple in target_platforms.items():
54+
(platform,) = Platform.from_string(tripple)
5555
if not req.marker or req.marker.evaluate(platform.env_markers("")):
5656
response.setdefault(requirement_line, []).append(p)
5757

tests/pypi/extension/extension_tests.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def _default(
101101
platform = None,
102102
platform_tags = None,
103103
env = None,
104+
marker = None,
104105
want_abis = None):
105106
return struct(
106107
arch_name = arch_name,
@@ -109,6 +110,7 @@ def _default(
109110
platform_tags = platform_tags or [],
110111
config_settings = config_settings,
111112
env = env or {},
113+
marker = marker or "",
112114
want_abis = want_abis or [],
113115
)
114116

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy