Skip to content

Commit a13fcd7

Browse files
aignasrickeylev
andauthored
feat(pypi): actually start using env_marker_setting (#2873)
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 #260 --------- Co-authored-by: Richard Levasseur <[email protected]>
1 parent c383c3b commit a13fcd7

15 files changed

+249
-387
lines changed

python/private/pypi/BUILD.bazel

Lines changed: 0 additions & 4 deletions
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/config.bzl.tmpl.bzlmod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ with your usecase. This may change in between rules_python versions without any
66
@generated by rules_python pip.parse bzlmod extension.
77
"""
88

9-
target_platforms = %%TARGET_PLATFORMS%%
9+
whl_map = %%WHL_MAP%%

python/private/pypi/extension.bzl

Lines changed: 23 additions & 12 deletions
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")
@@ -72,7 +73,8 @@ def _create_whl_repos(
7273
available_interpreters = INTERPRETER_LABELS,
7374
minor_mapping = MINOR_MAPPING,
7475
evaluate_markers = evaluate_markers_py,
75-
get_index_urls = None):
76+
get_index_urls = None,
77+
enable_pipstar = False):
7678
"""create all of the whl repositories
7779
7880
Args:
@@ -87,6 +89,7 @@ def _create_whl_repos(
8789
minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full
8890
python version used to parse package METADATA files.
8991
evaluate_markers: the function used to evaluate the markers.
92+
enable_pipstar: enable the pipstar feature.
9093
9194
Returns a {type}`struct` with the following attributes:
9295
whl_map: {type}`dict[str, list[struct]]` the output is keyed by the
@@ -216,7 +219,6 @@ def _create_whl_repos(
216219
enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs,
217220
environment = pip_attr.environment,
218221
envsubst = pip_attr.envsubst,
219-
experimental_target_platforms = pip_attr.experimental_target_platforms,
220222
group_deps = group_deps,
221223
group_name = group_name,
222224
pip_data_exclude = pip_attr.pip_data_exclude,
@@ -227,6 +229,9 @@ def _create_whl_repos(
227229
for p, args in whl_overrides.get(whl_name, {}).items()
228230
},
229231
)
232+
if not enable_pipstar:
233+
maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms
234+
230235
whl_library_args.update({k: v for k, v in maybe_args.items() if v})
231236
maybe_args_with_default = dict(
232237
# The following values have defaults next to them
@@ -249,6 +254,7 @@ def _create_whl_repos(
249254
auth_patterns = pip_attr.auth_patterns,
250255
python_version = major_minor,
251256
multiple_requirements_for_whl = len(requirements) > 1.,
257+
enable_pipstar = enable_pipstar,
252258
).items():
253259
repo_name = "{}_{}".format(pip_name, repo_name)
254260
if repo_name in whl_libraries:
@@ -277,7 +283,7 @@ def _create_whl_repos(
277283
},
278284
)
279285

280-
def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patterns, multiple_requirements_for_whl = False, python_version):
286+
def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patterns, multiple_requirements_for_whl = False, python_version, enable_pipstar = False):
281287
ret = {}
282288

283289
dists = requirement.whls
@@ -305,13 +311,14 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt
305311
args["urls"] = [distribution.url]
306312
args["sha256"] = distribution.sha256
307313
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-
]
314+
if not enable_pipstar:
315+
args["experimental_target_platforms"] = [
316+
# Get rid of the version fot the target platforms because we are
317+
# passing the interpreter any way. Ideally we should search of ways
318+
# how to pass the target platforms through the hub repo.
319+
p.partition("_")[2]
320+
for p in requirement.target_platforms
321+
]
315322

316323
# Pure python wheels or sdists may need to have a platform here
317324
target_platforms = None
@@ -357,7 +364,11 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt
357364

358365
return ret
359366

360-
def parse_modules(module_ctx, _fail = fail, simpleapi_download = simpleapi_download, **kwargs):
367+
def parse_modules(
368+
module_ctx,
369+
_fail = fail,
370+
simpleapi_download = simpleapi_download,
371+
**kwargs):
361372
"""Implementation of parsing the tag classes for the extension and return a struct for registering repositories.
362373
363374
Args:
@@ -639,7 +650,7 @@ def _pip_impl(module_ctx):
639650
module_ctx: module contents
640651
"""
641652

642-
mods = parse_modules(module_ctx)
653+
mods = parse_modules(module_ctx, enable_pipstar = rp_config.enable_pipstar)
643654

644655
# Build all of the wheel modifications if the tag class is called.
645656
_whl_mods_impl(mods.whl_mods)

python/private/pypi/generate_whl_library_build_bazel.bzl

Lines changed: 22 additions & 5 deletions
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 = "config.bzl",
91+
),
92+
"whl_map",
93+
),
94+
)
95+
kwargs["include"] = "whl_map"
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/hub_repository.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _impl(rctx):
4949
"config.bzl",
5050
rctx.attr._config_template,
5151
substitutions = {
52-
"%%TARGET_PLATFORMS%%": render.list(rctx.attr.target_platforms),
52+
"%%WHL_MAP%%": render.dict(rctx.attr.whl_map, value_repr = lambda x: "None"),
5353
},
5454
)
5555
rctx.template("requirements.bzl", rctx.attr._requirements_bzl_template, substitutions = {

python/private/pypi/pep508_deps.bzl

Lines changed: 30 additions & 101 deletions
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))

0 commit comments

Comments
 (0)