pex 2.64.1__py2.py3-none-any.whl → 2.69.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.
- pex/bin/pex.py +2 -1
- pex/build_backend/configuration.py +5 -5
- pex/build_backend/wrap.py +2 -19
- pex/cli/commands/lock.py +4 -2
- pex/cli/commands/run.py +10 -11
- pex/cli/pex.py +11 -4
- pex/dist_metadata.py +29 -2
- pex/docs/html/_pagefind/fragment/en_4250138.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_7125dad.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_785d562.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_8e94bb8.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/{en_17782b6.pf_fragment → en_a0396bb.pf_fragment} +0 -0
- pex/docs/html/_pagefind/fragment/en_a8a3588.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_c07d988.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_d718411.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/en_a2e3c5e.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind-entry.json +1 -1
- pex/docs/html/_pagefind/pagefind.en_4ce1afa9e3.pf_meta +0 -0
- pex/docs/html/_static/documentation_options.js +1 -1
- pex/docs/html/api/vars.html +5 -5
- pex/docs/html/buildingpex.html +5 -5
- pex/docs/html/genindex.html +5 -5
- pex/docs/html/index.html +5 -5
- pex/docs/html/recipes.html +5 -5
- pex/docs/html/scie.html +5 -5
- pex/docs/html/search.html +5 -5
- pex/docs/html/whatispex.html +5 -5
- pex/hashing.py +71 -9
- pex/interpreter_constraints.py +1 -1
- pex/jobs.py +13 -6
- pex/pep_376.py +21 -6
- pex/pep_427.py +30 -8
- pex/pex_builder.py +1 -4
- pex/pip/local_project.py +6 -14
- pex/pip/tool.py +3 -3
- pex/pip/vcs.py +93 -44
- pex/pip/version.py +7 -0
- pex/resolve/configured_resolve.py +13 -5
- pex/resolve/lock_downloader.py +1 -0
- pex/resolve/locker.py +30 -14
- pex/resolve/lockfile/create.py +2 -7
- pex/resolve/pre_resolved_resolver.py +1 -7
- pex/resolve/project.py +233 -47
- pex/resolve/resolver_configuration.py +1 -1
- pex/resolve/resolver_options.py +14 -9
- pex/resolve/venv_resolver.py +221 -65
- pex/resolver.py +59 -55
- pex/scie/__init__.py +40 -1
- pex/scie/model.py +2 -0
- pex/scie/science.py +25 -3
- pex/sdist.py +219 -0
- pex/version.py +1 -1
- pex/wheel.py +16 -12
- {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/METADATA +4 -4
- {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/RECORD +60 -59
- {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/entry_points.txt +1 -0
- pex/docs/html/_pagefind/fragment/en_1048255.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_3f7efc3.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_40667cd.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_55ee2f4.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_d6d92dd.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_d834316.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_ec2ce54.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/en_17effb2.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind.en_49ec86cf86.pf_meta +0 -0
- {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/WHEEL +0 -0
- {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/licenses/LICENSE +0 -0
- {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/pylock/pylock.toml +0 -0
- {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/top_level.txt +0 -0
pex/resolve/venv_resolver.py
CHANGED
|
@@ -5,14 +5,13 @@ from __future__ import absolute_import
|
|
|
5
5
|
|
|
6
6
|
import functools
|
|
7
7
|
import hashlib
|
|
8
|
-
import itertools
|
|
9
8
|
import os
|
|
10
9
|
from collections import defaultdict, deque
|
|
11
10
|
|
|
12
11
|
from pex import pex_warnings
|
|
13
12
|
from pex.atomic_directory import atomic_directory
|
|
14
13
|
from pex.cache.dirs import CacheDir, InstalledWheelDir
|
|
15
|
-
from pex.common import safe_relative_symlink
|
|
14
|
+
from pex.common import pluralize, safe_relative_symlink
|
|
16
15
|
from pex.compatibility import commonpath
|
|
17
16
|
from pex.dependency_configuration import DependencyConfiguration
|
|
18
17
|
from pex.dist_metadata import (
|
|
@@ -26,10 +25,9 @@ from pex.dist_metadata import (
|
|
|
26
25
|
from pex.exceptions import production_assert, reportable_unexpected_error_msg
|
|
27
26
|
from pex.fingerprinted_distribution import FingerprintedDistribution
|
|
28
27
|
from pex.installed_wheel import InstalledWheel
|
|
29
|
-
from pex.interpreter import PythonInterpreter
|
|
30
28
|
from pex.jobs import DEFAULT_MAX_JOBS, iter_map_parallel
|
|
31
29
|
from pex.orderedset import OrderedSet
|
|
32
|
-
from pex.pep_376 import Record
|
|
30
|
+
from pex.pep_376 import InstalledDirectory, InstalledFile, Record
|
|
33
31
|
from pex.pep_427 import InstallableType, InstallableWheel, InstallPaths, install_wheel_chroot
|
|
34
32
|
from pex.pep_503 import ProjectName
|
|
35
33
|
from pex.pip.version import PipVersion
|
|
@@ -47,7 +45,18 @@ from pex.wheel import WHEEL, Wheel
|
|
|
47
45
|
from pex.whl import repacked_whl
|
|
48
46
|
|
|
49
47
|
if TYPE_CHECKING:
|
|
50
|
-
from typing import
|
|
48
|
+
from typing import (
|
|
49
|
+
DefaultDict,
|
|
50
|
+
Deque,
|
|
51
|
+
FrozenSet,
|
|
52
|
+
Iterable,
|
|
53
|
+
Iterator,
|
|
54
|
+
List,
|
|
55
|
+
Mapping,
|
|
56
|
+
Set,
|
|
57
|
+
Tuple,
|
|
58
|
+
Union,
|
|
59
|
+
)
|
|
51
60
|
|
|
52
61
|
import attr # vendor:skip
|
|
53
62
|
else:
|
|
@@ -76,10 +85,11 @@ def _normalize_record(
|
|
|
76
85
|
if record_lines:
|
|
77
86
|
eol = "\r\n" if record_lines[0].endswith("\r\n") else "\n"
|
|
78
87
|
|
|
79
|
-
installed_files = [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
installed_files = [] # type: List[Union[InstalledFile, InstalledDirectory]]
|
|
89
|
+
for installed_file in Record.read(lines=iter(record_lines)):
|
|
90
|
+
if isinstance(installed_file, InstalledDirectory):
|
|
91
|
+
installed_files.append(installed_file)
|
|
92
|
+
elif isinstance(installed_file, InstalledFile) and (
|
|
83
93
|
(os.path.basename(installed_file.path) not in entry_point_scripts)
|
|
84
94
|
or (
|
|
85
95
|
scripts_dir
|
|
@@ -90,18 +100,20 @@ def _normalize_record(
|
|
|
90
100
|
)
|
|
91
101
|
)
|
|
92
102
|
)
|
|
93
|
-
)
|
|
94
|
-
|
|
103
|
+
):
|
|
104
|
+
installed_files.append(installed_file)
|
|
95
105
|
return Record.write_bytes(installed_files=installed_files, eol=eol)
|
|
96
106
|
|
|
97
107
|
|
|
98
108
|
def _install_distribution(
|
|
99
|
-
|
|
109
|
+
venv_distribution, # type: VenvDistribution
|
|
100
110
|
result_type, # type: InstallableType.Value
|
|
101
111
|
use_system_time, # type: bool
|
|
102
|
-
distribution, # type: Distribution
|
|
103
112
|
):
|
|
104
|
-
# type: (...) ->
|
|
113
|
+
# type: (...) -> ResolvedDistribution
|
|
114
|
+
|
|
115
|
+
interpreter = venv_distribution.target.interpreter
|
|
116
|
+
distribution = venv_distribution.distribution
|
|
105
117
|
|
|
106
118
|
production_assert(distribution.type is DistributionType.INSTALLED)
|
|
107
119
|
production_assert(distribution.metadata.files.metadata.type is MetadataType.DIST_INFO)
|
|
@@ -160,31 +172,86 @@ def _install_distribution(
|
|
|
160
172
|
raise AssertionError(reportable_unexpected_error_msg())
|
|
161
173
|
|
|
162
174
|
if result_type is InstallableType.INSTALLED_WHEEL_CHROOT:
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
return ResolvedDistribution(
|
|
176
|
+
target=venv_distribution.target,
|
|
177
|
+
fingerprinted_distribution=FingerprintedDistribution(
|
|
178
|
+
distribution=Distribution.load(installed_wheel.prefix_dir),
|
|
179
|
+
fingerprint=installed_wheel.fingerprint,
|
|
180
|
+
),
|
|
181
|
+
direct_requirements=venv_distribution.direct_requirements,
|
|
166
182
|
)
|
|
167
|
-
|
|
168
|
-
|
|
183
|
+
|
|
184
|
+
return ResolvedDistribution(
|
|
185
|
+
target=venv_distribution.target,
|
|
186
|
+
fingerprinted_distribution=repacked_whl(
|
|
187
|
+
installed_wheel,
|
|
188
|
+
fingerprint=installed_wheel.fingerprint,
|
|
189
|
+
use_system_time=use_system_time,
|
|
190
|
+
),
|
|
191
|
+
direct_requirements=venv_distribution.direct_requirements,
|
|
169
192
|
)
|
|
170
193
|
|
|
171
194
|
|
|
195
|
+
@attr.s(frozen=True)
|
|
196
|
+
class VenvDistribution(object):
|
|
197
|
+
target = attr.ib() # type: LocalInterpreter
|
|
198
|
+
distribution = attr.ib() # type: Distribution
|
|
199
|
+
direct_requirements = attr.ib() # type: Iterable[Requirement]
|
|
200
|
+
|
|
201
|
+
|
|
172
202
|
def _install_venv_distributions(
|
|
173
|
-
|
|
174
|
-
distributions, # type: Iterable[Distribution]
|
|
203
|
+
venv_resolve_results, # type: Iterable[VenvResolveResult]
|
|
175
204
|
max_install_jobs=DEFAULT_MAX_JOBS, # type: int
|
|
176
205
|
result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
|
|
177
206
|
use_system_time=False, # type: bool
|
|
178
207
|
):
|
|
179
|
-
# type: (...) -> Iterator[
|
|
208
|
+
# type: (...) -> Iterator[ResolvedDistribution]
|
|
209
|
+
|
|
210
|
+
seen = set() # type: Set[str]
|
|
211
|
+
|
|
212
|
+
venv_distributions = [] # type: List[VenvDistribution]
|
|
213
|
+
for venv_resolve_result in venv_resolve_results:
|
|
214
|
+
target = venv_resolve_result.target
|
|
215
|
+
direct_requirements = venv_resolve_result.direct_requirements_by_project_name
|
|
216
|
+
for re_resolved_distribution in venv_resolve_result.re_resolved_distributions:
|
|
217
|
+
wheel_file_name = Wheel.from_distribution(
|
|
218
|
+
re_resolved_distribution.distribution
|
|
219
|
+
).wheel_file_name
|
|
220
|
+
if wheel_file_name in seen:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
seen.add(wheel_file_name)
|
|
224
|
+
yield ResolvedDistribution(
|
|
225
|
+
target=target,
|
|
226
|
+
fingerprinted_distribution=re_resolved_distribution,
|
|
227
|
+
direct_requirements=direct_requirements.get(
|
|
228
|
+
re_resolved_distribution.project_name, ()
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
for venv_distribution in venv_resolve_result.venv_distributions:
|
|
232
|
+
wheel_file_name = Wheel.from_distribution(venv_distribution).wheel_file_name
|
|
233
|
+
if wheel_file_name in seen:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
seen.add(wheel_file_name)
|
|
237
|
+
venv_distributions.append(
|
|
238
|
+
VenvDistribution(
|
|
239
|
+
target=target,
|
|
240
|
+
distribution=venv_distribution,
|
|
241
|
+
direct_requirements=direct_requirements.get(
|
|
242
|
+
venv_distribution.metadata.project_name, ()
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
)
|
|
180
246
|
|
|
181
|
-
|
|
182
|
-
inputs=
|
|
247
|
+
for resolved_distribution in iter_map_parallel(
|
|
248
|
+
inputs=venv_distributions,
|
|
183
249
|
function=functools.partial(
|
|
184
|
-
_install_distribution,
|
|
250
|
+
_install_distribution, result_type=result_type, use_system_time=use_system_time
|
|
185
251
|
),
|
|
186
252
|
max_jobs=max_install_jobs,
|
|
187
|
-
)
|
|
253
|
+
):
|
|
254
|
+
yield resolved_distribution
|
|
188
255
|
|
|
189
256
|
|
|
190
257
|
@attr.s(frozen=True)
|
|
@@ -314,7 +381,11 @@ def _resolve_distributions(
|
|
|
314
381
|
elif meets_requirement(distribution, requirement):
|
|
315
382
|
production_assert(distribution.type is DistributionType.INSTALLED)
|
|
316
383
|
resolved.add(requirement.project_name)
|
|
317
|
-
|
|
384
|
+
editable_project_url = distribution.editable_install_url()
|
|
385
|
+
if (
|
|
386
|
+
not editable_project_url
|
|
387
|
+
and distribution.metadata.files.metadata.type is MetadataType.DIST_INFO
|
|
388
|
+
):
|
|
318
389
|
to_resolve.extend(
|
|
319
390
|
requirement.dependency(
|
|
320
391
|
requirement=dependency,
|
|
@@ -325,8 +396,15 @@ def _resolve_distributions(
|
|
|
325
396
|
)
|
|
326
397
|
yield distribution
|
|
327
398
|
else:
|
|
399
|
+
source_requirement = (
|
|
400
|
+
"{project} @ {url}".format(
|
|
401
|
+
project=distribution.metadata.project_name, url=editable_project_url
|
|
402
|
+
)
|
|
403
|
+
if editable_project_url
|
|
404
|
+
else str(distribution.as_requirement())
|
|
405
|
+
)
|
|
328
406
|
result = resolver.resolve_requirements(
|
|
329
|
-
requirements=[
|
|
407
|
+
requirements=[source_requirement],
|
|
330
408
|
targets=Targets.from_target(target),
|
|
331
409
|
transitive=False,
|
|
332
410
|
compile=compile,
|
|
@@ -359,27 +437,32 @@ def _resolve_distributions(
|
|
|
359
437
|
)
|
|
360
438
|
|
|
361
439
|
|
|
362
|
-
|
|
363
|
-
|
|
440
|
+
@attr.s(frozen=True)
|
|
441
|
+
class VenvResolveResult(object):
|
|
442
|
+
venv = attr.ib() # type: Virtualenv
|
|
443
|
+
venv_distributions = attr.ib() # type: Tuple[Distribution, ...]
|
|
444
|
+
re_resolved_distributions = attr.ib() # type: Tuple[FingerprintedDistribution, ...]
|
|
445
|
+
direct_requirements_by_project_name = attr.ib(
|
|
446
|
+
eq=False
|
|
447
|
+
) # type: Mapping[ProjectName, Iterable[Requirement]]
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def target(self):
|
|
451
|
+
# type: () -> LocalInterpreter
|
|
452
|
+
return LocalInterpreter.create(self.venv.interpreter)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _resolve_from_venv(
|
|
364
456
|
venv, # type: Virtualenv
|
|
365
|
-
requirement_configuration
|
|
366
|
-
pip_configuration
|
|
367
|
-
compile
|
|
368
|
-
ignore_errors
|
|
369
|
-
result_type
|
|
370
|
-
dependency_configuration
|
|
457
|
+
requirement_configuration, # type: RequirementConfiguration
|
|
458
|
+
pip_configuration, # type: PipConfiguration
|
|
459
|
+
compile, # type: bool
|
|
460
|
+
ignore_errors, # type: bool
|
|
461
|
+
result_type, # type: InstallableType.Value
|
|
462
|
+
dependency_configuration, # type: DependencyConfiguration
|
|
371
463
|
):
|
|
372
|
-
# type: (...) -> Union[
|
|
373
|
-
|
|
464
|
+
# type: (...) -> Union[VenvResolveResult, Error]
|
|
374
465
|
target = LocalInterpreter.create(venv.interpreter)
|
|
375
|
-
if not targets.is_empty:
|
|
376
|
-
return Error(
|
|
377
|
-
"You configured custom targets via --python, --interpreter-constraint, --platform or "
|
|
378
|
-
"--complete-platform but custom targets are not allowed when resolving from a virtual "
|
|
379
|
-
"environment.\n"
|
|
380
|
-
"For such resolves, the supported target is implicitly the one matching the venv "
|
|
381
|
-
"interpreter; in this case: {target}.".format(target=target.render_description())
|
|
382
|
-
)
|
|
383
466
|
|
|
384
467
|
if pip_configuration.version:
|
|
385
468
|
compatible_pip_version = (
|
|
@@ -471,7 +554,17 @@ def resolve_from_venv(
|
|
|
471
554
|
if venv_distribution.metadata.files.metadata.type is not MetadataType.DIST_INFO:
|
|
472
555
|
sdists_to_resolve.append(str(venv_distribution.as_requirement()))
|
|
473
556
|
else:
|
|
474
|
-
|
|
557
|
+
editable_project_url = venv_distribution.editable_install_url()
|
|
558
|
+
if editable_project_url:
|
|
559
|
+
sdists_to_resolve.append(
|
|
560
|
+
"{project_name} @ {url}".format(
|
|
561
|
+
project_name=venv_distribution.metadata.project_name,
|
|
562
|
+
url=editable_project_url,
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
else:
|
|
566
|
+
venv_distributions.append(venv_distribution)
|
|
567
|
+
|
|
475
568
|
direct_requirements_by_project_name[venv_distribution.metadata.project_name].add(
|
|
476
569
|
venv_distribution.as_requirement()
|
|
477
570
|
)
|
|
@@ -487,27 +580,90 @@ def resolve_from_venv(
|
|
|
487
580
|
dist.fingerprinted_distribution for dist in result.distributions
|
|
488
581
|
)
|
|
489
582
|
|
|
583
|
+
return VenvResolveResult(
|
|
584
|
+
venv=venv,
|
|
585
|
+
venv_distributions=tuple(venv_distributions),
|
|
586
|
+
re_resolved_distributions=tuple(fingerprinted_distributions),
|
|
587
|
+
direct_requirements_by_project_name=direct_requirements_by_project_name,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def resolve_from_venvs(
|
|
592
|
+
targets, # type: Targets
|
|
593
|
+
venvs, # type: Tuple[Virtualenv, ...]
|
|
594
|
+
requirement_configuration=RequirementConfiguration(), # type: RequirementConfiguration
|
|
595
|
+
pip_configuration=PipConfiguration(), # type: PipConfiguration
|
|
596
|
+
compile=False, # type: bool
|
|
597
|
+
ignore_errors=False, # type: bool
|
|
598
|
+
result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
|
|
599
|
+
dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
|
|
600
|
+
):
|
|
601
|
+
# type: (...) -> Union[ResolveResult, Error]
|
|
602
|
+
|
|
603
|
+
if not targets.is_empty:
|
|
604
|
+
return Error(
|
|
605
|
+
"You configured custom targets via --python, --interpreter-constraint, --platform or "
|
|
606
|
+
"--complete-platform but custom targets are not allowed when resolving from {venvs}.\n"
|
|
607
|
+
"For such resolves, the supported target is implicitly the one matching the venv "
|
|
608
|
+
"{interpreters}; in this case:{targets}.".format(
|
|
609
|
+
venvs="a virtual environment" if len(venvs) == 1 else "virtual environments",
|
|
610
|
+
interpreters=pluralize(venvs, "interpreter"),
|
|
611
|
+
targets=(
|
|
612
|
+
" {target}".format(
|
|
613
|
+
target=LocalInterpreter.create(venvs[0].interpreter).render_description()
|
|
614
|
+
)
|
|
615
|
+
if len(venvs) == 1
|
|
616
|
+
else "\n {targets}".format(
|
|
617
|
+
targets="\n ".join(
|
|
618
|
+
LocalInterpreter.create(venv.interpreter).render_description()
|
|
619
|
+
for venv in venvs
|
|
620
|
+
)
|
|
621
|
+
)
|
|
622
|
+
),
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
errors = [] # type: List[Error]
|
|
627
|
+
venv_resolve_results = [] # type: List[VenvResolveResult]
|
|
628
|
+
for result in iter_map_parallel(
|
|
629
|
+
venvs,
|
|
630
|
+
functools.partial(
|
|
631
|
+
_resolve_from_venv,
|
|
632
|
+
requirement_configuration=requirement_configuration,
|
|
633
|
+
pip_configuration=pip_configuration,
|
|
634
|
+
compile=compile,
|
|
635
|
+
ignore_errors=ignore_errors,
|
|
636
|
+
result_type=result_type,
|
|
637
|
+
dependency_configuration=dependency_configuration,
|
|
638
|
+
),
|
|
639
|
+
):
|
|
640
|
+
if isinstance(result, Error):
|
|
641
|
+
errors.append(result)
|
|
642
|
+
else:
|
|
643
|
+
venv_resolve_results.append(result)
|
|
644
|
+
|
|
645
|
+
if len(errors) == 1:
|
|
646
|
+
return errors[0]
|
|
647
|
+
elif errors:
|
|
648
|
+
return Error(
|
|
649
|
+
"Failed to resolve from {count} of {total} virtual environments:\n{failures}".format(
|
|
650
|
+
count=len(errors),
|
|
651
|
+
total=len(venvs),
|
|
652
|
+
failures="\n".join(
|
|
653
|
+
"{index}. {error}".format(index=index, error=error)
|
|
654
|
+
for index, error in enumerate(errors, start=1)
|
|
655
|
+
),
|
|
656
|
+
)
|
|
657
|
+
)
|
|
658
|
+
|
|
490
659
|
return ResolveResult(
|
|
491
660
|
dependency_configuration=dependency_configuration,
|
|
492
661
|
distributions=tuple(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
],
|
|
499
|
-
)
|
|
500
|
-
for fingerprinted_distribution in itertools.chain(
|
|
501
|
-
list(
|
|
502
|
-
_install_venv_distributions(
|
|
503
|
-
venv=venv,
|
|
504
|
-
distributions=venv_distributions,
|
|
505
|
-
max_install_jobs=pip_configuration.max_jobs,
|
|
506
|
-
result_type=result_type,
|
|
507
|
-
use_system_time=True,
|
|
508
|
-
)
|
|
509
|
-
),
|
|
510
|
-
fingerprinted_distributions,
|
|
662
|
+
_install_venv_distributions(
|
|
663
|
+
venv_resolve_results,
|
|
664
|
+
max_install_jobs=pip_configuration.max_jobs,
|
|
665
|
+
result_type=result_type,
|
|
666
|
+
use_system_time=True,
|
|
511
667
|
)
|
|
512
668
|
),
|
|
513
669
|
type=result_type,
|
pex/resolver.py
CHANGED
|
@@ -10,12 +10,11 @@ import hashlib
|
|
|
10
10
|
import itertools
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
|
-
import tarfile
|
|
14
13
|
import zipfile
|
|
15
14
|
from abc import abstractmethod
|
|
16
15
|
from collections import OrderedDict, defaultdict
|
|
17
16
|
|
|
18
|
-
from pex import targets
|
|
17
|
+
from pex import sdist, targets
|
|
19
18
|
from pex.atomic_directory import AtomicDirectory, atomic_directory
|
|
20
19
|
from pex.cache.dirs import BuiltWheelDir, CacheDir
|
|
21
20
|
from pex.common import (
|
|
@@ -65,6 +64,7 @@ from pex.resolve.resolvers import (
|
|
|
65
64
|
check_resolve,
|
|
66
65
|
)
|
|
67
66
|
from pex.resolve.target_system import TargetSystem, UniversalTarget
|
|
67
|
+
from pex.result import Error
|
|
68
68
|
from pex.targets import AbbreviatedPlatform, CompletePlatform, LocalInterpreter, Target, Targets
|
|
69
69
|
from pex.third_party.packaging.specifiers import SpecifierSet
|
|
70
70
|
from pex.third_party.packaging.tags import Tag
|
|
@@ -309,10 +309,7 @@ class _PipSession(object):
|
|
|
309
309
|
if isinstance(requirement, LocalProjectRequirement):
|
|
310
310
|
for request in self.requests:
|
|
311
311
|
yield BuildRequest.for_directory(
|
|
312
|
-
target=request.target,
|
|
313
|
-
source_path=requirement.path,
|
|
314
|
-
resolver=self.resolver,
|
|
315
|
-
pip_version=self.pip_version,
|
|
312
|
+
target=request.target, source_path=requirement.path
|
|
316
313
|
)
|
|
317
314
|
|
|
318
315
|
def generate_reports(self, max_parallel_jobs=None):
|
|
@@ -594,12 +591,17 @@ def _fingerprint_directory(path):
|
|
|
594
591
|
return CacheHelper.dir_hash(path, digest=_hasher())
|
|
595
592
|
|
|
596
593
|
|
|
594
|
+
class BuildError(Exception):
|
|
595
|
+
pass
|
|
596
|
+
|
|
597
|
+
|
|
597
598
|
def _fingerprint_local_project(
|
|
598
599
|
path, # type: str
|
|
599
600
|
target, # type: Target
|
|
600
601
|
resolver=None, # type: Optional[Resolver]
|
|
601
602
|
pip_version=None, # type: Optional[PipVersionValue]
|
|
602
603
|
):
|
|
604
|
+
# type: (...) -> str
|
|
603
605
|
if resolver:
|
|
604
606
|
build_system_resolver = resolver
|
|
605
607
|
else:
|
|
@@ -607,17 +609,20 @@ def _fingerprint_local_project(
|
|
|
607
609
|
|
|
608
610
|
build_system_resolver = ConfiguredResolver.default()
|
|
609
611
|
|
|
610
|
-
|
|
612
|
+
hasher = _hasher()
|
|
613
|
+
result = digest_local_project(
|
|
611
614
|
directory=path,
|
|
612
|
-
digest=
|
|
615
|
+
digest=hasher,
|
|
613
616
|
target=target,
|
|
614
617
|
resolver=build_system_resolver,
|
|
615
618
|
pip_version=pip_version,
|
|
616
619
|
)
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
620
|
+
if isinstance(result, Error):
|
|
621
|
+
raise BuildError(
|
|
622
|
+
"Failed to create an sdist for hashing from the local project at {path}: "
|
|
623
|
+
"{err}".format(path=path, err=result)
|
|
624
|
+
)
|
|
625
|
+
return hasher.hexdigest()
|
|
621
626
|
|
|
622
627
|
|
|
623
628
|
def _as_download_target(target):
|
|
@@ -649,27 +654,19 @@ class BuildRequest(object):
|
|
|
649
654
|
target, # type: Union[DownloadTarget, Target]
|
|
650
655
|
source_path, # type: str
|
|
651
656
|
subdirectory=None, # type: Optional[str]
|
|
652
|
-
resolver=None, # type: Optional[Resolver]
|
|
653
|
-
pip_version=None, # type: Optional[PipVersionValue]
|
|
654
657
|
):
|
|
655
658
|
# type: (...) -> BuildRequest
|
|
656
659
|
download_target = _as_download_target(target)
|
|
657
|
-
fingerprint = _fingerprint_local_project(
|
|
658
|
-
path=source_path,
|
|
659
|
-
target=download_target.target,
|
|
660
|
-
resolver=resolver,
|
|
661
|
-
pip_version=pip_version,
|
|
662
|
-
)
|
|
663
660
|
return cls(
|
|
664
661
|
download_target=download_target,
|
|
665
662
|
source_path=source_path,
|
|
666
|
-
fingerprint=
|
|
663
|
+
fingerprint=None,
|
|
667
664
|
subdirectory=subdirectory,
|
|
668
665
|
)
|
|
669
666
|
|
|
670
667
|
download_target = attr.ib(converter=_as_download_target) # type: DownloadTarget
|
|
671
668
|
source_path = attr.ib() # type: str
|
|
672
|
-
fingerprint = attr.ib() # type: str
|
|
669
|
+
fingerprint = attr.ib() # type: Optional[str]
|
|
673
670
|
subdirectory = attr.ib() # type: Optional[str]
|
|
674
671
|
|
|
675
672
|
@property
|
|
@@ -689,26 +686,26 @@ class BuildRequest(object):
|
|
|
689
686
|
if is_zip_sdist(self.source_path):
|
|
690
687
|
with open_zip(self.source_path) as zf:
|
|
691
688
|
zf.extractall(extract_dir)
|
|
692
|
-
elif is_tar_sdist(self.source_path):
|
|
693
|
-
with tarfile.open(self.source_path) as tf:
|
|
694
|
-
tf.extractall(extract_dir)
|
|
695
|
-
else:
|
|
696
|
-
raise BuildError(
|
|
697
|
-
"Unexpected archive type for sdist {project}".format(project=self.source_path)
|
|
698
|
-
)
|
|
699
689
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
690
|
+
listing = os.listdir(extract_dir)
|
|
691
|
+
if len(listing) != 1:
|
|
692
|
+
raise BuildError(
|
|
693
|
+
"Expected one top-level project directory to be extracted from {project}, "
|
|
694
|
+
"found {count}: {listing}".format(
|
|
695
|
+
project=self.source_path, count=len(listing), listing=", ".join(listing)
|
|
696
|
+
)
|
|
706
697
|
)
|
|
707
|
-
)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
698
|
+
project_directory = os.path.join(extract_dir, listing[0])
|
|
699
|
+
if self.subdirectory:
|
|
700
|
+
project_directory = os.path.join(project_directory, self.subdirectory)
|
|
701
|
+
return project_directory
|
|
702
|
+
|
|
703
|
+
if is_tar_sdist(self.source_path):
|
|
704
|
+
return sdist.extract_tarball(self.source_path, dest_dir=extract_dir)
|
|
705
|
+
|
|
706
|
+
raise BuildError(
|
|
707
|
+
"Unexpected archive type for sdist {project}".format(project=self.source_path)
|
|
708
|
+
)
|
|
712
709
|
|
|
713
710
|
def result(self, source_path=None):
|
|
714
711
|
# type: (Optional[str]) -> BuildResult
|
|
@@ -724,12 +721,16 @@ class BuildResult(object):
|
|
|
724
721
|
source_path=None, # type: Optional[str]
|
|
725
722
|
):
|
|
726
723
|
# type: (...) -> BuildResult
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
724
|
+
if build_request.fingerprint:
|
|
725
|
+
built_wheel = BuiltWheelDir.create(
|
|
726
|
+
sdist=source_path or build_request.source_path,
|
|
727
|
+
fingerprint=build_request.fingerprint,
|
|
728
|
+
target=build_request.target,
|
|
729
|
+
)
|
|
730
|
+
target_dir = built_wheel.dist_dir
|
|
731
|
+
else:
|
|
732
|
+
target_dir = os.path.join(safe_mkdtemp(), "build")
|
|
733
|
+
return cls(request=build_request, atomic_dir=AtomicDirectory(target_dir))
|
|
733
734
|
|
|
734
735
|
request = attr.ib() # type: BuildRequest
|
|
735
736
|
_atomic_dir = attr.ib() # type: AtomicDirectory
|
|
@@ -1290,14 +1291,7 @@ class BuildAndInstallRequest(object):
|
|
|
1290
1291
|
if is_wheel(dist_path):
|
|
1291
1292
|
to_install.add(InstallRequest.create(install_request.target, dist_path))
|
|
1292
1293
|
elif os.path.isdir(dist_path):
|
|
1293
|
-
to_build.add(
|
|
1294
|
-
BuildRequest.for_directory(
|
|
1295
|
-
install_request.target,
|
|
1296
|
-
dist_path,
|
|
1297
|
-
resolver=self._resolver,
|
|
1298
|
-
pip_version=self._pip_version,
|
|
1299
|
-
)
|
|
1300
|
-
)
|
|
1294
|
+
to_build.add(BuildRequest.for_directory(install_request.target, dist_path))
|
|
1301
1295
|
else:
|
|
1302
1296
|
to_build.add(BuildRequest.for_file(install_request.target, dist_path))
|
|
1303
1297
|
already_analyzed.add(metadata.project_name)
|
|
@@ -1907,11 +1901,21 @@ def download_requests(
|
|
|
1907
1901
|
def add_build_requests(requests):
|
|
1908
1902
|
# type: (Iterable[BuildRequest]) -> None
|
|
1909
1903
|
for request in requests:
|
|
1904
|
+
if request.fingerprint:
|
|
1905
|
+
fingerprint = request.fingerprint
|
|
1906
|
+
else:
|
|
1907
|
+
production_assert(os.path.isdir(request.source_path))
|
|
1908
|
+
fingerprint = _fingerprint_local_project(
|
|
1909
|
+
path=request.source_path,
|
|
1910
|
+
target=request.target,
|
|
1911
|
+
resolver=resolver,
|
|
1912
|
+
pip_version=pip_version,
|
|
1913
|
+
)
|
|
1910
1914
|
local_distributions.append(
|
|
1911
1915
|
LocalDistribution(
|
|
1912
1916
|
download_target=request.download_target,
|
|
1913
1917
|
path=request.source_path,
|
|
1914
|
-
fingerprint=
|
|
1918
|
+
fingerprint=fingerprint,
|
|
1915
1919
|
subdirectory=request.subdirectory,
|
|
1916
1920
|
)
|
|
1917
1921
|
)
|
pex/scie/__init__.py
CHANGED
|
@@ -222,6 +222,32 @@ def register_options(parser):
|
|
|
222
222
|
"to the patch level."
|
|
223
223
|
),
|
|
224
224
|
)
|
|
225
|
+
parser.add_argument(
|
|
226
|
+
"--scie-pbs-free-threaded",
|
|
227
|
+
"--no-scie-pbs-free-threaded",
|
|
228
|
+
dest="scie_pbs_free_threaded",
|
|
229
|
+
default=False,
|
|
230
|
+
type=bool,
|
|
231
|
+
action=HandleBoolAction,
|
|
232
|
+
help=(
|
|
233
|
+
"Should the Python Standalone Builds CPython distributions be free-threaded. If left "
|
|
234
|
+
"unspecified or otherwise turned off, creating a scie from a PEX with free-threaded "
|
|
235
|
+
"abi wheels will automatically turn this option on. Note that this option is not "
|
|
236
|
+
"compatible with `--scie-pbs-stripped`."
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
parser.add_argument(
|
|
240
|
+
"--scie-pbs-debug",
|
|
241
|
+
"--no-scie-pbs-debug",
|
|
242
|
+
dest="scie_pbs_debug",
|
|
243
|
+
default=False,
|
|
244
|
+
type=bool,
|
|
245
|
+
action=HandleBoolAction,
|
|
246
|
+
help=(
|
|
247
|
+
"Should the Python Standalone Builds CPython distributions be debug builds. Note that "
|
|
248
|
+
"this option is not compatible with `--scie-pbs-stripped`."
|
|
249
|
+
),
|
|
250
|
+
)
|
|
225
251
|
parser.add_argument(
|
|
226
252
|
"--scie-pbs-stripped",
|
|
227
253
|
"--no-scie-pbs-stripped",
|
|
@@ -232,7 +258,8 @@ def register_options(parser):
|
|
|
232
258
|
help=(
|
|
233
259
|
"Should the Python Standalone Builds CPython distributions used be stripped of debug "
|
|
234
260
|
"symbols or not. For Linux and Windows particularly, the stripped distributions are "
|
|
235
|
-
"less than half the size of the distributions that ship with debug symbols."
|
|
261
|
+
"less than half the size of the distributions that ship with debug symbols. Note that"
|
|
262
|
+
"this option is not compatible with `--scie-pbs-free-threaded` or `--scie-pbs-debug`."
|
|
236
263
|
),
|
|
237
264
|
)
|
|
238
265
|
parser.add_argument(
|
|
@@ -318,6 +345,10 @@ def render_options(options):
|
|
|
318
345
|
if options.python_version:
|
|
319
346
|
args.append("--scie-python-version")
|
|
320
347
|
args.append(".".join(map(str, options.python_version)))
|
|
348
|
+
if options.pbs_free_threaded:
|
|
349
|
+
args.append("--scie-pbs-free-threaded")
|
|
350
|
+
if options.pbs_debug:
|
|
351
|
+
args.append("--scie-pbs-debug")
|
|
321
352
|
if options.pbs_stripped:
|
|
322
353
|
args.append("--scie-pbs-stripped")
|
|
323
354
|
for hash_algorithm in options.hash_algorithms:
|
|
@@ -398,6 +429,12 @@ def extract_options(options):
|
|
|
398
429
|
)
|
|
399
430
|
)
|
|
400
431
|
|
|
432
|
+
if options.scie_pbs_stripped and (options.scie_pbs_free_threaded or options.scie_pbs_debug):
|
|
433
|
+
raise ValueError(
|
|
434
|
+
"Python Standalone Builds does not release stripped distributions for debug or "
|
|
435
|
+
"free-threaded builds."
|
|
436
|
+
)
|
|
437
|
+
|
|
401
438
|
science_binary = None # type: Optional[Union[File, Url]]
|
|
402
439
|
if options.scie_science_binary:
|
|
403
440
|
url_info = urlparse.urlparse(options.scie_science_binary)
|
|
@@ -420,6 +457,8 @@ def extract_options(options):
|
|
|
420
457
|
pbs_release=options.scie_pbs_release,
|
|
421
458
|
pypy_release=options.scie_pypy_release,
|
|
422
459
|
python_version=python_version,
|
|
460
|
+
pbs_free_threaded=options.scie_pbs_free_threaded,
|
|
461
|
+
pbs_debug=options.scie_pbs_debug,
|
|
423
462
|
pbs_stripped=options.scie_pbs_stripped,
|
|
424
463
|
hash_algorithms=tuple(options.scie_hash_algorithms),
|
|
425
464
|
science_binary=science_binary,
|