Skip to content

Commit a577763

Browse files
committed
simplify the dependency algorithm
1 parent 3757749 commit a577763

File tree

4 files changed

+152
-355
lines changed

4 files changed

+152
-355
lines changed

python/private/pypi/pep508_deps.bzl

Lines changed: 59 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -15,93 +15,73 @@
1515
"""This module is for implementing PEP508 compliant METADATA deps parsing.
1616
"""
1717

18-
load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION")
1918
load("//python/private:normalize_name.bzl", "normalize_name")
2019
load(":pep508_env.bzl", "env")
2120
load(":pep508_evaluate.bzl", "evaluate")
2221
load(":pep508_platform.bzl", "platform", "platform_from_str")
2322
load(":pep508_requirement.bzl", "requirement")
2423

25-
_ALL_OS_VALUES = [
26-
"windows",
27-
"osx",
28-
"linux",
29-
]
30-
_ALL_ARCH_VALUES = [
31-
"aarch64",
32-
"ppc64",
33-
"ppc64le",
34-
"s390x",
35-
"x86_32",
36-
"x86_64",
37-
]
38-
39-
def deps(name, *, requires_dist, platforms = [], extras = [], default_python_version = None):
24+
def deps(name, *, requires_dist, platforms = [], extras = [], excludes = [], host_python_version = None):
4025
"""Parse the RequiresDist from wheel METADATA
4126
4227
Args:
4328
name: {type}`str` the name of the wheel.
4429
requires_dist: {type}`list[str]` the list of RequiresDist lines from the
4530
METADATA file.
31+
excludes: {type}`list[str]` what packages should we exclude.
4632
extras: {type}`list[str]` the requested extras to generate targets for.
4733
platforms: {type}`list[str]` the list of target platform strings.
48-
default_python_version: {type}`str` the host python version.
34+
host_python_version: {type}`str` the host python version.
4935
5036
Returns:
5137
A struct with attributes:
5238
* deps: {type}`list[str]` dependencies to include unconditionally.
5339
* deps_select: {type}`dict[str, list[str]]` dependencies to include on particular
5440
subset of target platforms.
5541
"""
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
42+
reqs = sorted(
43+
[requirement(r) for r in requires_dist],
44+
key = lambda x: "{}:{}:".format(x.name, sorted(x.extras), x.marker),
45+
)
46+
deps = {}
47+
deps_select = {}
48+
name = normalize_name(name)
49+
want_extras = _resolve_extras(name, reqs, extras)
50+
51+
# drop self edges
52+
excludes = [name] + [normalize_name(x) for x in excludes]
53+
7054
platforms = [
71-
platform_from_str(p, python_version = default_python_version)
55+
platform_from_str(p, python_version = host_python_version)
7256
for p in platforms
7357
]
7458

7559
abis = sorted({p.abi: True for p in platforms if p.abi})
76-
if default_python_version and len(abis) > 1:
77-
_, _, minor_version = default_python_version.partition(".")
60+
if host_python_version and len(abis) > 1:
61+
_, _, minor_version = host_python_version.partition(".")
7862
minor_version, _, _ = minor_version.partition(".")
7963
default_abi = "cp3" + minor_version
8064
elif len(abis) > 1:
8165
fail(
82-
"all python versions need to be specified explicitly with the default_python_version, got: {}, {}".format(platforms, default_python_version),
66+
"all python versions need to be specified explicitly, got: {}".format(platforms),
8367
)
8468
else:
8569
default_abi = None
8670

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)
71+
reqs_by_name = {}
9572

9673
for req in reqs:
97-
if req.name == name:
98-
# drop self edges
74+
if req.name_ in excludes:
9975
continue
10076

101-
_add_req(
77+
reqs_by_name.setdefault(req.name, []).append(req)
78+
79+
for name, reqs in reqs_by_name.items():
80+
_add_reqs(
10281
deps,
10382
deps_select,
104-
req,
83+
normalize_name(name),
84+
reqs,
10585
extras = want_extras,
10686
platforms = platforms,
10787
default_abi = default_abi,
@@ -120,46 +100,20 @@ def _platform_str(self):
120100
if not self.os and not self.arch:
121101
return "//conditions:default"
122102
elif not self.arch:
123-
return "@platforms//os:{}".format(self.os)
103+
fail("remove")
124104
else:
125105
return "{}_{}".format(self.os, self.arch)
126106

127107
minor_version = self.abi[3:]
128108
if self.arch == None and self.os == None:
129-
return str(Label("//python/config_settings:is_python_3.{}".format(minor_version)))
109+
fail("remove")
130110

131111
return "cp3{}_{}_{}".format(
132112
minor_version,
133113
self.os or "anyos",
134114
self.arch or "anyarch",
135115
)
136116

137-
def _platform_specializations(self, cpu_values = _ALL_ARCH_VALUES, os_values = _ALL_OS_VALUES):
138-
"""Return the platform itself and all its unambiguous specializations.
139-
140-
For more info about specializations see
141-
https://bazel.build/docs/configurable-attributes
142-
"""
143-
specializations = []
144-
specializations.append(self)
145-
if self.arch == None:
146-
specializations.extend([
147-
platform(os = self.os, arch = arch, abi = self.abi)
148-
for arch in cpu_values
149-
])
150-
if self.os == None:
151-
specializations.extend([
152-
platform(os = os, arch = self.arch, abi = self.abi)
153-
for os in os_values
154-
])
155-
if self.os == None and self.arch == None:
156-
specializations.extend([
157-
platform(os = os, arch = arch, abi = self.abi)
158-
for os in os_values
159-
for arch in cpu_values
160-
])
161-
return specializations
162-
163117
def _add(deps, deps_select, dep, platform):
164118
dep = normalize_name(dep)
165119

@@ -186,53 +140,7 @@ def _add(deps, deps_select, dep, platform):
186140
return
187141

188142
# Add the platform-specific branch
189-
deps_select.setdefault(platform, {})
190-
191-
# Add the dep to specializations of the given platform if they
192-
# exist in the select statement.
193-
for p in _platform_specializations(platform):
194-
if p not in deps_select:
195-
continue
196-
197-
deps_select[p][dep] = True
198-
199-
if len(deps_select[platform]) == 1:
200-
# We are adding a new item to the select and we need to ensure that
201-
# existing dependencies from less specialized platforms are propagated
202-
# to the newly added dependency set.
203-
for p, _deps in deps_select.items():
204-
# Check if the existing platform overlaps with the given platform
205-
if p == platform or platform not in _platform_specializations(p):
206-
continue
207-
208-
deps_select[platform].update(_deps)
209-
210-
def _maybe_add_common_dep(deps, deps_select, platforms, dep):
211-
abis = sorted({p.abi: True for p in platforms if p.abi})
212-
if len(abis) < 2:
213-
return
214-
215-
platforms = [platform()] + [
216-
platform(abi = abi)
217-
for abi in abis
218-
]
219-
220-
# If the dep is targeting all target python versions, lets add it to
221-
# the common dependency list to simplify the select statements.
222-
for p in platforms:
223-
if p not in deps_select:
224-
return
225-
226-
if dep not in deps_select[p]:
227-
return
228-
229-
# All of the python version-specific branches have the dep, so lets add
230-
# it to the common deps.
231-
deps[dep] = True
232-
for p in platforms:
233-
deps_select[p].pop(dep)
234-
if not deps_select[p]:
235-
deps_select.pop(p)
143+
deps_select.setdefault(platform, {})[dep] = True
236144

237145
def _resolve_extras(self_name, reqs, extras):
238146
"""Resolve extras which are due to depending on self[some_other_extra].
@@ -289,72 +197,37 @@ def _resolve_extras(self_name, reqs, extras):
289197
# Poor mans set
290198
return sorted({x: None for x in extras})
291199

292-
def _add_req(deps, deps_select, req, *, extras, platforms, default_abi = None):
293-
if not req.marker:
294-
_add(deps, deps_select, req.name, None)
295-
return
296-
297-
match_os = len([
298-
tag
299-
for tag in [
300-
"os_name",
301-
"sys_platform",
302-
"platform_system",
303-
]
304-
if tag in req.marker
305-
]) > 0
306-
match_arch = "platform_machine" in req.marker
307-
match_version = "version" in req.marker
308-
309-
if not (match_os or match_arch or match_version):
310-
if [
311-
True
312-
for extra in extras
313-
for p in platforms
314-
if evaluate(
315-
req.marker,
316-
env = env(
317-
target_platform = p,
318-
extra = extra,
319-
),
320-
)
321-
]:
322-
_add(deps, deps_select, req.name, None)
323-
return
200+
def _add_reqs(deps, deps_select, dep, reqs, *, extras, platforms, default_abi = None):
201+
for req in reqs:
202+
if not req.marker:
203+
_add(deps, deps_select, dep, None)
204+
return
324205

206+
platforms_to_add = {}
325207
for plat in platforms:
326-
if not [
327-
True
328-
for extra in extras
329-
if evaluate(
330-
req.marker,
331-
env = env(
332-
target_platform = plat,
333-
extra = extra,
334-
),
335-
)
336-
]:
208+
if plat in platforms_to_add:
209+
# marker evaluation is more expensive than this check
337210
continue
338211

339-
if match_arch and default_abi:
340-
_add(deps, deps_select, req.name, plat)
341-
if plat.abi == default_abi:
342-
_add(deps, deps_select, req.name, platform(os = plat.os, arch = plat.arch))
343-
elif match_arch:
344-
_add(deps, deps_select, req.name, platform(os = plat.os, arch = plat.arch))
345-
elif match_os and default_abi:
346-
_add(deps, deps_select, req.name, platform(os = plat.os, abi = plat.abi))
347-
if plat.abi == default_abi:
348-
_add(deps, deps_select, req.name, platform(os = plat.os))
349-
elif match_os:
350-
_add(deps, deps_select, req.name, platform(os = plat.os))
351-
elif match_version and default_abi:
352-
_add(deps, deps_select, req.name, platform(abi = plat.abi))
353-
if plat.abi == default_abi:
354-
_add(deps, deps_select, req.name, platform())
355-
elif match_version:
356-
_add(deps, deps_select, req.name, None)
357-
else:
358-
fail("BUG: {} support is not implemented".format(req.marker))
212+
added = False
213+
for extra in extras:
214+
if added:
215+
break
216+
217+
for req in reqs:
218+
if evaluate(req.marker, env = env(target_platform = plat, extra = extra)):
219+
platforms_to_add[plat] = True
220+
added = True
221+
break
222+
223+
if len(platforms_to_add) == len(platforms):
224+
# the dep is in all target platforms, let's just add it to the regular
225+
# list
226+
_add(deps, deps_select, dep, None)
227+
return
359228

360-
_maybe_add_common_dep(deps, deps_select, platforms, req.name)
229+
for plat in platforms_to_add:
230+
if default_abi:
231+
_add(deps, deps_select, dep, plat)
232+
if plat.abi == default_abi or not default_abi:
233+
_add(deps, deps_select, dep, platform(os = plat.os, arch = plat.arch))

python/private/pypi/pep508_requirement.bzl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ def requirement(spec):
4747
requires, _, _ = requires.partition(char)
4848
extras = extras_unparsed.replace(" ", "").split(",")
4949
name = requires.strip(" ")
50+
name = normalize_name(name)
5051

5152
return struct(
52-
name = normalize_name(name).replace("_", "-"),
53+
name = name.replace("_", "-"),
54+
name_ = name,
5355
marker = marker.strip(" "),
5456
extras = extras,
5557
version = version,

0 commit comments

Comments
 (0)