-
-
Notifications
You must be signed in to change notification settings - Fork 31.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-114099: Additions to standard library to support iOS #117052
Changes from all commits
760751b
b41f9a4
0f3e9bc
1a9d965
c7fe185
732c4da
97a081d
95c367d
3d6d875
d12cfa0
95d11fb
48f4c1a
f584d29
9515f75
cf0b5ff
96aa042
24b3662
a4e09c9
41a3c1a
84ba760
4194849
9a91933
e6550b7
8654376
7a4dcaf
4451326
4386a7a
c1a1f0b
61559ac
abc2034
096078a
44bbf79
7419002
c121d5f
1ac4b26
857a0c3
aa65dc2
61e51ff
2ee4aba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import sys | ||
try: | ||
from ctypes import cdll, c_void_p, c_char_p, util | ||
except ImportError: | ||
# ctypes is an optional module. If it's not present, we're limited in what | ||
# we can tell about the system, but we don't want to prevent the module | ||
# from working. | ||
print("ctypes isn't available; iOS system calls will not be available") | ||
objc = None | ||
else: | ||
# ctypes is available. Load the ObjC library, and wrap the objc_getClass, | ||
# sel_registerName methods | ||
lib = util.find_library("objc") | ||
if lib is None: | ||
# Failed to load the objc library | ||
raise RuntimeError("ObjC runtime library couldn't be loaded") | ||
|
||
objc = cdll.LoadLibrary(lib) | ||
objc.objc_getClass.restype = c_void_p | ||
objc.objc_getClass.argtypes = [c_char_p] | ||
objc.sel_registerName.restype = c_void_p | ||
objc.sel_registerName.argtypes = [c_char_p] | ||
|
||
|
||
def get_platform_ios(): | ||
# Determine if this is a simulator using the multiarch value | ||
is_simulator = sys.implementation._multiarch.endswith("simulator") | ||
|
||
# We can't use ctypes; abort | ||
if not objc: | ||
return None | ||
|
||
# Most of the methods return ObjC objects | ||
objc.objc_msgSend.restype = c_void_p | ||
# All the methods used have no arguments. | ||
objc.objc_msgSend.argtypes = [c_void_p, c_void_p] | ||
|
||
# Equivalent of: | ||
# device = [UIDevice currentDevice] | ||
UIDevice = objc.objc_getClass(b"UIDevice") | ||
SEL_currentDevice = objc.sel_registerName(b"currentDevice") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
https://peps.python.org/pep-0008/#function-and-variable-names There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes - the intention was to mirror the objc naming, so that it's a little easier to see that the name comes from an "alien space", and might have some additional considerations - a sort of very weak Hungarian notation. In terms of pure functionality, it doesn't have any impact on the operation of the code. Any variable name will do; this naming convention is entirely as an affordance to future readers. For my money, the "hey, that's weird - they're not camel case" reaction is mildly desirable in this case. |
||
device = objc.objc_msgSend(UIDevice, SEL_currentDevice) | ||
|
||
# Equivalent of: | ||
# device_systemVersion = [device systemVersion] | ||
SEL_systemVersion = objc.sel_registerName(b"systemVersion") | ||
device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion) | ||
|
||
# Equivalent of: | ||
# device_systemName = [device systemName] | ||
SEL_systemName = objc.sel_registerName(b"systemName") | ||
device_systemName = objc.objc_msgSend(device, SEL_systemName) | ||
|
||
# Equivalent of: | ||
# device_model = [device model] | ||
SEL_model = objc.sel_registerName(b"model") | ||
device_model = objc.objc_msgSend(device, SEL_model) | ||
|
||
# UTF8String returns a const char*; | ||
SEL_UTF8String = objc.sel_registerName(b"UTF8String") | ||
objc.objc_msgSend.restype = c_char_p | ||
|
||
# Equivalent of: | ||
# system = [device_systemName UTF8String] | ||
# release = [device_systemVersion UTF8String] | ||
# model = [device_model UTF8String] | ||
system = objc.objc_msgSend(device_systemName, SEL_UTF8String).decode() | ||
release = objc.objc_msgSend(device_systemVersion, SEL_UTF8String).decode() | ||
model = objc.objc_msgSend(device_model, SEL_UTF8String).decode() | ||
|
||
return system, release, model, is_simulator |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -496,6 +496,30 @@ def mac_ver(release='', versioninfo=('', '', ''), machine=''): | |||||||||||||
# If that also doesn't work return the default values | ||||||||||||||
return release, versioninfo, machine | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
# A namedtuple for iOS version information. | ||||||||||||||
IOSVersionInfo = collections.namedtuple( | ||||||||||||||
"IOSVersionInfo", | ||||||||||||||
["system", "release", "model", "is_simulator"] | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def ios_ver(system="", release="", model="", is_simulator=False): | ||||||||||||||
"""Get iOS version information, and return it as a namedtuple: | ||||||||||||||
(system, release, model, is_simulator). | ||||||||||||||
|
||||||||||||||
If values can't be determined, they are set to values provided as | ||||||||||||||
parameters. | ||||||||||||||
""" | ||||||||||||||
if sys.platform == "ios": | ||||||||||||||
import _ios_support | ||||||||||||||
result = _ios_support.get_platform_ios() | ||||||||||||||
if result is not None: | ||||||||||||||
return IOSVersionInfo(*result) | ||||||||||||||
|
||||||||||||||
return IOSVersionInfo(system, release, model, is_simulator) | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
def _java_getprop(name, default): | ||||||||||||||
"""This private helper is deprecated in 3.13 and will be removed in 3.15""" | ||||||||||||||
from java.lang import System | ||||||||||||||
|
@@ -654,7 +678,7 @@ def _platform(*args): | |||||||||||||
if cleaned == platform: | ||||||||||||||
break | ||||||||||||||
platform = cleaned | ||||||||||||||
while platform[-1] == '-': | ||||||||||||||
while platform and platform[-1] == '-': | ||||||||||||||
platform = platform[:-1] | ||||||||||||||
|
||||||||||||||
return platform | ||||||||||||||
|
@@ -695,7 +719,7 @@ def _syscmd_file(target, default=''): | |||||||||||||
default in case the command should fail. | ||||||||||||||
|
||||||||||||||
""" | ||||||||||||||
if sys.platform in ('dos', 'win32', 'win16'): | ||||||||||||||
if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}: | ||||||||||||||
# XXX Others too ? | ||||||||||||||
return default | ||||||||||||||
|
||||||||||||||
|
@@ -859,6 +883,14 @@ def get_OpenVMS(): | |||||||||||||
csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) | ||||||||||||||
return 'Alpha' if cpu_number >= 128 else 'VAX' | ||||||||||||||
|
||||||||||||||
# On the iOS simulator, os.uname returns the architecture as uname.machine. | ||||||||||||||
# On device it returns the model name for some reason; but there's only one | ||||||||||||||
# CPU architecture for iOS devices, so we know the right answer. | ||||||||||||||
def get_ios(): | ||||||||||||||
if sys.implementation._multiarch.endswith("simulator"): | ||||||||||||||
return os.uname().machine | ||||||||||||||
return 'arm64' | ||||||||||||||
Comment on lines
+890
to
+892
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because of the way the build works for iOS, I think we can assume that _multiarch is always present. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirming: iOS builds will always have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is true for CPython, but it may change for different implementations that keep our stdlib. It's not a big deal, but generally I avoid adding a hard dependency on implementation details. This is certainly not a blocker, just a possible improvement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoiding hard dependencies on implementation details definitely makes sense; However, I'm not sure if using a fallback is entirely safe. Without any additional changes, using a fallback value for
The second point would at least complicate, and possibly prevent any binary module from loading; the first would lead to misleading or incorrect behavior. On that basis, I'd argue it would be safer to fail early, providing a clear indicator that something isn't configured correctly (or, at least, not configured the way that the CPython implementation expects). However, it's also a relatively straightforward change, so I'm happy to be overruled on this. |
||||||||||||||
|
||||||||||||||
def from_subprocess(): | ||||||||||||||
""" | ||||||||||||||
Fall back to `uname -p` | ||||||||||||||
|
@@ -1018,6 +1050,10 @@ def uname(): | |||||||||||||
system = 'Android' | ||||||||||||||
release = android_ver().release | ||||||||||||||
|
||||||||||||||
# Normalize responses on iOS | ||||||||||||||
if sys.platform == 'ios': | ||||||||||||||
system, release, _, _ = ios_ver() | ||||||||||||||
|
||||||||||||||
vals = system, node, release, version, machine | ||||||||||||||
# Replace 'unknown' values with the more portable '' | ||||||||||||||
_uname_cache = uname_result(*map(_unknown_as_blank, vals)) | ||||||||||||||
|
@@ -1297,11 +1333,14 @@ def platform(aliased=False, terse=False): | |||||||||||||
system, release, version = system_alias(system, release, version) | ||||||||||||||
|
||||||||||||||
if system == 'Darwin': | ||||||||||||||
# macOS (darwin kernel) | ||||||||||||||
macos_release = mac_ver()[0] | ||||||||||||||
if macos_release: | ||||||||||||||
system = 'macOS' | ||||||||||||||
release = macos_release | ||||||||||||||
# macOS and iOS both report as a "Darwin" kernel | ||||||||||||||
if sys.platform == "ios": | ||||||||||||||
system, release, _, _ = ios_ver() | ||||||||||||||
else: | ||||||||||||||
macos_release = mac_ver()[0] | ||||||||||||||
if macos_release: | ||||||||||||||
system = 'macOS' | ||||||||||||||
release = macos_release | ||||||||||||||
|
||||||||||||||
if system == 'Windows': | ||||||||||||||
# MS platforms | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,6 +21,7 @@ | |||||
|
||||||
# Keys for get_config_var() that are never converted to Python integers. | ||||||
_ALWAYS_STR = { | ||||||
'IPHONEOS_DEPLOYMENT_TARGET', | ||||||
'MACOSX_DEPLOYMENT_TARGET', | ||||||
} | ||||||
|
||||||
|
@@ -57,6 +58,7 @@ | |||||
'scripts': '{base}/Scripts', | ||||||
'data': '{base}', | ||||||
}, | ||||||
|
||||||
# Downstream distributors can overwrite the default install scheme. | ||||||
# This is done to support downstream modifications where distributors change | ||||||
# the installation layout (eg. different site-packages directory). | ||||||
|
@@ -114,8 +116,8 @@ def _getuserbase(): | |||||
if env_base: | ||||||
return env_base | ||||||
|
||||||
# Emscripten, VxWorks, and WASI have no home directories | ||||||
if sys.platform in {"emscripten", "vxworks", "wasi"}: | ||||||
# Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories | ||||||
if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}: | ||||||
return None | ||||||
|
||||||
def joinuser(*args): | ||||||
|
@@ -290,6 +292,7 @@ def _get_preferred_schemes(): | |||||
'home': 'posix_home', | ||||||
'user': 'osx_framework_user', | ||||||
} | ||||||
|
||||||
return { | ||||||
'prefix': 'posix_prefix', | ||||||
'home': 'posix_home', | ||||||
|
@@ -623,10 +626,15 @@ def get_platform(): | |||||
if m: | ||||||
release = m.group() | ||||||
elif osname[:6] == "darwin": | ||||||
import _osx_support | ||||||
osname, release, machine = _osx_support.get_platform_osx( | ||||||
get_config_vars(), | ||||||
osname, release, machine) | ||||||
if sys.platform == "ios": | ||||||
release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "12.0") | ||||||
osname = sys.platform | ||||||
machine = sys.implementation._multiarch | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again; this is iOS specific code. |
||||||
else: | ||||||
import _osx_support | ||||||
osname, release, machine = _osx_support.get_platform_osx( | ||||||
get_config_vars(), | ||||||
osname, release, machine) | ||||||
|
||||||
return f"{osname}-{release}-{machine}" | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above: iOS always has
_multiarch
, andget_platform_ios()
is only invoked byplatform.ios_ver()
, after being gated to be iOS-specific.