Skip to content

Commit 8a83cde

Browse files
committed
wip
1 parent 02990c5 commit 8a83cde

4 files changed

Lines changed: 409 additions & 224 deletions

File tree

python/private/pypi/whl_installer/wheel.py

Lines changed: 3 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -15,233 +15,14 @@
1515
"""Utility class to inspect an extracted wheel directory"""
1616

1717
import email
18-
import re
19-
from collections import defaultdict
20-
from dataclasses import dataclass
2118
from pathlib import Path
22-
from typing import Dict, List, Optional, Set, Tuple
19+
from typing import Dict, Optional, Set, Tuple
2320

2421
import installer
25-
from packaging.requirements import Requirement
2622
from pip._vendor.packaging.utils import canonicalize_name
2723

28-
from python.private.pypi.whl_installer.platform import (
29-
Platform,
30-
host_interpreter_version,
31-
)
32-
33-
34-
@dataclass(frozen=True)
35-
class FrozenDeps:
36-
deps: List[str]
37-
deps_select: Dict[str, List[str]]
38-
39-
40-
class Deps:
41-
"""Deps is a dependency builder that has a build() method to return FrozenDeps."""
42-
43-
def __init__(
44-
self,
45-
name: str,
46-
requires_dist: List[str],
47-
*,
48-
extras: Optional[Set[str]] = None,
49-
platforms: Optional[Set[Platform]] = None,
50-
):
51-
"""Create a new instance and parse the requires_dist
52-
53-
Args:
54-
name (str): The name of the whl distribution
55-
requires_dist (list[Str]): The Requires-Dist from the METADATA of the whl
56-
distribution.
57-
extras (set[str], optional): The list of requested extras, defaults to None.
58-
platforms (set[Platform], optional): The list of target platforms, defaults to
59-
None. If the list of platforms has multiple `minor_version` values, it
60-
will change the code to generate the select statements using
61-
`@rules_python//python/config_settings:is_python_3.y` conditions.
62-
"""
63-
self.name: str = Deps._normalize(name)
64-
self._platforms: Set[Platform] = platforms or set()
65-
self._target_versions = {
66-
(p.minor_version, p.micro_version) for p in platforms or {}
67-
}
68-
if platforms and len(self._target_versions) > 1:
69-
# TODO @aignas 2024-06-23: enable this to be set via a CLI arg
70-
# for being more explicit.
71-
self._default_minor_version, _ = host_interpreter_version()
72-
else:
73-
self._default_minor_version = None
74-
75-
if None in self._target_versions and len(self._target_versions) > 2:
76-
raise ValueError(
77-
f"all python versions need to be specified explicitly, got: {platforms}"
78-
)
79-
80-
# Sort so that the dictionary order in the FrozenDeps is deterministic
81-
# without the final sort because Python retains insertion order. That way
82-
# the sorting by platform is limited within the Platform class itself and
83-
# the unit-tests for the Deps can be simpler.
84-
reqs = sorted(
85-
(Requirement(wheel_req) for wheel_req in requires_dist),
86-
key=lambda x: f"{x.name}:{sorted(x.extras)}",
87-
)
88-
89-
want_extras = self._resolve_extras(reqs, extras)
90-
91-
# Then add all of the requirements in order
92-
self._deps: Set[str] = set()
93-
self._select: Dict[Platform, Set[str]] = defaultdict(set)
94-
95-
reqs_by_name = {}
96-
for req in reqs:
97-
reqs_by_name.setdefault(req.name, []).append(req)
98-
99-
for req_name, reqs in reqs_by_name.items():
100-
self._add_req(req_name, reqs, want_extras)
101-
102-
def _add(self, dep: str, platform: Optional[Platform]):
103-
dep = Deps._normalize(dep)
104-
105-
# Self-edges are processed in _resolve_extras
106-
if dep == self.name:
107-
return
108-
109-
if not platform:
110-
self._deps.add(dep)
111-
112-
# If the dep is in the platform-specific list, remove it from the select.
113-
pop_keys = []
114-
for p, deps in self._select.items():
115-
if dep not in deps:
116-
continue
117-
118-
deps.remove(dep)
119-
if not deps:
120-
pop_keys.append(p)
121-
122-
for p in pop_keys:
123-
self._select.pop(p)
124-
return
125-
126-
if dep in self._deps:
127-
# If the dep is already in the main dependency list, no need to add it in the
128-
# platform-specific dependency list.
129-
return
130-
131-
# Add the platform-specific dep
132-
self._select[platform].add(dep)
133-
134-
@staticmethod
135-
def _normalize(name: str) -> str:
136-
return re.sub(r"[-_.]+", "_", name).lower()
137-
138-
def _resolve_extras(
139-
self, reqs: List[Requirement], want_extras: Optional[Set[str]]
140-
) -> Set[str]:
141-
"""Resolve extras which are due to depending on self[some_other_extra].
142-
143-
Some packages may have cyclic dependencies resulting from extras being used, one example is
144-
`etils`, where we have one set of extras as aliases for other extras
145-
and we have an extra called 'all' that includes all other extras.
146-
147-
Example: github.com/google/etils/blob/a0b71032095db14acf6b33516bca6d885fe09e35/pyproject.toml#L32.
148-
149-
When the `requirements.txt` is generated by `pip-tools`, then it is likely that
150-
this step is not needed, but for other `requirements.txt` files this may be useful.
151-
152-
NOTE @aignas 2023-12-08: the extra resolution is not platform dependent,
153-
but in order for it to become platform dependent we would have to have
154-
separate targets for each extra in extras.
155-
"""
156-
157-
# Resolve any extra extras due to self-edges, empty string means no
158-
# extras The empty string in the set is just a way to make the handling
159-
# of no extras and a single extra easier and having a set of {"", "foo"}
160-
# is equivalent to having {"foo"}.
161-
extras: Set[str] = want_extras or {""}
162-
163-
self_reqs = []
164-
for req in reqs:
165-
if Deps._normalize(req.name) != self.name:
166-
continue
167-
168-
if req.marker is None:
169-
# I am pretty sure we cannot reach this code as it does not
170-
# make sense to specify packages in this way, but since it is
171-
# easy to handle, lets do it.
172-
#
173-
# TODO @aignas 2023-12-08: add a test
174-
extras = extras | req.extras
175-
else:
176-
# process these in a separate loop
177-
self_reqs.append(req)
178-
179-
# A double loop is not strictly optimal, but always correct without recursion
180-
for req in self_reqs:
181-
if any(req.marker.evaluate({"extra": extra}) for extra in extras):
182-
extras = extras | req.extras
183-
else:
184-
continue
185-
186-
# Iterate through all packages to ensure that we include all of the extras from previously
187-
# visited packages.
188-
for req_ in self_reqs:
189-
if any(req_.marker.evaluate({"extra": extra}) for extra in extras):
190-
extras = extras | req_.extras
191-
192-
return extras
193-
194-
def _add_req(self, req_name, reqs: List[Requirement], extras: Set[str]) -> None:
195-
platforms_to_add = set()
196-
for req in reqs:
197-
if req.marker is None:
198-
self._add(req.name, None)
199-
return
200-
201-
if not self._platforms:
202-
if any(req.marker.evaluate({"extra": extra}) for extra in extras):
203-
self._add(req.name, None)
204-
return
205-
206-
for plat in self._platforms:
207-
if plat in platforms_to_add:
208-
# marker evaluation is more expensive than this check
209-
continue
210-
211-
added = False
212-
for extra in extras:
213-
if added:
214-
break
215-
216-
if req.marker.evaluate(plat.env_markers(extra)):
217-
platforms_to_add.add(plat)
218-
added = True
219-
break
220-
221-
if not self._platforms:
222-
return
223-
224-
if len(platforms_to_add) == len(self._platforms):
225-
# the dep is in all target platforms, let's just add it to the regular
226-
# list
227-
self._add(req_name, None)
228-
return
229-
230-
for plat in platforms_to_add:
231-
if self._default_minor_version is not None:
232-
self._add(req_name, plat)
233-
234-
if (
235-
self._default_minor_version is None
236-
or plat.minor_version == self._default_minor_version
237-
):
238-
self._add(req_name, Platform(os=plat.os, arch=plat.arch))
239-
240-
def build(self) -> FrozenDeps:
241-
return FrozenDeps(
242-
deps=sorted(self._deps),
243-
deps_select={str(p): sorted(deps) for p, deps in self._select.items()},
244-
)
24+
from python.private.pypi.whl_installer.deps import Deps, FrozenDeps
25+
from python.private.pypi.whl_installer.platform import Platform
24526

24627

24728
class Wheel:

0 commit comments

Comments
 (0)