Skip to content

Commit ce4dd59

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 415e893 commit ce4dd59

File tree

3 files changed

+140
-94
lines changed

3 files changed

+140
-94
lines changed

python/private/pypi/extension.bzl

Lines changed: 132 additions & 91 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,6 +84,10 @@ 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_,
8893
want_abis = [
@@ -190,17 +195,19 @@ def _create_whl_repos(
190195
whl_group_mapping = {}
191196
requirement_cycles = {}
192197

198+
platforms = _platforms(
199+
python_version = pip_attr.python_version,
200+
minor_mapping = minor_mapping,
201+
config = config,
202+
)
203+
193204
if evaluate_markers:
194205
# This is most likely unit tests
195206
pass
196207
elif config.enable_pipstar:
197208
evaluate_markers = lambda _, requirements: evaluate_markers_star(
198209
requirements = requirements,
199-
platforms = _platforms(
200-
python_version = pip_attr.python_version,
201-
minor_mapping = minor_mapping,
202-
config = config,
203-
),
210+
platforms = platforms,
204211
)
205212
else:
206213
# NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either
@@ -235,18 +242,14 @@ def _create_whl_repos(
235242
requirements_osx = pip_attr.requirements_darwin,
236243
requirements_windows = pip_attr.requirements_windows,
237244
extra_pip_args = pip_attr.extra_pip_args,
238-
platforms = sorted(config.platforms), # here we only need keys
245+
platforms = sorted(platforms), # here we only need keys
239246
python_version = full_version(
240247
version = pip_attr.python_version,
241248
minor_mapping = minor_mapping,
242249
),
243250
logger = logger,
244251
),
245-
platforms = _platforms(
246-
python_version = pip_attr.python_version,
247-
minor_mapping = minor_mapping,
248-
config = config,
249-
),
252+
platforms = platforms,
250253
extra_pip_args = pip_attr.extra_pip_args,
251254
get_index_urls = get_index_urls,
252255
evaluate_markers = evaluate_markers,
@@ -385,7 +388,7 @@ def _whl_repo(*, src, whl_library_args, is_multiple_versions, download_only, net
385388
),
386389
)
387390

388-
def _configure(config, *, platform, os_name, arch_name, config_settings, env = {}, want_abis, platform_tags, override = False):
391+
def _configure(config, *, platform, os_name, arch_name, config_settings, env = {}, want_abis, platform_tags, marker, override = False):
389392
"""Set the value in the config if the value is provided"""
390393
config.setdefault("platforms", {})
391394
if platform and (os_name or arch_name or config_settings or platform_tags or env):
@@ -406,21 +409,25 @@ def _configure(config, *, platform, os_name, arch_name, config_settings, env = {
406409
# the lowest priority one needs to be the first one
407410
platform_tags = ["any"] + platform_tags
408411

412+
want_abis = want_abis or [
413+
"cp{0}{1}",
414+
"abi3",
415+
"none",
416+
]
417+
env = {
418+
# default to this
419+
"implementation_name": "cpython",
420+
} | env
421+
409422
config["platforms"][platform] = struct(
410423
name = platform.replace("-", "_").lower(),
411-
os_name = os_name,
412424
arch_name = arch_name,
413425
config_settings = config_settings,
414-
want_abis = want_abis or [
415-
"cp{0}{1}",
416-
"abi3",
417-
"none",
418-
],
426+
env = env,
427+
marker = marker,
428+
os_name = os_name,
419429
platform_tags = platform_tags,
420-
env = {
421-
# default to this
422-
"implementation_name": "cpython",
423-
} | env,
430+
want_abis = want_abis,
424431
)
425432
else:
426433
config["platforms"].pop(platform)
@@ -432,79 +439,106 @@ def _set_defaults(defaults):
432439
a little problematic.
433440
"""
434441

435-
# NOTE: We have this so that it is easier to maintain unit tests assuming certain
436-
# defaults
437-
for cpu in [
438-
"x86_64",
439-
"aarch64",
440-
# TODO @aignas 2025-05-19: only leave tier 0-1 cpus when stabilizing the
441-
# `pip.default` extension. i.e. drop the below values - users will have to
442-
# define themselves if they need them.
443-
"arm",
444-
"ppc",
445-
"s390x",
446-
]:
447-
_configure(
448-
defaults,
449-
arch_name = cpu,
450-
os_name = "linux",
451-
platform = "linux_{}".format(cpu),
452-
want_abis = [],
453-
config_settings = [
454-
"@platforms//os:linux",
455-
"@platforms//cpu:{}".format(cpu),
442+
for suffix, config in {
443+
"": struct(
444+
want_abis = [
445+
"cp{0}{1}",
446+
"abi3",
447+
"none",
456448
],
457-
platform_tags = [
458-
"linux_*_{}".format(cpu),
459-
"manylinux_*_{}".format(cpu),
449+
freethreaded = "no",
450+
marker = "",
451+
),
452+
"_freethreaded": struct(
453+
want_abis = [
454+
"cp{0}{1}t",
455+
"none",
460456
],
461-
env = {
462-
"platform_version": "0",
463-
},
464-
)
465-
for cpu, platform_tag_cpus in {
466-
"aarch64": ["universal2", "arm64"],
467-
"x86_64": ["universal2", "x86_64"],
457+
freethreaded = "yes",
458+
marker = "python_version ~= \"3.13\"",
459+
),
468460
}.items():
469-
_configure(
470-
defaults,
471-
arch_name = cpu,
472-
os_name = "osx",
473-
platform = "osx_{}".format(cpu),
474-
config_settings = [
475-
"@platforms//os:osx",
476-
"@platforms//cpu:{}".format(cpu),
477-
],
478-
want_abis = [],
479-
platform_tags = [
480-
"macosx_*_{}".format(suffix)
481-
for suffix in platform_tag_cpus
482-
],
483-
# We choose the oldest non-EOL version at the time when we release `rules_python`.
484-
# See https://endoflife.date/macos
485-
env = {
486-
"platform_version": "14.0",
487-
},
488-
)
461+
freethreaded_flag = Label("//python/config_settings:_is_py_freethreaded_{}".format(config.freethreaded))
462+
463+
# NOTE: We have this so that it is easier to maintain unit tests assuming certain
464+
# defaults
465+
for cpu in [
466+
"x86_64",
467+
"aarch64",
468+
# TODO @aignas 2025-05-19: only leave tier 0-1 cpus when stabilizing the
469+
# `pip.default` extension. i.e. drop the below values - users will have to
470+
# define themselves if they need them.
471+
"arm",
472+
"ppc",
473+
"s390x",
474+
]:
475+
_configure(
476+
defaults,
477+
arch_name = cpu,
478+
os_name = "linux",
479+
platform = "linux_{}{}".format(cpu, suffix),
480+
want_abis = config.want_abis,
481+
marker = config.marker,
482+
config_settings = [
483+
"@platforms//os:linux",
484+
"@platforms//cpu:{}".format(cpu),
485+
freethreaded_flag,
486+
],
487+
platform_tags = [
488+
"linux_*_{}".format(cpu),
489+
"manylinux_*_{}".format(cpu),
490+
],
491+
env = {
492+
"platform_version": "0",
493+
},
494+
)
495+
for cpu, platform_tag_cpus in {
496+
"aarch64": ["universal2", "arm64"],
497+
"x86_64": ["universal2", "x86_64"],
498+
}.items():
499+
_configure(
500+
defaults,
501+
arch_name = cpu,
502+
os_name = "osx",
503+
platform = "osx_{}{}".format(cpu, suffix),
504+
config_settings = [
505+
"@platforms//os:osx",
506+
"@platforms//cpu:{}".format(cpu),
507+
freethreaded_flag,
508+
],
509+
want_abis = config.want_abis,
510+
marker = config.marker,
511+
platform_tags = [
512+
"macosx_*_{}".format(suffix)
513+
for suffix in platform_tag_cpus
514+
],
515+
# We choose the oldest non-EOL version at the time when we release `rules_python`.
516+
# See https://endoflife.date/macos
517+
env = {
518+
"platform_version": "14.0",
519+
},
520+
)
489521

490-
for cpu, platform_tags in {
491-
"x86_64": ["win_amd64"],
492-
}.items():
493-
_configure(
494-
defaults,
495-
arch_name = cpu,
496-
os_name = "windows",
497-
platform = "windows_{}".format(cpu),
498-
config_settings = [
499-
"@platforms//os:windows",
500-
"@platforms//cpu:{}".format(cpu),
501-
],
502-
want_abis = [],
503-
platform_tags = platform_tags,
504-
env = {
505-
"platform_version": "0",
506-
},
507-
)
522+
for cpu, platform_tags in {
523+
"x86_64": ["win_amd64"],
524+
}.items():
525+
_configure(
526+
defaults,
527+
arch_name = cpu,
528+
os_name = "windows",
529+
platform = "windows_{}{}".format(cpu, suffix),
530+
config_settings = [
531+
"@platforms//os:windows",
532+
"@platforms//cpu:{}".format(cpu),
533+
freethreaded_flag,
534+
],
535+
want_abis = config.want_abis,
536+
marker = config.marker,
537+
platform_tags = platform_tags,
538+
env = {
539+
"platform_version": "0",
540+
},
541+
)
508542

509543
def parse_modules(
510544
module_ctx,
@@ -573,6 +607,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
573607
env = tag.env,
574608
os_name = tag.os_name,
575609
platform = tag.platform,
610+
marker = tag.marker,
576611
platform_tags = tag.platform_tags,
577612
want_abis = tag.want_abis,
578613
override = mod.is_root,
@@ -905,6 +940,12 @@ Supported keys:
905940
::::{note}
906941
This is only used if the {envvar}`RULES_PYTHON_ENABLE_PIPSTAR` is enabled.
907942
::::
943+
""",
944+
),
945+
"marker": attr.string(
946+
doc = """\
947+
A marker which will be evaluated to disable the target platform for certain python versions. This
948+
is especially useful when defining freethreaded platform variants.
908949
""",
909950
),
910951
# 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 = [

tests/pypi/extension/extension_tests.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def _default(
8484
platform = None,
8585
platform_tags = None,
8686
env = None,
87+
marker = None,
8788
want_abis = None):
8889
return struct(
8990
arch_name = arch_name,
@@ -92,6 +93,7 @@ def _default(
9293
platform_tags = platform_tags or [],
9394
config_settings = config_settings,
9495
env = env or {},
96+
marker = marker or "",
9597
want_abis = want_abis or [],
9698
)
9799

@@ -1068,7 +1070,7 @@ def _test_pipstar_platforms(env):
10681070
("osx", "aarch64"),
10691071
]
10701072
] + [
1071-
_default(platform = name)
1073+
_default(platform = name + suffix)
10721074
for name in [
10731075
"linux_x86_64",
10741076
"linux_aarch64",
@@ -1079,6 +1081,7 @@ def _test_pipstar_platforms(env):
10791081
"osx_aarch64",
10801082
"windows_x86_64",
10811083
]
1084+
for suffix in ["", "_freethreaded"]
10821085
],
10831086
parse = [
10841087
_parse(

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