pex 2.59.4__py2.py3-none-any.whl → 2.60.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 (114) hide show
  1. pex/build_backend/wrap.py +25 -4
  2. pex/cache/dirs.py +14 -4
  3. pex/cli/commands/lock.py +8 -5
  4. pex/common.py +57 -7
  5. pex/compatibility.py +1 -1
  6. pex/dist_metadata.py +48 -6
  7. pex/docs/html/_pagefind/fragment/en_39c0488.pf_fragment +0 -0
  8. pex/docs/html/_pagefind/fragment/en_3eeaaf4.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/fragment/en_a1dde36.pf_fragment +0 -0
  10. pex/docs/html/_pagefind/fragment/en_a755644.pf_fragment +0 -0
  11. pex/docs/html/_pagefind/fragment/en_b16e3bd.pf_fragment +0 -0
  12. pex/docs/html/_pagefind/fragment/{en_e323b0a.pf_fragment → en_c5d35a7.pf_fragment} +0 -0
  13. pex/docs/html/_pagefind/fragment/en_ec62bd2.pf_fragment +0 -0
  14. pex/docs/html/_pagefind/fragment/en_f32628f.pf_fragment +0 -0
  15. pex/docs/html/_pagefind/index/{en_9894162.pf_index → en_b211695.pf_index} +0 -0
  16. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  17. pex/docs/html/_pagefind/pagefind.en_e8a49380e5.pf_meta +0 -0
  18. pex/docs/html/_static/documentation_options.js +1 -1
  19. pex/docs/html/api/vars.html +5 -5
  20. pex/docs/html/buildingpex.html +5 -5
  21. pex/docs/html/genindex.html +5 -5
  22. pex/docs/html/index.html +5 -5
  23. pex/docs/html/recipes.html +5 -5
  24. pex/docs/html/scie.html +5 -5
  25. pex/docs/html/search.html +5 -5
  26. pex/docs/html/whatispex.html +5 -5
  27. pex/entry_points_txt.py +98 -0
  28. pex/environment.py +13 -10
  29. pex/finders.py +1 -1
  30. pex/installed_wheel.py +127 -0
  31. pex/interpreter.py +17 -5
  32. pex/interpreter_constraints.py +4 -4
  33. pex/pep_376.py +37 -385
  34. pex/pep_427.py +736 -248
  35. pex/pex_builder.py +4 -4
  36. pex/pex_info.py +8 -3
  37. pex/resolve/venv_resolver.py +98 -23
  38. pex/resolver.py +10 -3
  39. pex/sysconfig.py +5 -3
  40. pex/third_party/__init__.py +1 -1
  41. pex/tools/commands/repository.py +47 -24
  42. pex/vendor/__init__.py +4 -9
  43. pex/vendor/__main__.py +62 -41
  44. pex/vendor/_vendored/ansicolors/.layout.json +1 -1
  45. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/RECORD +11 -0
  46. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.pex-info/original-whl-info.json +1 -0
  47. pex/vendor/_vendored/appdirs/.layout.json +1 -1
  48. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/RECORD +7 -0
  49. pex/vendor/_vendored/appdirs/appdirs-1.4.4.pex-info/original-whl-info.json +1 -0
  50. pex/vendor/_vendored/attrs/.layout.json +1 -1
  51. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/RECORD +37 -0
  52. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.pex-info/original-whl-info.json +1 -0
  53. pex/vendor/_vendored/packaging_20_9/.layout.json +1 -1
  54. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/RECORD +20 -0
  55. pex/vendor/_vendored/packaging_20_9/packaging-20.9.pex-info/original-whl-info.json +1 -0
  56. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/RECORD +7 -0
  57. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.pex-info/original-whl-info.json +1 -0
  58. pex/vendor/_vendored/packaging_21_3/.layout.json +1 -1
  59. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/RECORD +20 -0
  60. pex/vendor/_vendored/packaging_21_3/packaging-21.3.pex-info/original-whl-info.json +1 -0
  61. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/RECORD +18 -0
  62. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.pex-info/original-whl-info.json +1 -0
  63. pex/vendor/_vendored/packaging_24_0/.layout.json +1 -1
  64. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/RECORD +22 -0
  65. pex/vendor/_vendored/packaging_24_0/packaging-24.0.pex-info/original-whl-info.json +1 -0
  66. pex/vendor/_vendored/packaging_25_0/.layout.json +1 -1
  67. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/RECORD +24 -0
  68. pex/vendor/_vendored/packaging_25_0/packaging-25.0.pex-info/original-whl-info.json +1 -0
  69. pex/vendor/_vendored/pip/.layout.json +1 -1
  70. pex/vendor/_vendored/pip/pip/_vendor/certifi/cacert.pem +63 -1
  71. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/RECORD +388 -0
  72. pex/vendor/_vendored/pip/pip-20.3.4.pex-info/original-whl-info.json +1 -0
  73. pex/vendor/_vendored/setuptools/.layout.json +1 -1
  74. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/RECORD +107 -0
  75. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.pex-info/original-whl-info.json +1 -0
  76. pex/vendor/_vendored/toml/.layout.json +1 -1
  77. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/RECORD +11 -0
  78. pex/vendor/_vendored/toml/toml-0.10.2.pex-info/original-whl-info.json +1 -0
  79. pex/vendor/_vendored/tomli/.layout.json +1 -1
  80. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/RECORD +10 -0
  81. pex/vendor/_vendored/tomli/tomli-2.0.1.pex-info/original-whl-info.json +1 -0
  82. pex/venv/installer.py +9 -5
  83. pex/version.py +1 -1
  84. pex/wheel.py +79 -15
  85. pex/whl.py +67 -0
  86. pex/windows/__init__.py +14 -11
  87. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/METADATA +4 -4
  88. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/RECORD +93 -77
  89. pex/docs/html/_pagefind/fragment/en_144b803.pf_fragment +0 -0
  90. pex/docs/html/_pagefind/fragment/en_1df1379.pf_fragment +0 -0
  91. pex/docs/html/_pagefind/fragment/en_2c6c6cb.pf_fragment +0 -0
  92. pex/docs/html/_pagefind/fragment/en_a916b1c.pf_fragment +0 -0
  93. pex/docs/html/_pagefind/fragment/en_b33e5d4.pf_fragment +0 -0
  94. pex/docs/html/_pagefind/fragment/en_c1c571a.pf_fragment +0 -0
  95. pex/docs/html/_pagefind/fragment/en_fda06e7.pf_fragment +0 -0
  96. pex/docs/html/_pagefind/pagefind.en_84c8322e7a.pf_meta +0 -0
  97. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/INSTALLER +0 -1
  98. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/INSTALLER +0 -1
  99. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/INSTALLER +0 -1
  100. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/INSTALLER +0 -1
  101. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/INSTALLER +0 -1
  102. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/INSTALLER +0 -1
  103. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/INSTALLER +0 -1
  104. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/INSTALLER +0 -1
  105. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/INSTALLER +0 -1
  106. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/INSTALLER +0 -1
  107. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/INSTALLER +0 -1
  108. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/INSTALLER +0 -1
  109. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/INSTALLER +0 -1
  110. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/WHEEL +0 -0
  111. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/entry_points.txt +0 -0
  112. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/licenses/LICENSE +0 -0
  113. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/pylock/pylock.toml +0 -0
  114. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/top_level.txt +0 -0
pex/docs/html/scie.html CHANGED
@@ -8,7 +8,7 @@
8
8
  <link rel="prefetch" href="_static/pex-logo-dark.png" as="image">
9
9
 
10
10
  <link rel="shortcut icon" href="_static/pex-icon.png"><!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25 -->
11
- <title>PEX with included Python interpreter - Pex Docs (v2.59.4)</title>
11
+ <title>PEX with included Python interpreter - Pex Docs (v2.60.0)</title>
12
12
  <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
13
13
  <link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
14
14
  <link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
@@ -163,7 +163,7 @@
163
163
  </label>
164
164
  </div>
165
165
  <div class="header-center">
166
- <a href="index.html"><div class="brand">Pex Docs (v2.59.4)</div></a>
166
+ <a href="index.html"><div class="brand">Pex Docs (v2.60.0)</div></a>
167
167
  </div>
168
168
  <div class="header-right">
169
169
  <div class="theme-toggle-container theme-toggle-header">
@@ -623,12 +623,12 @@ uuid
623
623
  </div>
624
624
  <div class="right-details">
625
625
  <div class="icons">
626
- <a class="muted-link " href="https://pypi.org/project/pex/2.59.4/" aria-label="PyPI"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
626
+ <a class="muted-link " href="https://pypi.org/project/pex/2.60.0/" aria-label="PyPI"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
627
627
  <path d="M439.8 200.5c-7.7-30.9-22.3-54.2-53.4-54.2h-40.1v47.4c0 36.8-31.2 67.8-66.8 67.8H172.7c-29.2 0-53.4 25-53.4 54.3v101.8c0 29 25.2 46 53.4 54.3 33.8 9.9 66.3 11.7 106.8 0 26.9-7.8 53.4-23.5 53.4-54.3v-40.7H226.2v-13.6h160.2c31.1 0 42.6-21.7 53.4-54.2 11.2-33.5 10.7-65.7 0-108.6zM286.2 404c11.1 0 20.1 9.1 20.1 20.3 0 11.3-9 20.4-20.1 20.4-11 0-20.1-9.2-20.1-20.4.1-11.3 9.1-20.3 20.1-20.3zM167.8 248.1h106.8c29.7 0 53.4-24.5 53.4-54.3V91.9c0-29-24.4-50.7-53.4-55.6-35.8-5.9-74.7-5.6-106.8.1-45.2 8-53.4 24.7-53.4 55.6v40.7h106.9v13.6h-147c-31.1 0-58.3 18.7-66.8 54.2-9.8 40.7-10.2 66.1 0 108.6 7.6 31.6 25.7 54.2 56.8 54.2H101v-48.8c0-35.3 30.5-66.4 66.8-66.4zm-6.7-142.6c-11.1 0-20.1-9.1-20.1-20.3.1-11.3 9-20.4 20.1-20.4 11 0 20.1 9.2 20.1 20.4s-9 20.3-20.1 20.3z">
628
628
  </path>
629
629
  </svg>
630
630
  </a>
631
- <a class="muted-link " href="https://github.com/pex-tool/pex/releases/download/v2.59.4/pex" aria-label="Download"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 640 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
631
+ <a class="muted-link " href="https://github.com/pex-tool/pex/releases/download/v2.60.0/pex" aria-label="Download"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 640 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
632
632
  <path d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zm-132.9 88.7L299.3 420.7c-6.2 6.2-16.4 6.2-22.6 0L171.3 315.3c-10.1-10.1-2.9-27.3 11.3-27.3H248V176c0-8.8 7.2-16 16-16h48c8.8 0 16 7.2 16 16v112h65.4c14.2 0 21.4 17.2 11.3 27.3z">
633
633
  </path>
634
634
  </svg>
@@ -672,7 +672,7 @@ uuid
672
672
 
673
673
  </aside>
674
674
  </div>
675
- </div><script src="_static/documentation_options.js?v=9966f4ea"></script>
675
+ </div><script src="_static/documentation_options.js?v=1bc0be70"></script>
676
676
  <script src="_static/doctools.js?v=9bcbadda"></script>
677
677
  <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
678
678
  <script src="_static/scripts/furo.js?v=46bd48cc"></script>
pex/docs/html/search.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <link rel="prefetch" href="_static/pex-logo-light.png" as="image">
7
7
  <link rel="prefetch" href="_static/pex-logo-dark.png" as="image">
8
8
 
9
- <link rel="shortcut icon" href="_static/pex-icon.png"><!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25 --><title>Search - Pex Docs (v2.59.4)</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
9
+ <link rel="shortcut icon" href="_static/pex-icon.png"><!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25 --><title>Search - Pex Docs (v2.60.0)</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
10
10
  <link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
11
11
  <link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
12
12
 
@@ -176,7 +176,7 @@
176
176
  </label>
177
177
  </div>
178
178
  <div class="header-center">
179
- <a href="index.html"><div class="brand">Pex Docs (v2.59.4)</div></a>
179
+ <a href="index.html"><div class="brand">Pex Docs (v2.60.0)</div></a>
180
180
  </div>
181
181
  <div class="header-right">
182
182
  <div class="theme-toggle-container theme-toggle-header">
@@ -273,12 +273,12 @@
273
273
  </div>
274
274
  <div class="right-details">
275
275
  <div class="icons">
276
- <a class="muted-link " href="https://pypi.org/project/pex/2.59.4/" aria-label="PyPI"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
276
+ <a class="muted-link " href="https://pypi.org/project/pex/2.60.0/" aria-label="PyPI"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
277
277
  <path d="M439.8 200.5c-7.7-30.9-22.3-54.2-53.4-54.2h-40.1v47.4c0 36.8-31.2 67.8-66.8 67.8H172.7c-29.2 0-53.4 25-53.4 54.3v101.8c0 29 25.2 46 53.4 54.3 33.8 9.9 66.3 11.7 106.8 0 26.9-7.8 53.4-23.5 53.4-54.3v-40.7H226.2v-13.6h160.2c31.1 0 42.6-21.7 53.4-54.2 11.2-33.5 10.7-65.7 0-108.6zM286.2 404c11.1 0 20.1 9.1 20.1 20.3 0 11.3-9 20.4-20.1 20.4-11 0-20.1-9.2-20.1-20.4.1-11.3 9.1-20.3 20.1-20.3zM167.8 248.1h106.8c29.7 0 53.4-24.5 53.4-54.3V91.9c0-29-24.4-50.7-53.4-55.6-35.8-5.9-74.7-5.6-106.8.1-45.2 8-53.4 24.7-53.4 55.6v40.7h106.9v13.6h-147c-31.1 0-58.3 18.7-66.8 54.2-9.8 40.7-10.2 66.1 0 108.6 7.6 31.6 25.7 54.2 56.8 54.2H101v-48.8c0-35.3 30.5-66.4 66.8-66.4zm-6.7-142.6c-11.1 0-20.1-9.1-20.1-20.3.1-11.3 9-20.4 20.1-20.4 11 0 20.1 9.2 20.1 20.4s-9 20.3-20.1 20.3z">
278
278
  </path>
279
279
  </svg>
280
280
  </a>
281
- <a class="muted-link " href="https://github.com/pex-tool/pex/releases/download/v2.59.4/pex" aria-label="Download"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 640 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
281
+ <a class="muted-link " href="https://github.com/pex-tool/pex/releases/download/v2.60.0/pex" aria-label="Download"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 640 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
282
282
  <path d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zm-132.9 88.7L299.3 420.7c-6.2 6.2-16.4 6.2-22.6 0L171.3 315.3c-10.1-10.1-2.9-27.3 11.3-27.3H248V176c0-8.8 7.2-16 16-16h48c8.8 0 16 7.2 16 16v112h65.4c14.2 0 21.4 17.2 11.3 27.3z">
283
283
  </path>
284
284
  </svg>
@@ -301,7 +301,7 @@
301
301
 
302
302
  </aside>
303
303
  </div>
304
- </div><script src="_static/documentation_options.js?v=9966f4ea"></script>
304
+ </div><script src="_static/documentation_options.js?v=1bc0be70"></script>
305
305
  <script src="_static/doctools.js?v=9bcbadda"></script>
306
306
  <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
307
307
  <script src="_static/scripts/furo.js?v=46bd48cc"></script>
@@ -8,7 +8,7 @@
8
8
  <link rel="prefetch" href="_static/pex-logo-dark.png" as="image">
9
9
 
10
10
  <link rel="shortcut icon" href="_static/pex-icon.png"><!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25 -->
11
- <title>What are .pex files? - Pex Docs (v2.59.4)</title>
11
+ <title>What are .pex files? - Pex Docs (v2.60.0)</title>
12
12
  <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
13
13
  <link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
14
14
  <link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
@@ -163,7 +163,7 @@
163
163
  </label>
164
164
  </div>
165
165
  <div class="header-center">
166
- <a href="index.html"><div class="brand">Pex Docs (v2.59.4)</div></a>
166
+ <a href="index.html"><div class="brand">Pex Docs (v2.60.0)</div></a>
167
167
  </div>
168
168
  <div class="header-right">
169
169
  <div class="theme-toggle-container theme-toggle-header">
@@ -322,12 +322,12 @@ build executable .pex files. This is described more thoroughly in
322
322
  </div>
323
323
  <div class="right-details">
324
324
  <div class="icons">
325
- <a class="muted-link " href="https://pypi.org/project/pex/2.59.4/" aria-label="PyPI"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
325
+ <a class="muted-link " href="https://pypi.org/project/pex/2.60.0/" aria-label="PyPI"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
326
326
  <path d="M439.8 200.5c-7.7-30.9-22.3-54.2-53.4-54.2h-40.1v47.4c0 36.8-31.2 67.8-66.8 67.8H172.7c-29.2 0-53.4 25-53.4 54.3v101.8c0 29 25.2 46 53.4 54.3 33.8 9.9 66.3 11.7 106.8 0 26.9-7.8 53.4-23.5 53.4-54.3v-40.7H226.2v-13.6h160.2c31.1 0 42.6-21.7 53.4-54.2 11.2-33.5 10.7-65.7 0-108.6zM286.2 404c11.1 0 20.1 9.1 20.1 20.3 0 11.3-9 20.4-20.1 20.4-11 0-20.1-9.2-20.1-20.4.1-11.3 9.1-20.3 20.1-20.3zM167.8 248.1h106.8c29.7 0 53.4-24.5 53.4-54.3V91.9c0-29-24.4-50.7-53.4-55.6-35.8-5.9-74.7-5.6-106.8.1-45.2 8-53.4 24.7-53.4 55.6v40.7h106.9v13.6h-147c-31.1 0-58.3 18.7-66.8 54.2-9.8 40.7-10.2 66.1 0 108.6 7.6 31.6 25.7 54.2 56.8 54.2H101v-48.8c0-35.3 30.5-66.4 66.8-66.4zm-6.7-142.6c-11.1 0-20.1-9.1-20.1-20.3.1-11.3 9-20.4 20.1-20.4 11 0 20.1 9.2 20.1 20.4s-9 20.3-20.1 20.3z">
327
327
  </path>
328
328
  </svg>
329
329
  </a>
330
- <a class="muted-link " href="https://github.com/pex-tool/pex/releases/download/v2.59.4/pex" aria-label="Download"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 640 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
330
+ <a class="muted-link " href="https://github.com/pex-tool/pex/releases/download/v2.60.0/pex" aria-label="Download"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 640 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
331
331
  <path d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zm-132.9 88.7L299.3 420.7c-6.2 6.2-16.4 6.2-22.6 0L171.3 315.3c-10.1-10.1-2.9-27.3 11.3-27.3H248V176c0-8.8 7.2-16 16-16h48c8.8 0 16 7.2 16 16v112h65.4c14.2 0 21.4 17.2 11.3 27.3z">
332
332
  </path>
333
333
  </svg>
@@ -371,7 +371,7 @@ build executable .pex files. This is described more thoroughly in
371
371
 
372
372
  </aside>
373
373
  </div>
374
- </div><script src="_static/documentation_options.js?v=9966f4ea"></script>
374
+ </div><script src="_static/documentation_options.js?v=1bc0be70"></script>
375
375
  <script src="_static/doctools.js?v=9bcbadda"></script>
376
376
  <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
377
377
  <script src="_static/scripts/furo.js?v=46bd48cc"></script>
@@ -0,0 +1,98 @@
1
+ # Copyright 2025 Pex project contributors.
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE).
3
+
4
+ from __future__ import absolute_import, print_function
5
+
6
+ import itertools
7
+ import os
8
+ from textwrap import dedent
9
+
10
+ from pex import windows
11
+ from pex.common import safe_open
12
+ from pex.dist_metadata import CallableEntryPoint, EntryPoints
13
+ from pex.exceptions import reportable_unexpected_error_msg
14
+ from pex.executables import chmod_plus_x
15
+ from pex.interpreter import PythonInterpreter
16
+ from pex.os import WINDOWS
17
+ from pex.typing import TYPE_CHECKING
18
+
19
+ if TYPE_CHECKING:
20
+ from typing import Iterator, Optional, Text, Tuple
21
+
22
+
23
+ def install_scripts(
24
+ dest_dir, # type: str
25
+ entry_points, # type: EntryPoints
26
+ interpreter=None, # type: Optional[PythonInterpreter]
27
+ overwrite=True, # type: bool
28
+ ):
29
+ # type: (...) -> Iterator[Tuple[Text, Text]]
30
+
31
+ if not entry_points:
32
+ return
33
+
34
+ if entry_points.source is None:
35
+ raise AssertionError(reportable_unexpected_error_msg())
36
+
37
+ script_src = entry_points.source
38
+ shebang = interpreter.shebang() if interpreter else "#!python"
39
+ for named_entry_point, gui in itertools.chain.from_iterable(
40
+ ((value, gui) for value in entry_points.get(key, {}).values())
41
+ for key, gui in (("console_scripts", False), ("gui_scripts", True))
42
+ ):
43
+ entry_point = named_entry_point.entry_point
44
+ if isinstance(entry_point, CallableEntryPoint):
45
+ script = dedent(
46
+ """\
47
+ {shebang}
48
+ # -*- coding: utf-8 -*-
49
+ import importlib
50
+ import sys
51
+
52
+ entry_point = importlib.import_module({modname!r})
53
+ for attr in {attrs!r}:
54
+ entry_point = getattr(entry_point, attr)
55
+
56
+ if __name__ == "__main__":
57
+ import os
58
+ pex_root_fallback = os.environ.get("_PEX_ROOT_FALLBACK")
59
+ if pex_root_fallback:
60
+ import atexit
61
+ import shutil
62
+
63
+ atexit.register(shutil.rmtree, pex_root_fallback, True)
64
+
65
+ sys.exit(entry_point())
66
+ """
67
+ ).format(shebang=shebang, modname=entry_point.module, attrs=entry_point.attrs)
68
+ else:
69
+ script = dedent(
70
+ """\
71
+ {shebang}
72
+ # -*- coding: utf-8 -*-
73
+ import runpy
74
+ import sys
75
+
76
+ if __name__ == "__main__":
77
+ import os
78
+ pex_root_fallback = os.environ.get("_PEX_ROOT_FALLBACK")
79
+ if pex_root_fallback:
80
+ import atexit
81
+ import shutil
82
+
83
+ atexit.register(shutil.rmtree, pex_root_fallback, True)
84
+
85
+ runpy.run_module({modname!r}, run_name="__main__", alter_sys=True)
86
+ sys.exit(0)
87
+ """
88
+ ).format(shebang=shebang, modname=entry_point.module)
89
+ script_abspath = os.path.join(dest_dir, named_entry_point.name)
90
+ if WINDOWS:
91
+ script_abspath = windows.create_script(
92
+ script_abspath, script, gui=gui, overwrite=overwrite
93
+ )
94
+ elif overwrite or not os.path.exists(script_abspath):
95
+ with safe_open(script_abspath, "w") as fp:
96
+ fp.write(script)
97
+ chmod_plus_x(fp.name)
98
+ yield script_src, script_abspath
pex/environment.py CHANGED
@@ -26,6 +26,7 @@ from pex.third_party.packaging import specifiers
26
26
  from pex.third_party.packaging.tags import Tag
27
27
  from pex.tracer import TRACER
28
28
  from pex.typing import TYPE_CHECKING
29
+ from pex.whl import repacked_whl
29
30
 
30
31
  if TYPE_CHECKING:
31
32
  from typing import (
@@ -305,12 +306,7 @@ class PEXEnvironment(object):
305
306
 
306
307
  def iter_distributions(self, result_type_wheel_file=False):
307
308
  # type: (bool) -> Iterator[FingerprintedDistribution]
308
- if result_type_wheel_file:
309
- if not self._pex_info.deps_are_wheel_files:
310
- raise ResolveError(
311
- "Cannot resolve .whl files from PEX at {pex}; its dependencies are in the "
312
- "form of pre-installed wheel chroots.".format(pex=self.source_pex)
313
- )
309
+ if result_type_wheel_file and self._pex_info.deps_are_wheel_files:
314
310
  with TRACER.timed(
315
311
  "Searching dependency cache: {cache}".format(
316
312
  cache=os.path.join(self.source_pex, self._pex_info.internal_cache)
@@ -334,10 +330,17 @@ class PEXEnvironment(object):
334
330
  ):
335
331
  for distribution_name, fingerprint in self._pex_info.distributions.items():
336
332
  dist_path = os.path.join(internal_cache, distribution_name)
337
- yield FingerprintedDistribution(
338
- distribution=Distribution.load(dist_path),
339
- fingerprint=fingerprint,
340
- )
333
+ if result_type_wheel_file:
334
+ yield repacked_whl(
335
+ installed_wheel=dist_path,
336
+ distribution_name=distribution_name,
337
+ fingerprint=fingerprint,
338
+ use_system_time=True,
339
+ )
340
+ else:
341
+ yield FingerprintedDistribution(
342
+ distribution=Distribution.load(dist_path), fingerprint=fingerprint
343
+ )
341
344
 
342
345
  def _update_candidate_distributions(self, distribution_iter):
343
346
  # type: (Iterable[FingerprintedDistribution]) -> None
pex/finders.py CHANGED
@@ -15,7 +15,7 @@ from pex.dist_metadata import (
15
15
  NamedEntryPoint,
16
16
  )
17
17
  from pex.executables import is_python_script
18
- from pex.pep_376 import InstalledWheel
18
+ from pex.installed_wheel import InstalledWheel
19
19
  from pex.pep_503 import ProjectName
20
20
  from pex.sysconfig import SCRIPT_DIR, script_name
21
21
  from pex.typing import TYPE_CHECKING, cast
pex/installed_wheel.py ADDED
@@ -0,0 +1,127 @@
1
+ # Copyright 2025 Pex project contributors.
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE).
3
+
4
+ from __future__ import absolute_import
5
+
6
+ import hashlib
7
+ import json
8
+ import os
9
+
10
+ from pex.typing import TYPE_CHECKING, cast
11
+ from pex.util import CacheHelper
12
+ from pex.wheel import WHEEL, Wheel, WheelMetadataLoadError
13
+
14
+ if TYPE_CHECKING:
15
+ from typing import Optional, Text
16
+
17
+ import attr # vendor:skip
18
+ else:
19
+ import pex.third_party.attr as attr
20
+
21
+
22
+ @attr.s(frozen=True)
23
+ class InstalledWheel(object):
24
+ class LoadError(Exception):
25
+ """Indicates an installed wheel was not loadable at a particular path."""
26
+
27
+ _LAYOUT_JSON_FILENAME = ".layout.json"
28
+
29
+ @classmethod
30
+ def layout_file(cls, prefix_dir):
31
+ # type: (str) -> str
32
+ return os.path.join(prefix_dir, cls._LAYOUT_JSON_FILENAME)
33
+
34
+ @classmethod
35
+ def save(
36
+ cls,
37
+ prefix_dir, # type: str
38
+ stash_dir, # type: str
39
+ record_relpath, # type: Text
40
+ root_is_purelib, # type: bool
41
+ ):
42
+ # type: (...) -> InstalledWheel
43
+
44
+ # We currently need the installed wheel chroot hash for PEX-INFO / boot purposes. It is
45
+ # expensive to calculate; so we do it here 1 time when saving the installed wheel.
46
+ fingerprint = CacheHelper.dir_hash(prefix_dir, hasher=hashlib.sha256)
47
+
48
+ layout = {
49
+ "stash_dir": stash_dir,
50
+ "record_relpath": record_relpath,
51
+ "fingerprint": fingerprint,
52
+ "root_is_purelib": root_is_purelib,
53
+ }
54
+ with open(cls.layout_file(prefix_dir), "w") as fp:
55
+ json.dump(layout, fp, sort_keys=True, separators=(",", ":"))
56
+ return cls(
57
+ prefix_dir=prefix_dir,
58
+ stash_dir=stash_dir,
59
+ record_relpath=record_relpath,
60
+ fingerprint=fingerprint,
61
+ root_is_purelib=root_is_purelib,
62
+ )
63
+
64
+ @classmethod
65
+ def load(cls, prefix_dir):
66
+ # type: (str) -> InstalledWheel
67
+ layout_file = cls.layout_file(prefix_dir)
68
+ try:
69
+ with open(layout_file) as fp:
70
+ layout = json.load(fp)
71
+ except (IOError, OSError) as e:
72
+ raise cls.LoadError(
73
+ "Failed to load an installed wheel layout from {layout_file}: {err}".format(
74
+ layout_file=layout_file, err=e
75
+ )
76
+ )
77
+ if not isinstance(layout, dict):
78
+ raise cls.LoadError(
79
+ "The installed wheel layout file at {layout_file} must contain a single top-level "
80
+ "object, found: {value}.".format(layout_file=layout_file, value=layout)
81
+ )
82
+ stash_dir = layout.get("stash_dir")
83
+ record_relpath = layout.get("record_relpath")
84
+ if not stash_dir or not record_relpath:
85
+ raise cls.LoadError(
86
+ "The installed wheel layout file at {layout_file} must contain an object with both "
87
+ "`stash_dir` and `record_relpath` attributes, found: {value}".format(
88
+ layout_file=layout_file, value=layout
89
+ )
90
+ )
91
+
92
+ fingerprint = layout.get("fingerprint")
93
+
94
+ # N.B.: Caching root_is_purelib was not part of the original InstalledWheel layout data; so
95
+ # we materialize the property if needed to support older installed wheel chroots.
96
+ root_is_purelib = layout.get("root_is_purelib")
97
+ if root_is_purelib is None:
98
+ try:
99
+ wheel = WHEEL.load(prefix_dir)
100
+ except WheelMetadataLoadError as e:
101
+ raise cls.LoadError(
102
+ "Failed to determine if installed wheel at {location} is platform-specific: "
103
+ "{err}".format(location=prefix_dir, err=e)
104
+ )
105
+ root_is_purelib = wheel.root_is_purelib
106
+
107
+ return cls(
108
+ prefix_dir=prefix_dir,
109
+ stash_dir=cast(str, stash_dir),
110
+ record_relpath=cast(str, record_relpath),
111
+ fingerprint=cast("Optional[str]", fingerprint),
112
+ root_is_purelib=root_is_purelib,
113
+ )
114
+
115
+ prefix_dir = attr.ib() # type: str
116
+ stash_dir = attr.ib() # type: str
117
+ record_relpath = attr.ib() # type: Text
118
+ fingerprint = attr.ib() # type: Optional[str]
119
+ root_is_purelib = attr.ib() # type: bool
120
+
121
+ def wheel_file_name(self):
122
+ # type: () -> str
123
+ return Wheel.load(self.prefix_dir).wheel_file_name
124
+
125
+ def stashed_path(self, *components):
126
+ # type: (*str) -> str
127
+ return os.path.join(self.prefix_dir, self.stash_dir, *components)
pex/interpreter.py CHANGED
@@ -483,7 +483,7 @@ class PythonIdentity(object):
483
483
  env_markers=self._env_markers.as_dict(),
484
484
  configured_macosx_deployment_target=self._configured_macosx_deployment_target,
485
485
  )
486
- return json.dumps(values, sort_keys=True)
486
+ return json.dumps(values, sort_keys=True, separators=(",", ":"))
487
487
 
488
488
  @property
489
489
  def binary(self):
@@ -1525,13 +1525,19 @@ class PythonInterpreter(object):
1525
1525
  self._supported_platforms = frozenset(self._identity.iter_supported_platforms())
1526
1526
  return self._supported_platforms
1527
1527
 
1528
- def shebang(self, args=None):
1529
- # type: (Optional[Text]) -> Text
1528
+ def shebang(
1529
+ self,
1530
+ args=None, # type: Optional[Text]
1531
+ encoding_line="", # type: str
1532
+ ):
1533
+ # type: (...) -> Text
1530
1534
  """Return the contents of an appropriate shebang for this interpreter and args.
1531
1535
 
1532
1536
  The shebang will include the leading `#!` but will not include a trailing new line character.
1533
1537
  """
1534
- return create_shebang(self._binary, python_args=args)
1538
+ return create_shebang(
1539
+ adjust_to_final_path(self._binary), python_args=args, encoding_line=encoding_line
1540
+ )
1535
1541
 
1536
1542
  def create_isolated_cmd(
1537
1543
  self,
@@ -1637,6 +1643,7 @@ def create_shebang(
1637
1643
  python_exe, # type: Text
1638
1644
  python_args=None, # type: Optional[Text]
1639
1645
  max_shebang_length=MAX_SHEBANG_LENGTH, # type: int
1646
+ encoding_line="", # type: str
1640
1647
  ):
1641
1648
  # type: (...) -> Text
1642
1649
  """Return the contents of an appropriate shebang for the given Python interpreter and args.
@@ -1662,6 +1669,7 @@ def create_shebang(
1662
1669
  dedent(
1663
1670
  """\
1664
1671
  #!/bin/sh
1672
+ {encoding_line}
1665
1673
  # N.B.: This python script executes via a /bin/sh re-exec as a hack to work around a
1666
1674
  # potential maximum shebang length of {max_shebang_length} bytes on this system which
1667
1675
  # the python interpreter `exec`ed below would violate.
@@ -1669,6 +1677,10 @@ def create_shebang(
1669
1677
  '''
1670
1678
  """
1671
1679
  )
1672
- .format(max_shebang_length=max_shebang_length, python=python)
1680
+ .format(
1681
+ encoding_line=encoding_line.rstrip(),
1682
+ max_shebang_length=max_shebang_length,
1683
+ python=python,
1684
+ )
1673
1685
  .strip()
1674
1686
  )
@@ -375,10 +375,10 @@ COMPATIBLE_PYTHON_VERSIONS = (
375
375
  PythonVersion(Lifecycle.EOL, 3, 6, 15),
376
376
  PythonVersion(Lifecycle.EOL, 3, 7, 17),
377
377
  PythonVersion(Lifecycle.EOL, 3, 8, 20),
378
- PythonVersion(Lifecycle.STABLE, 3, 9, 23),
379
- PythonVersion(Lifecycle.STABLE, 3, 10, 18),
380
- PythonVersion(Lifecycle.STABLE, 3, 11, 13),
381
- PythonVersion(Lifecycle.STABLE, 3, 12, 11),
378
+ PythonVersion(Lifecycle.STABLE, 3, 9, 24),
379
+ PythonVersion(Lifecycle.STABLE, 3, 10, 19),
380
+ PythonVersion(Lifecycle.STABLE, 3, 11, 14),
381
+ PythonVersion(Lifecycle.STABLE, 3, 12, 12),
382
382
  PythonVersion(Lifecycle.STABLE, 3, 13, 8),
383
383
  PythonVersion(Lifecycle.STABLE, 3, 14, 0),
384
384
  PythonVersion(Lifecycle.DEV, 3, 15, 0),