Skip to content

Commit

Permalink
Merge pull request #12962 from freakboy3742/ios-support
Browse files Browse the repository at this point in the history
  • Loading branch information
jezdez authored Oct 8, 2024
2 parents 93fa89c + 9a8e6a7 commit d5c8c11
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 9 deletions.
1 change: 1 addition & 0 deletions news/12961.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support for PEP 730 iOS wheels was added.
27 changes: 25 additions & 2 deletions src/pip/_internal/utils/compatibility_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
generic_tags,
interpreter_name,
interpreter_version,
ios_platforms,
mac_platforms,
)

_osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
_apple_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")


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


def _mac_platforms(arch: str) -> List[str]:
match = _osx_arch_pat.match(arch)
match = _apple_arch_pat.match(arch)
if match:
name, major, minor, actual_arch = match.groups()
mac_version = (int(major), int(minor))
Expand All @@ -43,6 +44,26 @@ def _mac_platforms(arch: str) -> List[str]:
return arches


def _ios_platforms(arch: str) -> List[str]:
match = _apple_arch_pat.match(arch)
if match:
name, major, minor, actual_multiarch = match.groups()
ios_version = (int(major), int(minor))
arches = [
# Since we have always only checked that the platform starts
# with "ios", for backwards-compatibility we extract the
# actual prefix provided by the user in case they provided
# something like "ioscustom_". It may be good to remove
# this as undocumented or deprecate it in the future.
"{}_{}".format(name, arch[len("ios_") :])
for arch in ios_platforms(ios_version, actual_multiarch)
]
else:
# arch pattern didn't match (?!)
arches = [arch]
return arches


def _custom_manylinux_platforms(arch: str) -> List[str]:
arches = [arch]
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
Expand All @@ -68,6 +89,8 @@ def _get_custom_platforms(arch: str) -> List[str]:
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
if arch.startswith("macosx"):
arches = _mac_platforms(arch)
elif arch.startswith("ios"):
arches = _ios_platforms(arch)
elif arch_prefix in ["manylinux2014", "manylinux2010"]:
arches = _custom_manylinux_platforms(arch)
else:
Expand Down
6 changes: 6 additions & 0 deletions src/pip/_vendor/distlib/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ def _build_shebang(self, executable, post_interp):
"""
if os.name != 'posix':
simple_shebang = True
elif getattr(sys, "cross_compiling", False):
# In a cross-compiling environment, the shebang will likely be a
# script; this *must* be invoked with the "safe" version of the
# shebang, or else using os.exec() to run the entry script will
# fail, raising "OSError 8 [Errno 8] Exec format error".
simple_shebang = False
else:
# Add 3 for '#!' prefix and newline suffix.
shebang_length = len(executable) + len(post_interp) + 3
Expand Down
69 changes: 64 additions & 5 deletions src/pip/_vendor/packaging/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
logger = logging.getLogger(__name__)

PythonVersion = Sequence[int]
MacVersion = Tuple[int, int]
AppleVersion = Tuple[int, int]

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


def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> list[str]:
def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
formats = [cpu_arch]
if cpu_arch == "x86_64":
if version < (10, 4):
Expand Down Expand Up @@ -396,7 +396,7 @@ def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> list[str]:


def mac_platforms(
version: MacVersion | None = None, arch: str | None = None
version: AppleVersion | None = None, arch: str | None = None
) -> Iterator[str]:
"""
Yields the platform tags for a macOS system.
Expand All @@ -408,7 +408,7 @@ def mac_platforms(
"""
version_str, _, cpu_arch = platform.mac_ver()
if version is None:
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
if version == (10, 16):
# When built against an older macOS SDK, Python will report macOS 10.16
# instead of the real version.
Expand All @@ -424,7 +424,7 @@ def mac_platforms(
stdout=subprocess.PIPE,
text=True,
).stdout
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
else:
version = version
if arch is None:
Expand Down Expand Up @@ -483,6 +483,63 @@ def mac_platforms(
)


def ios_platforms(
version: AppleVersion | None = None, multiarch: str | None = None
) -> Iterator[str]:
"""
Yields the platform tags for an iOS system.
:param version: A two-item tuple specifying the iOS version to generate
platform tags for. Defaults to the current iOS version.
:param multiarch: The CPU architecture+ABI to generate platform tags for -
(the value used by `sys.implementation._multiarch` e.g.,
`arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
multiarch value.
"""
if version is None:
# if iOS is the current platform, ios_ver *must* be defined. However,
# it won't exist for CPython versions before 3.13, which causes a mypy
# error.
_, release, _, _ = platform.ios_ver() # type: ignore[attr-defined]
version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))

if multiarch is None:
multiarch = sys.implementation._multiarch
multiarch = multiarch.replace("-", "_")

ios_platform_template = "ios_{major}_{minor}_{multiarch}"

# Consider any iOS major.minor version from the version requested, down to
# 12.0. 12.0 is the first iOS version that is known to have enough features
# to support CPython. Consider every possible minor release up to X.9. There
# highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
# candidates that won't ever match doesn't really hurt, and it saves us from
# having to keep an explicit list of known iOS versions in the code. Return
# the results descending order of version number.

# If the requested major version is less than 12, there won't be any matches.
if version[0] < 12:
return

# Consider the actual X.Y version that was requested.
yield ios_platform_template.format(
major=version[0], minor=version[1], multiarch=multiarch
)

# Consider every minor version from X.0 to the minor version prior to the
# version requested by the platform.
for minor in range(version[1] - 1, -1, -1):
yield ios_platform_template.format(
major=version[0], minor=minor, multiarch=multiarch
)

for major in range(version[0] - 1, 11, -1):
for minor in range(9, -1, -1):
yield ios_platform_template.format(
major=major, minor=minor, multiarch=multiarch
)


def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
linux = _normalize_string(sysconfig.get_platform())
if not linux.startswith("linux_"):
Expand Down Expand Up @@ -512,6 +569,8 @@ def platform_tags() -> Iterator[str]:
"""
if platform.system() == "Darwin":
return mac_platforms()
elif platform.system() == "iOS":
return ios_platforms()
elif platform.system() == "Linux":
return _linux_platforms()
else:
Expand Down
22 changes: 22 additions & 0 deletions tests/unit/test_models_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,28 @@ def test_not_supported_multiarch_darwin(self) -> None:
assert not w.supported(tags=intel)
assert not w.supported(tags=universal)

def test_supported_ios_version(self) -> None:
"""
Wheels build for iOS 12.3 are supported on iOS 15.1
"""
tags = compatibility_tags.get_supported(
"313", platforms=["ios_15_1_arm64_iphoneos"], impl="cp"
)
w = Wheel("simple-0.1-cp313-none-ios_12_3_arm64_iphoneos.whl")
assert w.supported(tags=tags)
w = Wheel("simple-0.1-cp313-none-ios_15_1_arm64_iphoneos.whl")
assert w.supported(tags=tags)

def test_not_supported_ios_version(self) -> None:
"""
Wheels built for macOS 15.1 are not supported on 12.3
"""
tags = compatibility_tags.get_supported(
"313", platforms=["ios_12_3_arm64_iphoneos"], impl="cp"
)
w = Wheel("simple-0.1-cp313-none-ios_15_1_arm64_iphoneos.whl")
assert not w.supported(tags=tags)

def test_support_index_min(self) -> None:
"""
Test results from `support_index_min`
Expand Down
17 changes: 15 additions & 2 deletions tools/vendoring/patches/distlib.patch
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ index cfa45d2af..e16292b83 100644
@@ -49,6 +49,24 @@ if __name__ == '__main__':
sys.exit(%(func)s())
'''

+# Pre-fetch the contents of all executable wrapper stubs.
+# This is to address https://github.com/pypa/pip/issues/12666.
+# When updating pip, we rename the old pip in place before installing the
Expand All @@ -24,9 +24,22 @@ index cfa45d2af..e16292b83 100644
+ if r.name.endswith(".exe")
+}
+

def enquote_executable(executable):
if ' ' in executable:
@@ -164,6 +164,12 @@ class ScriptMaker(object):
"""
if os.name != 'posix':
simple_shebang = True
+ elif getattr(sys, "cross_compiling", False):
+ # In a cross-compiling environment, the shebang will likely be a
+ # script; this *must* be invoked with the "safe" version of the
+ # shebang, or else using os.exec() to run the entry script will
+ # fail, raising "OSError 8 [Errno 8] Exec format error".
+ simple_shebang = False
else:
# Add 3 for '#!' prefix and newline suffix.
shebang_length = len(executable) + len(post_interp) + 3
@@ -409,15 +427,11 @@ class ScriptMaker(object):
bits = '32'
platform_suffix = '-arm' if get_platform() == 'win-arm64' else ''
Expand Down
122 changes: 122 additions & 0 deletions tools/vendoring/patches/packaging.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
diff --git a/src/pip/_vendor/packaging/tags.py b/src/pip/_vendor/packaging/tags.py
index 6667d2990..cb11c60b8 100644
--- a/src/pip/_vendor/packaging/tags.py
+++ b/src/pip/_vendor/packaging/tags.py
@@ -25,7 +25,7 @@ from . import _manylinux, _musllinux
logger = logging.getLogger(__name__)

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

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


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


def mac_platforms(
- version: MacVersion | None = None, arch: str | None = None
+ version: AppleVersion | None = None, arch: str | None = None
) -> Iterator[str]:
"""
Yields the platform tags for a macOS system.
@@ -408,7 +408,7 @@ def mac_platforms(
"""
version_str, _, cpu_arch = platform.mac_ver()
if version is None:
- version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
if version == (10, 16):
# When built against an older macOS SDK, Python will report macOS 10.16
# instead of the real version.
@@ -424,7 +424,7 @@ def mac_platforms(
stdout=subprocess.PIPE,
text=True,
).stdout
- version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
else:
version = version
if arch is None:
@@ -483,6 +483,63 @@ def mac_platforms(
)


+def ios_platforms(
+ version: AppleVersion | None = None, multiarch: str | None = None
+) -> Iterator[str]:
+ """
+ Yields the platform tags for an iOS system.
+
+ :param version: A two-item tuple specifying the iOS version to generate
+ platform tags for. Defaults to the current iOS version.
+ :param multiarch: The CPU architecture+ABI to generate platform tags for -
+ (the value used by `sys.implementation._multiarch` e.g.,
+ `arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
+ multiarch value.
+ """
+ if version is None:
+ # if iOS is the current platform, ios_ver *must* be defined. However,
+ # it won't exist for CPython versions before 3.13, which causes a mypy
+ # error.
+ _, release, _, _ = platform.ios_ver() # type: ignore[attr-defined]
+ version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
+
+ if multiarch is None:
+ multiarch = sys.implementation._multiarch
+ multiarch = multiarch.replace("-", "_")
+
+ ios_platform_template = "ios_{major}_{minor}_{multiarch}"
+
+ # Consider any iOS major.minor version from the version requested, down to
+ # 12.0. 12.0 is the first iOS version that is known to have enough features
+ # to support CPython. Consider every possible minor release up to X.9. There
+ # highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
+ # candidates that won't ever match doesn't really hurt, and it saves us from
+ # having to keep an explicit list of known iOS versions in the code. Return
+ # the results descending order of version number.
+
+ # If the requested major version is less than 12, there won't be any matches.
+ if version[0] < 12:
+ return
+
+ # Consider the actual X.Y version that was requested.
+ yield ios_platform_template.format(
+ major=version[0], minor=version[1], multiarch=multiarch
+ )
+
+ # Consider every minor version from X.0 to the minor version prior to the
+ # version requested by the platform.
+ for minor in range(version[1] - 1, -1, -1):
+ yield ios_platform_template.format(
+ major=version[0], minor=minor, multiarch=multiarch
+ )
+
+ for major in range(version[0] - 1, 11, -1):
+ for minor in range(9, -1, -1):
+ yield ios_platform_template.format(
+ major=major, minor=minor, multiarch=multiarch
+ )
+
+
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
linux = _normalize_string(sysconfig.get_platform())
if not linux.startswith("linux_"):
@@ -512,6 +569,8 @@ def platform_tags() -> Iterator[str]:
"""
if platform.system() == "Darwin":
return mac_platforms()
+ elif platform.system() == "iOS":
+ return ios_platforms()
elif platform.system() == "Linux":
return _linux_platforms()
else:

0 comments on commit d5c8c11

Please sign in to comment.