Skip to content

Commit a75ff3f

Browse files
freakboy3742dnicolodi
authored andcommitted
ENH: add support for targeting iOS
1 parent 352ded1 commit a75ff3f

File tree

7 files changed

+133
-11
lines changed

7 files changed

+133
-11
lines changed

docs/reference/environment-variables.rst

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ Environment variables used by meson-python
6161
.. _cross build definition file: https://mesonbuild.com/Cross-compilation.html
6262
.. _cibuildwheel: https://cibuildwheel.readthedocs.io/en/stable/
6363

64+
.. envvar:: IPHONEOS_DEPLOYMENT_TARGET
65+
66+
This environment variable is used to specify the target iOS platform version
67+
to the Xcode development tools. If this environment variable is set,
68+
``meson-python`` will use the specified iOS version for the Python wheel
69+
platform tag, instead than the iOS platform default of 13.0.
70+
71+
This variable must be set to a major/minor version, for example ``13.0`` or
72+
``15.4``.
73+
74+
Note that ``IPHONEOS_DEPLOYMENT_TARGET`` is the only supported mechanism
75+
for specifying the target iOS version. Although the iOS toolchain supports
76+
the use of ``-mios-version-min`` compile and link flags to set the target iOS
77+
version, ``meson-python`` will not set the Python wheel platform tag
78+
correctly unless ``IPHONEOS_DEPLOYMENT_TARGET`` is set.
79+
6480
.. envvar:: FORCE_COLOR
6581

6682
Setting this environment variable to any value forces the use of ANSI
@@ -69,11 +85,10 @@ Environment variables used by meson-python
6985

7086
.. envvar:: MACOSX_DEPLOYMENT_TARGET
7187

72-
This environment variables is used of specifying the target macOS platform
73-
major version to the Xcode development tools. If this environment variable
74-
is set, ``meson-python`` will use the specified macOS version for the
75-
Python wheel platform tag instead than the macOS version of the build
76-
machine.
88+
This environment variable is used to specify the target macOS platform major
89+
version to the Xcode development tools. If this environment variable is set,
90+
``meson-python`` will use the specified macOS version for the Python wheel
91+
platform tag instead than the macOS version of the build machine.
7792

7893
This variable must be set to macOS major versions only: ``10.9`` to
7994
``10.15``, ``11``, ``12``, ``13``, ...
@@ -84,10 +99,11 @@ Environment variables used by meson-python
8499
are currently designed to specify compatibility only with major version
85100
number granularity.
86101

87-
Another way of specifying the target macOS platform is to use the
88-
``-mmacosx-version-min`` compile and link flags. However, it is not
89-
possible for ``meson-python`` to detect this, and it will not set the
90-
Python wheel platform tag accordingly.
102+
Note that ``MACOSX_DEPLOYMENT_TARGET`` is the only supported mechanism for
103+
specifying the target macOS version. Although the macOS toolchain supports
104+
the use of ``-mmacosx-version-min`` compile and link flags to set the target
105+
macOS version, ``meson-python`` will not set the Python wheel platform tag
106+
correctly unless ``MACOSX_DEPLOYMENT_TARGET`` is set.
91107

92108
.. envvar:: MESON
93109

docs/reference/meson-compatibility.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ versions.
5252
populate the package license and license files from the ones
5353
declared via the ``project()`` call in ``meson.build``.
5454

55+
.. option:: 1.9.0
56+
57+
Meson 1.9.0 or later is required to support building for iOS.
58+
5559
Build front-ends by default build packages in an isolated Python
5660
environment where build dependencies are installed. Most often, unless
5761
a package or its build dependencies declare explicitly a version

mesonpy/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,30 @@ def __init__(
719719
''')
720720
self._meson_cross_file.write_text(cross_file_data, encoding='utf-8')
721721
self._meson_args['setup'].extend(('--cross-file', os.fspath(self._meson_cross_file)))
722+
elif sysconfig.get_platform().startswith('ios-'):
723+
ios_ver = platform.ios_ver() # type: ignore[attr-defined]
724+
725+
arch = platform.machine()
726+
family = 'aarch64' if arch == 'arm64' else arch
727+
subsystem = 'ios-simulator' if ios_ver.is_simulator else 'ios'
728+
729+
cross_file_data = textwrap.dedent(f'''
730+
[binaries]
731+
c = '{arch}-apple-{subsystem}-clang'
732+
cpp = '{arch}-apple-{subsystem}-clang++'
733+
objc = '{arch}-apple-{subsystem}-clang'
734+
objcpp = '{arch}-apple-{subsystem}-clang++'
735+
ar = '{arch}-apple-{subsystem}-ar'
736+
737+
[host_machine]
738+
system = 'ios'
739+
subsystem = {subsystem!r}
740+
cpu = {arch!r}
741+
cpu_family = {family!r}
742+
endian = 'little'
743+
''')
744+
self._meson_cross_file.write_text(cross_file_data, encoding='utf-8')
745+
self._meson_args['setup'].extend(('--cross-file', os.fspath(self._meson_cross_file)))
722746

723747
# write the native file
724748
native_file_data = textwrap.dedent(f'''

mesonpy/_tags.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,28 @@ def _get_macosx_platform_tag() -> str:
160160
return f'macosx_{major}_{minor}_{arch}'
161161

162162

163+
def _get_ios_platform_tag() -> str:
164+
# Override the iOS version if one is provided via the
165+
# IPHONEOS_DEPLOYMENT_TARGET environment variable.
166+
try:
167+
version = tuple(map(int, os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', '').split('.')))[:2]
168+
except ValueError:
169+
version = tuple(map(int, platform.ios_ver().release.split('.')))[:2] # type: ignore[attr-defined]
170+
171+
# Although _multiarch is an internal implementation detail, it's a core part
172+
# of how CPython is implemented on iOS; this attribute is also relied upon
173+
# by `packaging` as part of tag determiniation.
174+
multiarch = sys.implementation._multiarch.replace('-', '_')
175+
176+
return f"ios_{version[0]}_{version[1]}_{multiarch}"
177+
178+
163179
def get_platform_tag() -> str:
164180
platform = sysconfig.get_platform()
165181
if platform.startswith('macosx'):
166182
return _get_macosx_platform_tag()
183+
if platform.startswith('ios'):
184+
return _get_ios_platform_tag()
167185
if _32_BIT_INTERPRETER:
168186
# 32-bit Python running on a 64-bit kernel.
169187
if platform == 'linux-x86_64':

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ backend-path = ['.']
88
requires = [
99
'meson >= 0.64.0; python_version < "3.12"',
1010
'meson >= 1.2.3; python_version >= "3.12"',
11-
'packaging >= 23.2',
11+
'packaging >= 23.2; sys_platform != "ios"',
12+
'packaging >= 24.2; sys_platform == "ios"',
1213
'pyproject-metadata >= 0.9.0',
1314
'tomli >= 1.0.0; python_version < "3.11"',
1415
]
@@ -37,7 +38,8 @@ classifiers = [
3738
dependencies = [
3839
'meson >= 0.64.0; python_version < "3.12"',
3940
'meson >= 1.2.3; python_version >= "3.12"',
40-
'packaging >= 23.2',
41+
'packaging >= 23.2; sys_platform != "ios"',
42+
'packaging >= 24.2; sys_platform == "ios"',
4143
'pyproject-metadata >= 0.9.0',
4244
'tomli >= 1.0.0; python_version < "3.11"',
4345
]

tests/test_project.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44

55
import ast
66
import os
7+
import platform
78
import shutil
89
import sys
10+
import sysconfig
911
import textwrap
1012

13+
from unittest.mock import Mock
14+
1115

1216
if sys.version_info < (3, 11):
1317
import tomli as tomllib
@@ -373,3 +377,37 @@ def test_archflags_envvar_parsing_invalid(package_purelib_and_platlib, monkeypat
373377
finally:
374378
# revert environment variable setting done by the in-process build
375379
os.environ.pop('_PYTHON_HOST_PLATFORM', None)
380+
381+
382+
@pytest.mark.skipif(sys.version_info < (3, 13), reason='requires Python 3.13 or higher')
383+
@pytest.mark.parametrize('multiarch', [
384+
'arm64-iphoneos',
385+
'arm64-iphonesimulator',
386+
'x86_64-iphonesimulator',
387+
])
388+
def test_ios_project(package_simple, monkeypatch, multiarch, tmp_path):
389+
arch, abi = multiarch.split('-')
390+
subsystem = 'ios-simulator' if abi == 'iphonesimulator' else 'ios'
391+
392+
# Mock being on iOS
393+
monkeypatch.setattr(sys, 'platform', 'ios')
394+
monkeypatch.setattr(platform, 'machine', Mock(return_value=arch))
395+
monkeypatch.setattr(sysconfig, 'get_platform', Mock(return_value=f"ios-13.0-{multiarch}"))
396+
ios_ver = platform.IOSVersionInfo('iOS', '13.0', 'iPhone', multiarch.endswith('simulator'))
397+
monkeypatch.setattr(platform, 'ios_ver', Mock(return_value=ios_ver))
398+
399+
# Create an iOS project.
400+
project = mesonpy.Project(source_dir=package_simple, build_dir=tmp_path)
401+
402+
# Meson configuration points at the cross file
403+
assert project._meson_args['setup'] == ['--cross-file', os.fspath(tmp_path / 'meson-python-cross-file.ini')]
404+
405+
# Meson config files exist, and have some relevant keys
406+
assert (tmp_path / 'meson-python-native-file.ini').exists()
407+
assert (tmp_path / 'meson-python-cross-file.ini').exists()
408+
409+
cross_config = (tmp_path / 'meson-python-cross-file.ini').read_text()
410+
411+
assert "\nsystem = 'ios'\n" in cross_config
412+
assert f"\nc = '{arch}-apple-{subsystem}-clang'\n" in cross_config
413+
assert f"\nsubsystem = '{subsystem}'\n" in cross_config

tests/test_tags.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sysconfig
1111

1212
from collections import defaultdict
13+
from unittest.mock import Mock
1314

1415
import packaging.tags
1516
import pytest
@@ -74,6 +75,25 @@ def test_python_host_platform(monkeypatch):
7475
assert mesonpy._tags.get_platform_tag().endswith('x86_64')
7576

7677

78+
@pytest.mark.skipif(sys.version_info < (3, 13), reason='requires Python 3.13 or higher')
79+
@pytest.mark.skipif(sys.platform != 'darwin', reason='macOS specific test')
80+
def test_ios_platform_tag(monkeypatch):
81+
# Mock being on iOS
82+
monkeypatch.setattr(sys.implementation, '_multiarch', 'arm64-iphoneos')
83+
monkeypatch.setattr(sysconfig, 'get_platform', Mock(return_value='ios-13.0-arm64-iphoneos'))
84+
ios_ver = platform.IOSVersionInfo('iOS', '13.0', 'iPhone', False)
85+
monkeypatch.setattr(platform, 'ios_ver', Mock(return_value=ios_ver))
86+
87+
# Check the default value
88+
assert next(packaging.tags.ios_platforms((13, 0))) == mesonpy._tags.get_platform_tag()
89+
90+
# Check the value when IPHONEOS_DEPLOYMENT_TARGET is set.
91+
for major in range(13, 20):
92+
for minor in range(3):
93+
monkeypatch.setenv('IPHONEOS_DEPLOYMENT_TARGET', f'{major}.{minor}')
94+
assert next(packaging.tags.ios_platforms((major, minor))) == mesonpy._tags.get_platform_tag()
95+
96+
7797
def wheel_builder_test_factory(content, pure=True, limited_api=False):
7898
manifest = defaultdict(list)
7999
manifest.update({key: [(pathlib.Path(x), os.path.join('build', x)) for x in value] for key, value in content.items()})

0 commit comments

Comments
 (0)