Skip to content

Commit bf70dca

Browse files
freakboy3742hugovkmhsmithned-deily
committed
[3.9] 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 1946402 commit bf70dca

File tree

92 files changed

+811
-120
lines changed

Some content is hidden

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

92 files changed

+811
-120
lines changed

Doc/library/os.rst

+5
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,11 @@ process and user.
638638
:func:`socket.gethostname` or even
639639
``socket.gethostbyaddr(socket.gethostname())``.
640640

641+
On macOS, iOS and Android, this returns the *kernel* name and version (i.e.,
642+
``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android). :func:`platform.uname()`
643+
can be used to get the user-facing operating system name and version on iOS and
644+
Android.
645+
641646
.. availability:: recent flavors of Unix.
642647

643648
.. versionchanged:: 3.3

Doc/library/platform.rst

+23-1
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

+17-1
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;
@@ -155,6 +162,8 @@ for the controller classes, all defined in this module.
155162
+------------------------+-----------------------------------------+-------+
156163
| ``'chromium-browser'`` | :class:`Chromium('chromium-browser')` | |
157164
+------------------------+-----------------------------------------+-------+
165+
| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) |
166+
+------------------------+-----------------------------------------+-------+
158167

159168
Notes:
160169

@@ -169,11 +178,18 @@ Notes:
169178
Only on Windows platforms.
170179

171180
(3)
172-
Only on macOS platform.
181+
Only on macOS.
182+
183+
(4)
184+
Only on iOS.
185+
173186

174187
.. versionadded:: 3.3
175188
Support for Chrome/Chromium has been added.
176189

190+
.. versionchanged:: 3.13
191+
Support for iOS has been added.
192+
177193
Here are some simple examples::
178194

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

Lib/_ios_support.py

+71
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

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from io import BytesIO
66
from test.support import run_unittest
77

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

Lib/distutils/tests/test_sysconfig.py

+4-1
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 TESTFN, run_unittest, check_warnings, swap_item
13+
from test.support import TESTFN, is_apple_mobile, requires_subprocess, run_unittest, check_warnings, swap_item
1414

1515
class SysconfigTestCase(support.EnvironGuard, unittest.TestCase):
1616
def setUp(self):
@@ -29,6 +29,7 @@ def cleanup_testfn(self):
2929
elif os.path.isdir(TESTFN):
3030
shutil.rmtree(TESTFN)
3131

32+
@unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile")
3233
def test_get_config_h_filename(self):
3334
config_h = sysconfig.get_config_h_filename()
3435
self.assertTrue(os.path.isfile(config_h), config_h)
@@ -45,6 +46,7 @@ def test_get_config_vars(self):
4546
self.assertIsInstance(cvars, dict)
4647
self.assertTrue(cvars)
4748

49+
@unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile")
4850
def test_srcdir(self):
4951
# See Issues #15322, #15364.
5052
srcdir = sysconfig.get_config_var('srcdir')
@@ -244,6 +246,7 @@ def test_SO_in_vars(self):
244246
self.assertIsNotNone(vars['SO'])
245247
self.assertEqual(vars['SO'], vars['EXT_SUFFIX'])
246248

249+
@requires_subprocess()
247250
def test_customize_compiler_before_get_config_vars(self):
248251
# Issue #21923: test that a Distribution compiler
249252
# instance can be called without an explicit call to

Lib/distutils/unixccompiler.py

+8-6
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

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

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

@@ -169,7 +184,7 @@ def check_environ ():
169184
if _environ_checked:
170185
return
171186

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

Lib/lib2to3/tests/test_parser.py

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +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+
@test.support.requires_subprocess()
6566
def test_load_grammar_from_subprocess(self):
6667
tmpdir = tempfile.mkdtemp()
6768
tmpsubdir = os.path.join(tmpdir, 'subdir')

Lib/platform.py

+46-7
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,30 @@ def mac_ver(release='', versioninfo=('', '', ''), machine=''):
449449
# If that also doesn't work return the default values
450450
return release, versioninfo, machine
451451

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

454478
from java.lang import System
@@ -574,7 +598,7 @@ def _platform(*args):
574598
if cleaned == platform:
575599
break
576600
platform = cleaned
577-
while platform[-1] == '-':
601+
while platform and platform[-1] == '-':
578602
platform = platform[:-1]
579603

580604
return platform
@@ -615,7 +639,7 @@ def _syscmd_file(target, default=''):
615639
default in case the command should fail.
616640
617641
"""
618-
if sys.platform in ('dos', 'win32', 'win16'):
642+
if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}:
619643
# XXX Others too ?
620644
return default
621645

@@ -757,6 +781,14 @@ def get_OpenVMS():
757781
csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
758782
return 'Alpha' if cpu_number >= 128 else 'VAX'
759783

784+
# On the iOS simulator, os.uname returns the architecture as uname.machine.
785+
# On device it returns the model name for some reason; but there's only one
786+
# CPU architecture for iOS devices, so we know the right answer.
787+
def get_ios():
788+
if sys.implementation._multiarch.endswith("simulator"):
789+
return os.uname().machine
790+
return 'arm64'
791+
760792
def from_subprocess():
761793
"""
762794
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)