Skip to content

Commit 1d8eb90

Browse files
authored
Add support for --pip-version 25.3. (#2968)
1 parent 9187c3e commit 1d8eb90

File tree

12 files changed

+197
-84
lines changed

12 files changed

+197
-84
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ jobs:
8989
# Unit tests:
9090
# -----------
9191
- test-cmd: test-py27-pip20.3.4--patched
92-
- test-cmd: test-py314-pip25.2
93-
- test-cmd: test-py315-pip25.2
92+
- test-cmd: test-py314-pip25.3
93+
- test-cmd: test-py315-pip25.3
9494
- test-cmd: test-pypy311-pip24.3.1
9595

9696
# Integration tests, split most into two shards:
@@ -104,14 +104,14 @@ jobs:
104104
- test-cmd: test-py38-pip22.3.1-integration
105105
pex-test-pos-args: --shard 2/2
106106

107-
- test-cmd: test-py314-pip25.2-integration
107+
- test-cmd: test-py314-pip25.3-integration
108108
pex-test-pos-args: --shard 1/2
109-
- test-cmd: test-py314-pip25.2-integration
109+
- test-cmd: test-py314-pip25.3-integration
110110
pex-test-pos-args: --shard 2/2
111111

112-
- test-cmd: test-py315-pip25.2-integration
112+
- test-cmd: test-py315-pip25.3-integration
113113
pex-test-pos-args: --shard 1/2
114-
- test-cmd: test-py315-pip25.2-integration
114+
- test-cmd: test-py315-pip25.3-integration
115115
pex-test-pos-args: --shard 2/2
116116

117117
# PyPy is slow enough to require extra sharding.
@@ -162,12 +162,12 @@ jobs:
162162
matrix:
163163
include:
164164
- python-version: "3.14"
165-
test-cmd: test-py314-pip25.2
165+
test-cmd: test-py314-pip25.3
166166
- python-version: "3.14"
167-
test-cmd: test-py314-pip25.2-integration
167+
test-cmd: test-py314-pip25.3-integration
168168
pex-test-pos-args: --shard 1/2
169169
- python-version: "3.14"
170-
test-cmd: test-py314-pip25.2-integration
170+
test-cmd: test-py314-pip25.3-integration
171171
pex-test-pos-args: --shard 2/2
172172
steps:
173173
- name: Checkout Pex
@@ -253,19 +253,19 @@ jobs:
253253
- python-version: "3.11"
254254
test-cmd: typecheck package docs
255255
- python-version: "3.14"
256-
test-cmd: test-py314-pip25.2
256+
test-cmd: test-py314-pip25.3
257257
artifact-name: unit
258258
pex-test-pos-args: --junit-report ../dist/test-results/unit.xml
259259
- python-version: "3.14"
260-
test-cmd: test-py314-pip25.2-integration
260+
test-cmd: test-py314-pip25.3-integration
261261
artifact-name: integration-1
262262
pex-test-pos-args: --shard 1/3 --junit-report ../dist/test-results/integration-1.xml
263263
- python-version: "3.14"
264-
test-cmd: test-py314-pip25.2-integration
264+
test-cmd: test-py314-pip25.3-integration
265265
artifact-name: integration-2
266266
pex-test-pos-args: --shard 2/3 --junit-report ../dist/test-results/integration-2.xml
267267
- python-version: "3.14"
268-
test-cmd: test-py314-pip25.2-integration
268+
test-cmd: test-py314-pip25.3-integration
269269
artifact-name: integration-3
270270
pex-test-pos-args: --shard 3/3 --junit-report ../dist/test-results/integration-3.xml
271271
steps:

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.66.0
4+
5+
This release adds support for `--pip-version 25.3`.
6+
7+
* Add support for `--pip-version 25.3`. (#2968)
8+
39
## 2.65.0
410

511
This release adds support for PEX scies using CPython free-threaded builds. Most such scies should

pex/hashing.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,16 @@ def zip_hash(
290290
dirs.add(dirname)
291291
dirname = os.path.dirname(dirname)
292292

293-
accept_dirs = frozenset(d for d in dirs if dir_filter(d))
293+
accept_dirs = frozenset(
294+
d for d in dirs if dir_filter(os.path.relpath(d, relpath) if relpath else d)
295+
)
294296
reject_dirs = tuple("{dir}/".format(dir=path) for path in (dirs - accept_dirs))
295297
accept_files = sorted(
296298
name
297299
for name in namelist
298-
if not name.endswith("/") and not name.startswith(reject_dirs) and file_filter(name)
300+
if not name.endswith("/")
301+
and not name.startswith(reject_dirs)
302+
and file_filter(os.path.relpath(name, relpath) if relpath else name)
299303
)
300304

301305
hashed_names = (

pex/pip/tool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,7 @@ def _spawn_pip_isolated_job(
552552
)
553553
return Job(command=command, process=process, finalizer=finalizer, context="pip")
554554

555-
@staticmethod
556-
def _iter_build_configuration_options(build_configuration):
555+
def _iter_build_configuration_options(self, build_configuration):
557556
# type: (BuildConfiguration) -> Iterator[str]
558557

559558
# N.B.: BuildConfiguration maintains invariants that ensure --only-binary, --no-binary,
@@ -576,7 +575,8 @@ def _iter_build_configuration_options(build_configuration):
576575
if build_configuration.prefer_older_binary:
577576
yield "--prefer-binary"
578577

579-
if build_configuration.use_pep517 is not None:
578+
# N.B.: In 25.3 `--use-pep517` became the default and only option.
579+
if build_configuration.use_pep517 is not None and self.version < PipVersion.v25_3:
580580
yield "--use-pep517" if build_configuration.use_pep517 else "--no-use-pep517"
581581

582582
if not build_configuration.build_isolation:

pex/pip/vcs.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@
2424
from pex.hashing import HintedDigest
2525

2626

27+
def _project_name_re(project_name):
28+
# type: (ProjectName) -> str
29+
return project_name.normalized.replace("-", "[-_.]+")
30+
31+
2732
def _built_source_dist_pattern(project_name):
2833
# type: (ProjectName) -> Pattern
2934
return re.compile(
30-
r"(?P<project_name>{project_name})-(?P<version>.+)\.zip".format(
31-
project_name=project_name.normalized.replace("-", "[-_.]+")
35+
r"(?P<project_name>{project_name_re})-(?P<version>.+)\.zip".format(
36+
project_name_re=_project_name_re(project_name)
3237
),
3338
re.IGNORECASE,
3439
)
@@ -78,8 +83,11 @@ def fingerprint_downloaded_vcs_archive(
7883
return Fingerprint.from_digest(digest), archive_path
7984

8085

81-
def _vcs_dir_filter(vcs):
82-
# type: (VCS.Value) -> Callable[[Text], bool]
86+
def _vcs_dir_filter(
87+
vcs, # type: VCS.Value
88+
project_name, # type: ProjectName
89+
):
90+
# type: (...) -> Callable[[Text], bool]
8391

8492
# Ignore VCS control directories for the purposes of fingerprinting the version controlled
8593
# source tree. VCS control directories can contain non-reproducible content (Git at least
@@ -92,10 +100,27 @@ def _vcs_dir_filter(vcs):
92100
# hashes the same.
93101
vcs_control_dir = ".{vcs}".format(vcs=vcs)
94102

95-
return lambda dir_path: (
96-
not is_pyc_dir(dir_path) and os.path.basename(dir_path) != vcs_control_dir
103+
# N.B.: If the VCS project uses setuptools as its build backend, depending on the version of
104+
# Pip used, the VCS checkout can have a `<project name>.egg-info/` directory littering its root
105+
# left over from Pip generating project metadata to determine version and dependencies. No other
106+
# well known build-backend has this problem at this time (checked hatchling, poetry-core,
107+
# pdm-backend and uv_build).
108+
# C.F.: https://github.com/pypa/pip/pull/13602
109+
egg_info_dir_re = re.compile(
110+
r"^{project_name_re}\.egg-info$".format(project_name_re=_project_name_re(project_name)),
111+
re.IGNORECASE,
97112
)
98113

114+
def vcs_dir_filter(dir_path):
115+
# type: (Text) -> bool
116+
if is_pyc_dir(dir_path):
117+
return False
118+
119+
base_dir_name = dir_path.split(os.sep)[0]
120+
return base_dir_name != vcs_control_dir and not egg_info_dir_re.match(base_dir_name)
121+
122+
return vcs_dir_filter
123+
99124

100125
def _vcs_file_filter(vcs):
101126
# type: (VCS.Value) -> Callable[[Text], bool]
@@ -132,12 +157,13 @@ def digest_vcs_archive(
132157
zip_path=archive_path,
133158
digest=digest,
134159
relpath=top_dir,
135-
dir_filter=_vcs_dir_filter(vcs),
160+
dir_filter=_vcs_dir_filter(vcs, project_name),
136161
file_filter=_vcs_file_filter(vcs),
137162
)
138163

139164

140165
def digest_vcs_repo(
166+
project_name, # type: ProjectName
141167
repo_path, # type: str
142168
vcs, # type: VCS.Value
143169
digest, # type: HintedDigest
@@ -148,6 +174,6 @@ def digest_vcs_repo(
148174
hashing.dir_hash(
149175
directory=os.path.join(repo_path, subdirectory) if subdirectory else repo_path,
150176
digest=digest,
151-
dir_filter=_vcs_dir_filter(vcs),
177+
dir_filter=_vcs_dir_filter(vcs, project_name),
152178
file_filter=_vcs_file_filter(vcs),
153179
)

pex/pip/version.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,13 @@ def latest_compatible(cls, target=None):
369369
requires_python=">=3.9,<3.16",
370370
)
371371

372+
v25_3 = PipVersionValue(
373+
version="25.3",
374+
setuptools_version="80.9.0",
375+
wheel_version="0.45.1",
376+
requires_python=">=3.9,<3.16",
377+
)
378+
372379
VENDORED = v20_3_4_patched
373380
LATEST = LatestPipVersion()
374381
LATEST_COMPATIBLE = LatestCompatiblePipVersion()

pex/resolve/locker.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,16 @@ def _extract_resolve_data(artifact_url):
291291
def _maybe_record_wheel(self, url):
292292
# type: (str) -> ArtifactURL
293293
artifact_url = self.parse_url_and_maybe_record_fingerprint(url)
294+
295+
# N.B.: Lock resolves driven by `pip install --dry-run --report` will only consult PEP-658
296+
# `.whl.metadata` side-car files in the happy path; so we must use these as a proxy for the
297+
# `.whl` file they are paired with.
298+
# See: https://peps.python.org/pep-0658/
299+
if not self._lock_is_via_pip_download and artifact_url.url_info.path.endswith(".metadata"):
300+
artifact_url = ArtifactURL.from_url_info(
301+
artifact_url.url_info._replace(path=artifact_url.url_info.path[:-9])
302+
)
303+
294304
if artifact_url.is_wheel:
295305
pin, partial_artifact = self._extract_resolve_data(artifact_url)
296306

@@ -445,6 +455,7 @@ def analyze(self, line):
445455
if isinstance(artifact_url.scheme, VCSScheme):
446456
digest = Sha256()
447457
digest_vcs_repo(
458+
project_name=build_result.pin.project_name,
448459
repo_path=build_result.path,
449460
vcs=artifact_url.scheme.vcs,
450461
digest=digest,

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.65.0"
4+
__version__ = "2.66.0"

tests/integration/cli/commands/test_lock.py

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -914,24 +914,38 @@ def test_update_targeted_impossible(
914914
"pip: ERROR: No matching distribution found for urllib3<1.27,>=1.21.1",
915915
]
916916
else:
917-
expected_lines = [
918-
"pip: ERROR: Cannot install requests==2.26.0 because these package versions have "
919-
"conflicting dependencies.",
920-
"pip: ERROR: ResolutionImpossible: for help visit "
921-
"https://pip.pypa.io/en/latest/topics/dependency-resolution/"
922-
"#dealing-with-dependency-conflicts",
923-
"pip: ",
924-
"pip: The conflict is caused by:",
925-
"pip: requests 2.26.0 depends on urllib3<1.27 and >=1.21.1",
926-
"pip: The user requested (constraint) urllib3<1.16",
927-
"pip: ",
928-
"pip: To fix this you could try to:",
929-
"pip: 1. loosen the range of package versions you've specified",
930-
"pip: 2. remove package versions to allow {pip_to} attempt to solve the dependency "
931-
"conflict".format(
932-
pip_to="pip" if pip_version.version < PipVersion.v24_1.version else "pip to"
933-
),
934-
]
917+
expected_lines = (
918+
[
919+
"pip: ERROR: Cannot install requests==2.26.0 because these package versions have "
920+
"conflicting dependencies.",
921+
"pip: ERROR: ResolutionImpossible: for help visit "
922+
"https://pip.pypa.io/en/latest/topics/dependency-resolution/"
923+
"#dealing-with-dependency-conflicts",
924+
"pip: ",
925+
"pip: The conflict is caused by:",
926+
"pip: requests 2.26.0 depends on urllib3<1.27 and >=1.21.1",
927+
"pip: The user requested (constraint) urllib3<1.16",
928+
"pip: ",
929+
]
930+
+ (
931+
[
932+
"pip: Additionally, some packages in these conflicts have no matching "
933+
"distributions available for your environment:",
934+
"pip: urllib3",
935+
"pip: ",
936+
]
937+
if pip_version >= PipVersion.v25_3
938+
else []
939+
)
940+
+ [
941+
"pip: To fix this you could try to:",
942+
"pip: 1. loosen the range of package versions you've specified",
943+
"pip: 2. remove package versions to allow {pip_to} attempt to solve the "
944+
"dependency conflict".format(
945+
pip_to="pip" if pip_version.version < PipVersion.v24_1.version else "pip to"
946+
),
947+
]
948+
)
935949
assert expected_lines == error_lines[12:], "\n".join(
936950
difflib.unified_diff(expected_lines, error_lines[12:])
937951
)
@@ -1013,25 +1027,39 @@ def test_update_add_impossible(
10131027
"pip: ERROR: No matching distribution found for certifi<2017.4.17",
10141028
]
10151029
else:
1016-
expected_lines = [
1017-
"pip: ERROR: Cannot install conflicting-certifi-requirement==1.2.3 and "
1018-
"requests==2.26.0 because these package versions have conflicting dependencies.",
1019-
"pip: ERROR: ResolutionImpossible: for help visit "
1020-
"https://pip.pypa.io/en/latest/topics/dependency-resolution/"
1021-
"#dealing-with-dependency-conflicts",
1022-
"pip: ",
1023-
"pip: The conflict is caused by:",
1024-
"pip: requests 2.26.0 depends on certifi>=2017.4.17",
1025-
"pip: conflicting-certifi-requirement 1.2.3 depends on certifi<2017.4.17",
1026-
"pip: The user requested (constraint) certifi==2021.5.30",
1027-
"pip: ",
1028-
"pip: To fix this you could try to:",
1029-
"pip: 1. loosen the range of package versions you've specified",
1030-
"pip: 2. remove package versions to allow {pip_to} attempt to solve the dependency "
1031-
"conflict".format(
1032-
pip_to="pip" if pip_version.version < PipVersion.v24_1.version else "pip to"
1033-
),
1034-
]
1030+
expected_lines = (
1031+
[
1032+
"pip: ERROR: Cannot install conflicting-certifi-requirement==1.2.3 and "
1033+
"requests==2.26.0 because these package versions have conflicting dependencies.",
1034+
"pip: ERROR: ResolutionImpossible: for help visit "
1035+
"https://pip.pypa.io/en/latest/topics/dependency-resolution/"
1036+
"#dealing-with-dependency-conflicts",
1037+
"pip: ",
1038+
"pip: The conflict is caused by:",
1039+
"pip: requests 2.26.0 depends on certifi>=2017.4.17",
1040+
"pip: conflicting-certifi-requirement 1.2.3 depends on certifi<2017.4.17",
1041+
"pip: The user requested (constraint) certifi==2021.5.30",
1042+
"pip: ",
1043+
]
1044+
+ (
1045+
[
1046+
"pip: Additionally, some packages in these conflicts have no matching "
1047+
"distributions available for your environment:",
1048+
"pip: certifi",
1049+
"pip: ",
1050+
]
1051+
if pip_version >= PipVersion.v25_3
1052+
else []
1053+
)
1054+
+ [
1055+
"pip: To fix this you could try to:",
1056+
"pip: 1. loosen the range of package versions you've specified",
1057+
"pip: 2. remove package versions to allow {pip_to} attempt to solve the "
1058+
"dependency conflict".format(
1059+
pip_to="pip" if pip_version.version < PipVersion.v24_1.version else "pip to"
1060+
),
1061+
]
1062+
)
10351063
assert expected_lines == error_lines[13:], "\n".join(
10361064
difflib.unified_diff(expected_lines, error_lines[12:])
10371065
)

0 commit comments

Comments
 (0)