Skip to content

Commit 3a7e044

Browse files
freakboy3742hugovkmhsmithned-deily
committed
[3.11] pythongh-114099: Additions to standard library to support iOS (pythonGH-117052)
Co-authored-by: Hugo van Kemenade <[email protected]> Co-authored-by: Malcolm Smith <[email protected]> Co-authored-by: Ned Deily <[email protected]>
1 parent 9ea7af1 commit 3a7e044

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+569
-85
lines changed

Doc/library/os.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,11 @@ process and user.
735735
:func:`socket.gethostname` or even
736736
``socket.gethostbyaddr(socket.gethostname())``.
737737

738+
On macOS, iOS and Android, this returns the *kernel* name and version (i.e.,
739+
``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android). :func:`platform.uname()`
740+
can be used to get the user-facing operating system name and version on iOS and
741+
Android.
742+
738743
.. availability:: Unix.
739744

740745
.. versionchanged:: 3.3

Doc/library/platform.rst

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ Cross Platform
148148
Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``,
149149
``'Windows'``. An empty string is returned if the value cannot be determined.
150150

151+
On iOS and Android, this returns the user-facing OS name (i.e, ``'iOS``,
152+
``'iPadOS'`` or ``'Android'``). To obtain the kernel name (``'Darwin'`` or
153+
``'Linux'``), use :func:`os.uname()`.
151154

152155
.. function:: system_alias(system, release, version)
153156

@@ -161,6 +164,8 @@ Cross Platform
161164
Returns the system's release version, e.g. ``'#3 on degas'``. An empty string is
162165
returned if the value cannot be determined.
163166

167+
On iOS and Android, this is the user-facing OS version. To obtain the
168+
Darwin or Linux kernel version, use :func:`os.uname()`.
164169

165170
.. function:: uname()
166171

@@ -230,7 +235,6 @@ Windows Platform
230235
macOS Platform
231236
--------------
232237

233-
234238
.. function:: mac_ver(release='', versioninfo=('','',''), machine='')
235239

236240
Get macOS version information and return it as tuple ``(release, versioninfo,
@@ -240,6 +244,24 @@ macOS Platform
240244
Entries which cannot be determined are set to ``''``. All tuple entries are
241245
strings.
242246

247+
iOS Platform
248+
------------
249+
250+
.. function:: ios_ver(system='', release='', model='', is_simulator=False)
251+
252+
Get iOS version information and return it as a
253+
:func:`~collections.namedtuple` with the following attributes:
254+
255+
* ``system`` is the OS name; either ``'iOS'`` or ``'iPadOS'``.
256+
* ``release`` is the iOS version number as a string (e.g., ``'17.2'``).
257+
* ``model`` is the device model identifier; this will be a string like
258+
``'iPhone13,2'`` for a physical device, or ``'iPhone'`` on a simulator.
259+
* ``is_simulator`` is a boolean describing if the app is running on a
260+
simulator or a physical device.
261+
262+
Entries which cannot be determined are set to the defaults given as
263+
parameters.
264+
243265

244266
Unix Platforms
245267
--------------

Doc/library/webbrowser.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ allow the remote browser to maintain its own windows on the display. If remote
3333
browsers are not available on Unix, the controlling process will launch a new
3434
browser and wait.
3535

36+
On iOS, the :envvar:`BROWSER` environment variable, as well as any arguments
37+
controlling autoraise, browser preference, and new tab/window creation will be
38+
ignored. Web pages will *always* be opened in the user's preferred browser, in
39+
a new tab, with the browser being brought to the foreground. The use of the
40+
:mod:`webbrowser` module on iOS requires the :mod:`ctypes` module. If
41+
:mod:`ctypes` isn't available, calls to :func:`.open` will fail.
42+
3643
The script :program:`webbrowser` can be used as a command-line interface for the
3744
module. It accepts a URL as the argument. It accepts the following optional
3845
parameters: ``-n`` opens the URL in a new browser window, if possible;
@@ -157,6 +164,8 @@ for the controller classes, all defined in this module.
157164
+------------------------+-----------------------------------------+-------+
158165
| ``'chromium-browser'`` | :class:`Chromium('chromium-browser')` | |
159166
+------------------------+-----------------------------------------+-------+
167+
| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) |
168+
+------------------------+-----------------------------------------+-------+
160169

161170
Notes:
162171

@@ -171,14 +180,21 @@ Notes:
171180
Only on Windows platforms.
172181

173182
(3)
174-
Only on macOS platform.
183+
Only on macOS.
184+
185+
(4)
186+
Only on iOS.
187+
175188

176189
.. versionadded:: 3.3
177190
Support for Chrome/Chromium has been added.
178191

179192
.. deprecated-removed:: 3.11 3.13
180193
:class:`MacOSX` is deprecated, use :class:`MacOSXOSAScript` instead.
181194

195+
.. versionchanged:: 3.13
196+
Support for iOS has been added.
197+
182198
Here are some simple examples::
183199

184200
url = 'https://docs.python.org/'

Lib/_ios_support.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import sys
2+
try:
3+
from ctypes import cdll, c_void_p, c_char_p, util
4+
except ImportError:
5+
# ctypes is an optional module. If it's not present, we're limited in what
6+
# we can tell about the system, but we don't want to prevent the module
7+
# from working.
8+
print("ctypes isn't available; iOS system calls will not be available")
9+
objc = None
10+
else:
11+
# ctypes is available. Load the ObjC library, and wrap the objc_getClass,
12+
# sel_registerName methods
13+
lib = util.find_library("objc")
14+
if lib is None:
15+
# Failed to load the objc library
16+
raise RuntimeError("ObjC runtime library couldn't be loaded")
17+
18+
objc = cdll.LoadLibrary(lib)
19+
objc.objc_getClass.restype = c_void_p
20+
objc.objc_getClass.argtypes = [c_char_p]
21+
objc.sel_registerName.restype = c_void_p
22+
objc.sel_registerName.argtypes = [c_char_p]
23+
24+
25+
def get_platform_ios():
26+
# Determine if this is a simulator using the multiarch value
27+
is_simulator = sys.implementation._multiarch.endswith("simulator")
28+
29+
# We can't use ctypes; abort
30+
if not objc:
31+
return None
32+
33+
# Most of the methods return ObjC objects
34+
objc.objc_msgSend.restype = c_void_p
35+
# All the methods used have no arguments.
36+
objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
37+
38+
# Equivalent of:
39+
# device = [UIDevice currentDevice]
40+
UIDevice = objc.objc_getClass(b"UIDevice")
41+
SEL_currentDevice = objc.sel_registerName(b"currentDevice")
42+
device = objc.objc_msgSend(UIDevice, SEL_currentDevice)
43+
44+
# Equivalent of:
45+
# device_systemVersion = [device systemVersion]
46+
SEL_systemVersion = objc.sel_registerName(b"systemVersion")
47+
device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion)
48+
49+
# Equivalent of:
50+
# device_systemName = [device systemName]
51+
SEL_systemName = objc.sel_registerName(b"systemName")
52+
device_systemName = objc.objc_msgSend(device, SEL_systemName)
53+
54+
# Equivalent of:
55+
# device_model = [device model]
56+
SEL_model = objc.sel_registerName(b"model")
57+
device_model = objc.objc_msgSend(device, SEL_model)
58+
59+
# UTF8String returns a const char*;
60+
SEL_UTF8String = objc.sel_registerName(b"UTF8String")
61+
objc.objc_msgSend.restype = c_char_p
62+
63+
# Equivalent of:
64+
# system = [device_systemName UTF8String]
65+
# release = [device_systemVersion UTF8String]
66+
# model = [device_model UTF8String]
67+
system = objc.objc_msgSend(device_systemName, SEL_UTF8String).decode()
68+
release = objc.objc_msgSend(device_systemVersion, SEL_UTF8String).decode()
69+
model = objc.objc_msgSend(device_model, SEL_UTF8String).decode()
70+
71+
return system, release, model, is_simulator

Lib/distutils/tests/test_cygwinccompiler.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import os
55
from io import BytesIO
66

7+
if sys.platform != 'win32':
8+
raise unittest.SkipTest("Cygwin tests only needed on Windows")
9+
710
from distutils import cygwinccompiler
811
from distutils.cygwinccompiler import (check_config_h,
912
CONFIG_H_OK, CONFIG_H_NOTOK,

Lib/distutils/tests/test_sysconfig.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from distutils import sysconfig
1111
from distutils.ccompiler import get_default_compiler
1212
from distutils.tests import support
13-
from test.support import swap_item, requires_subprocess, is_wasi
13+
from test.support import swap_item, requires_subprocess, is_apple_mobile, is_wasi
1414
from test.support.os_helper import TESTFN
1515
from test.support.warnings_helper import check_warnings
1616

@@ -33,6 +33,7 @@ def cleanup_testfn(self):
3333
shutil.rmtree(TESTFN)
3434

3535
@unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
36+
@unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile")
3637
def test_get_config_h_filename(self):
3738
config_h = sysconfig.get_config_h_filename()
3839
self.assertTrue(os.path.isfile(config_h), config_h)
@@ -50,6 +51,7 @@ def test_get_config_vars(self):
5051
self.assertTrue(cvars)
5152

5253
@unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
54+
@unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile")
5355
def test_srcdir(self):
5456
# See Issues #15322, #15364.
5557
srcdir = sysconfig.get_config_var('srcdir')

Lib/distutils/unixccompiler.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,9 @@ def find_library_file(self, dirs, lib, debug=0):
270270
static_f = self.library_filename(lib, lib_type='static')
271271

272272
if sys.platform == 'darwin':
273-
# On OSX users can specify an alternate SDK using
274-
# '-isysroot', calculate the SDK root if it is specified
275-
# (and use it further on)
273+
# On macOS users can specify an alternate SDK using
274+
# '-isysroot <path>' or --sysroot=<path>, calculate the SDK root
275+
# if it is specified (and use it further on)
276276
#
277277
# Note that, as of Xcode 7, Apple SDKs may contain textual stub
278278
# libraries with .tbd extensions rather than the normal .dylib
@@ -291,12 +291,14 @@ def find_library_file(self, dirs, lib, debug=0):
291291
cflags = sysconfig.get_config_var('CFLAGS')
292292
m = re.search(r'-isysroot\s*(\S+)', cflags)
293293
if m is None:
294-
sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC'))
294+
m = re.search(r'--sysroot=(\S+)', cflags)
295+
if m is None:
296+
sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC'))
297+
else:
298+
sysroot = m.group(1)
295299
else:
296300
sysroot = m.group(1)
297301

298-
299-
300302
for dir in dirs:
301303
shared = os.path.join(dir, shared_f)
302304
dylib = os.path.join(dir, dylib_f)

Lib/distutils/util.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,25 @@ def get_host_platform():
8989
if m:
9090
release = m.group()
9191
elif osname[:6] == "darwin":
92-
import _osx_support, distutils.sysconfig
93-
osname, release, machine = _osx_support.get_platform_osx(
94-
distutils.sysconfig.get_config_vars(),
95-
osname, release, machine)
92+
import distutils.sysconfig
93+
config_vars = distutils.sysconfig.get_config_vars()
94+
if sys.platform == "ios":
95+
release = config_vars.get("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
96+
osname = sys.platform
97+
machine = sys.implementation._multiarch
98+
elif sys.platform == "tvos":
99+
release = config_vars.get("TVOS_DEPLOYMENT_TARGET", "9.0")
100+
osname = sys.platform
101+
machine = sys.implementation._multiarch
102+
elif sys.platform == "watchos":
103+
release = config_vars.get("WATCHOS_DEPLOYMENT_TARGET", "4.0")
104+
osname = sys.platform
105+
machine = sys.implementation._multiarch
106+
else:
107+
import _osx_support
108+
osname, release, machine = _osx_support.get_platform_osx(
109+
config_vars,
110+
osname, release, machine)
96111

97112
return "%s-%s-%s" % (osname, release, machine)
98113

@@ -170,7 +185,7 @@ def check_environ ():
170185
if _environ_checked:
171186
return
172187

173-
if os.name == 'posix' and 'HOME' not in os.environ:
188+
if os.name == 'posix' and 'HOME' not in os.environ and sys.platform not in {"ios", "tvos", "watchos"}:
174189
try:
175190
import pwd
176191
os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]

Lib/lib2to3/tests/test_parser.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ def test_load_grammar_from_pickle(self):
6262
shutil.rmtree(tmpdir)
6363

6464
@unittest.skipIf(sys.executable is None, 'sys.executable required')
65-
@unittest.skipIf(
66-
sys.platform in {'emscripten', 'wasi'}, 'requires working subprocess'
67-
)
65+
@test.support.requires_subprocess()
6866
def test_load_grammar_from_subprocess(self):
6967
tmpdir = tempfile.mkdtemp()
7068
tmpsubdir = os.path.join(tmpdir, 'subdir')

Lib/platform.py

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,30 @@ def mac_ver(release='', versioninfo=('', '', ''), machine=''):
452452
# If that also doesn't work return the default values
453453
return release, versioninfo, machine
454454

455+
456+
# A namedtuple for iOS version information.
457+
IOSVersionInfo = collections.namedtuple(
458+
"IOSVersionInfo",
459+
["system", "release", "model", "is_simulator"]
460+
)
461+
462+
463+
def ios_ver(system="", release="", model="", is_simulator=False):
464+
"""Get iOS version information, and return it as a namedtuple:
465+
(system, release, model, is_simulator).
466+
467+
If values can't be determined, they are set to values provided as
468+
parameters.
469+
"""
470+
if sys.platform == "ios":
471+
import _ios_support
472+
result = _ios_support.get_platform_ios()
473+
if result is not None:
474+
return IOSVersionInfo(*result)
475+
476+
return IOSVersionInfo(system, release, model, is_simulator)
477+
478+
455479
def _java_getprop(name, default):
456480

457481
from java.lang import System
@@ -567,7 +591,7 @@ def _platform(*args):
567591
if cleaned == platform:
568592
break
569593
platform = cleaned
570-
while platform[-1] == '-':
594+
while platform and platform[-1] == '-':
571595
platform = platform[:-1]
572596

573597
return platform
@@ -608,7 +632,7 @@ def _syscmd_file(target, default=''):
608632
default in case the command should fail.
609633
610634
"""
611-
if sys.platform in ('dos', 'win32', 'win16'):
635+
if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}:
612636
# XXX Others too ?
613637
return default
614638

@@ -750,6 +774,14 @@ def get_OpenVMS():
750774
csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
751775
return 'Alpha' if cpu_number >= 128 else 'VAX'
752776

777+
# On the iOS simulator, os.uname returns the architecture as uname.machine.
778+
# On device it returns the model name for some reason; but there's only one
779+
# CPU architecture for iOS devices, so we know the right answer.
780+
def get_ios():
781+
if sys.implementation._multiarch.endswith("simulator"):
782+
return os.uname().machine
783+
return 'arm64'
784+
753785
def from_subprocess():
754786
"""
755787
Fall back to `uname -p`
@@ -904,6 +936,10 @@ def uname():
904936
system = 'Windows'
905937
release = 'Vista'
906938

939+
# Normalize responses on iOS
940+
if sys.platform == 'ios':
941+
system, release, _, _ = ios_ver()
942+
907943
vals = system, node, release, version, machine
908944
# Replace 'unknown' values with the more portable ''
909945
_uname_cache = uname_result(*map(_unknown_as_blank, vals))
@@ -1216,11 +1252,14 @@ def platform(aliased=0, terse=0):
12161252
system, release, version = system_alias(system, release, version)
12171253

12181254
if system == 'Darwin':
1219-
# macOS (darwin kernel)
1220-
macos_release = mac_ver()[0]
1221-
if macos_release:
1222-
system = 'macOS'
1223-
release = macos_release
1255+
# macOS and iOS both report as a "Darwin" kernel
1256+
if sys.platform == "ios":
1257+
system, release, _, _ = ios_ver()
1258+
else:
1259+
macos_release = mac_ver()[0]
1260+
if macos_release:
1261+
system = 'macOS'
1262+
release = macos_release
12241263

12251264
if system == 'Windows':
12261265
# MS platforms

0 commit comments

Comments
 (0)