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.

Files changed (69) hide show
  1. pex/bin/pex.py +2 -1
  2. pex/build_backend/configuration.py +5 -5
  3. pex/build_backend/wrap.py +2 -19
  4. pex/cli/commands/lock.py +4 -2
  5. pex/cli/commands/run.py +10 -11
  6. pex/cli/pex.py +11 -4
  7. pex/dist_metadata.py +29 -2
  8. pex/docs/html/_pagefind/fragment/en_4250138.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/fragment/en_7125dad.pf_fragment +0 -0
  10. pex/docs/html/_pagefind/fragment/en_785d562.pf_fragment +0 -0
  11. pex/docs/html/_pagefind/fragment/en_8e94bb8.pf_fragment +0 -0
  12. pex/docs/html/_pagefind/fragment/{en_17782b6.pf_fragment → en_a0396bb.pf_fragment} +0 -0
  13. pex/docs/html/_pagefind/fragment/en_a8a3588.pf_fragment +0 -0
  14. pex/docs/html/_pagefind/fragment/en_c07d988.pf_fragment +0 -0
  15. pex/docs/html/_pagefind/fragment/en_d718411.pf_fragment +0 -0
  16. pex/docs/html/_pagefind/index/en_a2e3c5e.pf_index +0 -0
  17. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  18. pex/docs/html/_pagefind/pagefind.en_4ce1afa9e3.pf_meta +0 -0
  19. pex/docs/html/_static/documentation_options.js +1 -1
  20. pex/docs/html/api/vars.html +5 -5
  21. pex/docs/html/buildingpex.html +5 -5
  22. pex/docs/html/genindex.html +5 -5
  23. pex/docs/html/index.html +5 -5
  24. pex/docs/html/recipes.html +5 -5
  25. pex/docs/html/scie.html +5 -5
  26. pex/docs/html/search.html +5 -5
  27. pex/docs/html/whatispex.html +5 -5
  28. pex/hashing.py +71 -9
  29. pex/interpreter_constraints.py +1 -1
  30. pex/jobs.py +13 -6
  31. pex/pep_376.py +21 -6
  32. pex/pep_427.py +30 -8
  33. pex/pex_builder.py +1 -4
  34. pex/pip/local_project.py +6 -14
  35. pex/pip/tool.py +3 -3
  36. pex/pip/vcs.py +93 -44
  37. pex/pip/version.py +7 -0
  38. pex/resolve/configured_resolve.py +13 -5
  39. pex/resolve/lock_downloader.py +1 -0
  40. pex/resolve/locker.py +30 -14
  41. pex/resolve/lockfile/create.py +2 -7
  42. pex/resolve/pre_resolved_resolver.py +1 -7
  43. pex/resolve/project.py +233 -47
  44. pex/resolve/resolver_configuration.py +1 -1
  45. pex/resolve/resolver_options.py +14 -9
  46. pex/resolve/venv_resolver.py +221 -65
  47. pex/resolver.py +59 -55
  48. pex/scie/__init__.py +40 -1
  49. pex/scie/model.py +2 -0
  50. pex/scie/science.py +25 -3
  51. pex/sdist.py +219 -0
  52. pex/version.py +1 -1
  53. pex/wheel.py +16 -12
  54. {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/METADATA +4 -4
  55. {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/RECORD +60 -59
  56. {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/entry_points.txt +1 -0
  57. pex/docs/html/_pagefind/fragment/en_1048255.pf_fragment +0 -0
  58. pex/docs/html/_pagefind/fragment/en_3f7efc3.pf_fragment +0 -0
  59. pex/docs/html/_pagefind/fragment/en_40667cd.pf_fragment +0 -0
  60. pex/docs/html/_pagefind/fragment/en_55ee2f4.pf_fragment +0 -0
  61. pex/docs/html/_pagefind/fragment/en_d6d92dd.pf_fragment +0 -0
  62. pex/docs/html/_pagefind/fragment/en_d834316.pf_fragment +0 -0
  63. pex/docs/html/_pagefind/fragment/en_ec2ce54.pf_fragment +0 -0
  64. pex/docs/html/_pagefind/index/en_17effb2.pf_index +0 -0
  65. pex/docs/html/_pagefind/pagefind.en_49ec86cf86.pf_meta +0 -0
  66. {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/WHEEL +0 -0
  67. {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/licenses/LICENSE +0 -0
  68. {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/pylock/pylock.toml +0 -0
  69. {pex-2.64.1.dist-info → pex-2.69.0.dist-info}/top_level.txt +0 -0
@@ -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 DefaultDict, Deque, FrozenSet, Iterable, Iterator, List, Mapping, Set, Union
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
- installed_file
81
- for installed_file in Record.read(lines=iter(record_lines))
82
- if (
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
- interpreter, # type: PythonInterpreter
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: (...) -> FingerprintedDistribution
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 FingerprintedDistribution(
164
- distribution=Distribution.load(installed_wheel.prefix_dir),
165
- fingerprint=installed_wheel.fingerprint,
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
- return repacked_whl(
168
- installed_wheel, fingerprint=installed_wheel.fingerprint, use_system_time=use_system_time
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
- venv, # type: Virtualenv
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[FingerprintedDistribution]
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
- return iter_map_parallel(
182
- inputs=distributions,
247
+ for resolved_distribution in iter_map_parallel(
248
+ inputs=venv_distributions,
183
249
  function=functools.partial(
184
- _install_distribution, venv.interpreter, result_type, use_system_time
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
- if distribution.metadata.files.metadata.type is MetadataType.DIST_INFO:
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=[str(distribution.as_requirement())],
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
- def resolve_from_venv(
363
- targets, # type: Targets
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=RequirementConfiguration(), # type: RequirementConfiguration
366
- pip_configuration=PipConfiguration(), # type: PipConfiguration
367
- compile=False, # type: bool
368
- ignore_errors=False, # type: bool
369
- result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
370
- dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
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[ResolveResult, Error]
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
- venv_distributions.append(venv_distribution)
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
- ResolvedDistribution(
494
- target=target,
495
- fingerprinted_distribution=fingerprinted_distribution,
496
- direct_requirements=direct_requirements_by_project_name[
497
- fingerprinted_distribution.project_name
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
- return digest_local_project(
612
+ hasher = _hasher()
613
+ result = digest_local_project(
611
614
  directory=path,
612
- digest=_hasher(),
615
+ digest=hasher,
613
616
  target=target,
614
617
  resolver=build_system_resolver,
615
618
  pip_version=pip_version,
616
619
  )
617
-
618
-
619
- class BuildError(Exception):
620
- pass
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=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
- listing = os.listdir(extract_dir)
701
- if len(listing) != 1:
702
- raise BuildError(
703
- "Expected one top-level project directory to be extracted from {project}, "
704
- "found {count}: {listing}".format(
705
- project=self.source_path, count=len(listing), listing=", ".join(listing)
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
- project_directory = os.path.join(extract_dir, listing[0])
709
- if self.subdirectory:
710
- project_directory = os.path.join(project_directory, self.subdirectory)
711
- return project_directory
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
- built_wheel = BuiltWheelDir.create(
728
- sdist=source_path or build_request.source_path,
729
- fingerprint=build_request.fingerprint,
730
- target=build_request.target,
731
- )
732
- return cls(request=build_request, atomic_dir=AtomicDirectory(built_wheel.dist_dir))
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=request.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,