Skip to content

Commit abe51c4

Browse files
authored
Fix --venv-repository to ignore uv-specific metadata files. (#3302)
This issue was spotted over here: pantsbuild/pants#23710
1 parent ebf4223 commit abe51c4

5 files changed

Lines changed: 136 additions & 5 deletions

File tree

‎CHANGES.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release Notes
22

3+
## 2.104.1
4+
5+
This release fixes `--venv-repository` to ignore `uv`-specific `.dist-info` metadata files. This
6+
fixes PEX builds from uv venvs to be reproducible when they should be. For `--rc` builds, `pexrc`
7+
is upgraded to `0.37.1` to pick up the equivalent fix in `pexrc`.
8+
39
## 2.104.0
410

511
This release adds support for Pip 26.2.2. Note that the Pip 26.2.2 release is in response to the

‎pex/pep_427.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
Callable,
5252
DefaultDict,
5353
Dict,
54+
FrozenSet,
5455
Iterable,
5556
Iterator,
5657
List,
@@ -63,8 +64,6 @@
6364
)
6465

6566
import attr # vendor:skip
66-
67-
from pex.installed_wheel import InstalledWheel # noqa
6867
else:
6968
from pex.third_party import attr
7069

@@ -978,6 +977,16 @@ def is_entry_point_script(script_path):
978977
pex_info_dir_relpath = wheel.pex_metadata_path()
979978
installer_relpath = wheel.metadata_path("INSTALLER")
980979
requested_relpath = wheel.metadata_path("REQUESTED")
980+
ignored_relpaths = frozenset(
981+
(
982+
# These are undocumented files used by uv to track wheels linked out of its cache. We
983+
# run across these when resolving from a `--venv-repository` and ignore them since they
984+
# can contain variable metadata that is private to uv and otherwise un-needed and can
985+
# lead to unstable wheel contents.
986+
wheel.metadata_path("uv_build.json"),
987+
wheel.metadata_path("uv_cache.json"),
988+
)
989+
) # type: FrozenSet[str]
981990
zip_metadata_relpath = wheel.pex_metadata_path(ZipMetadata.FILENAME)
982991

983992
installed_files = [] # type: List[Union[InstalledFile, InstalledDirectory]]
@@ -1003,6 +1012,8 @@ def is_entry_point_script(script_path):
10031012
if installed_file.path in (installer_relpath, requested_relpath, zip_metadata_relpath):
10041013
# We'll generate these metadata files below as needed.
10051014
continue
1015+
if installed_file.path in ignored_relpaths:
1016+
continue
10061017

10071018
if not compile and installed_file.path.endswith(".pyc"):
10081019
continue

‎pex/rc/pexrc.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939

4040
PEXRC_RELEASES_URL = "https://github.com/pex-tool/pex.rc/releases"
41-
MIN_PEXRC_VERSION = Version("0.32.1")
41+
MIN_PEXRC_VERSION = Version("0.37.1")
4242
PEXRC_REQUIREMENT = SpecifierSet("~={min_version}".format(min_version=MIN_PEXRC_VERSION))
4343

4444

‎pex/version.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Copyright 2015 Pex project contributors.
22
# Licensed under the Apache License, Version 2.0 (see LICENSE).
33

4-
__version__ = "2.104.0"
4+
__version__ = "2.104.1"

‎tests/integration/resolve/test_venv_resolver.py‎

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33

44
from __future__ import absolute_import, print_function
55

6+
import filecmp
67
import json
78
import os.path
89
import re
910
import sys
1011
from collections import defaultdict
1112
from textwrap import dedent
1213

14+
import colors # vendor:skip
1315
import pytest
1416

15-
from pex.common import safe_copy, safe_mkdir, safe_open, safe_rmtree
17+
from pex.common import safe_copy, safe_mkdir, safe_open, safe_rmtree, touch
1618
from pex.dist_metadata import ProjectNameAndVersion
1719
from pex.pep_425 import CompatibilityTags
1820
from pex.pep_503 import ProjectName
@@ -299,3 +301,115 @@ def create_multiplatform_pex(*requirements):
299301
),
300302
re_flags=re.DOTALL,
301303
)
304+
305+
306+
@mark_skip_if_uv_not_supported
307+
def test_uv_cache_json_ignored(tmpdir):
308+
# type: (Tempdir) -> None
309+
310+
project = tmpdir.join("project")
311+
with safe_open(os.path.join(project, "pyproject.toml"), "w") as fp:
312+
fp.write(
313+
dedent(
314+
"""\
315+
[build-system]
316+
requires = ["uv_build>=0.13.4,<0.14"]
317+
build-backend = "uv_build"
318+
319+
[project]
320+
name = "blue-moo"
321+
version = "0.1.0"
322+
323+
dependencies = [
324+
"ansicolors",
325+
"cowsay<6",
326+
]
327+
328+
[project.scripts]
329+
blue-moo = "blue_moo:quack"
330+
"""
331+
)
332+
)
333+
with safe_open(os.path.join(project, "src", "blue_moo", "__init__.py"), "w") as fp:
334+
fp.write(
335+
dedent(
336+
"""\
337+
import sys
338+
339+
import colors
340+
import cowsay
341+
342+
343+
def quack(*message):
344+
cowsay.tux(colors.cyan(" ".join(message or sys.argv[1:])))
345+
"""
346+
)
347+
)
348+
349+
uv_cache_dir = tmpdir.join("uv-cache")
350+
351+
def create_uv_venv():
352+
# type: () -> str
353+
venv = tmpdir.join("venv")
354+
subprocess.check_call(
355+
args=[
356+
"uv",
357+
"--cache-dir",
358+
uv_cache_dir,
359+
"venv",
360+
"--python",
361+
"{impl}{major}.{minor}".format(
362+
impl="pypy" if IS_PYPY else "python",
363+
major=sys.version_info[0],
364+
minor=sys.version_info[1],
365+
),
366+
venv,
367+
]
368+
)
369+
subprocess.check_call(
370+
args=["uv", "--cache-dir", uv_cache_dir, "pip", "install", "--python", venv, project]
371+
)
372+
return venv
373+
374+
def build_blue_moo_pex(
375+
venv, # type: str
376+
name, # type: str
377+
):
378+
# type: (...) -> str
379+
pex_root = tmpdir.join("pex-root")
380+
pex = tmpdir.join(name)
381+
run_pex_command(
382+
args=[
383+
"--pex-root",
384+
pex_root,
385+
"--runtime-pex-root",
386+
pex_root,
387+
"--venv-repository",
388+
venv,
389+
"-c",
390+
"blue-moo",
391+
"-o",
392+
pex,
393+
]
394+
).assert_success()
395+
return pex
396+
397+
venv_dir = create_uv_venv()
398+
pex1 = build_blue_moo_pex(venv_dir, "first")
399+
assert "| {message} |".format(message=colors.cyan("Moo?")) in subprocess.check_output(
400+
args=[sys.executable, pex1, "Moo?"]
401+
).decode("utf-8")
402+
403+
safe_rmtree(uv_cache_dir)
404+
safe_rmtree(venv_dir)
405+
406+
# N.B.: This triggers a change to blue_moo-0.1.0.dist-info/uv_cache.json
407+
touch(os.path.join(project, "pyproject.toml"))
408+
409+
venv_dir = create_uv_venv()
410+
pex2 = build_blue_moo_pex(venv_dir, "second")
411+
assert "| {message} |".format(message=colors.cyan("Moo?")) in subprocess.check_output(
412+
args=[sys.executable, pex2, "Moo?"]
413+
).decode("utf-8")
414+
415+
assert filecmp.cmp(pex1, pex2, shallow=False)

0 commit comments

Comments
 (0)