Skip to content

Commit 22e0d8c

Browse files
committed
feat(pypi): actually start using env_marker_setting
Summary: - `pep508_deps` is now much simpler, because the hard work is done in analysis phase - `whl_library` BUILD.bazel tests now also have a test for the legacy flow. One thing that I noticed is that now we have an implicit dependency on the python toolchain when getting all of the `whl` target tree. This is a filegroup target that includes dependent wheels. However, we fallback to the flag values if we don't have the toolchain, so we should be good in general. Overall I like how this is turning out because we don't need to pipe the `target_platforms` anymore when we enable `PIPSTAR` feature. This means that we can start creating fewer whl_library instances - e.g. a `py3-none-any` wheel can be fetched once instead of once per python interpreter version. I'll leave this optimization for a later time. Work towards bazel-contrib#260
1 parent efc7589 commit 22e0d8c

File tree

12 files changed

+228
-379
lines changed

12 files changed

+228
-379
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ END_UNRELEASED_TEMPLATE
9797
* (pypi) Starlark-based evaluation of environment markers (requirements.txt conditionals)
9898
available (not enabled by default) for improved multi-platform build support.
9999
Set the `RULES_PYTHON_ENABLE_PIPSTAR=1` environment variable to enable it.
100+
Work towards [#260](https://github.com/bazel-contrib/rules_python/issues/260).
100101

101102
{#v0-0-0-removed}
102103
### Removed

python/private/pypi/BUILD.bazel

-4
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,9 @@ bzl_library(
236236
name = "pep508_deps_bzl",
237237
srcs = ["pep508_deps.bzl"],
238238
deps = [
239-
":pep508_env_bzl",
240239
":pep508_evaluate_bzl",
241-
":pep508_platform_bzl",
242240
":pep508_requirement_bzl",
243-
"//python/private:full_version_bzl",
244241
"//python/private:normalize_name_bzl",
245-
"@pythons_hub//:versions_bzl",
246242
],
247243
)
248244

python/private/pypi/extension.bzl

+12-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
load("@bazel_features//:features.bzl", "bazel_features")
1818
load("@pythons_hub//:interpreters.bzl", "INTERPRETER_LABELS")
1919
load("@pythons_hub//:versions.bzl", "MINOR_MAPPING")
20+
load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config")
2021
load("//python/private:auth.bzl", "AUTH_ATTRS")
2122
load("//python/private:full_version.bzl", "full_version")
2223
load("//python/private:normalize_name.bzl", "normalize_name")
@@ -216,7 +217,6 @@ def _create_whl_repos(
216217
enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs,
217218
environment = pip_attr.environment,
218219
envsubst = pip_attr.envsubst,
219-
experimental_target_platforms = pip_attr.experimental_target_platforms,
220220
group_deps = group_deps,
221221
group_name = group_name,
222222
pip_data_exclude = pip_attr.pip_data_exclude,
@@ -227,6 +227,9 @@ def _create_whl_repos(
227227
for p, args in whl_overrides.get(whl_name, {}).items()
228228
},
229229
)
230+
if not rp_config.enable_pipstar:
231+
maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms
232+
230233
whl_library_args.update({k: v for k, v in maybe_args.items() if v})
231234
maybe_args_with_default = dict(
232235
# The following values have defaults next to them
@@ -305,13 +308,14 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt
305308
args["urls"] = [distribution.url]
306309
args["sha256"] = distribution.sha256
307310
args["filename"] = distribution.filename
308-
args["experimental_target_platforms"] = [
309-
# Get rid of the version fot the target platforms because we are
310-
# passing the interpreter any way. Ideally we should search of ways
311-
# how to pass the target platforms through the hub repo.
312-
p.partition("_")[2]
313-
for p in requirement.target_platforms
314-
]
311+
if not rp_config.enable_pipstar:
312+
args["experimental_target_platforms"] = [
313+
# Get rid of the version fot the target platforms because we are
314+
# passing the interpreter any way. Ideally we should search of ways
315+
# how to pass the target platforms through the hub repo.
316+
p.partition("_")[2]
317+
for p in requirement.target_platforms
318+
]
315319

316320
# Pure python wheels or sdists may need to have a platform here
317321
target_platforms = None

python/private/pypi/generate_whl_library_build_bazel.bzl

+22-5
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ _RENDER = {
2626
"entry_points": render.dict,
2727
"extras": render.list,
2828
"group_deps": render.list,
29+
"include": str,
2930
"requires_dist": render.list,
3031
"srcs_exclude": render.list,
3132
"tags": render.list,
32-
"target_platforms": lambda x: render.list(x) if x else "target_platforms",
33+
"target_platforms": render.list,
3334
}
3435

3536
# NOTE @aignas 2024-10-25: We have to keep this so that files in
@@ -62,28 +63,44 @@ def generate_whl_library_build_bazel(
6263
A complete BUILD file as a string
6364
"""
6465

65-
fn = "whl_library_targets"
66+
loads = []
6667
if kwargs.get("tags"):
68+
fn = "whl_library_targets"
69+
6770
# legacy path
6871
unsupported_args = [
6972
"requires",
7073
"metadata_name",
7174
"metadata_version",
75+
"include",
7276
]
7377
else:
74-
fn = "{}_from_requires".format(fn)
78+
fn = "whl_library_targets_from_requires"
7579
unsupported_args = [
7680
"dependencies",
7781
"dependencies_by_platform",
82+
"target_platforms",
83+
"default_python_version",
7884
]
85+
dep_template = kwargs.get("dep_template")
86+
loads.append(
87+
"""load("{}", "{}")""".format(
88+
dep_template.format(
89+
name = "",
90+
target = "requirements.bzl",
91+
),
92+
"all_whl_requirements_by_package",
93+
),
94+
)
95+
kwargs["include"] = "sorted(all_whl_requirements_by_package)"
7996

8097
for arg in unsupported_args:
8198
if kwargs.get(arg):
8299
fail("BUG, unsupported arg: '{}'".format(arg))
83100

84-
loads = [
101+
loads.extend([
85102
"""load("@rules_python//python/private/pypi:whl_library_targets.bzl", "{}")""".format(fn),
86-
]
103+
])
87104

88105
additional_content = []
89106
if annotation:

python/private/pypi/pep508_deps.bzl

+30-101
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,27 @@
1515
"""This module is for implementing PEP508 compliant METADATA deps parsing.
1616
"""
1717

18-
load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION", "MINOR_MAPPING")
19-
load("//python/private:full_version.bzl", "full_version")
2018
load("//python/private:normalize_name.bzl", "normalize_name")
21-
load(":pep508_env.bzl", "env")
2219
load(":pep508_evaluate.bzl", "evaluate")
23-
load(":pep508_platform.bzl", "platform", "platform_from_str")
2420
load(":pep508_requirement.bzl", "requirement")
2521

2622
def deps(
2723
name,
2824
*,
2925
requires_dist,
30-
platforms = [],
3126
extras = [],
3227
excludes = [],
33-
default_python_version = None,
34-
minor_mapping = MINOR_MAPPING):
28+
include = []):
3529
"""Parse the RequiresDist from wheel METADATA
3630
3731
Args:
3832
name: {type}`str` the name of the wheel.
3933
requires_dist: {type}`list[str]` the list of RequiresDist lines from the
4034
METADATA file.
4135
excludes: {type}`list[str]` what packages should we exclude.
36+
include: {type}`list[str]` what packages should we exclude. If it is not
37+
specified, then we will include all deps from `requires_dist`.
4238
extras: {type}`list[str]` the requested extras to generate targets for.
43-
platforms: {type}`list[str]` the list of target platform strings.
44-
default_python_version: {type}`str` the host python version.
45-
minor_mapping: {type}`type[str, str]` the minor mapping to use when
46-
resolving to the full python version as DEFAULT_PYTHON_VERSION can by
47-
of format `3.x`.
4839
4940
Returns:
5041
A struct with attributes:
@@ -60,39 +51,20 @@ def deps(
6051
deps_select = {}
6152
name = normalize_name(name)
6253
want_extras = _resolve_extras(name, reqs, extras)
54+
include = [normalize_name(n) for n in include]
6355

6456
# drop self edges
6557
excludes = [name] + [normalize_name(x) for x in excludes]
6658

67-
default_python_version = default_python_version or DEFAULT_PYTHON_VERSION
68-
if default_python_version:
69-
# if it is not bzlmod, then DEFAULT_PYTHON_VERSION may be unset
70-
default_python_version = full_version(
71-
version = default_python_version,
72-
minor_mapping = minor_mapping,
73-
)
74-
platforms = [
75-
platform_from_str(p, python_version = default_python_version)
76-
for p in platforms
77-
]
78-
79-
abis = sorted({p.abi: True for p in platforms if p.abi})
80-
if default_python_version and len(abis) > 1:
81-
_, _, tail = default_python_version.partition(".")
82-
default_abi = "cp3" + tail
83-
elif len(abis) > 1:
84-
fail(
85-
"all python versions need to be specified explicitly, got: {}".format(platforms),
86-
)
87-
else:
88-
default_abi = None
89-
9059
reqs_by_name = {}
9160

9261
for req in reqs:
9362
if req.name_ in excludes:
9463
continue
9564

65+
if include and req.name_ not in include:
66+
continue
67+
9668
reqs_by_name.setdefault(req.name, []).append(req)
9769

9870
for name, reqs in reqs_by_name.items():
@@ -102,55 +74,25 @@ def deps(
10274
normalize_name(name),
10375
reqs,
10476
extras = want_extras,
105-
platforms = platforms,
106-
default_abi = default_abi,
10777
)
10878

10979
return struct(
11080
deps = sorted(deps),
11181
deps_select = {
112-
_platform_str(p): sorted(deps)
113-
for p, deps in deps_select.items()
82+
d: markers
83+
for d, markers in sorted(deps_select.items())
11484
},
11585
)
11686

117-
def _platform_str(self):
118-
if self.abi == None:
119-
return "{}_{}".format(self.os, self.arch)
120-
121-
return "{}_{}_{}".format(
122-
self.abi,
123-
self.os or "anyos",
124-
self.arch or "anyarch",
125-
)
126-
127-
def _add(deps, deps_select, dep, platform):
87+
def _add(deps, deps_select, dep, markers = None):
12888
dep = normalize_name(dep)
12989

130-
if platform == None:
90+
if not markers:
13191
deps[dep] = True
132-
133-
# If the dep is in the platform-specific list, remove it from the select.
134-
pop_keys = []
135-
for p, _deps in deps_select.items():
136-
if dep not in _deps:
137-
continue
138-
139-
_deps.pop(dep)
140-
if not _deps:
141-
pop_keys.append(p)
142-
143-
for p in pop_keys:
144-
deps_select.pop(p)
145-
return
146-
147-
if dep in deps:
148-
# If the dep is already in the main dependency list, no need to add it in the
149-
# platform-specific dependency list.
150-
return
151-
152-
# Add the platform-specific branch
153-
deps_select.setdefault(platform, {})[dep] = True
92+
elif len(markers) == 1:
93+
deps_select[dep] = markers[0]
94+
else:
95+
deps_select[dep] = "({})".format(") or (".join(sorted(markers)))
15496

15597
def _resolve_extras(self_name, reqs, extras):
15698
"""Resolve extras which are due to depending on self[some_other_extra].
@@ -207,37 +149,24 @@ def _resolve_extras(self_name, reqs, extras):
207149
# Poor mans set
208150
return sorted({x: None for x in extras})
209151

210-
def _add_reqs(deps, deps_select, dep, reqs, *, extras, platforms, default_abi = None):
152+
def _add_reqs(deps, deps_select, dep, reqs, *, extras):
211153
for req in reqs:
212154
if not req.marker:
213-
_add(deps, deps_select, dep, None)
155+
_add(deps, deps_select, dep)
214156
return
215157

216-
platforms_to_add = {}
217-
for plat in platforms:
218-
if plat in platforms_to_add:
219-
# marker evaluation is more expensive than this check
220-
continue
221-
222-
added = False
223-
for extra in extras:
224-
if added:
158+
markers = {}
159+
for req in reqs:
160+
for x in extras:
161+
m = evaluate(req.marker, env = {"extra": x}, strict = False)
162+
if m == False:
163+
continue
164+
elif m == True:
165+
_add(deps, deps_select, dep)
225166
break
167+
else:
168+
markers[m] = None
169+
continue
226170

227-
for req in reqs:
228-
if evaluate(req.marker, env = env(target_platform = plat, extra = extra)):
229-
platforms_to_add[plat] = True
230-
added = True
231-
break
232-
233-
if len(platforms_to_add) == len(platforms):
234-
# the dep is in all target platforms, let's just add it to the regular
235-
# list
236-
_add(deps, deps_select, dep, None)
237-
return
238-
239-
for plat in platforms_to_add:
240-
if default_abi:
241-
_add(deps, deps_select, dep, plat)
242-
if plat.abi == default_abi or not default_abi:
243-
_add(deps, deps_select, dep, platform(os = plat.os, arch = plat.arch))
171+
if markers:
172+
_add(deps, deps_select, dep, sorted(markers))

python/private/pypi/whl_installer/wheel_installer.py

-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ def _extract_wheel(
126126
_setup_namespace_pkg_compatibility(installation_dir)
127127

128128
metadata = {
129-
"python_version": f"{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}",
130129
"entry_points": [
131130
{
132131
"name": name,

python/private/pypi/whl_library.bzl

-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
2222
load(":attrs.bzl", "ATTRS", "use_isolated")
2323
load(":deps.bzl", "all_repo_names", "record_files")
2424
load(":generate_whl_library_build_bazel.bzl", "generate_whl_library_build_bazel")
25-
load(":parse_requirements.bzl", "host_platform")
2625
load(":parse_whl_name.bzl", "parse_whl_name")
2726
load(":patch_whl.bzl", "patch_whl")
2827
load(":pypi_repo_utils.bzl", "pypi_repo_utils")
@@ -362,7 +361,6 @@ def _whl_library_impl(rctx):
362361

363362
metadata = json.decode(rctx.read("metadata.json"))
364363
rctx.delete("metadata.json")
365-
python_version = metadata["python_version"]
366364

367365
# NOTE @aignas 2024-06-22: this has to live on until we stop supporting
368366
# passing `twine` as a `:pkg` library via the `WORKSPACE` builds.
@@ -400,9 +398,7 @@ def _whl_library_impl(rctx):
400398
entry_points = entry_points,
401399
metadata_name = metadata.name,
402400
metadata_version = metadata.version,
403-
default_python_version = python_version,
404401
requires_dist = metadata.requires_dist,
405-
target_platforms = rctx.attr.experimental_target_platforms or [host_platform(rctx)],
406402
# TODO @aignas 2025-04-14: load through the hub:
407403
annotation = None if not rctx.attr.annotation else struct(**json.decode(rctx.read(rctx.attr.annotation))),
408404
data_exclude = rctx.attr.pip_data_exclude,

0 commit comments

Comments
 (0)