Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release Notes

## 2.104.1

This release fixes `--venv-repository` to ignore `uv`-specific `.dist-info` metadata files. This
fixes PEX builds from uv venvs to be reproducible when they should be. For `--rc` builds, `pexrc`
is upgraded to `0.37.1` to pick up the equivalent fix in `pexrc`.

## 2.104.0

This release adds support for Pip 26.2.2. Note that the Pip 26.2.2 release is in response to the
Expand Down
15 changes: 13 additions & 2 deletions pex/pep_427.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
Callable,
DefaultDict,
Dict,
FrozenSet,
Iterable,
Iterator,
List,
Expand All @@ -63,8 +64,6 @@
)

import attr # vendor:skip

from pex.installed_wheel import InstalledWheel # noqa
else:
from pex.third_party import attr

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

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

if not compile and installed_file.path.endswith(".pyc"):
continue
Expand Down
2 changes: 1 addition & 1 deletion pex/rc/pexrc.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


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


Expand Down
2 changes: 1 addition & 1 deletion pex/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015 Pex project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

__version__ = "2.104.0"
__version__ = "2.104.1"
116 changes: 115 additions & 1 deletion tests/integration/resolve/test_venv_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

from __future__ import absolute_import, print_function

import filecmp
import json
import os.path
import re
import sys
from collections import defaultdict
from textwrap import dedent

import colors # vendor:skip
import pytest

from pex.common import safe_copy, safe_mkdir, safe_open, safe_rmtree
from pex.common import safe_copy, safe_mkdir, safe_open, safe_rmtree, touch
from pex.dist_metadata import ProjectNameAndVersion
from pex.pep_425 import CompatibilityTags
from pex.pep_503 import ProjectName
Expand Down Expand Up @@ -299,3 +301,115 @@ def create_multiplatform_pex(*requirements):
),
re_flags=re.DOTALL,
)


@mark_skip_if_uv_not_supported
def test_uv_cache_json_ignored(tmpdir):
# type: (Tempdir) -> None

project = tmpdir.join("project")
with safe_open(os.path.join(project, "pyproject.toml"), "w") as fp:
fp.write(
dedent(
"""\
[build-system]
requires = ["uv_build>=0.13.4,<0.14"]
build-backend = "uv_build"

[project]
name = "blue-moo"
version = "0.1.0"

dependencies = [
"ansicolors",
"cowsay<6",
]

[project.scripts]
blue-moo = "blue_moo:quack"
"""
)
)
with safe_open(os.path.join(project, "src", "blue_moo", "__init__.py"), "w") as fp:
fp.write(
dedent(
"""\
import sys

import colors
import cowsay


def quack(*message):
cowsay.tux(colors.cyan(" ".join(message or sys.argv[1:])))
"""
)
)

uv_cache_dir = tmpdir.join("uv-cache")

def create_uv_venv():
# type: () -> str
venv = tmpdir.join("venv")
subprocess.check_call(
args=[
"uv",
"--cache-dir",
uv_cache_dir,
"venv",
"--python",
"{impl}{major}.{minor}".format(
impl="pypy" if IS_PYPY else "python",
major=sys.version_info[0],
minor=sys.version_info[1],
),
venv,
]
)
subprocess.check_call(
args=["uv", "--cache-dir", uv_cache_dir, "pip", "install", "--python", venv, project]
)
return venv

def build_blue_moo_pex(
venv, # type: str
name, # type: str
):
# type: (...) -> str
pex_root = tmpdir.join("pex-root")
pex = tmpdir.join(name)
run_pex_command(
args=[
"--pex-root",
pex_root,
"--runtime-pex-root",
pex_root,
"--venv-repository",
venv,
"-c",
"blue-moo",
"-o",
pex,
]
).assert_success()
return pex

venv_dir = create_uv_venv()
pex1 = build_blue_moo_pex(venv_dir, "first")
assert "| {message} |".format(message=colors.cyan("Moo?")) in subprocess.check_output(
args=[sys.executable, pex1, "Moo?"]
).decode("utf-8")

safe_rmtree(uv_cache_dir)
safe_rmtree(venv_dir)

# N.B.: This triggers a change to blue_moo-0.1.0.dist-info/uv_cache.json
touch(os.path.join(project, "pyproject.toml"))

venv_dir = create_uv_venv()
pex2 = build_blue_moo_pex(venv_dir, "second")
assert "| {message} |".format(message=colors.cyan("Moo?")) in subprocess.check_output(
args=[sys.executable, pex2, "Moo?"]
).decode("utf-8")

assert filecmp.cmp(pex1, pex2, shallow=False)
Loading