pex 2.54.2__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 (180) hide show
  1. pex/auth.py +1 -1
  2. pex/bin/pex.py +15 -2
  3. pex/build_backend/configuration.py +5 -5
  4. pex/build_backend/wrap.py +27 -23
  5. pex/build_system/pep_517.py +4 -1
  6. pex/cache/dirs.py +17 -12
  7. pex/cli/commands/lock.py +302 -165
  8. pex/cli/commands/pip/core.py +4 -12
  9. pex/cli/commands/pip/wheel.py +1 -1
  10. pex/cli/commands/run.py +13 -20
  11. pex/cli/commands/venv.py +85 -16
  12. pex/cli/pex.py +11 -4
  13. pex/common.py +57 -7
  14. pex/compatibility.py +1 -1
  15. pex/dependency_configuration.py +87 -15
  16. pex/dist_metadata.py +143 -25
  17. pex/docs/html/_pagefind/fragment/en_4250138.pf_fragment +0 -0
  18. pex/docs/html/_pagefind/fragment/en_7125dad.pf_fragment +0 -0
  19. pex/docs/html/_pagefind/fragment/en_785d562.pf_fragment +0 -0
  20. pex/docs/html/_pagefind/fragment/en_8e94bb8.pf_fragment +0 -0
  21. pex/docs/html/_pagefind/fragment/en_a0396bb.pf_fragment +0 -0
  22. pex/docs/html/_pagefind/fragment/en_a8a3588.pf_fragment +0 -0
  23. pex/docs/html/_pagefind/fragment/en_c07d988.pf_fragment +0 -0
  24. pex/docs/html/_pagefind/fragment/en_d718411.pf_fragment +0 -0
  25. pex/docs/html/_pagefind/index/en_a2e3c5e.pf_index +0 -0
  26. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  27. pex/docs/html/_pagefind/pagefind.en_4ce1afa9e3.pf_meta +0 -0
  28. pex/docs/html/_static/documentation_options.js +1 -1
  29. pex/docs/html/_static/pygments.css +164 -146
  30. pex/docs/html/_static/styles/furo.css +1 -1
  31. pex/docs/html/_static/styles/furo.css.map +1 -1
  32. pex/docs/html/api/vars.html +25 -34
  33. pex/docs/html/buildingpex.html +25 -34
  34. pex/docs/html/genindex.html +24 -33
  35. pex/docs/html/index.html +25 -34
  36. pex/docs/html/recipes.html +25 -34
  37. pex/docs/html/scie.html +25 -34
  38. pex/docs/html/search.html +24 -33
  39. pex/docs/html/whatispex.html +25 -34
  40. pex/entry_points_txt.py +98 -0
  41. pex/environment.py +54 -33
  42. pex/finders.py +1 -1
  43. pex/hashing.py +71 -9
  44. pex/installed_wheel.py +141 -0
  45. pex/interpreter.py +41 -38
  46. pex/interpreter_constraints.py +25 -25
  47. pex/interpreter_implementation.py +40 -0
  48. pex/jobs.py +13 -6
  49. pex/pep_376.py +68 -384
  50. pex/pep_425.py +11 -2
  51. pex/pep_427.py +937 -205
  52. pex/pep_508.py +4 -5
  53. pex/pex_builder.py +5 -8
  54. pex/pex_info.py +14 -9
  55. pex/pip/dependencies/__init__.py +85 -13
  56. pex/pip/dependencies/requires.py +38 -3
  57. pex/pip/foreign_platform/__init__.py +4 -3
  58. pex/pip/installation.py +2 -2
  59. pex/pip/local_project.py +6 -14
  60. pex/pip/package_repositories/__init__.py +78 -0
  61. pex/pip/package_repositories/link_collector.py +96 -0
  62. pex/pip/tool.py +139 -33
  63. pex/pip/vcs.py +109 -43
  64. pex/pip/version.py +8 -1
  65. pex/requirements.py +121 -16
  66. pex/resolve/config.py +5 -1
  67. pex/resolve/configured_resolve.py +32 -10
  68. pex/resolve/configured_resolver.py +10 -39
  69. pex/resolve/downloads.py +4 -3
  70. pex/resolve/lock_downloader.py +16 -23
  71. pex/resolve/lock_resolver.py +41 -51
  72. pex/resolve/locked_resolve.py +89 -32
  73. pex/resolve/locker.py +145 -101
  74. pex/resolve/locker_patches.py +123 -197
  75. pex/resolve/lockfile/create.py +232 -87
  76. pex/resolve/lockfile/download_manager.py +5 -1
  77. pex/resolve/lockfile/json_codec.py +103 -28
  78. pex/resolve/lockfile/model.py +13 -35
  79. pex/resolve/lockfile/pep_751.py +117 -98
  80. pex/resolve/lockfile/requires_dist.py +17 -262
  81. pex/resolve/lockfile/subset.py +11 -0
  82. pex/resolve/lockfile/targets.py +445 -0
  83. pex/resolve/lockfile/updater.py +22 -10
  84. pex/resolve/package_repository.py +406 -0
  85. pex/resolve/pex_repository_resolver.py +1 -1
  86. pex/resolve/pre_resolved_resolver.py +19 -16
  87. pex/resolve/project.py +233 -47
  88. pex/resolve/requirement_configuration.py +28 -10
  89. pex/resolve/resolver_configuration.py +18 -32
  90. pex/resolve/resolver_options.py +234 -28
  91. pex/resolve/resolvers.py +3 -12
  92. pex/resolve/target_options.py +18 -2
  93. pex/resolve/target_system.py +908 -0
  94. pex/resolve/venv_resolver.py +670 -0
  95. pex/resolver.py +673 -209
  96. pex/scie/__init__.py +40 -1
  97. pex/scie/model.py +2 -0
  98. pex/scie/science.py +25 -3
  99. pex/sdist.py +219 -0
  100. pex/sh_boot.py +24 -21
  101. pex/sysconfig.py +5 -3
  102. pex/targets.py +31 -10
  103. pex/third_party/__init__.py +1 -1
  104. pex/tools/commands/repository.py +48 -25
  105. pex/vendor/__init__.py +4 -9
  106. pex/vendor/__main__.py +65 -41
  107. pex/vendor/_vendored/ansicolors/.layout.json +1 -1
  108. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/RECORD +11 -0
  109. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.pex-info/original-whl-info.json +1 -0
  110. pex/vendor/_vendored/appdirs/.layout.json +1 -1
  111. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/RECORD +7 -0
  112. pex/vendor/_vendored/appdirs/appdirs-1.4.4.pex-info/original-whl-info.json +1 -0
  113. pex/vendor/_vendored/attrs/.layout.json +1 -1
  114. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/RECORD +37 -0
  115. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.pex-info/original-whl-info.json +1 -0
  116. pex/vendor/_vendored/packaging_20_9/.layout.json +1 -1
  117. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/RECORD +20 -0
  118. pex/vendor/_vendored/packaging_20_9/packaging-20.9.pex-info/original-whl-info.json +1 -0
  119. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/RECORD +7 -0
  120. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.pex-info/original-whl-info.json +1 -0
  121. pex/vendor/_vendored/packaging_21_3/.layout.json +1 -1
  122. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/RECORD +20 -0
  123. pex/vendor/_vendored/packaging_21_3/packaging-21.3.pex-info/original-whl-info.json +1 -0
  124. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/RECORD +18 -0
  125. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.pex-info/original-whl-info.json +1 -0
  126. pex/vendor/_vendored/packaging_24_0/.layout.json +1 -1
  127. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/RECORD +22 -0
  128. pex/vendor/_vendored/packaging_24_0/packaging-24.0.pex-info/original-whl-info.json +1 -0
  129. pex/vendor/_vendored/packaging_25_0/.layout.json +1 -1
  130. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/RECORD +24 -0
  131. pex/vendor/_vendored/packaging_25_0/packaging-25.0.pex-info/original-whl-info.json +1 -0
  132. pex/vendor/_vendored/pip/.layout.json +1 -1
  133. pex/vendor/_vendored/pip/pip/_vendor/certifi/cacert.pem +63 -1
  134. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/RECORD +388 -0
  135. pex/vendor/_vendored/pip/pip-20.3.4.pex-info/original-whl-info.json +1 -0
  136. pex/vendor/_vendored/setuptools/.layout.json +1 -1
  137. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/RECORD +107 -0
  138. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.pex-info/original-whl-info.json +1 -0
  139. pex/vendor/_vendored/toml/.layout.json +1 -1
  140. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/RECORD +11 -0
  141. pex/vendor/_vendored/toml/toml-0.10.2.pex-info/original-whl-info.json +1 -0
  142. pex/vendor/_vendored/tomli/.layout.json +1 -1
  143. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/RECORD +10 -0
  144. pex/vendor/_vendored/tomli/tomli-2.0.1.pex-info/original-whl-info.json +1 -0
  145. pex/venv/installer.py +46 -19
  146. pex/venv/venv_pex.py +6 -3
  147. pex/version.py +1 -1
  148. pex/wheel.py +188 -40
  149. pex/whl.py +67 -0
  150. pex/windows/__init__.py +14 -11
  151. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/METADATA +6 -5
  152. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/RECORD +157 -133
  153. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/entry_points.txt +1 -0
  154. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/pylock/pylock.toml +1 -1
  155. pex/docs/html/_pagefind/fragment/en_42c9d8c.pf_fragment +0 -0
  156. pex/docs/html/_pagefind/fragment/en_45dd5a2.pf_fragment +0 -0
  157. pex/docs/html/_pagefind/fragment/en_4ca74d2.pf_fragment +0 -0
  158. pex/docs/html/_pagefind/fragment/en_77273d5.pf_fragment +0 -0
  159. pex/docs/html/_pagefind/fragment/en_87a59c5.pf_fragment +0 -0
  160. pex/docs/html/_pagefind/fragment/en_8dc89b5.pf_fragment +0 -0
  161. pex/docs/html/_pagefind/fragment/en_9d1319b.pf_fragment +0 -0
  162. pex/docs/html/_pagefind/fragment/en_e55df9d.pf_fragment +0 -0
  163. pex/docs/html/_pagefind/index/en_1e98c6f.pf_index +0 -0
  164. pex/docs/html/_pagefind/pagefind.en_d1c488ecae.pf_meta +0 -0
  165. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/INSTALLER +0 -1
  166. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/INSTALLER +0 -1
  167. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/INSTALLER +0 -1
  168. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/INSTALLER +0 -1
  169. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/INSTALLER +0 -1
  170. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/INSTALLER +0 -1
  171. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/INSTALLER +0 -1
  172. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/INSTALLER +0 -1
  173. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/INSTALLER +0 -1
  174. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/INSTALLER +0 -1
  175. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/INSTALLER +0 -1
  176. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/INSTALLER +0 -1
  177. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/INSTALLER +0 -1
  178. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/WHEEL +0 -0
  179. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/licenses/LICENSE +0 -0
  180. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/top_level.txt +0 -0
@@ -133,11 +133,9 @@ def download_distributions(configuration):
133
133
  requirements=requirement_configuration.requirements,
134
134
  requirement_files=requirement_configuration.requirement_files,
135
135
  constraint_files=requirement_configuration.constraint_files,
136
- indexes=resolver_configuration.repos_configuration.indexes,
137
- find_links=resolver_configuration.repos_configuration.find_links,
136
+ repos_configuration=resolver_configuration.repos_configuration,
138
137
  resolver_version=pip_configuration.resolver_version,
139
138
  network_configuration=resolver_configuration.network_configuration,
140
- password_entries=pip_configuration.repos_configuration.password_entries,
141
139
  build_configuration=pip_configuration.build_configuration,
142
140
  transitive=pip_configuration.transitive,
143
141
  max_parallel_jobs=pip_configuration.max_jobs,
@@ -162,11 +160,9 @@ def download_distributions(configuration):
162
160
  extras=resolver_configuration.extras,
163
161
  dependency_groups=resolver_configuration.dependency_groups,
164
162
  constraint_files=requirement_configuration.constraint_files,
165
- indexes=resolver_configuration.repos_configuration.indexes,
166
- find_links=resolver_configuration.repos_configuration.find_links,
163
+ repos_configuration=resolver_configuration.repos_configuration,
167
164
  resolver_version=pip_configuration.resolver_version,
168
165
  network_configuration=resolver_configuration.network_configuration,
169
- password_entries=pip_configuration.repos_configuration.password_entries,
170
166
  build_configuration=pip_configuration.build_configuration,
171
167
  transitive=pip_configuration.transitive,
172
168
  max_parallel_jobs=pip_configuration.max_jobs,
@@ -188,11 +184,9 @@ def download_distributions(configuration):
188
184
  constraint_files=requirement_configuration.constraint_files,
189
185
  allow_prereleases=pip_configuration.allow_prereleases,
190
186
  transitive=pip_configuration.transitive,
191
- indexes=resolver_configuration.repos_configuration.indexes,
192
- find_links=resolver_configuration.repos_configuration.find_links,
187
+ repos_configuration=resolver_configuration.repos_configuration,
193
188
  resolver_version=pip_configuration.resolver_version,
194
189
  network_configuration=resolver_configuration.network_configuration,
195
- password_entries=pip_configuration.repos_configuration.password_entries,
196
190
  build_configuration=pip_configuration.build_configuration,
197
191
  max_parallel_jobs=pip_configuration.max_jobs,
198
192
  pip_log=pip_configuration.log,
@@ -229,10 +223,8 @@ def build_wheels(
229
223
  package_index_configuration=PackageIndexConfiguration.create(
230
224
  pip_version=configuration.pip_configuration.version,
231
225
  resolver_version=configuration.pip_configuration.resolver_version,
232
- indexes=configuration.pip_configuration.repos_configuration.indexes,
233
- find_links=configuration.pip_configuration.repos_configuration.find_links,
226
+ repos_configuration=configuration.pip_configuration.repos_configuration,
234
227
  network_configuration=configuration.pip_configuration.network_configuration,
235
- password_entries=configuration.pip_configuration.repos_configuration.password_entries,
236
228
  use_pip_config=configuration.pip_configuration.use_pip_config,
237
229
  extra_pip_requirements=configuration.pip_configuration.extra_requirements,
238
230
  keyring_provider=configuration.pip_configuration.keyring_provider,
@@ -55,7 +55,7 @@ class Wheel(BuildTimeCommand):
55
55
  core.build_wheels(
56
56
  configuration,
57
57
  tuple(
58
- BuildRequest.create(
58
+ BuildRequest.for_file(
59
59
  target=target,
60
60
  source_path=sdist.path,
61
61
  subdirectory=sdist.subdirectory,
pex/cli/commands/run.py CHANGED
@@ -10,12 +10,11 @@ import os.path
10
10
  import posixpath
11
11
  import shutil
12
12
  import sys
13
- import tarfile
14
13
  from argparse import _ActionsContainer
15
- from contextlib import closing
16
14
 
17
15
  from pex import dependency_configuration, interpreter
18
16
  from pex import resolver as pip_resolver
17
+ from pex import sdist
19
18
  from pex.artifact_url import ArtifactURL, Fingerprint
20
19
  from pex.atomic_directory import atomic_directory
21
20
  from pex.build_system import pep_517
@@ -616,11 +615,9 @@ class Run(CacheAwareMixin, BuildTimeCommand):
616
615
  constraint_files=run_spec.all_requirements.constraint_files,
617
616
  allow_prereleases=pip_configuration.allow_prereleases,
618
617
  transitive=False,
619
- indexes=pip_configuration.repos_configuration.indexes,
620
- find_links=pip_configuration.repos_configuration.find_links,
618
+ repos_configuration=pip_configuration.repos_configuration,
621
619
  resolver_version=pip_configuration.resolver_version,
622
620
  network_configuration=pip_configuration.network_configuration,
623
- password_entries=pip_configuration.repos_configuration.password_entries,
624
621
  build_configuration=pip_configuration.build_configuration,
625
622
  max_parallel_jobs=pip_configuration.max_jobs,
626
623
  pip_log=pip_configuration.log,
@@ -678,19 +675,19 @@ class Run(CacheAwareMixin, BuildTimeCommand):
678
675
  return package, os.path.join(lock_dest_dir, lock_path)
679
676
  elif is_sdist(distribution.path):
680
677
  if is_tar_sdist(distribution.path):
681
- with closing(tarfile.open(distribution.path)) as tf:
682
- tf.extractall(lock_dest_dir)
678
+ sdist_root = sdist.extract_tarball(distribution.path, dest_dir=lock_dest_dir)
683
679
  else:
684
680
  with open_zip(distribution.path) as zf:
685
681
  zf.extractall(lock_dest_dir)
686
- entries = glob.glob(os.path.join(lock_dest_dir, "*"))
687
- if len(entries) != 1:
688
- return Error(
689
- "Expected {sdist} to have 1 entry, found {count}: {entries}".format(
690
- sdist=distribution.path, count=len(entries), entries=entries
682
+ entries = glob.glob(os.path.join(lock_dest_dir, "*"))
683
+ if len(entries) != 1:
684
+ return Error(
685
+ "Expected {sdist} to have 1 entry, found {count}: {entries}".format(
686
+ sdist=distribution.path, count=len(entries), entries=entries
687
+ )
691
688
  )
692
- )
693
- sdist_root = entries[0]
689
+ sdist_root = entries[0]
690
+
694
691
  for lock_name in run_spec.locks:
695
692
  lock_path = os.path.join(sdist_root, lock_name)
696
693
  if os.path.exists(lock_path):
@@ -724,11 +721,9 @@ class Run(CacheAwareMixin, BuildTimeCommand):
724
721
  requirements=run_spec.all_requirements.requirements,
725
722
  requirement_files=run_spec.all_requirements.requirement_files,
726
723
  constraint_files=run_spec.all_requirements.constraint_files,
727
- indexes=pip_configuration.repos_configuration.indexes,
728
- find_links=pip_configuration.repos_configuration.find_links,
724
+ repos_configuration=pip_configuration.repos_configuration,
729
725
  resolver_version=pip_configuration.resolver_version,
730
726
  network_configuration=pip_configuration.network_configuration,
731
- password_entries=pip_configuration.repos_configuration.password_entries,
732
727
  build_configuration=pip_configuration.build_configuration,
733
728
  transitive=pip_configuration.transitive,
734
729
  max_parallel_jobs=pip_configuration.max_jobs,
@@ -746,11 +741,9 @@ class Run(CacheAwareMixin, BuildTimeCommand):
746
741
  constraint_files=run_spec.all_requirements.constraint_files,
747
742
  allow_prereleases=pip_configuration.allow_prereleases,
748
743
  transitive=pip_configuration.transitive,
749
- indexes=pip_configuration.repos_configuration.indexes,
750
- find_links=pip_configuration.repos_configuration.find_links,
744
+ repos_configuration=pip_configuration.repos_configuration,
751
745
  resolver_version=pip_configuration.resolver_version,
752
746
  network_configuration=pip_configuration.network_configuration,
753
- password_entries=pip_configuration.repos_configuration.password_entries,
754
747
  build_configuration=pip_configuration.build_configuration,
755
748
  max_parallel_jobs=pip_configuration.max_jobs,
756
749
  pip_log=pip_configuration.log,
pex/cli/commands/venv.py CHANGED
@@ -8,14 +8,17 @@ import logging
8
8
  import os.path
9
9
  from argparse import ArgumentParser, _ActionsContainer
10
10
 
11
- from pex import pex_warnings
11
+ from pex import dependency_configuration, pex_warnings
12
12
  from pex.cli.command import BuildTimeCommand
13
13
  from pex.commands.command import JsonMixin, OutputMixin
14
14
  from pex.common import CopyMode, open_zip, pluralize
15
- from pex.dist_metadata import Distribution
15
+ from pex.dependency_configuration import DependencyConfiguration
16
+ from pex.dist_metadata import Distribution, Requirement
16
17
  from pex.enum import Enum
17
18
  from pex.executables import is_python_script, is_script
18
19
  from pex.executor import Executor
20
+ from pex.fingerprinted_distribution import FingerprintedDistribution
21
+ from pex.orderedset import OrderedSet
19
22
  from pex.pex import PEX
20
23
  from pex.pex_info import PexInfo
21
24
  from pex.resolve import configured_resolve, requirement_options, resolver_options, target_options
@@ -23,6 +26,7 @@ from pex.resolve.resolver_configuration import (
23
26
  LockRepositoryConfiguration,
24
27
  PexRepositoryConfiguration,
25
28
  PipConfiguration,
29
+ PylockRepositoryConfiguration,
26
30
  )
27
31
  from pex.result import Error, Ok, Result, try_
28
32
  from pex.targets import LocalInterpreter, Target, Targets
@@ -35,7 +39,7 @@ from pex.venv.installer_configuration import InstallerConfiguration
35
39
  from pex.venv.virtualenv import Virtualenv
36
40
 
37
41
  if TYPE_CHECKING:
38
- from typing import Any, Dict, Iterable, Optional
42
+ from typing import Any, Dict, Iterable, Optional, Sequence
39
43
 
40
44
 
41
45
  logger = logging.getLogger(__name__)
@@ -123,8 +127,10 @@ class Venv(OutputMixin, JsonMixin, BuildTimeCommand):
123
127
  include_pex_lock=True,
124
128
  include_pylock=True,
125
129
  include_pre_resolved=True,
130
+ include_venv_repository=True,
126
131
  )
127
132
  requirement_options.register(parser)
133
+ dependency_configuration.register(parser)
128
134
 
129
135
  @classmethod
130
136
  def add_extra_arguments(
@@ -285,11 +291,13 @@ class Venv(OutputMixin, JsonMixin, BuildTimeCommand):
285
291
  )
286
292
 
287
293
  requirement_configuration = requirement_options.configure(self.options)
294
+ dependency_config = dependency_configuration.configure(self.options)
288
295
  with TRACER.timed("Resolving distributions"):
289
296
  resolved = configured_resolve.resolve(
290
297
  targets=targets,
291
298
  requirement_configuration=requirement_configuration,
292
299
  resolver_configuration=resolver_configuration,
300
+ dependency_configuration=dependency_config,
293
301
  )
294
302
 
295
303
  pex = None # type: Optional[PEX]
@@ -298,6 +306,8 @@ class Venv(OutputMixin, JsonMixin, BuildTimeCommand):
298
306
  pex = PEX(resolver_configuration.pex_repository, interpreter=target.get_interpreter())
299
307
  elif isinstance(resolver_configuration, LockRepositoryConfiguration):
300
308
  lock = resolver_configuration.lock_file_path
309
+ elif isinstance(resolver_configuration, PylockRepositoryConfiguration):
310
+ lock = resolver_configuration.lock_file_path
301
311
 
302
312
  with TRACER.timed(
303
313
  "Installing {count} {wheels} in {subject} at {dest_dir}".format(
@@ -308,10 +318,6 @@ class Venv(OutputMixin, JsonMixin, BuildTimeCommand):
308
318
  )
309
319
  ):
310
320
  hermetic_scripts = not update and installer_configuration.hermetic_scripts
311
- distributions = tuple(
312
- resolved_distribution.distribution
313
- for resolved_distribution in resolved.distributions
314
- )
315
321
  provenance = (
316
322
  Provenance.create(venv=venv)
317
323
  if venv
@@ -322,27 +328,41 @@ class Venv(OutputMixin, JsonMixin, BuildTimeCommand):
322
328
  pex=pex,
323
329
  installer_configuration=installer_configuration,
324
330
  provenance=provenance,
325
- distributions=distributions,
331
+ distributions=tuple(
332
+ resolved_distribution.distribution
333
+ for resolved_distribution in resolved.distributions
334
+ ),
326
335
  dest_dir=dest_dir,
327
336
  hermetic_scripts=hermetic_scripts,
328
337
  venv=venv,
329
338
  )
330
339
  elif venv:
331
- installer.populate_venv_distributions(
340
+ _install_from_resolve(
332
341
  venv=venv,
333
- distributions=distributions,
334
342
  provenance=provenance,
335
- copy_mode=(
336
- CopyMode.COPY
337
- if installer_configuration.site_packages_copies
338
- else CopyMode.LINK
343
+ installer_configuration=installer_configuration,
344
+ dependency_config=dependency_config,
345
+ requirements=tuple(
346
+ OrderedSet(
347
+ itertools.chain.from_iterable(
348
+ resolved_distribution.direct_requirements
349
+ for resolved_distribution in resolved.distributions
350
+ )
351
+ )
352
+ ),
353
+ distributions=tuple(
354
+ resolved_distribution.fingerprinted_distribution
355
+ for resolved_distribution in resolved.distributions
339
356
  ),
340
357
  hermetic_scripts=hermetic_scripts,
341
358
  )
342
359
  else:
343
360
  installer.populate_flat_distributions(
344
361
  dest_dir=dest_dir,
345
- distributions=distributions,
362
+ distributions=tuple(
363
+ resolved_distribution.distribution
364
+ for resolved_distribution in resolved.distributions
365
+ ),
346
366
  provenance=provenance,
347
367
  copy_mode=(
348
368
  CopyMode.COPY
@@ -366,7 +386,10 @@ class Venv(OutputMixin, JsonMixin, BuildTimeCommand):
366
386
  try_(
367
387
  installer.ensure_pip_installed(
368
388
  venv,
369
- distributions=distributions,
389
+ distributions=tuple(
390
+ resolved_distribution.distribution
391
+ for resolved_distribution in resolved.distributions
392
+ ),
370
393
  scope=installer_configuration.scope,
371
394
  collisions_ok=installer_configuration.collisions_ok,
372
395
  source=source,
@@ -439,3 +462,49 @@ def _install_from_pex(
439
462
  )
440
463
  else:
441
464
  installer.populate_flat_sources(dst=dest_dir, pex=pex, provenance=provenance)
465
+
466
+
467
+ def _install_from_resolve(
468
+ venv, # type: Virtualenv
469
+ provenance, # type: Provenance
470
+ installer_configuration, # type: InstallerConfiguration
471
+ dependency_config, # type: DependencyConfiguration
472
+ requirements, # type: Sequence[Requirement]
473
+ distributions, # type: Sequence[FingerprintedDistribution]
474
+ hermetic_scripts, # type: bool
475
+ ):
476
+ # type: (...) -> None
477
+
478
+ activated_dists = tuple(dist.distribution for dist in distributions)
479
+ installer.populate_venv_distributions(
480
+ venv=venv,
481
+ distributions=activated_dists,
482
+ provenance=provenance,
483
+ copy_mode=(
484
+ CopyMode.COPY if installer_configuration.site_packages_copies else CopyMode.LINK
485
+ ),
486
+ hermetic_scripts=hermetic_scripts,
487
+ )
488
+
489
+ pex_info = PexInfo.default()
490
+ pex_info.includes_tools = False
491
+ pex_info.venv = True
492
+ pex_info.venv_copies = installer_configuration.copies
493
+ pex_info.venv_site_packages_copies = installer_configuration.site_packages_copies
494
+ pex_info.venv_system_site_packages = installer_configuration.system_site_packages
495
+ pex_info.venv_hermetic_scripts = installer_configuration.hermetic_scripts
496
+ dependency_config.configure(pex_info)
497
+ for requirement in requirements:
498
+ pex_info.add_requirement(requirement)
499
+ for distribution in distributions:
500
+ pex_info.add_distribution(os.path.basename(distribution.location), distribution.fingerprint)
501
+
502
+ installer.install_pex_main(
503
+ target_dir=provenance.target_dir,
504
+ venv=venv,
505
+ pex_info=pex_info,
506
+ activated_dists=activated_dists,
507
+ shebang=provenance.calculate_shebang(hermetic_scripts=hermetic_scripts),
508
+ venv_python=provenance.target_python,
509
+ bin_path=installer_configuration.bin_path,
510
+ )
pex/cli/pex.py CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  from __future__ import absolute_import
5
5
 
6
+ import sys
6
7
  from argparse import ArgumentError, ArgumentTypeError
7
8
 
8
9
  from pex.cli import commands
@@ -12,7 +13,7 @@ from pex.result import catch
12
13
  from pex.typing import TYPE_CHECKING
13
14
 
14
15
  if TYPE_CHECKING:
15
- from typing import Union
16
+ from typing import Optional, Union
16
17
 
17
18
 
18
19
  class Pex3(Main[BuildTimeCommand]):
@@ -23,14 +24,20 @@ class Pex3(Main[BuildTimeCommand]):
23
24
  """
24
25
 
25
26
 
26
- def main():
27
- # type: () -> Union[int, str]
27
+ def main(subcommand=None):
28
+ # type: (Optional[str]) -> Union[int, str]
28
29
 
29
30
  pex3 = Pex3(command_types=commands.all_commands())
30
31
  try:
31
- with pex3.parsed_command() as command:
32
+ args = [subcommand] + sys.argv[1:] if subcommand else None
33
+ with pex3.parsed_command(args=args) as command:
32
34
  result = catch(command.run)
33
35
  result.maybe_display()
34
36
  return result.exit_code
35
37
  except (ArgumentError, ArgumentTypeError, GlobalConfigurationError) as e:
36
38
  return str(e)
39
+
40
+
41
+ def run():
42
+ # type: () -> Union[int, str]
43
+ return main("run")
pex/common.py CHANGED
@@ -204,6 +204,56 @@ _MKDTEMP_SINGLETON = MktempTeardownRegistry()
204
204
  setattr(zipfile, "ZIP64_LIMIT", (1 << 32) - 1)
205
205
 
206
206
 
207
+ class _ZipFileTypeValue(Enum.Value):
208
+ def __init__(
209
+ self,
210
+ value, # type: str
211
+ deterministic_mode, # type: int
212
+ ):
213
+ # type: (...) -> None
214
+ super(_ZipFileTypeValue, self).__init__(value)
215
+ self.deterministic_mode = deterministic_mode
216
+
217
+ @property
218
+ def deterministic_external_attr(self):
219
+ # type: () -> int
220
+ return self.deterministic_mode << 16
221
+
222
+
223
+ class ZipFileType(Enum["ZipFileType.Value"]):
224
+ @classmethod
225
+ def from_zip_info(cls, zip_info):
226
+ # type: (ZipInfo) -> ZipFileType.Value
227
+
228
+ if zip_info.filename.endswith("/"):
229
+ return ZipFileType.DIRECTORY
230
+
231
+ mode = zip_info.external_attr >> 16
232
+ if stat.S_IXUSR & mode:
233
+ return ZipFileType.EXECUTABLE
234
+
235
+ return ZipFileType.FILE
236
+
237
+ @classmethod
238
+ def from_path(cls, path):
239
+ # type: (Text) -> ZipFileType.Value
240
+ if os.path.isdir(path):
241
+ return ZipFileType.DIRECTORY
242
+ elif is_exe(path):
243
+ return ZipFileType.EXECUTABLE
244
+ return ZipFileType.FILE
245
+
246
+ class Value(_ZipFileTypeValue):
247
+ pass
248
+
249
+ DIRECTORY = Value("directory", 0o755)
250
+ EXECUTABLE = Value("executable", 0o755)
251
+ FILE = Value("file", 0o644)
252
+
253
+
254
+ ZipFileType.seal()
255
+
256
+
207
257
  class ZipFileEx(ZipFile):
208
258
  """A ZipFile that works around several issues in the stdlib.
209
259
 
@@ -218,8 +268,8 @@ class ZipFileEx(ZipFile):
218
268
  @classmethod
219
269
  def zip_info_from_file(
220
270
  cls,
221
- filename, # type: str
222
- arcname=None, # type: Optional[str]
271
+ filename, # type: Text
272
+ arcname=None, # type: Optional[Text]
223
273
  date_time=None, # type: Optional[time.struct_time]
224
274
  file_mode=None, # type: Optional[int]
225
275
  compress=True, # type: bool
@@ -259,8 +309,8 @@ class ZipFileEx(ZipFile):
259
309
 
260
310
  def write_deterministic(
261
311
  self,
262
- filename, # type: str
263
- arcname=None, # type: Optional[str]
312
+ filename, # type: Text
313
+ arcname=None, # type: Optional[Text]
264
314
  digest=None, # type: Optional[Digest]
265
315
  deterministic=True, # type: bool
266
316
  compress=True, # type: bool
@@ -270,7 +320,7 @@ class ZipFileEx(ZipFile):
270
320
  filename,
271
321
  arcname=arcname,
272
322
  date_time=DETERMINISTIC_DATETIME.timetuple(),
273
- file_mode=0o755 if (is_exe(filename) or os.path.isdir(filename)) else 0o644,
323
+ file_mode=ZipFileType.from_path(filename).deterministic_mode,
274
324
  digest=digest,
275
325
  compress=compress,
276
326
  )
@@ -278,8 +328,8 @@ class ZipFileEx(ZipFile):
278
328
 
279
329
  def write_ex(
280
330
  self,
281
- filename, # type: str
282
- arcname=None, # type: Optional[str]
331
+ filename, # type: Text
332
+ arcname=None, # type: Optional[Text]
283
333
  date_time=None, # type: Optional[time.struct_time]
284
334
  file_mode=None, # type: Optional[int]
285
335
  digest=None, # type: Optional[Digest]
pex/compatibility.py CHANGED
@@ -62,7 +62,7 @@ if PY2:
62
62
  # type: (AnyStr, Text) -> bytes
63
63
  if isinstance(st, unicode):
64
64
  return st.encode(encoding)
65
- elif isinstance(st, bytes):
65
+ elif isinstance(st, (str, bytes)):
66
66
  return st
67
67
  else:
68
68
  raise ValueError("Cannot convert %s to bytes" % type(st))
@@ -4,10 +4,11 @@
4
4
  from __future__ import absolute_import
5
5
 
6
6
  import itertools
7
+ import re
7
8
  from argparse import Namespace, _ActionsContainer
8
9
  from collections import defaultdict
9
10
 
10
- from pex.dist_metadata import Distribution, Requirement
11
+ from pex.dist_metadata import Distribution, Requirement, RequirementParseError
11
12
  from pex.orderedset import OrderedSet
12
13
  from pex.pep_503 import ProjectName
13
14
  from pex.pex_info import PexInfo
@@ -22,13 +23,69 @@ else:
22
23
  from pex.third_party import attr
23
24
 
24
25
 
26
+ @attr.s(frozen=True)
27
+ class Override(object):
28
+ class InvalidError(ValueError):
29
+ pass
30
+
31
+ @classmethod
32
+ def parse(cls, override):
33
+ # type: (str) -> Override
34
+
35
+ project_name = None # type: Optional[ProjectName]
36
+ raw_requirement = override
37
+
38
+ # See: https://peps.python.org/pep-0508/#names
39
+ project_name_re = r"[A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9]"
40
+ match = re.match(
41
+ r"^(?P<project>{project_name_re})\s*=\s*(?P<requirement>{project_name_re}.*)$".format(
42
+ project_name_re=project_name_re
43
+ ),
44
+ override,
45
+ re.IGNORECASE,
46
+ )
47
+ if match:
48
+ raw_project_name = match.group("project")
49
+ raw_requirement = match.group("requirement")
50
+ try:
51
+ project_name = ProjectName(raw_project_name)
52
+ except ProjectName.InvalidError as e:
53
+ raise cls.InvalidError(
54
+ "Invalid project name {raw_project_name!r} in override {override!r}: "
55
+ "{err}".format(raw_project_name=raw_project_name, override=override, err=e)
56
+ )
57
+ try:
58
+ requirement = Requirement.parse(raw_requirement)
59
+ except RequirementParseError as e:
60
+ raise cls.InvalidError(
61
+ "Invalid override requirement {raw_requirement!r}: {err}".format(
62
+ raw_requirement=raw_requirement, err=e
63
+ )
64
+ )
65
+
66
+ return cls(project_name=project_name or requirement.project_name, requirement=requirement)
67
+
68
+ project_name = attr.ib() # type: ProjectName
69
+ requirement = attr.ib() # type: Requirement
70
+
71
+ def __str__(self):
72
+ # type: () -> str
73
+
74
+ if self.requirement.project_name == self.project_name:
75
+ return str(self.requirement)
76
+
77
+ return "{project_name}={requirement}".format(
78
+ project_name=self.project_name, requirement=self.requirement
79
+ )
80
+
81
+
25
82
  @attr.s(frozen=True)
26
83
  class DependencyConfiguration(object):
27
84
  @classmethod
28
85
  def create(
29
86
  cls,
30
87
  excluded=(), # type: Iterable[Union[str, Requirement]]
31
- overridden=(), # type: Iterable[Union[str, Requirement]]
88
+ overridden=(), # type: Iterable[Union[str, Override]]
32
89
  ):
33
90
  # type: (...) -> DependencyConfiguration
34
91
 
@@ -36,8 +93,8 @@ class DependencyConfiguration(object):
36
93
  OrderedSet
37
94
  ) # type: DefaultDict[ProjectName, OrderedSet[Requirement]]
38
95
  for o in overridden:
39
- override = o if isinstance(o, Requirement) else Requirement.parse(o)
40
- overridden_projects[override.project_name].add(override)
96
+ override = o if isinstance(o, Override) else Override.parse(o)
97
+ overridden_projects[override.project_name].add(override.requirement)
41
98
 
42
99
  return cls(
43
100
  excluded=tuple(
@@ -64,25 +121,32 @@ class DependencyConfiguration(object):
64
121
  # type: (PexInfo) -> None
65
122
  for excluded in self.excluded:
66
123
  pex_info.add_exclude(excluded)
67
- for override in sorted(
68
- itertools.chain.from_iterable(self.overridden.values()), key=lambda req: req.key
69
- ):
70
- pex_info.add_override(override)
124
+ for project_name, overrides in self.overridden.items():
125
+ for override in sorted(overrides, key=lambda req: req.key):
126
+ pex_info.add_override(Override(project_name=project_name, requirement=override))
71
127
 
72
128
  def excluded_by(self, item):
73
129
  # type: (Union[Distribution, Requirement]) -> Tuple[Requirement, ...]
74
130
  return tuple(req for req in self.excluded if item in req)
75
131
 
76
132
  def all_overrides(self):
77
- # type: () -> Tuple[Requirement, ...]
78
- return tuple(itertools.chain.from_iterable(self.overridden.values()))
133
+ # type: () -> Tuple[Override, ...]
134
+ return tuple(
135
+ Override(project_name=project_name, requirement=requirement)
136
+ for project_name, requirements in self.overridden.items()
137
+ for requirement in requirements
138
+ )
79
139
 
80
140
  def overrides_for(self, requirement):
81
141
  # type: (Requirement) -> Tuple[Requirement, ...]
82
142
  return self.overridden.get(requirement.project_name, ())
83
143
 
84
- def overridden_by(self, requirement, target):
85
- # type: (Requirement, Target) -> Optional[Requirement]
144
+ def overridden_by(
145
+ self,
146
+ requirement, # type: Requirement
147
+ target, # type: Target
148
+ ):
149
+ # type: (...) -> Optional[Requirement]
86
150
  overrides = self.overrides_for(requirement)
87
151
  applicable_overrides = [
88
152
  override for override in overrides if target.requirement_applies(override)
@@ -152,9 +216,17 @@ def register(parser):
152
216
  type=str,
153
217
  action="append",
154
218
  help=(
155
- "Specifies a transitive requirement to override when resolving. Any distribution "
156
- "requirement in the PEX's resolve that matches the override project name is replaced "
157
- "with the given override requirement. This option can be used multiple times."
219
+ "Specifies a transitive requirement to override when resolving. Overrides can either "
220
+ "modify an existing dependency on a project name by changing extras, version "
221
+ "constraints or markers or else they can completely swap out the dependency for a "
222
+ "dependency on another project altogether. For the former, simply supply the "
223
+ "requirement you wish. For example, specifying `--override cowsay==5.0` will override "
224
+ "any transitive dependency on cowsay that has any combination of extras, version "
225
+ "constraints or markers with the requirement `cowsay==5.0`. To completely replace "
226
+ "cowsay with another library altogether, you can specify an override like "
227
+ "`--override cowsay=my-cowsay>2`. This will replace any transitive dependency on "
228
+ "cowsay that has any combination of extras, version constraints or markers with the "
229
+ "requirement `my-cowsay>2`. This option can be used multiple times."
158
230
  ),
159
231
  )
160
232