Skip to content

Commit 68dfedb

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 68dfedb

File tree

5 files changed

+110
-40
lines changed

5 files changed

+110
-40
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: 50 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,17 @@ 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+
p: "{abi}_{os}_{cpu}".format(
235+
abi = platforms[p].abi,
236+
os = platforms[p].os_name,
237+
cpu = platforms[p].arch_name,
238+
)
239+
for p in plats
240+
}
241+
for k, plats in requirements.items()
242+
},
223243
python_interpreter = pip_attr.python_interpreter,
224244
python_interpreter_target = python_interpreter_target,
225245
srcs = pip_attr._evaluate_markers_srcs,
@@ -235,18 +255,14 @@ def _create_whl_repos(
235255
requirements_osx = pip_attr.requirements_darwin,
236256
requirements_windows = pip_attr.requirements_windows,
237257
extra_pip_args = pip_attr.extra_pip_args,
238-
platforms = sorted(config.platforms), # here we only need keys
258+
platforms = sorted(platforms), # here we only need keys
239259
python_version = full_version(
240260
version = pip_attr.python_version,
241261
minor_mapping = minor_mapping,
242262
),
243263
logger = logger,
244264
),
245-
platforms = _platforms(
246-
python_version = pip_attr.python_version,
247-
minor_mapping = minor_mapping,
248-
config = config,
249-
),
265+
platforms = platforms,
250266
extra_pip_args = pip_attr.extra_pip_args,
251267
get_index_urls = get_index_urls,
252268
evaluate_markers = evaluate_markers,
@@ -385,7 +401,7 @@ def _whl_repo(*, src, whl_library_args, is_multiple_versions, download_only, net
385401
),
386402
)
387403

388-
def _configure(config, *, platform, os_name, arch_name, config_settings, env = {}, want_abis, platform_tags, override = False):
404+
def _configure(config, *, platform, os_name, arch_name, config_settings, env = {}, want_abis, platform_tags, marker, override = False):
389405
"""Set the value in the config if the value is provided"""
390406
config.setdefault("platforms", {})
391407
if platform and (os_name or arch_name or config_settings or platform_tags or env):
@@ -406,21 +422,25 @@ def _configure(config, *, platform, os_name, arch_name, config_settings, env = {
406422
# the lowest priority one needs to be the first one
407423
platform_tags = ["any"] + platform_tags
408424

425+
want_abis = want_abis or [
426+
"cp{0}{1}",
427+
"abi3",
428+
"none",
429+
]
430+
env = {
431+
# default to this
432+
"implementation_name": "cpython",
433+
} | env
434+
409435
config["platforms"][platform] = struct(
410436
name = platform.replace("-", "_").lower(),
411-
os_name = os_name,
412437
arch_name = arch_name,
413438
config_settings = config_settings,
414-
want_abis = want_abis or [
415-
"cp{0}{1}",
416-
"abi3",
417-
"none",
418-
],
439+
env = env,
440+
marker = marker,
441+
os_name = os_name,
419442
platform_tags = platform_tags,
420-
env = {
421-
# default to this
422-
"implementation_name": "cpython",
423-
} | env,
443+
want_abis = want_abis,
424444
)
425445
else:
426446
config["platforms"].pop(platform)
@@ -491,6 +511,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
491511
env = tag.env,
492512
os_name = tag.os_name,
493513
platform = tag.platform,
514+
marker = tag.marker,
494515
platform_tags = tag.platform_tags,
495516
want_abis = tag.want_abis,
496517
override = mod.is_root,
@@ -823,6 +844,12 @@ Supported keys:
823844
::::{note}
824845
This is only used if the {envvar}`RULES_PYTHON_ENABLE_PIPSTAR` is enabled.
825846
::::
847+
""",
848+
),
849+
"marker": attr.string(
850+
doc = """\
851+
A marker which will be evaluated to disable the target platform for certain python versions. This
852+
is especially useful when defining freethreaded platform variants.
826853
""",
827854
),
828855
# The values for PEP508 env marker evaluation during the lock file parsing

python/private/pypi/requirements_files_by_platform.bzl

Lines changed: 4 additions & 2 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+
# TODO @aignas 2025-07-06: this assumes that the platforms start with
41+
# cp313.3_<suffix>
42+
match = [p for p in platforms if p.partition("_")[-1].startswith(prefix)]
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 = [

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