pex 2.61.0__py2.py3-none-any.whl → 2.62.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pex might be problematic. Click here for more details.

Files changed (46) hide show
  1. pex/cache/dirs.py +3 -8
  2. pex/cli/commands/pip/wheel.py +1 -1
  3. pex/docs/html/_pagefind/fragment/en_19d749c.pf_fragment +0 -0
  4. pex/docs/html/_pagefind/fragment/en_358f3c1.pf_fragment +0 -0
  5. pex/docs/html/_pagefind/fragment/{en_7f63cbe.pf_fragment → en_4a62b9a.pf_fragment} +0 -0
  6. pex/docs/html/_pagefind/fragment/{en_9ce19f0.pf_fragment → en_5c1928c.pf_fragment} +0 -0
  7. pex/docs/html/_pagefind/fragment/en_a6b0f05.pf_fragment +0 -0
  8. pex/docs/html/_pagefind/fragment/en_a77f515.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/fragment/en_ed65506.pf_fragment +0 -0
  10. pex/docs/html/_pagefind/fragment/{en_cd3f4ce.pf_fragment → en_f5aab8f.pf_fragment} +0 -0
  11. pex/docs/html/_pagefind/index/en_856ae3c.pf_index +0 -0
  12. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  13. pex/docs/html/_pagefind/pagefind.en_92d55346bf.pf_meta +0 -0
  14. pex/docs/html/_static/documentation_options.js +1 -1
  15. pex/docs/html/api/vars.html +5 -5
  16. pex/docs/html/buildingpex.html +5 -5
  17. pex/docs/html/genindex.html +5 -5
  18. pex/docs/html/index.html +5 -5
  19. pex/docs/html/recipes.html +5 -5
  20. pex/docs/html/scie.html +5 -5
  21. pex/docs/html/search.html +5 -5
  22. pex/docs/html/whatispex.html +5 -5
  23. pex/resolve/locker.py +6 -51
  24. pex/resolve/locker_patches.py +123 -209
  25. pex/resolve/lockfile/create.py +26 -12
  26. pex/resolve/lockfile/targets.py +292 -26
  27. pex/resolve/pre_resolved_resolver.py +21 -12
  28. pex/resolve/requirement_configuration.py +15 -8
  29. pex/resolve/target_system.py +512 -119
  30. pex/resolver.py +269 -118
  31. pex/venv/venv_pex.py +1 -1
  32. pex/version.py +1 -1
  33. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/METADATA +4 -4
  34. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/RECORD +39 -39
  35. pex/docs/html/_pagefind/fragment/en_5c93d32.pf_fragment +0 -0
  36. pex/docs/html/_pagefind/fragment/en_6262b88.pf_fragment +0 -0
  37. pex/docs/html/_pagefind/fragment/en_d1987a6.pf_fragment +0 -0
  38. pex/docs/html/_pagefind/fragment/en_d7a44fe.pf_fragment +0 -0
  39. pex/docs/html/_pagefind/fragment/en_f6e4117.pf_fragment +0 -0
  40. pex/docs/html/_pagefind/index/en_3ad4a26.pf_index +0 -0
  41. pex/docs/html/_pagefind/pagefind.en_50edc69c3c.pf_meta +0 -0
  42. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/WHEEL +0 -0
  43. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/entry_points.txt +0 -0
  44. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/licenses/LICENSE +0 -0
  45. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/pylock/pylock.toml +0 -0
  46. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/top_level.txt +0 -0
@@ -3,224 +3,82 @@
3
3
 
4
4
  from __future__ import absolute_import
5
5
 
6
+ import json
6
7
  import os
7
8
 
8
- try:
9
- from . import requires_python # type:ignore[attr-defined] # This file will be relocated.
10
-
11
- python_full_versions = requires_python.PYTHON_FULL_VERSIONS
12
- python_versions = requires_python.PYTHON_VERSIONS
13
- python_majors = sorted(set(version[0] for version in python_full_versions))
14
- except ImportError:
15
- python_full_versions = []
16
- python_versions = []
17
- python_majors = []
18
-
19
- os_names = []
20
- platform_systems = []
21
- sys_platforms = []
22
- platform_tag_regexps = []
9
+ from pex.interpreter_implementation import InterpreterImplementation
10
+ from pex.resolve.target_system import UniversalTarget
23
11
 
24
12
  # N.B.: The following environment variables are used by the Pex runtime to control Pip and must be
25
13
  # kept in-sync with `locker.py`.
26
- target_systems_file = os.environ.pop("_PEX_TARGET_SYSTEMS_FILE", None)
27
-
28
- if target_systems_file:
29
- import json
30
-
31
- with open(target_systems_file) as fp:
32
- target_systems = json.load(fp)
33
- os_names = target_systems["os_names"]
34
- platform_systems = target_systems["platform_systems"]
35
- sys_platforms = target_systems["sys_platforms"]
36
- platform_tag_regexps = target_systems["platform_tag_regexps"]
14
+ target_systems_file = os.environ.pop("_PEX_UNIVERSAL_TARGET_FILE")
15
+ with open(target_systems_file) as fp:
16
+ universal_target = UniversalTarget.from_dict(json.load(fp))
37
17
 
38
18
 
39
19
  def patch_marker_evaluate():
40
20
  from pip._vendor.packaging import markers
41
21
 
42
- from pex.exceptions import production_assert
43
- from pex.interpreter_implementation import InterpreterImplementation
44
- from pex.typing import TYPE_CHECKING
45
-
46
- if TYPE_CHECKING:
47
- from typing import Any, Callable, Dict, Iterable, List, Tuple, Type
48
-
49
- interpreter_implementation = os.environ.get("_PEX_INTERPRETER_IMPLEMENTATION")
50
- interpreter_implementations = [] # type: List[str]
51
- if interpreter_implementation:
52
- interpreter_implementations.append(str(interpreter_implementation))
53
- else:
54
- interpreter_implementations.extend(
55
- str(implementation) for implementation in InterpreterImplementation.values()
56
- )
57
-
58
- original_eval_op = markers._eval_op
22
+ from pex.pep_503 import ProjectName
23
+ from pex.sorted_tuple import SortedTuple
24
+ from pex.third_party import attr
59
25
 
60
- # N.B.: The `packaging` distribution vendored with Pip>=24.1 has a patch that tests the
61
- # `python_full_version` marker environment value to see if it ends with "+", in which case it
62
- # appends "local" to it (see: https://github.com/pypa/packaging/pull/802). We support this test
63
- # by ensuring our value for `python_full_version`, either a `skip` sentinel or a list value,
64
- # both support the `endswith(value)` test and always return `False`.
65
- class NeverEndsWithMixin(object):
66
- def endswith(self, *args, **kwargs):
67
- # type: (...) -> bool
68
- return False
26
+ no_extras_marker_env = universal_target.marker_env()
69
27
 
70
- class NeverEndsWithStr(NeverEndsWithMixin, str):
71
- pass
72
-
73
- skip = NeverEndsWithStr()
74
-
75
- class NeverEndsWithList(NeverEndsWithMixin, list):
76
- pass
77
-
78
- def versions_to_string(
79
- versions, # type: Iterable[Tuple[int, ...]]
80
- version_modifier=lambda version: version, # type: Callable[[str], str]
81
- list_type=list, # type: Type[List]
82
- ):
83
- # type: (...) -> List[str]
84
- return list_type(version_modifier(".".join(map(str, version))) for version in versions)
85
-
86
- original_extra = markers.default_environment().get("extra")
87
- python_versions_strings = versions_to_string(python_versions) or skip
88
- python_full_versions_strings = (
89
- versions_to_string(
90
- python_full_versions,
91
- # N.B.: We replicate the logic `packaging` vendored with Pip>=24.1 has for dealing with
92
- # non-tagged CPython builds, which is fine to apply across all versions of Pip and its
93
- # vendored packaging that we support.
94
- version_modifier=lambda version: (
95
- version + "local" if version.endswith("+") else version
96
- ),
97
- list_type=NeverEndsWithList,
98
- )
99
- or skip
100
- )
101
- os_names_strings = os_names or skip
102
- platform_systems_strings = platform_systems or skip
103
- sys_platforms_strings = sys_platforms or skip
104
-
105
- class EvaluationEnvironment(dict):
106
- @classmethod
107
- def get_env(
108
- cls,
109
- environment, # type: Dict[Any, Any]
110
- name, # type: Any
111
- ):
112
- # type: (...) -> Any
113
- production_assert(
114
- isinstance(environment, cls),
115
- "Expected environment to come from the {function} function, "
116
- "which we patch to return {expected_type}, but was {actual_type}",
117
- function=markers.default_environment,
118
- expected_type=cls,
119
- actual_type=type(environment),
120
- )
121
- return environment[name]
122
-
123
- def __missing__(self, name):
124
- # type: (Any) -> Any
125
- if name == "extra":
126
- if original_extra is None:
127
- raise markers.UndefinedEnvironmentName(
128
- "'extra' does not exist in evaluation environment."
129
- )
130
- return original_extra
131
- if name == "platform_python_implementation":
132
- return interpreter_implementations
133
- if name == "python_version":
134
- return python_versions_strings
135
- if name == "python_full_version":
136
- return python_full_versions_strings
137
- if name == "os_name":
138
- return os_names_strings
139
- if name == "platform_system":
140
- return platform_systems_strings
141
- if name == "sys_platform":
142
- return sys_platforms_strings
143
- return skip
144
-
145
- def _eval_op(
146
- lhs, # type: Any
147
- op, # type: Any
148
- rhs, # type: Any
149
- ):
150
- # type: (...) -> bool
151
- if lhs is skip or rhs is skip:
152
- return True
153
- return any(
154
- original_eval_op(left, op, right)
155
- for left in (lhs if isinstance(lhs, list) else [lhs])
156
- for right in (rhs if isinstance(rhs, list) else [rhs])
157
- )
28
+ def evaluate(self, environment=None):
29
+ extra = environment.get("extra") if environment else None
30
+ if extra:
31
+ return attr.evolve(
32
+ no_extras_marker_env, extras=SortedTuple([ProjectName(extra).normalized])
33
+ ).evaluate(self)
34
+ return no_extras_marker_env.evaluate(self)
158
35
 
159
- def _evaluate_markers(
160
- marker_values, # type: List[Any]
161
- environment, # type: Dict[str, str]
162
- ):
163
- # type: (...) -> bool
164
- groups = [[]] # type: List[List[bool]]
165
-
166
- for marker in marker_values:
167
- if isinstance(marker, list):
168
- groups[-1].append(_evaluate_markers(marker, environment))
169
- elif isinstance(marker, tuple):
170
- lhs, op, rhs = marker
171
-
172
- if isinstance(lhs, markers.Variable):
173
- lhs_value = EvaluationEnvironment.get_env(environment, lhs.value)
174
- rhs_value = rhs.value
175
- else:
176
- lhs_value = lhs.value
177
- rhs_value = EvaluationEnvironment.get_env(environment, rhs.value)
178
-
179
- groups[-1].append(_eval_op(lhs_value, op, rhs_value))
180
- else:
181
- assert marker in ["and", "or"]
182
- if marker == "or":
183
- groups.append([])
184
-
185
- return any(all(item) for item in groups)
186
-
187
- # Works with all Pip vendored packaging distributions.
188
- markers.default_environment = EvaluationEnvironment
189
-
190
- # Covers Pip<24.1 vendored packaging.
191
- markers._get_env = EvaluationEnvironment.get_env
192
-
193
- markers._eval_op = _eval_op
194
-
195
- # Covers Pip>=25.1 vendored packaging where an assertion is added in the tuple case that lhs is
196
- # a string, defeating our EvaluationEnvironment patching above which returns lists of strings
197
- # for some environment markers. If it weren't for the assertion, we could use the existing
198
- # function.
199
- markers._evaluate_markers = _evaluate_markers
36
+ markers.Marker.evaluate = evaluate
200
37
 
201
38
 
202
39
  def patch_wheel_model():
203
40
  from pip._internal.models.wheel import Wheel
204
41
 
42
+ from pex.interpreter_constraints import iter_compatible_versions
43
+ from pex.resolve.target_system import TargetSystem
44
+
45
+ python_versions = sorted(
46
+ set(version[:2] for version in iter_compatible_versions(universal_target.requires_python))
47
+ )
48
+ python_majors = sorted(set(version[0] for version in python_versions))
49
+
205
50
  Wheel.support_index_min = lambda *args, **kwargs: 0
206
51
 
207
52
  # N.B.: Pip 25.1 updated the Wheel model, removing pyversions, abis and plats and replacing
208
53
  # these with a set of file-tags Tag objects. We unify with these helpers.
209
54
 
210
55
  def get_abi_info(self):
211
- if hasattr(self, "abis"):
212
- abis = set(self.abis)
213
- else:
214
- abis = {file_tag.abi for file_tag in self.file_tags}
215
-
216
- is_abi3 = {"abi3"} == abis
217
- is_abi_none = {"none"} == abis
218
- return is_abi3, is_abi_none
219
-
220
- def get_py_versions(self):
221
- if hasattr(self, "pyversions"):
222
- return self.pyversions
223
- return tuple(file_tag.interpreter for file_tag in self.file_tags)
56
+ if not hasattr(self, "is_abi3"):
57
+ abis = (
58
+ set(self.abis)
59
+ if hasattr(self, "abis")
60
+ else {file_tag.abi for file_tag in self.file_tags}
61
+ )
62
+ self.is_abi3 = {"abi3"} == abis
63
+ self.is_abi_none = {"none"} == abis
64
+ return self.is_abi3, self.is_abi_none
65
+
66
+ def get_py_versions_info(self):
67
+ if not hasattr(self, "pyversions_info"):
68
+ pyversions_info = []
69
+ for file_tag in self.file_tags:
70
+ # For the format, see: https://peps.python.org/pep-0425/#python-tag
71
+ match = re.search(
72
+ r"^(?P<impl>\D{2,})(?P<major>\d)(?P<minor>\d+)?", file_tag.interpreter
73
+ )
74
+ if not match:
75
+ continue
76
+ impl = match.group("impl")
77
+ major = int(match.group("major"))
78
+ minor = match.group("minor")
79
+ pyversions_info.append((impl, major, minor))
80
+ self.pyversions_info = tuple(pyversions_info)
81
+ return self.pyversions_info
224
82
 
225
83
  def get_platforms(self):
226
84
  if hasattr(self, "plats"):
@@ -235,18 +93,10 @@ def patch_wheel_model():
235
93
  if not hasattr(self, "_versions"):
236
94
  versions = set()
237
95
  is_abi3, is_abi_none = get_abi_info(self)
238
- for pyversion in get_py_versions(self):
239
- # For the format, see: https://peps.python.org/pep-0425/#python-tag
240
- match = re.search(r"^(?P<impl>\D{2,})(?P<major>\d)(?P<minor>\d+)?", pyversion)
241
- if not match:
242
- continue
243
-
244
- impl = match.group("impl")
245
- if impl not in ("cp", "pp", "py", "cpython", "pypy"):
96
+ for impl, major, minor in get_py_versions_info(self):
97
+ if impl not in ("py", "cp", "cpython", "pp", "pypy"):
246
98
  continue
247
99
 
248
- major = int(match.group("major"))
249
- minor = match.group("minor")
250
100
  if is_abi_none or (is_abi3 and major == 3):
251
101
  versions.add(major)
252
102
  elif minor:
@@ -263,19 +113,83 @@ def patch_wheel_model():
263
113
 
264
114
  supported_checks.append(supported_version)
265
115
 
266
- if platform_tag_regexps:
116
+ if universal_target.systems and set(universal_target.systems) != set(TargetSystem.values()):
267
117
  import re
268
118
 
269
- def supported_platform_tag(self, *_args, **_kwargs):
119
+ # See: https://peps.python.org/pep-0425/#platform-tag for more about the wheel platform tag.
120
+ platform_tag_substrings = []
121
+ for system in universal_target.systems:
122
+ if system is TargetSystem.LINUX:
123
+ platform_tag_substrings.append("linux")
124
+ elif system is TargetSystem.MAC:
125
+ platform_tag_substrings.append("macosx")
126
+ elif system is TargetSystem.WINDOWS:
127
+ platform_tag_substrings.append("win")
128
+
129
+ def supported_os_tag(self, *_args, **_kwargs):
130
+ platforms = get_platforms(self)
131
+ if any(plat == "any" for plat in platforms):
132
+ return True
133
+ for platform_tag_substring in platform_tag_substrings:
134
+ if any((platform_tag_substring in plat) for plat in platforms):
135
+ return True
136
+ return False
137
+
138
+ supported_checks.append(supported_os_tag)
139
+
140
+ platform_machine_values = universal_target.extra_markers.get_values("platform_machine")
141
+ if platform_machine_values:
142
+ import re
143
+
144
+ # See: https://peps.python.org/pep-0425/#platform-tag for more about the wheel platform tag.
145
+ platform_machine_regexps = tuple(
146
+ re.compile(re.escape(machine), flags=re.IGNORECASE)
147
+ for machine in platform_machine_values
148
+ )
149
+
150
+ def supported_machine_tag(self, *_args, **_kwargs):
270
151
  platforms = get_platforms(self)
152
+
271
153
  if any(plat == "any" for plat in platforms):
272
154
  return True
273
- for platform_tag_regexp in platform_tag_regexps:
274
- if any(re.search(platform_tag_regexp, plat) for plat in platforms):
155
+
156
+ if platform_machine_values.inclusive:
157
+ for platform_machine_regexp in platform_machine_regexps:
158
+ if any(platform_machine_regexp.search(plat) for plat in platforms):
159
+ return True
160
+ return False
161
+
162
+ if all(
163
+ all(platform_machine_regexp.search(plat) for plat in platforms)
164
+ for platform_machine_regexp in platform_machine_regexps
165
+ ):
166
+ return False
167
+
168
+ return True
169
+
170
+ supported_checks.append(supported_machine_tag)
171
+
172
+ if universal_target.implementation:
173
+
174
+ def supported_impl(self, *_args, **_kwargs):
175
+ for impl, _, _ in get_py_versions_info(self):
176
+ if impl == "py":
177
+ return True
178
+
179
+ if (
180
+ universal_target.implementation is InterpreterImplementation.CPYTHON
181
+ and impl in ("cp", "cpython")
182
+ ):
183
+ return True
184
+
185
+ if universal_target.implementation is InterpreterImplementation.PYPY and impl in (
186
+ "pp",
187
+ "pypy",
188
+ ):
275
189
  return True
276
190
  return False
277
191
 
278
- supported_checks.append(supported_platform_tag)
192
+ supported_checks.append(supported_impl)
279
193
 
280
194
  Wheel.supported = lambda *args, **kwargs: all(
281
195
  check(*args, **kwargs) for check in supported_checks
@@ -41,7 +41,7 @@ from pex.resolve.locked_resolve import (
41
41
  from pex.resolve.locker import Locker
42
42
  from pex.resolve.lockfile.download_manager import DownloadManager
43
43
  from pex.resolve.lockfile.model import Lockfile
44
- from pex.resolve.lockfile.targets import LockTargets
44
+ from pex.resolve.lockfile.targets import calculate_download_input
45
45
  from pex.resolve.package_repository import ReposConfiguration
46
46
  from pex.resolve.pep_691.fingerprint_service import FingerprintService
47
47
  from pex.resolve.requirement_configuration import RequirementConfiguration
@@ -49,7 +49,7 @@ from pex.resolve.resolved_requirement import Pin, ResolvedRequirement
49
49
  from pex.resolve.resolver_configuration import BuildConfiguration, PipConfiguration, ResolverVersion
50
50
  from pex.resolve.resolvers import Resolver
51
51
  from pex.resolver import BuildRequest, Downloaded, DownloadTarget, ResolveObserver, WheelBuilder
52
- from pex.resolver import download as pip_download
52
+ from pex.resolver import download_requests as pip_download_requests
53
53
  from pex.result import Error, try_
54
54
  from pex.targets import Target, Targets
55
55
  from pex.tracer import TRACER
@@ -333,12 +333,22 @@ class LockObserver(ResolveObserver):
333
333
  dist_metadatas_by_download_target[local_distribution.download_target].add(
334
334
  DistMetadata.load(local_distribution.path)
335
335
  )
336
+ elif os.path.isfile(local_distribution.path):
337
+ build_requests.add(
338
+ BuildRequest.for_file(
339
+ target=local_distribution.download_target,
340
+ source_path=local_distribution.path,
341
+ subdirectory=local_distribution.subdirectory,
342
+ )
343
+ )
336
344
  else:
337
345
  build_requests.add(
338
- BuildRequest.create(
346
+ BuildRequest.for_directory(
339
347
  target=local_distribution.download_target,
340
348
  source_path=local_distribution.path,
341
349
  subdirectory=local_distribution.subdirectory,
350
+ resolver=self.resolver,
351
+ pip_version=self.package_index_configuration.pip_version,
342
352
  )
343
353
  )
344
354
 
@@ -348,7 +358,12 @@ class LockObserver(ResolveObserver):
348
358
  for analysis in self._analysis:
349
359
  lock_result = analysis.analyzer.lock_result
350
360
  build_requests.update(
351
- BuildRequest.create(target=analysis.download_target, source_path=local_project)
361
+ BuildRequest.for_directory(
362
+ target=analysis.download_target,
363
+ source_path=local_project,
364
+ resolver=self.resolver,
365
+ pip_version=self.package_index_configuration.pip_version,
366
+ )
352
367
  for local_project in lock_result.local_projects
353
368
  )
354
369
  resolved_requirements_by_download_target[
@@ -511,7 +526,7 @@ def create(
511
526
 
512
527
  download_dir = safe_mkdtemp()
513
528
  with TRACER.timed("Calculating lock targets"):
514
- lock_targets = LockTargets.calculate(
529
+ download_input = calculate_download_input(
515
530
  targets=targets,
516
531
  requirement_configuration=requirement_configuration,
517
532
  network_configuration=network_configuration,
@@ -519,11 +534,9 @@ def create(
519
534
  universal_target=lock_configuration.universal_target,
520
535
  )
521
536
  try:
522
- downloaded = pip_download(
523
- targets=lock_targets.targets,
524
- requirements=requirement_configuration.requirements,
525
- requirement_files=requirement_configuration.requirement_files,
526
- constraint_files=requirement_configuration.constraint_files,
537
+ downloaded = pip_download_requests(
538
+ requests=download_input.download_requests,
539
+ direct_requirements=download_input.direct_requirements,
527
540
  allow_prereleases=pip_configuration.allow_prereleases,
528
541
  transitive=pip_configuration.transitive,
529
542
  repos_configuration=pip_configuration.repos_configuration,
@@ -540,7 +553,6 @@ def create(
540
553
  extra_pip_requirements=pip_configuration.extra_requirements,
541
554
  keyring_provider=pip_configuration.keyring_provider,
542
555
  dependency_configuration=dependency_configuration,
543
- universal_targets=lock_targets.universal_targets,
544
556
  )
545
557
  except resolvers.ResolveError as e:
546
558
  return Error(str(e))
@@ -553,7 +565,9 @@ def create(
553
565
  download_dir=download_dir, locked_resolves=locked_resolves
554
566
  )
555
567
  create_lock_download_manager.store_all(
556
- targets=lock_targets.targets.unique_targets(),
568
+ targets=OrderedSet(
569
+ download_request.target for download_request in download_input.download_requests
570
+ ),
557
571
  lock_configuration=lock_configuration,
558
572
  resolver=configured_resolver,
559
573
  repos_configuration=pip_configuration.repos_configuration,