Skip to content

Commit 0a2cf05

Browse files
committed
refactor: load target_platforms through the hub
This PR moves the parsing of `Requires-Dist` to the analysis phase within the `whl_library_targets_from_requires` macro. The original `whl_library_targets` macro has been left unchanged so that I don't have to reinvent the unit tests - it is well covered under tests. Before this PR we had to wire the `target_platforms` via the `experimental_target_platforms` attr in the `whl_library`, which means that whenever this would change (e.g. the minor Python version changes), the wheel would be re-extracted even though the final result may be the same. This also cleans up the code by removing left over TODO notes or code that no longer make sense. Work towards bazel-contrib#260, bazel-contrib#2319
1 parent 8fc25de commit 0a2cf05

14 files changed

+358
-205
lines changed

config.bzl.tmpl.bzlmod

Whitespace-only changes.

python/private/pypi/BUILD.bazel

+2-12
Original file line numberDiff line numberDiff line change
@@ -212,15 +212,6 @@ bzl_library(
212212
],
213213
)
214214

215-
bzl_library(
216-
name = "pep508_bzl",
217-
srcs = ["pep508.bzl"],
218-
deps = [
219-
":pep508_env_bzl",
220-
":pep508_evaluate_bzl",
221-
],
222-
)
223-
224215
bzl_library(
225216
name = "pep508_deps_bzl",
226217
srcs = ["pep508_deps.bzl"],
@@ -378,13 +369,12 @@ bzl_library(
378369
":attrs_bzl",
379370
":deps_bzl",
380371
":generate_whl_library_build_bazel_bzl",
381-
":parse_whl_name_bzl",
382372
":patch_whl_bzl",
383-
":pep508_deps_bzl",
373+
":pep508_requirement_bzl",
384374
":pypi_repo_utils_bzl",
385375
":whl_metadata_bzl",
386-
":whl_target_platforms_bzl",
387376
"//python/private:auth_bzl",
377+
"//python/private:bzlmod_enabled_bzl",
388378
"//python/private:envsubst_bzl",
389379
"//python/private:is_standalone_interpreter_bzl",
390380
"//python/private:repo_utils_bzl",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Extra configuration values that are exposed from the hub repository for spoke repositories to access.
2+
3+
NOTE: This is internal `rules_python` API and if you would like to depend on it, please raise an issue
4+
with your usecase. This may change in between rules_python versions without any notice.
5+
6+
@generated by rules_python pip.parse bzlmod extension.
7+
"""
8+
9+
target_platforms = %%TARGET_PLATFORMS%%

python/private/pypi/extension.bzl

+18-23
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ load(":simpleapi_download.bzl", "simpleapi_download")
3232
load(":whl_config_setting.bzl", "whl_config_setting")
3333
load(":whl_library.bzl", "whl_library")
3434
load(":whl_repo_name.bzl", "pypi_repo_name", "whl_repo_name")
35-
load(":whl_target_platforms.bzl", "whl_target_platforms")
3635

3736
def _major_minor_version(version):
3837
version = semver(version)
@@ -68,7 +67,6 @@ def _create_whl_repos(
6867
*,
6968
pip_attr,
7069
whl_overrides,
71-
evaluate_markers = evaluate_markers,
7270
available_interpreters = INTERPRETER_LABELS,
7371
get_index_urls = None):
7472
"""create all of the whl repositories
@@ -77,7 +75,6 @@ def _create_whl_repos(
7775
module_ctx: {type}`module_ctx`.
7876
pip_attr: {type}`struct` - the struct that comes from the tag class iteration.
7977
whl_overrides: {type}`dict[str, struct]` - per-wheel overrides.
80-
evaluate_markers: the function to use to evaluate markers.
8178
get_index_urls: A function used to get the index URLs
8279
available_interpreters: {type}`dict[str, Label]` The dictionary of available
8380
interpreters that have been registered using the `python` bzlmod extension.
@@ -162,14 +159,12 @@ def _create_whl_repos(
162159
requirements_osx = pip_attr.requirements_darwin,
163160
requirements_windows = pip_attr.requirements_windows,
164161
extra_pip_args = pip_attr.extra_pip_args,
162+
# TODO @aignas 2025-04-15: pass the full version into here
165163
python_version = major_minor,
166164
logger = logger,
167165
),
168166
extra_pip_args = pip_attr.extra_pip_args,
169167
get_index_urls = get_index_urls,
170-
# NOTE @aignas 2025-02-24: we will use the "cp3xx_os_arch" platform labels
171-
# for converting to the PEP508 environment and will evaluate them in starlark
172-
# without involving the interpreter at all.
173168
evaluate_markers = evaluate_markers,
174169
logger = logger,
175170
)
@@ -191,7 +186,6 @@ def _create_whl_repos(
191186
enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs,
192187
environment = pip_attr.environment,
193188
envsubst = pip_attr.envsubst,
194-
experimental_target_platforms = pip_attr.experimental_target_platforms,
195189
group_deps = group_deps,
196190
group_name = group_name,
197191
pip_data_exclude = pip_attr.pip_data_exclude,
@@ -244,6 +238,12 @@ def _create_whl_repos(
244238
},
245239
extra_aliases = extra_aliases,
246240
whl_libraries = whl_libraries,
241+
target_platforms = {
242+
plat: None
243+
for reqs in requirements_by_platform.values()
244+
for req in reqs
245+
for plat in req.target_platforms
246+
},
247247
)
248248

249249
def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patterns, multiple_requirements_for_whl = False, python_version):
@@ -274,20 +274,11 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt
274274
args["urls"] = [distribution.url]
275275
args["sha256"] = distribution.sha256
276276
args["filename"] = distribution.filename
277-
args["experimental_target_platforms"] = requirement.target_platforms
278277

279278
# Pure python wheels or sdists may need to have a platform here
280279
target_platforms = None
281280
if distribution.filename.endswith(".whl") and not distribution.filename.endswith("-any.whl"):
282-
parsed_whl = parse_whl_name(distribution.filename)
283-
whl_platforms = whl_target_platforms(
284-
platform_tag = parsed_whl.platform_tag,
285-
)
286-
args["experimental_target_platforms"] = [
287-
p
288-
for p in requirement.target_platforms
289-
if [None for wp in whl_platforms if p.endswith(wp.target_platform)]
290-
]
281+
pass
291282
elif multiple_requirements_for_whl:
292283
target_platforms = requirement.target_platforms
293284

@@ -416,6 +407,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
416407
hub_group_map = {}
417408
exposed_packages = {}
418409
extra_aliases = {}
410+
target_platforms = {}
419411
whl_libraries = {}
420412

421413
for mod in module_ctx.modules:
@@ -498,6 +490,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
498490
for whl_name, aliases in out.extra_aliases.items():
499491
extra_aliases[hub_name].setdefault(whl_name, {}).update(aliases)
500492
exposed_packages.setdefault(hub_name, {}).update(out.exposed_packages)
493+
target_platforms.setdefault(hub_name, {}).update(out.target_platforms)
501494
whl_libraries.update(out.whl_libraries)
502495

503496
# TODO @aignas 2024-04-05: how do we support different requirement
@@ -535,6 +528,10 @@ You cannot use both the additive_build_content and additive_build_content_file a
535528
}
536529
for hub_name, extra_whl_aliases in extra_aliases.items()
537530
},
531+
target_platforms = {
532+
hub_name: sorted(p)
533+
for hub_name, p in target_platforms.items()
534+
},
538535
whl_libraries = {
539536
k: dict(sorted(args.items()))
540537
for k, args in sorted(whl_libraries.items())
@@ -626,15 +623,13 @@ def _pip_impl(module_ctx):
626623
},
627624
packages = mods.exposed_packages.get(hub_name, []),
628625
groups = mods.hub_group_map.get(hub_name),
626+
target_platforms = mods.target_platforms.get(hub_name, []),
629627
)
630628

631629
if bazel_features.external_deps.extension_metadata_has_reproducible:
632-
# If we are not using the `experimental_index_url feature, the extension is fully
633-
# deterministic and we don't need to create a lock entry for it.
634-
#
635-
# In order to be able to dogfood the `experimental_index_url` feature before it gets
636-
# stabilized, we have created the `_pip_non_reproducible` function, that will result
637-
# in extra entries in the lock file.
630+
# NOTE @aignas 2025-04-15: this is set to be reproducible, because the
631+
# results after calling the PyPI index should be reproducible on each
632+
# machine.
638633
return module_ctx.extension_metadata(reproducible = True)
639634
else:
640635
return None

python/private/pypi/generate_whl_library_build_bazel.bzl

+18-5
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,23 @@ _RENDER = {
2121
"copy_files": render.dict,
2222
"data": render.list,
2323
"data_exclude": render.list,
24-
"dependencies": render.list,
25-
"dependencies_by_platform": lambda x: render.dict(x, value_repr = render.list),
2624
"entry_points": render.dict,
25+
"extras": render.list,
2726
"group_deps": render.list,
27+
"requires_dist": render.list,
2828
"srcs_exclude": render.list,
29-
"tags": render.list,
29+
"target_platforms": lambda x: render.list(x) if x else "target_platforms",
3030
}
3131

3232
# NOTE @aignas 2024-10-25: We have to keep this so that files in
3333
# this repository can be publicly visible without the need for
3434
# export_files
3535
_TEMPLATE = """\
36-
load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets")
36+
{loads}
3737
3838
package(default_visibility = ["//visibility:public"])
3939
40-
whl_library_targets(
40+
whl_library_targets_from_requires(
4141
{kwargs}
4242
)
4343
"""
@@ -57,6 +57,18 @@ def generate_whl_library_build_bazel(
5757
A complete BUILD file as a string
5858
"""
5959

60+
loads = [
61+
"""load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires")""",
62+
]
63+
if not kwargs.setdefault("target_platforms", None):
64+
dep_template = kwargs["dep_template"]
65+
loads.append(
66+
"load(\"{}\", \"{}\")".format(
67+
dep_template.format(name = "", target = "config.bzl"),
68+
"target_platforms",
69+
),
70+
)
71+
6072
additional_content = []
6173
if annotation:
6274
kwargs["data"] = annotation.data
@@ -70,6 +82,7 @@ def generate_whl_library_build_bazel(
7082
contents = "\n".join(
7183
[
7284
_TEMPLATE.format(
85+
loads = "\n".join(loads),
7386
kwargs = render.indent("\n".join([
7487
"{} = {},".format(k, _RENDER.get(k, repr)(v))
7588
for k, v in sorted(kwargs.items())

python/private/pypi/hub_repository.bzl

+16-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ def _impl(rctx):
4545
macro_tmpl = "@@{name}//{{}}:{{}}".format(name = rctx.attr.name)
4646

4747
rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
48-
rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
48+
rctx.template(
49+
"config.bzl",
50+
rctx.attr._config_template,
51+
substitutions = {
52+
"%%TARGET_PLATFORMS%%": render.list(rctx.attr.target_platforms),
53+
},
54+
)
55+
rctx.template("requirements.bzl", rctx.attr._requirements_bzl_template, substitutions = {
4956
"%%ALL_DATA_REQUIREMENTS%%": render.list([
5057
macro_tmpl.format(p, "data")
5158
for p in bzl_packages
@@ -80,14 +87,21 @@ The list of packages that will be exposed via all_*requirements macros. Defaults
8087
mandatory = True,
8188
doc = "The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name.",
8289
),
90+
"target_platforms": attr.string_list(
91+
mandatory = True,
92+
doc = "All of the target platforms for the hub repo",
93+
),
8394
"whl_map": attr.string_dict(
8495
mandatory = True,
8596
doc = """\
8697
The wheel map where values are json.encoded strings of the whl_map constructed
8798
in the pip.parse tag class.
8899
""",
89100
),
90-
"_template": attr.label(
101+
"_config_template": attr.label(
102+
default = ":config.bzl.tmpl.bzlmod",
103+
),
104+
"_requirements_bzl_template": attr.label(
91105
default = ":requirements.bzl.tmpl.bzlmod",
92106
),
93107
},

python/private/pypi/pep508.bzl

-23
This file was deleted.

python/private/pypi/pep508_deps.bzl

+34-25
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""This module is for implementing PEP508 compliant METADATA deps parsing.
1616
"""
1717

18+
load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION")
1819
load("//python/private:normalize_name.bzl", "normalize_name")
1920
load(":pep508_env.bzl", "env")
2021
load(":pep508_evaluate.bzl", "evaluate")
@@ -35,7 +36,7 @@ _ALL_ARCH_VALUES = [
3536
"x86_64",
3637
]
3738

38-
def deps(name, *, requires_dist, platforms = [], extras = [], host_python_version = None):
39+
def deps(name, *, requires_dist, platforms = [], extras = [], default_python_version = None):
3940
"""Parse the RequiresDist from wheel METADATA
4041
4142
Args:
@@ -44,46 +45,59 @@ def deps(name, *, requires_dist, platforms = [], extras = [], host_python_versio
4445
METADATA file.
4546
extras: {type}`list[str]` the requested extras to generate targets for.
4647
platforms: {type}`list[str]` the list of target platform strings.
47-
host_python_version: {type}`str` the host python version.
48+
default_python_version: {type}`str` the host python version.
4849
4950
Returns:
5051
A struct with attributes:
5152
* deps: {type}`list[str]` dependencies to include unconditionally.
5253
* deps_select: {type}`dict[str, list[str]]` dependencies to include on particular
5354
subset of target platforms.
5455
"""
55-
reqs = sorted(
56-
[requirement(r) for r in requires_dist],
57-
key = lambda x: "{}:{}:".format(x.name, sorted(x.extras), x.marker),
58-
)
59-
deps = {}
60-
deps_select = {}
61-
name = normalize_name(name)
62-
want_extras = _resolve_extras(name, reqs, extras)
63-
64-
# drop self edges
65-
reqs = [r for r in reqs if r.name != name]
66-
56+
if not platforms:
57+
fail("'platforms' arg is mandatory")
58+
59+
# TODO @aignas 2025-04-16: this `default_abi` variable is only
60+
# here so that we can have selects without "is_python_version" conditions.
61+
# This would happen in the `multi_pip_parse` logic in WORKSPACE.
62+
#
63+
# In followup PRs I would like to remove this logic and mainly rely on the
64+
# fact that there will be only a single ABI when internal rules pass the
65+
# values in the WORKSPACE case and in that case we can get the default
66+
# version from the target platform strings. What is more when we drop
67+
# WORKSPACE, we can simplify the logic in `_add_req` substantially and get
68+
# rid of `default_abi` all together.
69+
default_python_version = default_python_version or DEFAULT_PYTHON_VERSION
6770
platforms = [
68-
platform_from_str(p, python_version = host_python_version)
71+
platform_from_str(p, python_version = default_python_version)
6972
for p in platforms
70-
] or [
71-
platform_from_str("", python_version = host_python_version),
7273
]
7374

7475
abis = sorted({p.abi: True for p in platforms if p.abi})
75-
if host_python_version and len(abis) > 1:
76-
_, _, minor_version = host_python_version.partition(".")
76+
if default_python_version and len(abis) > 1:
77+
_, _, minor_version = default_python_version.partition(".")
7778
minor_version, _, _ = minor_version.partition(".")
7879
default_abi = "cp3" + minor_version
7980
elif len(abis) > 1:
8081
fail(
81-
"all python versions need to be specified explicitly, got: {}".format(platforms),
82+
"all python versions need to be specified explicitly with the default_python_version, got: {}, {}".format(platforms, default_python_version),
8283
)
8384
else:
8485
default_abi = None
8586

87+
reqs = sorted(
88+
[requirement(r) for r in requires_dist],
89+
key = lambda x: "{}:{}:".format(x.name, sorted(x.extras), x.marker),
90+
)
91+
deps = {}
92+
deps_select = {}
93+
name = normalize_name(name)
94+
want_extras = _resolve_extras(name, reqs, extras)
95+
8696
for req in reqs:
97+
if req.name == name:
98+
# drop self edges
99+
continue
100+
87101
_add_req(
88102
deps,
89103
deps_select,
@@ -280,11 +294,6 @@ def _add_req(deps, deps_select, req, *, extras, platforms, default_abi = None):
280294
_add(deps, deps_select, req.name, None)
281295
return
282296

283-
# NOTE @aignas 2023-12-08: in order to have reasonable select statements
284-
# we do have to have some parsing of the markers, so it begs the question
285-
# if packaging should be reimplemented in Starlark to have the best solution
286-
# for now we will implement it in Python and see what the best parsing result
287-
# can be before making this decision.
288297
match_os = len([
289298
tag
290299
for tag in [

0 commit comments

Comments
 (0)