Skip to content

Commit 9fd6b86

Browse files
authored
Merge branch 'main' into update-non-PEP-440-wheel-filename-deprecation-notice
2 parents 7ddafd0 + d5c8c11 commit 9fd6b86

File tree

7 files changed

+255
-9
lines changed

7 files changed

+255
-9
lines changed

news/12961.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support for PEP 730 iOS wheels was added.

src/pip/_internal/utils/compatibility_tags.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
generic_tags,
1313
interpreter_name,
1414
interpreter_version,
15+
ios_platforms,
1516
mac_platforms,
1617
)
1718

18-
_osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
19+
_apple_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
1920

2021

2122
def version_info_to_nodot(version_info: Tuple[int, ...]) -> str:
@@ -24,7 +25,7 @@ def version_info_to_nodot(version_info: Tuple[int, ...]) -> str:
2425

2526

2627
def _mac_platforms(arch: str) -> List[str]:
27-
match = _osx_arch_pat.match(arch)
28+
match = _apple_arch_pat.match(arch)
2829
if match:
2930
name, major, minor, actual_arch = match.groups()
3031
mac_version = (int(major), int(minor))
@@ -43,6 +44,26 @@ def _mac_platforms(arch: str) -> List[str]:
4344
return arches
4445

4546

47+
def _ios_platforms(arch: str) -> List[str]:
48+
match = _apple_arch_pat.match(arch)
49+
if match:
50+
name, major, minor, actual_multiarch = match.groups()
51+
ios_version = (int(major), int(minor))
52+
arches = [
53+
# Since we have always only checked that the platform starts
54+
# with "ios", for backwards-compatibility we extract the
55+
# actual prefix provided by the user in case they provided
56+
# something like "ioscustom_". It may be good to remove
57+
# this as undocumented or deprecate it in the future.
58+
"{}_{}".format(name, arch[len("ios_") :])
59+
for arch in ios_platforms(ios_version, actual_multiarch)
60+
]
61+
else:
62+
# arch pattern didn't match (?!)
63+
arches = [arch]
64+
return arches
65+
66+
4667
def _custom_manylinux_platforms(arch: str) -> List[str]:
4768
arches = [arch]
4869
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
@@ -68,6 +89,8 @@ def _get_custom_platforms(arch: str) -> List[str]:
6889
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
6990
if arch.startswith("macosx"):
7091
arches = _mac_platforms(arch)
92+
elif arch.startswith("ios"):
93+
arches = _ios_platforms(arch)
7194
elif arch_prefix in ["manylinux2014", "manylinux2010"]:
7295
arches = _custom_manylinux_platforms(arch)
7396
else:

src/pip/_vendor/distlib/scripts.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ def _build_shebang(self, executable, post_interp):
164164
"""
165165
if os.name != 'posix':
166166
simple_shebang = True
167+
elif getattr(sys, "cross_compiling", False):
168+
# In a cross-compiling environment, the shebang will likely be a
169+
# script; this *must* be invoked with the "safe" version of the
170+
# shebang, or else using os.exec() to run the entry script will
171+
# fail, raising "OSError 8 [Errno 8] Exec format error".
172+
simple_shebang = False
167173
else:
168174
# Add 3 for '#!' prefix and newline suffix.
169175
shebang_length = len(executable) + len(post_interp) + 3

src/pip/_vendor/packaging/tags.py

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
logger = logging.getLogger(__name__)
2626

2727
PythonVersion = Sequence[int]
28-
MacVersion = Tuple[int, int]
28+
AppleVersion = Tuple[int, int]
2929

3030
INTERPRETER_SHORT_NAMES: dict[str, str] = {
3131
"python": "py", # Generic.
@@ -363,7 +363,7 @@ def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
363363
return "i386"
364364

365365

366-
def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> list[str]:
366+
def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
367367
formats = [cpu_arch]
368368
if cpu_arch == "x86_64":
369369
if version < (10, 4):
@@ -396,7 +396,7 @@ def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> list[str]:
396396

397397

398398
def mac_platforms(
399-
version: MacVersion | None = None, arch: str | None = None
399+
version: AppleVersion | None = None, arch: str | None = None
400400
) -> Iterator[str]:
401401
"""
402402
Yields the platform tags for a macOS system.
@@ -408,7 +408,7 @@ def mac_platforms(
408408
"""
409409
version_str, _, cpu_arch = platform.mac_ver()
410410
if version is None:
411-
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
411+
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
412412
if version == (10, 16):
413413
# When built against an older macOS SDK, Python will report macOS 10.16
414414
# instead of the real version.
@@ -424,7 +424,7 @@ def mac_platforms(
424424
stdout=subprocess.PIPE,
425425
text=True,
426426
).stdout
427-
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
427+
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
428428
else:
429429
version = version
430430
if arch is None:
@@ -483,6 +483,63 @@ def mac_platforms(
483483
)
484484

485485

486+
def ios_platforms(
487+
version: AppleVersion | None = None, multiarch: str | None = None
488+
) -> Iterator[str]:
489+
"""
490+
Yields the platform tags for an iOS system.
491+
492+
:param version: A two-item tuple specifying the iOS version to generate
493+
platform tags for. Defaults to the current iOS version.
494+
:param multiarch: The CPU architecture+ABI to generate platform tags for -
495+
(the value used by `sys.implementation._multiarch` e.g.,
496+
`arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
497+
multiarch value.
498+
"""
499+
if version is None:
500+
# if iOS is the current platform, ios_ver *must* be defined. However,
501+
# it won't exist for CPython versions before 3.13, which causes a mypy
502+
# error.
503+
_, release, _, _ = platform.ios_ver() # type: ignore[attr-defined]
504+
version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
505+
506+
if multiarch is None:
507+
multiarch = sys.implementation._multiarch
508+
multiarch = multiarch.replace("-", "_")
509+
510+
ios_platform_template = "ios_{major}_{minor}_{multiarch}"
511+
512+
# Consider any iOS major.minor version from the version requested, down to
513+
# 12.0. 12.0 is the first iOS version that is known to have enough features
514+
# to support CPython. Consider every possible minor release up to X.9. There
515+
# highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
516+
# candidates that won't ever match doesn't really hurt, and it saves us from
517+
# having to keep an explicit list of known iOS versions in the code. Return
518+
# the results descending order of version number.
519+
520+
# If the requested major version is less than 12, there won't be any matches.
521+
if version[0] < 12:
522+
return
523+
524+
# Consider the actual X.Y version that was requested.
525+
yield ios_platform_template.format(
526+
major=version[0], minor=version[1], multiarch=multiarch
527+
)
528+
529+
# Consider every minor version from X.0 to the minor version prior to the
530+
# version requested by the platform.
531+
for minor in range(version[1] - 1, -1, -1):
532+
yield ios_platform_template.format(
533+
major=version[0], minor=minor, multiarch=multiarch
534+
)
535+
536+
for major in range(version[0] - 1, 11, -1):
537+
for minor in range(9, -1, -1):
538+
yield ios_platform_template.format(
539+
major=major, minor=minor, multiarch=multiarch
540+
)
541+
542+
486543
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
487544
linux = _normalize_string(sysconfig.get_platform())
488545
if not linux.startswith("linux_"):
@@ -512,6 +569,8 @@ def platform_tags() -> Iterator[str]:
512569
"""
513570
if platform.system() == "Darwin":
514571
return mac_platforms()
572+
elif platform.system() == "iOS":
573+
return ios_platforms()
515574
elif platform.system() == "Linux":
516575
return _linux_platforms()
517576
else:

tests/unit/test_models_wheel.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,28 @@ def test_not_supported_multiarch_darwin(self) -> None:
148148
assert not w.supported(tags=intel)
149149
assert not w.supported(tags=universal)
150150

151+
def test_supported_ios_version(self) -> None:
152+
"""
153+
Wheels build for iOS 12.3 are supported on iOS 15.1
154+
"""
155+
tags = compatibility_tags.get_supported(
156+
"313", platforms=["ios_15_1_arm64_iphoneos"], impl="cp"
157+
)
158+
w = Wheel("simple-0.1-cp313-none-ios_12_3_arm64_iphoneos.whl")
159+
assert w.supported(tags=tags)
160+
w = Wheel("simple-0.1-cp313-none-ios_15_1_arm64_iphoneos.whl")
161+
assert w.supported(tags=tags)
162+
163+
def test_not_supported_ios_version(self) -> None:
164+
"""
165+
Wheels built for macOS 15.1 are not supported on 12.3
166+
"""
167+
tags = compatibility_tags.get_supported(
168+
"313", platforms=["ios_12_3_arm64_iphoneos"], impl="cp"
169+
)
170+
w = Wheel("simple-0.1-cp313-none-ios_15_1_arm64_iphoneos.whl")
171+
assert not w.supported(tags=tags)
172+
151173
def test_support_index_min(self) -> None:
152174
"""
153175
Test results from `support_index_min`

tools/vendoring/patches/distlib.patch

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ index cfa45d2af..e16292b83 100644
55
@@ -49,6 +49,24 @@ if __name__ == '__main__':
66
sys.exit(%(func)s())
77
'''
8-
8+
99
+# Pre-fetch the contents of all executable wrapper stubs.
1010
+# This is to address https://github.com/pypa/pip/issues/12666.
1111
+# When updating pip, we rename the old pip in place before installing the
@@ -24,9 +24,22 @@ index cfa45d2af..e16292b83 100644
2424
+ if r.name.endswith(".exe")
2525
+}
2626
+
27-
27+
2828
def enquote_executable(executable):
2929
if ' ' in executable:
30+
@@ -164,6 +164,12 @@ class ScriptMaker(object):
31+
"""
32+
if os.name != 'posix':
33+
simple_shebang = True
34+
+ elif getattr(sys, "cross_compiling", False):
35+
+ # In a cross-compiling environment, the shebang will likely be a
36+
+ # script; this *must* be invoked with the "safe" version of the
37+
+ # shebang, or else using os.exec() to run the entry script will
38+
+ # fail, raising "OSError 8 [Errno 8] Exec format error".
39+
+ simple_shebang = False
40+
else:
41+
# Add 3 for '#!' prefix and newline suffix.
42+
shebang_length = len(executable) + len(post_interp) + 3
3043
@@ -409,15 +427,11 @@ class ScriptMaker(object):
3144
bits = '32'
3245
platform_suffix = '-arm' if get_platform() == 'win-arm64' else ''
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
diff --git a/src/pip/_vendor/packaging/tags.py b/src/pip/_vendor/packaging/tags.py
2+
index 6667d2990..cb11c60b8 100644
3+
--- a/src/pip/_vendor/packaging/tags.py
4+
+++ b/src/pip/_vendor/packaging/tags.py
5+
@@ -25,7 +25,7 @@ from . import _manylinux, _musllinux
6+
logger = logging.getLogger(__name__)
7+
8+
PythonVersion = Sequence[int]
9+
-MacVersion = Tuple[int, int]
10+
+AppleVersion = Tuple[int, int]
11+
12+
INTERPRETER_SHORT_NAMES: dict[str, str] = {
13+
"python": "py", # Generic.
14+
@@ -363,7 +363,7 @@ def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
15+
return "i386"
16+
17+
18+
-def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> list[str]:
19+
+def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
20+
formats = [cpu_arch]
21+
if cpu_arch == "x86_64":
22+
if version < (10, 4):
23+
@@ -396,7 +396,7 @@ def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> list[str]:
24+
25+
26+
def mac_platforms(
27+
- version: MacVersion | None = None, arch: str | None = None
28+
+ version: AppleVersion | None = None, arch: str | None = None
29+
) -> Iterator[str]:
30+
"""
31+
Yields the platform tags for a macOS system.
32+
@@ -408,7 +408,7 @@ def mac_platforms(
33+
"""
34+
version_str, _, cpu_arch = platform.mac_ver()
35+
if version is None:
36+
- version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
37+
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
38+
if version == (10, 16):
39+
# When built against an older macOS SDK, Python will report macOS 10.16
40+
# instead of the real version.
41+
@@ -424,7 +424,7 @@ def mac_platforms(
42+
stdout=subprocess.PIPE,
43+
text=True,
44+
).stdout
45+
- version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
46+
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
47+
else:
48+
version = version
49+
if arch is None:
50+
@@ -483,6 +483,63 @@ def mac_platforms(
51+
)
52+
53+
54+
+def ios_platforms(
55+
+ version: AppleVersion | None = None, multiarch: str | None = None
56+
+) -> Iterator[str]:
57+
+ """
58+
+ Yields the platform tags for an iOS system.
59+
+
60+
+ :param version: A two-item tuple specifying the iOS version to generate
61+
+ platform tags for. Defaults to the current iOS version.
62+
+ :param multiarch: The CPU architecture+ABI to generate platform tags for -
63+
+ (the value used by `sys.implementation._multiarch` e.g.,
64+
+ `arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
65+
+ multiarch value.
66+
+ """
67+
+ if version is None:
68+
+ # if iOS is the current platform, ios_ver *must* be defined. However,
69+
+ # it won't exist for CPython versions before 3.13, which causes a mypy
70+
+ # error.
71+
+ _, release, _, _ = platform.ios_ver() # type: ignore[attr-defined]
72+
+ version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
73+
+
74+
+ if multiarch is None:
75+
+ multiarch = sys.implementation._multiarch
76+
+ multiarch = multiarch.replace("-", "_")
77+
+
78+
+ ios_platform_template = "ios_{major}_{minor}_{multiarch}"
79+
+
80+
+ # Consider any iOS major.minor version from the version requested, down to
81+
+ # 12.0. 12.0 is the first iOS version that is known to have enough features
82+
+ # to support CPython. Consider every possible minor release up to X.9. There
83+
+ # highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
84+
+ # candidates that won't ever match doesn't really hurt, and it saves us from
85+
+ # having to keep an explicit list of known iOS versions in the code. Return
86+
+ # the results descending order of version number.
87+
+
88+
+ # If the requested major version is less than 12, there won't be any matches.
89+
+ if version[0] < 12:
90+
+ return
91+
+
92+
+ # Consider the actual X.Y version that was requested.
93+
+ yield ios_platform_template.format(
94+
+ major=version[0], minor=version[1], multiarch=multiarch
95+
+ )
96+
+
97+
+ # Consider every minor version from X.0 to the minor version prior to the
98+
+ # version requested by the platform.
99+
+ for minor in range(version[1] - 1, -1, -1):
100+
+ yield ios_platform_template.format(
101+
+ major=version[0], minor=minor, multiarch=multiarch
102+
+ )
103+
+
104+
+ for major in range(version[0] - 1, 11, -1):
105+
+ for minor in range(9, -1, -1):
106+
+ yield ios_platform_template.format(
107+
+ major=major, minor=minor, multiarch=multiarch
108+
+ )
109+
+
110+
+
111+
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
112+
linux = _normalize_string(sysconfig.get_platform())
113+
if not linux.startswith("linux_"):
114+
@@ -512,6 +569,8 @@ def platform_tags() -> Iterator[str]:
115+
"""
116+
if platform.system() == "Darwin":
117+
return mac_platforms()
118+
+ elif platform.system() == "iOS":
119+
+ return ios_platforms()
120+
elif platform.system() == "Linux":
121+
return _linux_platforms()
122+
else:

0 commit comments

Comments
 (0)