From 760751b6a487bacc02a6cf09a7eb6d129e525d4d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 16 Mar 2024 11:13:58 +0800 Subject: [PATCH 01/35] Mark a concurrency test as requiring fork. --- Lib/test/test_concurrent_futures/test_thread_pool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py index 5926a632aa4bec..16043fd1235614 100644 --- a/Lib/test/test_concurrent_futures/test_thread_pool.py +++ b/Lib/test/test_concurrent_futures/test_thread_pool.py @@ -49,6 +49,7 @@ def test_idle_thread_reuse(self): self.assertEqual(len(executor._threads), 1) executor.shutdown(wait=True) + @support.requires_fork() @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork') @support.requires_resource('cpu') def test_hang_global_shutdown_lock(self): From b41f9a4786a21a436c9ea1a495268aca0cfbb0c0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 16 Mar 2024 11:16:54 +0800 Subject: [PATCH 02/35] Lower the marshalling stack depth for iOS. --- Python/marshal.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Python/marshal.c b/Python/marshal.c index daec7415b3fc7e..21d242bbb9757e 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -14,6 +14,10 @@ #include "pycore_setobject.h" // _PySet_NextEntry() #include "marshal.h" // Py_MARSHAL_VERSION +#ifdef __APPLE__ +# include "TargetConditionals.h" +#endif /* __APPLE__ */ + /*[clinic input] module marshal [clinic start generated code]*/ @@ -33,11 +37,14 @@ module marshal * #if defined(MS_WINDOWS) && defined(_DEBUG) */ #if defined(MS_WINDOWS) -#define MAX_MARSHAL_STACK_DEPTH 1000 +# define MAX_MARSHAL_STACK_DEPTH 1000 #elif defined(__wasi__) -#define MAX_MARSHAL_STACK_DEPTH 1500 +# define MAX_MARSHAL_STACK_DEPTH 1500 +// TARGET_OS_IPHONE covers any non-macOS Apple platform. +#elif defined(__APPLE__) && TARGET_OS_IPHONE +# define MAX_MARSHAL_STACK_DEPTH 1500 #else -#define MAX_MARSHAL_STACK_DEPTH 2000 +# define MAX_MARSHAL_STACK_DEPTH 2000 #endif #define TYPE_NULL '0' From 0f3e9bc72d6124edbdcf5a9562a119f1d4c8b5ef Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Mar 2024 11:07:39 +0800 Subject: [PATCH 03/35] Add iOS webbrowser support. --- Doc/library/webbrowser.rst | 17 +++++++- Lib/test/test_webbrowser.py | 81 ++++++++++++++++++++++++++++++++++++- Lib/webbrowser.py | 77 +++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 3 deletions(-) diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst index 4667b81e38ada2..a2870ec939936b 100644 --- a/Doc/library/webbrowser.rst +++ b/Doc/library/webbrowser.rst @@ -33,6 +33,13 @@ allow the remote browser to maintain its own windows on the display. If remote browsers are not available on Unix, the controlling process will launch a new browser and wait. +On iOS, the :envvar:`BROWSER` environment variable, as well as any arguments +controlling autoraise, browser preference, and new tab/window creation will be +ignored. Web pages will *always* be opened in the user's preferred browser, in +a new tab, with the browser being brought to the foreground. The use of the +:mod:`webbrowser` module on iOS requires the :mod:`ctypes` module. If +:mod:`ctypes` isn't available, calls to :func:`.open` will fail. + The script :program:`webbrowser` can be used as a command-line interface for the module. It accepts a URL as the argument. It accepts the following optional parameters: ``-n`` opens the URL in a new browser window, if possible; @@ -147,6 +154,8 @@ for the controller classes, all defined in this module. +------------------------+-----------------------------------------+-------+ | ``'chromium-browser'`` | ``Chromium('chromium-browser')`` | | +------------------------+-----------------------------------------+-------+ +| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) | ++------------------------+-----------------------------------------+-------+ Notes: @@ -161,7 +170,10 @@ Notes: Only on Windows platforms. (3) - Only on macOS platform. + Only on macOS. + +(4) + Only on iOS. .. versionadded:: 3.2 A new :class:`!MacOSXOSAScript` class has been added @@ -176,6 +188,9 @@ Notes: Removed browsers include Grail, Mosaic, Netscape, Galeon, Skipstone, Iceape, and Firefox versions 35 and below. +.. versionchanged:: 3.13 + Support for iOS was added. + Here are some simple examples:: url = 'https://docs.python.org/' diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 8c074cb28a87e3..733544bbb2aeca 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -5,11 +5,14 @@ import subprocess from unittest import mock from test import support +from test.support import is_apple_mobile from test.support import import_helper from test.support import os_helper +from test.support import requires_subprocess +from test.support import threading_helper -if not support.has_subprocess_support: - raise unittest.SkipTest("test webserver requires subprocess") +# The webbrowser module uses threading locks +threading_helper.requires_working_threading(module=True) URL = 'https://www.example.com' CMD_NAME = 'test' @@ -24,6 +27,7 @@ def wait(self, seconds=None): return 0 +@requires_subprocess() class CommandTestMixin: def _test(self, meth, *, args=[URL], kw={}, options, arguments): @@ -219,6 +223,71 @@ def test_open_new_tab(self): arguments=['openURL({},new-tab)'.format(URL)]) +@unittest.skipUnless(sys.platform == "ios", "Test only applicable to iOS") +class IOSBrowserTest(unittest.TestCase): + def _obj_ref(self, *args): + # Construct a string representation of the arguments that can be used + # as a proxy for object instance references + return "|".join(str(a) for a in args) + + def setUp(self): + # Intercept the the objc library. Wrap the calls to get the + # references to classes and selectors to return strings, and + # wrap msgSend to return stringified object references + self.orig_objc = webbrowser.objc + + webbrowser.objc = mock.Mock() + webbrowser.objc.objc_getClass = lambda cls: f"C#{cls.decode()}" + webbrowser.objc.sel_registerName = lambda sel: f"S#{sel.decode()}" + webbrowser.objc.objc_msgSend.side_effect = self._obj_ref + + def tearDown(self): + webbrowser.objc = self.orig_objc + + def _test(self, meth, **kwargs): + # The browser always gets focus, there's no concept of separate browser + # windows, and there's no API-level control over creating a new tab. + # Therefore, all calls to webbrowser are effectively the same. + getattr(webbrowser, meth)(URL, **kwargs) + + # The ObjC String version of the URL is created with UTF-8 encoding + url_string_args = [ + "C#NSString", + "S#stringWithCString:encoding:", + b'https://www.example.com', + 4, + ] + # The NSURL version of the URL is created from that string + url_obj_args = [ + "C#NSURL", + "S#URLWithString:", + self._obj_ref(*url_string_args), + ] + # The openURL call is invoked on the shared application + shared_app_args = ["C#UIApplication", "S#sharedApplication"] + + # Verify that the last call is the one that opens the URL. + webbrowser.objc.objc_msgSend.assert_called_with( + self._obj_ref(*shared_app_args), + "S#openURL:options:completionHandler:", + self._obj_ref(*url_obj_args), + None, + None + ) + + def test_open(self): + self._test('open') + + def test_open_with_autoraise_false(self): + self._test('open', autoraise=False) + + def test_open_new(self): + self._test('open_new') + + def test_open_new_tab(self): + self._test('open_new_tab') + + class BrowserRegistrationTest(unittest.TestCase): def setUp(self): @@ -314,6 +383,10 @@ def test_synthesize(self): webbrowser.register(name, None, webbrowser.GenericBrowser(name)) webbrowser.get(sys.executable) + @unittest.skipIf( + is_apple_mobile, + "Apple mobile doesn't allow modifying browser with environment" + ) def test_environment(self): webbrowser = import_helper.import_fresh_module('webbrowser') try: @@ -325,6 +398,10 @@ def test_environment(self): webbrowser = import_helper.import_fresh_module('webbrowser') webbrowser.get() + @unittest.skipIf( + is_apple_mobile, + "Apple mobile doesn't allow modifying browser with environment" + ) def test_environment_preferred(self): webbrowser = import_helper.import_fresh_module('webbrowser') try: diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 0424c53b7ccaf9..6d8f21feda7431 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -478,6 +478,9 @@ def register_standard_browsers(): # OS X can use below Unix support (but we prefer using the OS X # specific stuff) + if sys.platform == "ios": + register("iosbrowser", None, IOSBrowser(), preferred=True) + if sys.platform == "serenityos": # SerenityOS webbrowser, simply called "Browser". register("Browser", None, BackgroundBrowser("Browser")) @@ -599,6 +602,80 @@ def open(self, url, new=0, autoraise=True): rc = osapipe.close() return not rc +# +# Platform support for iOS +# +if sys.platform == "ios": + try: + from ctypes import cdll, c_void_p, c_char_p, c_ulong + from ctypes import util + except ImportError: + # If ctypes isn't available, we can't trigger the browser + objc = None + else: + # ctypes is available. Load the ObjC library, and wrap the + # objc_getClass, sel_registerName and objc_msgSend methods + objc = cdll.LoadLibrary(util.find_library(b"objc")) + if objc: + 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] + # The return type of objc_msgSend is always c_void_p; but the + # argument types vary with the specific call. + objc.objc_msgSend.restype = c_void_p + + class IOSBrowser(BaseBrowser): + def open(self, url, new=0, autoraise=True): + sys.audit("webbrowser.open", url) + # If ctypes isn't available, we can't open a browser + if objc is None: + return False + + # This is the equivalent of: + # NSString url_string = + # [NSString stringWithCString:url.encode("utf-8") + # encoding:NSUTF8StringEncoding]; + NSString = objc.objc_getClass(b"NSString") + constructor = objc.sel_registerName(b"stringWithCString:encoding:") + objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_char_p, c_ulong] + url_string = objc.objc_msgSend( + NSString, + constructor, + url.encode("utf-8"), + 4, # NSUTF8StringEncoding = 4 + ) + + # Create an NSURL object representing the URL + # This is the equivalent of: + # NSURL *nsurl = [NSURL URLWithString:url]; + NSURL = objc.objc_getClass(b"NSURL") + urlWithString_ = objc.sel_registerName(b"URLWithString:") + objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_void_p] + ns_url = objc.objc_msgSend(NSURL, urlWithString_, url_string) + + # Get the shared UIApplication instance + # This code is the equivalent of: + # UIApplication shared_app = [UIApplication sharedApplication] + UIApplication = objc.objc_getClass(b"UIApplication") + sharedApplication = objc.sel_registerName(b"sharedApplication") + objc.objc_msgSend.argtypes = [c_void_p, c_void_p] + shared_app = objc.objc_msgSend(UIApplication, sharedApplication) + + # Open the URL on the shared application + # This code is the equivalent of: + # [shared_app openURL:ns_url + # options:NIL + # completionHandler:NIL]; + openURL_ = objc.sel_registerName(b"openURL:options:completionHandler:") + objc.objc_msgSend.argtypes = [ + c_void_p, c_void_p, c_void_p, c_void_p, c_void_p + ] + objc.objc_msgSend.restype = None + objc.objc_msgSend(shared_app, openURL_, ns_url, None, None) + + return True + def main(): import getopt From 1a9d965d532f0d6d2be525bc4240786fdc703d79 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Mar 2024 11:41:19 +0800 Subject: [PATCH 04/35] Updates to site and sysconfig modules for iOS support. --- Lib/site.py | 4 ++-- Lib/sysconfig/__init__.py | 39 ++++++++++++++++++++++++++++++++------ Lib/test/test_sysconfig.py | 19 +++++++++++++++++-- Modules/getpath.c | 5 +++++ Modules/getpath.py | 2 +- 5 files changed, 58 insertions(+), 11 deletions(-) diff --git a/Lib/site.py b/Lib/site.py index 2aee63e24ca52b..d3cbd81cc64e50 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -280,8 +280,8 @@ def _getuserbase(): if env_base: return env_base - # Emscripten, VxWorks, and WASI have no home directories - if sys.platform in {"emscripten", "vxworks", "wasi"}: + # iOS, tvOS, watchOS, Emscripten, VxWorks, and WASI have no home directories + if sys.platform in {"ios", "emscripten", "tvos", "vxworks", "wasi", "watchos"}: return None def joinuser(*args): diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 07ab27c7fb0c35..5af5785231b200 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -57,6 +57,17 @@ 'scripts': '{base}/Scripts', 'data': '{base}', }, + 'ios': { + 'stdlib': '{installed_base}/lib/python{py_version_short}', + 'platstdlib': '{installed_base}/lib/python{py_version_short}', + 'purelib': '{installed_base}/lib/python{py_version_short}/site-packages', + 'platlib': '{installed_base}/lib/python{py_version_short}/site-packages', + 'include': '{installed_base}/include/{implementation_lower}', + 'platinclude': '{installed_base}/include/{implementation_lower}', + 'scripts': '{installed_base}/bin', + 'data': '{installed_base}/Resources', + }, + # 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 +125,8 @@ def _getuserbase(): if env_base: return env_base - # Emscripten, VxWorks, and WASI have no home directories - if sys.platform in {"emscripten", "vxworks", "wasi"}: + # iOS, tvOS, watchOS, Emscripten, VxWorks, and WASI have no home directories + if sys.platform in {"ios", "emscripten", "tvos", "vxworks", "wasi", "watchos"}: return None def joinuser(*args): @@ -290,6 +301,13 @@ def _get_preferred_schemes(): 'home': 'posix_home', 'user': 'osx_framework_user', } + if sys.platform in {'ios', 'tvos', 'watchos'}: + return { + 'prefix': sys.platform, + 'home': sys.platform, + 'user': sys.platform, + } + return { 'prefix': 'posix_prefix', 'home': 'posix_home', @@ -507,6 +525,9 @@ def _init_config_vars(): import _osx_support _osx_support.customize_config_vars(_CONFIG_VARS) + if sys.platform in {'ios', 'tvos', 'watchos'}: + _CONFIG_VARS['installed_base'] = sys.executable + global _CONFIG_VARS_INITIALIZED _CONFIG_VARS_INITIALIZED = True @@ -623,10 +644,16 @@ 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 in {"ios", "tvos", "watchos"}: + import _ios_support + _, release, _, _ = _ios_support.get_platform_ios() + osname = sys.platform + machine = sys.implementation._multiarch + else: + import _osx_support + osname, release, machine = _osx_support.get_platform_osx( + get_config_vars(), + osname, release, machine) return f"{osname}-{release}-{machine}" diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index c8315bbc8b727d..14e74593ead529 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -8,7 +8,11 @@ from copy import copy from test.support import ( - captured_stdout, PythonSymlink, requires_subprocess, is_wasi + captured_stdout, + is_apple_mobile, + is_wasi, + PythonSymlink, + requires_subprocess, ) from test.support.import_helper import import_module from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink, @@ -346,12 +350,17 @@ def test_get_platform(self): # XXX more platforms to tests here @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") + @unittest.skipIf(is_apple_mobile, + f"{sys.platform} doesn't distribute config.h in the runtime environment") def test_get_config_h_filename(self): config_h = sysconfig.get_config_h_filename() self.assertTrue(os.path.isfile(config_h), config_h) def test_get_scheme_names(self): - wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv'] + wanted = [ + "ios", "nt", "posix_home", "posix_prefix", + "posix_venv", "nt_venv", "venv" + ] if HAS_USER_BASE: wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) @@ -423,6 +432,8 @@ def test_library(self): self.assertTrue(library.startswith(f'python{major}{minor}')) self.assertTrue(library.endswith('.dll')) self.assertEqual(library, ldlibrary) + elif is_apple_mobile: + self.assertEqual(ldlibrary, "Python.framework/Python") else: self.assertTrue(library.startswith(f'libpython{major}.{minor}')) self.assertTrue(library.endswith('.a')) @@ -476,6 +487,8 @@ def test_platform_in_subprocess(self): self.assertEqual(my_platform, test_platform) @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") + @unittest.skipIf(is_apple_mobile, + f"{sys.platform} doesn't distribute config.h in the runtime environment") def test_srcdir(self): # See Issues #15322, #15364. srcdir = sysconfig.get_config_var('srcdir') @@ -556,6 +569,8 @@ class MakefileTests(unittest.TestCase): @unittest.skipIf(sys.platform.startswith('win'), 'Test is not Windows compatible') @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") + @unittest.skipIf(is_apple_mobile, + f"{sys.platform} doesn't distribute config.h in the runtime environment") def test_get_makefile_filename(self): makefile = sysconfig.get_makefile_filename() self.assertTrue(os.path.isfile(makefile), makefile) diff --git a/Modules/getpath.c b/Modules/getpath.c index abed139028244a..5bbbc069379497 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -19,6 +19,7 @@ #ifdef __APPLE__ # include # include +# include #endif /* Reference the precompiled getpath.py */ @@ -923,7 +924,11 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) #ifdef MS_WINDOWS !decode_to_dict(dict, "os_name", "nt") || #elif defined(__APPLE__) +# if TARGET_OS_IOS + !decode_to_dict(dict, "os_name", "ios") || +# else !decode_to_dict(dict, "os_name", "darwin") || +# endif #else !decode_to_dict(dict, "os_name", "posix") || #endif diff --git a/Modules/getpath.py b/Modules/getpath.py index 1410ffdbed8c70..0b0eefb4d449b3 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -173,7 +173,7 @@ platlibdir = config.get('platlibdir') or PLATLIBDIR -if os_name == 'posix' or os_name == 'darwin': +if os_name == "posix" or os_name in {"darwin", "ios", "tvos", "watchos"}: BUILDDIR_TXT = 'pybuilddir.txt' BUILD_LANDMARK = 'Modules/Setup.local' DEFAULT_PROGRAM_NAME = f'python{VERSION_MAJOR}' From c7fe185b37dd3a461ad3780f19c21b9309b25019 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Mar 2024 13:39:45 +0800 Subject: [PATCH 05/35] Update platform module to provide ios_ver. --- Doc/library/platform.rst | 18 +++++++++- Lib/_ios_support.py | 70 +++++++++++++++++++++++++++++++++++++++ Lib/platform.py | 43 ++++++++++++++++++++---- Lib/test/test_platform.py | 34 +++++++++++++++++++ Lib/webbrowser.py | 10 +++--- 5 files changed, 164 insertions(+), 11 deletions(-) create mode 100644 Lib/_ios_support.py diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 4bc3956449b930..ef2515e3b3cec0 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -238,7 +238,6 @@ Windows Platform macOS Platform -------------- - .. function:: mac_ver(release='', versioninfo=('','',''), machine='') Get macOS version information and return it as tuple ``(release, versioninfo, @@ -248,6 +247,23 @@ macOS Platform Entries which cannot be determined are set to ``''``. All tuple entries are strings. +iOS Platform +------------ + +.. function:: ios_ver(system='', release='', machine='', is_simulator=False) + + Get iOS version information and return it as tuple ``(system, release, + machine, is_simulator)`` with *release* being a tuple ``(major, minor)``. + + ``system`` is the OS name; either ``iOS`` or ``iPadOS``. ``release`` is the + iOS version number as a string (e.g., ``"17.2"``). ``machine`` is the device + model identifier; this will be a string like ``iPhone14,1`` for a physical + device, or ``iPhone`` on a simulator. ``is_simulator`` is a boolean + describing if the app is running on a simulator or a physical device. + + Entries which cannot be determined are set to ``''``. All tuple entries are + strings. + Unix Platforms -------------- diff --git a/Lib/_ios_support.py b/Lib/_ios_support.py new file mode 100644 index 00000000000000..b9c8daafc0424f --- /dev/null +++ b/Lib/_ios_support.py @@ -0,0 +1,70 @@ +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 platform + # module from working. + objc = None +else: + # ctypes is available. Load the ObjC library, and wrap the objc_getClass, + # sel_registerName and objc_msgSend methods + objc = cdll.LoadLibrary(util.find_library("objc")) + if objc._name: + 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] + # The calls to objc_msgSend all have no arguments; but the return types + # vary with the specific call. + objc.objc_msgSend.argtypes = [c_void_p, c_void_p] + else: + # Failed to load the objc library + objc = None + + +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 + + # Most of the methods return ObjC objects + objc.objc_msgSend.restype = c_void_p + + # Equivalent of: + # device = [UIDevice currentDevice] + UIDevice = objc.objc_getClass(b"UIDevice") + SEL_currentDevice = objc.sel_registerName(b"currentDevice") + 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 diff --git a/Lib/platform.py b/Lib/platform.py index 2756f298f9676f..4f37b76f94d5a3 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -496,6 +496,21 @@ def mac_ver(release='', versioninfo=('', '', ''), machine=''): # If that also doesn't work return the default values return release, versioninfo, machine +def ios_ver(system="", release="", model="", is_simulator=False): + """Get iOS version information, and return it as a tuple: + (system, release, model, is_simulator). + + If values can't be determined, they are set to values provided as + parameters. + """ + import _ios_support + result = _ios_support.get_platform_ios() + if result is not None: + return result + else: + return 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 +669,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 @@ -818,6 +833,13 @@ def get_OpenVMS(): csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' + # On iOS, os.uname returns the architecture as uname.machine. On device it + # doesn't; but there's only one CPU architecture on device. + def get_ios(): + if sys.implementation._multiarch.endswith("simulator"): + return os.uname().machine + return 'arm64' + def from_subprocess(): """ Fall back to `uname -p` @@ -972,6 +994,13 @@ def uname(): system = 'Windows' release = 'Vista' + # Normalize responses on Apple mobile platforms + if sys.platform in {'ios', 'tvos'}: + system, release, model, is_simulator = ios_ver() + + if is_simulator: + machine = f'{model}Simulator' + vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' _uname_cache = uname_result(*map(_unknown_as_blank, vals)) @@ -1251,11 +1280,13 @@ 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 + if sys.platform in {'ios', 'tvos'}: + system, release, _, _ = ios_ver() + else: + macos_release = mac_ver()[0] + if macos_release: + system = 'macOS' + release = macos_release if system == 'Windows': # MS platforms diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 9f8aeeea257311..3488515ba4f5a6 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -409,6 +409,40 @@ def test_mac_ver_with_fork(self): # parent support.wait_process(pid, exitcode=0) + def test_ios_ver(self): + system, release, model, is_simulator = platform.ios_ver() + if sys.platform == "ios": + # We can't assert specific values without reproducing the logic of + # ios_ver(), so we check that the values are broadly what we expect. + + # System is either iOS or iPadOS, depending on the test device + self.assertIn(system, {"iOS", "iPadOS"}) + + # Release is a major.minor specifier + major, minor = release.split(".") + self.assertTrue(major.isdigit()) + self.assertTrue(minor.isdigit()) + + # If this is a simulator, we get a high level device descriptor + # with no identifying model number. If this is a physical device, + # we get a model descriptor like "iPhone13,1" + if is_simulator: + self.assertIn(model, {"iPhone", "iPad"}) + else: + self.assertTrue( + (model.startswith("iPhone") or model.startswith("iPad")) + and "," in model + ) + + self.assertEqual(type(is_simulator), bool) + else: + # On non-iOS platforms, calling ios_ver doesn't fail; you get + # default values + self.assertEqual(system, "") + self.assertEqual(release, "") + self.assertEqual(model, "") + self.assertFalse(is_simulator) + @unittest.skipIf(support.is_emscripten, "Does not apply to Emscripten") def test_libc_ver(self): # check that libc_ver(executable) doesn't raise an exception diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 6d8f21feda7431..e5a6281e7ccf25 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -607,16 +607,15 @@ def open(self, url, new=0, autoraise=True): # if sys.platform == "ios": try: - from ctypes import cdll, c_void_p, c_char_p, c_ulong - from ctypes import util + from ctypes import cdll, c_void_p, c_char_p, c_ulong, util except ImportError: # If ctypes isn't available, we can't trigger the browser objc = None else: # ctypes is available. Load the ObjC library, and wrap the # objc_getClass, sel_registerName and objc_msgSend methods - objc = cdll.LoadLibrary(util.find_library(b"objc")) - if objc: + objc = cdll.LoadLibrary(util.find_library("objc")) + if objc._name: objc.objc_getClass.restype = c_void_p objc.objc_getClass.argtypes = [c_char_p] objc.sel_registerName.restype = c_void_p @@ -624,6 +623,9 @@ def open(self, url, new=0, autoraise=True): # The return type of objc_msgSend is always c_void_p; but the # argument types vary with the specific call. objc.objc_msgSend.restype = c_void_p + else: + # Failed to load the objc library + objc = None class IOSBrowser(BaseBrowser): def open(self, url, new=0, autoraise=True): From 732c4da60295f4b165e94002a5ffe3326d7e9d53 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Mar 2024 14:36:12 +0800 Subject: [PATCH 06/35] Add changenote. --- .../next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst diff --git a/Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst b/Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst new file mode 100644 index 00000000000000..4ead609ee9e986 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst @@ -0,0 +1 @@ +Standard library was modified to allow for iOS platform differences. From 97a081d7606ee725367cbba09e095504a667bd0f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Mar 2024 14:42:54 +0800 Subject: [PATCH 07/35] Correct some consistency issues. --- Lib/platform.py | 8 +++----- Lib/sysconfig/__init__.py | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 4f37b76f94d5a3..57cf9df35e9f34 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -996,10 +996,7 @@ def uname(): # Normalize responses on Apple mobile platforms if sys.platform in {'ios', 'tvos'}: - system, release, model, is_simulator = ios_ver() - - if is_simulator: - machine = f'{model}Simulator' + system, release, machine, _ = ios_ver() vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' @@ -1280,7 +1277,8 @@ def platform(aliased=False, terse=False): system, release, version = system_alias(system, release, version) if system == 'Darwin': - if sys.platform in {'ios', 'tvos'}: + # macOS and iOS both report as a "Darwin" kernel + if sys.platform == "ios": system, release, _, _ = ios_ver() else: macos_release = mac_ver()[0] diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 5af5785231b200..c3b96bca0be4b1 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -303,9 +303,9 @@ def _get_preferred_schemes(): } if sys.platform in {'ios', 'tvos', 'watchos'}: return { - 'prefix': sys.platform, - 'home': sys.platform, - 'user': sys.platform, + 'prefix': "ios", + 'home': "ios", + 'user': "ios", } return { From 95c367dd72582a649a819a5f59b0e8b4121d907f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 20 Mar 2024 07:08:36 +0800 Subject: [PATCH 08/35] Modify testios Makefile target to output failures once. --- Doc/library/platform.rst | 7 +++---- Makefile.pre.in | 8 +++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index ef2515e3b3cec0..007371df088f01 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -256,13 +256,12 @@ iOS Platform machine, is_simulator)`` with *release* being a tuple ``(major, minor)``. ``system`` is the OS name; either ``iOS`` or ``iPadOS``. ``release`` is the - iOS version number as a string (e.g., ``"17.2"``). ``machine`` is the device - model identifier; this will be a string like ``iPhone14,1`` for a physical + iOS version number as a string (e.g., ``'17.2'``). ``machine`` is the device + model identifier; this will be a string like ``iPhone13,2`` for a physical device, or ``iPhone`` on a simulator. ``is_simulator`` is a boolean describing if the app is running on a simulator or a physical device. - Entries which cannot be determined are set to ``''``. All tuple entries are - strings. + Entries which cannot be determined are set to ``''``. Unix Platforms diff --git a/Makefile.pre.in b/Makefile.pre.in index b9f790a14af4cd..a767e6d4af5efb 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2044,7 +2044,13 @@ testios: # Run the test suite for the Xcode project, targeting the iOS simulator. # If the suite fails, extract and print the console output, then re-raise the failure if ! xcodebuild test -project $(XCFOLDER)/iOSTestbed.xcodeproj -scheme "iOSTestbed" -destination "platform=iOS Simulator,name=iPhone SE (3rd Generation)" -resultBundlePath $(XCRESULT) ; then \ - xcrun xcresulttool get --path $(XCRESULT) --id $$(xcrun xcresulttool get --path $(XCRESULT) --format json | $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['actions']['_values'][0]['actionResult']['logRef']['id']['_value'])"); \ + xcrun xcresulttool get --path $(XCRESULT) \ + --id $$( \ + xcrun xcresulttool get --path $(XCRESULT) --format json | \ + $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['actions']['_values'][0]['actionResult']['logRef']['id']['_value'])" \ + ) \ + --format json | \ + $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['subsections']['_values'][1]['subsections']['_values'][0]['emittedOutput']['_value'])"; \ echo ; \ exit 1; \ fi From 3d6d8750200b69147776d8974b081d7984052629 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 20 Mar 2024 07:21:14 +0800 Subject: [PATCH 09/35] Avoid calling iOS APIs on tvOS/watchOS. --- Lib/platform.py | 4 ++-- Lib/sysconfig/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 57cf9df35e9f34..37f4b201fd65c6 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -994,8 +994,8 @@ def uname(): system = 'Windows' release = 'Vista' - # Normalize responses on Apple mobile platforms - if sys.platform in {'ios', 'tvos'}: + # Normalize responses on iOS + if sys.platform == 'ios': system, release, machine, _ = ios_ver() vals = system, node, release, version, machine diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index c3b96bca0be4b1..7562eda0deaf6c 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -644,7 +644,7 @@ def get_platform(): if m: release = m.group() elif osname[:6] == "darwin": - if sys.platform in {"ios", "tvos", "watchos"}: + if sys.platform == "ios": import _ios_support _, release, _, _ = _ios_support.get_platform_ios() osname = sys.platform From d12cfa0c46ca3f3b78874fb6b122216db2857bdc Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 20 Mar 2024 08:21:54 +0800 Subject: [PATCH 10/35] Add a record for the new stdlib module. --- Python/stdlib_module_names.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h index 2445a5c838a7d7..ac9d91b5e12885 100644 --- a/Python/stdlib_module_names.h +++ b/Python/stdlib_module_names.h @@ -38,6 +38,7 @@ static const char* _Py_stdlib_module_names[] = { "_heapq", "_imp", "_io", +"_ios_support", "_json", "_locale", "_lsprof", From 95d11fbaa1d3820fae7c62d032b8a94e476ec487 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 20 Mar 2024 08:42:00 +0800 Subject: [PATCH 11/35] Add protection against running platform.ios_ver on macOS/Linux, which have objc dylibs. --- Lib/platform.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 37f4b201fd65c6..c71cc83b0dfadb 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -503,12 +503,13 @@ def ios_ver(system="", release="", model="", is_simulator=False): If values can't be determined, they are set to values provided as parameters. """ - import _ios_support - result = _ios_support.get_platform_ios() - if result is not None: - return result - else: - return system, release, model, is_simulator + if sys.platform == "ios": + import _ios_support + result = _ios_support.get_platform_ios() + if result is not None: + return result + + return system, release, model, is_simulator def _java_getprop(name, default): From 48f4c1aef00477607a1393c012bf7aa60f89830a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 20 Mar 2024 14:07:47 +0800 Subject: [PATCH 12/35] Improve markup in the description of ios_ver() --- Doc/library/platform.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 007371df088f01..9765171133e379 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -255,11 +255,12 @@ iOS Platform Get iOS version information and return it as tuple ``(system, release, machine, is_simulator)`` with *release* being a tuple ``(major, minor)``. - ``system`` is the OS name; either ``iOS`` or ``iPadOS``. ``release`` is the - iOS version number as a string (e.g., ``'17.2'``). ``machine`` is the device - model identifier; this will be a string like ``iPhone13,2`` for a physical - device, or ``iPhone`` on a simulator. ``is_simulator`` is a boolean - describing if the app is running on a simulator or a physical device. + * ``system`` is the OS name; either ``iOS`` or ``iPadOS``. + * ``release`` is the iOS version number as a string (e.g., ``'17.2'``). + * ``machine`` is the device model identifier; this will be a string like + ``iPhone13,2`` for a physical device, or ``iPhone`` on a simulator. + * ``is_simulator`` is a boolean describing if the app is running on a + simulator or a physical device. Entries which cannot be determined are set to ``''``. From f584d29e1a006fa47215c8b18748ef0fcb16d2bf Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 20 Mar 2024 14:28:30 +0800 Subject: [PATCH 13/35] Use a namedtuple for ios_ver(). --- Lib/platform.py | 14 +++++++++++--- Lib/test/test_platform.py | 18 +++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index c71cc83b0dfadb..2de82fafe01ca2 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -496,8 +496,16 @@ 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 tuple: + """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 @@ -507,9 +515,9 @@ def ios_ver(system="", release="", model="", is_simulator=False): import _ios_support result = _ios_support.get_platform_ios() if result is not None: - return result + return IOSVersionInfo(*result) - return system, release, model, is_simulator + return IOSVersionInfo(system, release, model, is_simulator) def _java_getprop(name, default): diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 3488515ba4f5a6..71b723593b9b96 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -410,8 +410,16 @@ def test_mac_ver_with_fork(self): support.wait_process(pid, exitcode=0) def test_ios_ver(self): - system, release, model, is_simulator = platform.ios_ver() + result = platform.ios_ver() if sys.platform == "ios": + system, release, model, is_simulator = result + + # Result is a namedtuple + self.assertEqual(result.system, system) + self.assertEqual(result.release, release) + self.assertEqual(result.model, model) + self.assertEqual(result.is_simulator, is_simulator) + # We can't assert specific values without reproducing the logic of # ios_ver(), so we check that the values are broadly what we expect. @@ -438,10 +446,10 @@ def test_ios_ver(self): else: # On non-iOS platforms, calling ios_ver doesn't fail; you get # default values - self.assertEqual(system, "") - self.assertEqual(release, "") - self.assertEqual(model, "") - self.assertFalse(is_simulator) + self.assertEqual(result.system, "") + self.assertEqual(result.release, "") + self.assertEqual(result.model, "") + self.assertFalse(result.is_simulator) @unittest.skipIf(support.is_emscripten, "Does not apply to Emscripten") def test_libc_ver(self): From 9515f7574de0855c772c7521f1c57d0623f9c2c5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 20 Mar 2024 14:58:29 +0800 Subject: [PATCH 14/35] Ensure the minimum iOS version is used in sysconfig.get_platform() --- Lib/sysconfig/__init__.py | 4 ++-- Lib/test/pythoninfo.py | 1 + Makefile.pre.in | 2 ++ configure | 7 +++++-- configure.ac | 5 ++++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 7562eda0deaf6c..0456fa11c60262 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -21,6 +21,7 @@ # Keys for get_config_var() that are never converted to Python integers. _ALWAYS_STR = { + 'IOS_DEPLOYMENT_TARGET', 'MACOSX_DEPLOYMENT_TARGET', } @@ -645,8 +646,7 @@ def get_platform(): release = m.group() elif osname[:6] == "darwin": if sys.platform == "ios": - import _ios_support - _, release, _, _ = _ios_support.get_platform_ios() + release = get_config_vars().get("IOS_DEPLOYMENT_TARGET", "12.0") osname = sys.platform machine = sys.implementation._multiarch else: diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 814358746d6d8a..a6b743fa7a9851 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -287,6 +287,7 @@ def format_groups(groups): "HOMEDRIVE", "HOMEPATH", "IDLESTARTUP", + "IOS_DEPLOYMENT_TARGET", "LANG", "LDFLAGS", "LDSHARED", diff --git a/Makefile.pre.in b/Makefile.pre.in index a767e6d4af5efb..a9d57fd11a2d02 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -191,6 +191,8 @@ RESSRCDIR= @RESSRCDIR@ # deployment target is active during build. MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@ @EXPORT_MACOSX_DEPLOYMENT_TARGET@export MACOSX_DEPLOYMENT_TARGET +# iOS Deployment target selected during configure +IOS_DEPLOYMENT_TARGET=@CONFIGURE_IOS_DEPLOYMENT_TARGET@ # Option to install to strip binaries STRIPFLAG=-s diff --git a/configure b/configure index 229f0d32d322dd..b6af59374ef0a1 100755 --- a/configure +++ b/configure @@ -976,7 +976,7 @@ LDFLAGS CFLAGS CC HAS_XCRUN -IOS_DEPLOYMENT_TARGET +CONFIGURE_IOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET _PYTHON_HOST_PLATFORM @@ -4597,6 +4597,10 @@ fi CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' +# Record the value of IOS_DEPLOYMENT_TARGET enforced by the selected host triple. +CONFIGURE_IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET} + + # checks for alternative programs # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just @@ -4634,7 +4638,6 @@ case $ac_sys_system in #( as_fn_append CFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" as_fn_append LDFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" - ;; #( *) : ;; diff --git a/configure.ac b/configure.ac index cd17977738482d..96ed7f1337b371 100644 --- a/configure.ac +++ b/configure.ac @@ -866,6 +866,10 @@ AC_SUBST([EXPORT_MACOSX_DEPLOYMENT_TARGET]) CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' +# Record the value of IOS_DEPLOYMENT_TARGET enforced by the selected host triple. +CONFIGURE_IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET} +AC_SUBST([CONFIGURE_IOS_DEPLOYMENT_TARGET]) + # checks for alternative programs # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just @@ -903,7 +907,6 @@ AS_CASE([$ac_sys_system], [iOS], [ AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) - AC_SUBST([IOS_DEPLOYMENT_TARGET]) ], ) From cf0b5ff484a4ded3e6698a2b756152942e09b687 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 22 Mar 2024 05:48:24 +0800 Subject: [PATCH 15/35] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/webbrowser.rst | 2 +- Lib/_ios_support.py | 2 +- Lib/site.py | 4 ++-- Lib/sysconfig/__init__.py | 4 ++-- .../Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst | 2 +- Modules/getpath.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst index a2870ec939936b..c1c4619d9df776 100644 --- a/Doc/library/webbrowser.rst +++ b/Doc/library/webbrowser.rst @@ -189,7 +189,7 @@ Notes: Skipstone, Iceape, and Firefox versions 35 and below. .. versionchanged:: 3.13 - Support for iOS was added. + Support for iOS has been added. Here are some simple examples:: diff --git a/Lib/_ios_support.py b/Lib/_ios_support.py index b9c8daafc0424f..d206bcb60c9d86 100644 --- a/Lib/_ios_support.py +++ b/Lib/_ios_support.py @@ -29,7 +29,7 @@ def get_platform_ios(): # We can't use ctypes; abort if not objc: - return + return None # Most of the methods return ObjC objects objc.objc_msgSend.restype = c_void_p diff --git a/Lib/site.py b/Lib/site.py index d3cbd81cc64e50..162bbec4f8f41b 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -280,8 +280,8 @@ def _getuserbase(): if env_base: return env_base - # iOS, tvOS, watchOS, Emscripten, VxWorks, and WASI have no home directories - if sys.platform in {"ios", "emscripten", "tvos", "vxworks", "wasi", "watchos"}: + # 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): diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 0456fa11c60262..91959791fb9193 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -126,8 +126,8 @@ def _getuserbase(): if env_base: return env_base - # iOS, tvOS, watchOS, Emscripten, VxWorks, and WASI have no home directories - if sys.platform in {"ios", "emscripten", "tvos", "vxworks", "wasi", "watchos"}: + # 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): diff --git a/Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst b/Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst index 4ead609ee9e986..9b57cbb812db4a 100644 --- a/Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst +++ b/Misc/NEWS.d/next/Library/2024-03-19-14-35-57.gh-issue-114099.siNSpK.rst @@ -1 +1 @@ -Standard library was modified to allow for iOS platform differences. +Modify standard library to allow for iOS platform differences. diff --git a/Modules/getpath.py b/Modules/getpath.py index 0b0eefb4d449b3..1724c92fc76d64 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -173,7 +173,7 @@ platlibdir = config.get('platlibdir') or PLATLIBDIR -if os_name == "posix" or os_name in {"darwin", "ios", "tvos", "watchos"}: +if os_name in {"darwin", "ios", "posix", "tvos", "watchos"}: BUILDDIR_TXT = 'pybuilddir.txt' BUILD_LANDMARK = 'Modules/Setup.local' DEFAULT_PROGRAM_NAME = f'python{VERSION_MAJOR}' From 96aa042e32a8413486c845cb0908ebd098f31d4a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 22 Mar 2024 13:19:16 +0800 Subject: [PATCH 16/35] Correct the documentation of ios_ver() --- Doc/library/platform.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 9765171133e379..703f435d707190 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -250,19 +250,20 @@ macOS Platform iOS Platform ------------ -.. function:: ios_ver(system='', release='', machine='', is_simulator=False) +.. function:: ios_ver(system='', release='', model='', is_simulator=False) - Get iOS version information and return it as tuple ``(system, release, - machine, is_simulator)`` with *release* being a tuple ``(major, minor)``. + Get iOS version information and return it as a + :func:`~collections.namedtuple` with the following attributes: * ``system`` is the OS name; either ``iOS`` or ``iPadOS``. * ``release`` is the iOS version number as a string (e.g., ``'17.2'``). - * ``machine`` is the device model identifier; this will be a string like - ``iPhone13,2`` for a physical device, or ``iPhone`` on a simulator. + * ``model`` is the device model identifier; this will be a string like + ``iPhone13,2`` for a physical device, or ``'iPhone'`` on a simulator. * ``is_simulator`` is a boolean describing if the app is running on a simulator or a physical device. - Entries which cannot be determined are set to ``''``. + Entries which cannot be determined are set to the defaults given as + parameters. Unix Platforms From 24b36625023c541b7d31a7211a43bc28d0ad72b5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 22 Mar 2024 14:47:28 +0800 Subject: [PATCH 17/35] Clarified some discrepancies in the platform module. --- Doc/library/os.rst | 5 +++++ Doc/library/platform.rst | 4 ++++ Lib/_ios_support.py | 29 +++++++++++++++-------------- Lib/platform.py | 7 ++++--- Lib/test/test_platform.py | 11 +++++++++++ 5 files changed, 39 insertions(+), 17 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 09d8228f986e47..8fecfe6b28dbd6 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -784,6 +784,11 @@ process and user. :func:`socket.gethostname` or even ``socket.gethostbyaddr(socket.gethostname())``. + On macOS, iOS and Android, this returns the *kernel* name and version (i.e., + ``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android), not the user-facing + operating system name. :func:`platform.uname()` can be used to get the + user-facing operating system name on iOS and Android. + .. availability:: Unix. .. versionchanged:: 3.3 diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 703f435d707190..4b477fa80a8dd5 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -148,6 +148,8 @@ Cross Platform Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``, ``'Windows'``. An empty string is returned if the value cannot be determined. + On iOS, this returns the OS name (i.e, ``'iOS``` or ``'iPadOS'``), rather than + the kernel name (``'Darwin'``). .. function:: system_alias(system, release, version) @@ -161,6 +163,8 @@ Cross Platform Returns the system's release version, e.g. ``'#3 on degas'``. An empty string is returned if the value cannot be determined. + On iOS, this is the iOS version, not the Darwin kernel version. To obtain + the Darwin kernel version, use :func:`os.uname()`. .. function:: uname() diff --git a/Lib/_ios_support.py b/Lib/_ios_support.py index d206bcb60c9d86..db3fe23e45bca0 100644 --- a/Lib/_ios_support.py +++ b/Lib/_ios_support.py @@ -3,24 +3,23 @@ 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 platform - # module from working. + # 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 and objc_msgSend methods - objc = cdll.LoadLibrary(util.find_library("objc")) - if objc._name: - 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] - # The calls to objc_msgSend all have no arguments; but the return types - # vary with the specific call. - objc.objc_msgSend.argtypes = [c_void_p, c_void_p] - else: + # sel_registerName methods + lib = util.find_library("objc") + if lib is None: # Failed to load the objc library - objc = None + 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(): @@ -33,6 +32,8 @@ def get_platform_ios(): # 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] diff --git a/Lib/platform.py b/Lib/platform.py index 2de82fafe01ca2..749cd36a2989e3 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -842,8 +842,9 @@ def get_OpenVMS(): csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' - # On iOS, os.uname returns the architecture as uname.machine. On device it - # doesn't; but there's only one CPU architecture on device. + # 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 @@ -1005,7 +1006,7 @@ def uname(): # Normalize responses on iOS if sys.platform == 'ios': - system, release, machine, _ = ios_ver() + system, release, _, _ = ios_ver() vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 71b723593b9b96..49a7dfec65018d 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -219,6 +219,10 @@ def test_uname(self): self.assertEqual(res[-1], res.processor) self.assertEqual(len(res), 6) + if sys.platform == "ios": + self.assertIn(res.system, {"iOS", "iPadOS"}) + self.assertIn(res.release, platform.ios_ver().release) + @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") def test_uname_win32_without_wmi(self): def raises_oserror(*a): @@ -451,6 +455,13 @@ def test_ios_ver(self): self.assertEqual(result.model, "") self.assertFalse(result.is_simulator) + # Check the fallback values can be overridden by arguments + override = platform.ios_ver("Foo", "Bar", "Whiz", True) + self.assertEqual(override.system, "Foo") + self.assertEqual(override.release, "Bar") + self.assertEqual(override.model, "Whiz") + self.assertTrue(override.is_simulator) + @unittest.skipIf(support.is_emscripten, "Does not apply to Emscripten") def test_libc_ver(self): # check that libc_ver(executable) doesn't raise an exception From a4e09c976f9a95b7f2492dca9fb81b6cee31fa7e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 22 Mar 2024 14:51:29 +0800 Subject: [PATCH 18/35] Simplify getpath handling. --- Modules/getpath.c | 5 ----- Modules/getpath.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Modules/getpath.c b/Modules/getpath.c index 5bbbc069379497..abed139028244a 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -19,7 +19,6 @@ #ifdef __APPLE__ # include # include -# include #endif /* Reference the precompiled getpath.py */ @@ -924,11 +923,7 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) #ifdef MS_WINDOWS !decode_to_dict(dict, "os_name", "nt") || #elif defined(__APPLE__) -# if TARGET_OS_IOS - !decode_to_dict(dict, "os_name", "ios") || -# else !decode_to_dict(dict, "os_name", "darwin") || -# endif #else !decode_to_dict(dict, "os_name", "posix") || #endif diff --git a/Modules/getpath.py b/Modules/getpath.py index 1724c92fc76d64..1410ffdbed8c70 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -173,7 +173,7 @@ platlibdir = config.get('platlibdir') or PLATLIBDIR -if os_name in {"darwin", "ios", "posix", "tvos", "watchos"}: +if os_name == 'posix' or os_name == 'darwin': BUILDDIR_TXT = 'pybuilddir.txt' BUILD_LANDMARK = 'Modules/Setup.local' DEFAULT_PROGRAM_NAME = f'python{VERSION_MAJOR}' From 41a3c1ad703d4bf8198c0cde60a3d3dc3504b0fc Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 22 Mar 2024 15:11:03 +0800 Subject: [PATCH 19/35] Modify webbrowser module to use _ios_support as a helper. --- Lib/webbrowser.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index e5a6281e7ccf25..1c0360c7780635 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -606,26 +606,8 @@ def open(self, url, new=0, autoraise=True): # Platform support for iOS # if sys.platform == "ios": - try: - from ctypes import cdll, c_void_p, c_char_p, c_ulong, util - except ImportError: - # If ctypes isn't available, we can't trigger the browser - objc = None - else: - # ctypes is available. Load the ObjC library, and wrap the - # objc_getClass, sel_registerName and objc_msgSend methods - objc = cdll.LoadLibrary(util.find_library("objc")) - if objc._name: - 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] - # The return type of objc_msgSend is always c_void_p; but the - # argument types vary with the specific call. - objc.objc_msgSend.restype = c_void_p - else: - # Failed to load the objc library - objc = None + from _ios_support import objc + from ctypes import c_void_p, c_char_p, c_ulong class IOSBrowser(BaseBrowser): def open(self, url, new=0, autoraise=True): @@ -634,6 +616,9 @@ def open(self, url, new=0, autoraise=True): if objc is None: return False + # All the messages in this call return object references. + objc.objc_msgSend.restype = c_void_p + # This is the equivalent of: # NSString url_string = # [NSString stringWithCString:url.encode("utf-8") @@ -673,6 +658,7 @@ def open(self, url, new=0, autoraise=True): objc.objc_msgSend.argtypes = [ c_void_p, c_void_p, c_void_p, c_void_p, c_void_p ] + # Method returns void objc.objc_msgSend.restype = None objc.objc_msgSend(shared_app, openURL_, ns_url, None, None) From 84ba760d249f3b6cbb65655007b3930a3af273a9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 22 Mar 2024 15:11:32 +0800 Subject: [PATCH 20/35] Simplify sysconfig schemes for iOS. --- Lib/sysconfig/__init__.py | 16 ---------------- Lib/test/test_sysconfig.py | 5 +---- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 91959791fb9193..990e426d35830e 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -58,16 +58,6 @@ 'scripts': '{base}/Scripts', 'data': '{base}', }, - 'ios': { - 'stdlib': '{installed_base}/lib/python{py_version_short}', - 'platstdlib': '{installed_base}/lib/python{py_version_short}', - 'purelib': '{installed_base}/lib/python{py_version_short}/site-packages', - 'platlib': '{installed_base}/lib/python{py_version_short}/site-packages', - 'include': '{installed_base}/include/{implementation_lower}', - 'platinclude': '{installed_base}/include/{implementation_lower}', - 'scripts': '{installed_base}/bin', - 'data': '{installed_base}/Resources', - }, # Downstream distributors can overwrite the default install scheme. # This is done to support downstream modifications where distributors change @@ -302,12 +292,6 @@ def _get_preferred_schemes(): 'home': 'posix_home', 'user': 'osx_framework_user', } - if sys.platform in {'ios', 'tvos', 'watchos'}: - return { - 'prefix': "ios", - 'home': "ios", - 'user': "ios", - } return { 'prefix': 'posix_prefix', diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 14e74593ead529..78d468736f8063 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -357,10 +357,7 @@ def test_get_config_h_filename(self): self.assertTrue(os.path.isfile(config_h), config_h) def test_get_scheme_names(self): - wanted = [ - "ios", "nt", "posix_home", "posix_prefix", - "posix_venv", "nt_venv", "venv" - ] + wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv'] if HAS_USER_BASE: wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) From 41948490efdf7350b0b05098a5aef59361835a32 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 25 Mar 2024 07:24:20 +0800 Subject: [PATCH 21/35] Apply suggestions from code review Co-authored-by: Malcolm Smith --- Doc/library/os.rst | 6 +++--- Doc/library/platform.rst | 9 +++++---- Lib/test/test_platform.py | 2 +- Lib/test/test_sysconfig.py | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 8fecfe6b28dbd6..5149ed4bad0fd8 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -785,9 +785,9 @@ process and user. ``socket.gethostbyaddr(socket.gethostname())``. On macOS, iOS and Android, this returns the *kernel* name and version (i.e., - ``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android), not the user-facing - operating system name. :func:`platform.uname()` can be used to get the - user-facing operating system name on iOS and Android. + ``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android). :func:`platform.uname()` + can be used to get the user-facing operating system name and version on iOS and + Android. .. availability:: Unix. diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 4b477fa80a8dd5..11ab8f46123681 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -148,8 +148,9 @@ Cross Platform Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``, ``'Windows'``. An empty string is returned if the value cannot be determined. - On iOS, this returns the OS name (i.e, ``'iOS``` or ``'iPadOS'``), rather than - the kernel name (``'Darwin'``). + On iOS and Android, this returns the user-facing OS name (i.e, ``'iOS``, + ``'iPadOS'`` or ``'Android'``). To obtain the kernel name (``'Darwin'`` or + ``'Linux'``), use :func:`os.uname()`. .. function:: system_alias(system, release, version) @@ -163,8 +164,8 @@ Cross Platform Returns the system's release version, e.g. ``'#3 on degas'``. An empty string is returned if the value cannot be determined. - On iOS, this is the iOS version, not the Darwin kernel version. To obtain - the Darwin kernel version, use :func:`os.uname()`. + On iOS and Android, this is the user-facing OS version. To obtain the + Darwin or Linux kernel version, use :func:`os.uname()`. .. function:: uname() diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 49a7dfec65018d..c05fe8a81608bd 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -221,7 +221,7 @@ def test_uname(self): if sys.platform == "ios": self.assertIn(res.system, {"iOS", "iPadOS"}) - self.assertIn(res.release, platform.ios_ver().release) + self.assertEqual(res.release, platform.ios_ver().release) @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") def test_uname_win32_without_wmi(self): diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 78d468736f8063..43726abe737cdd 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -485,7 +485,7 @@ def test_platform_in_subprocess(self): @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") @unittest.skipIf(is_apple_mobile, - f"{sys.platform} doesn't distribute config.h in the runtime environment") + f"{sys.platform} doesn't include config folder at runtime") def test_srcdir(self): # See Issues #15322, #15364. srcdir = sysconfig.get_config_var('srcdir') @@ -567,7 +567,7 @@ class MakefileTests(unittest.TestCase): 'Test is not Windows compatible') @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") @unittest.skipIf(is_apple_mobile, - f"{sys.platform} doesn't distribute config.h in the runtime environment") + f"{sys.platform} doesn't include config folder at runtime") def test_get_makefile_filename(self): makefile = sysconfig.get_makefile_filename() self.assertTrue(os.path.isfile(makefile), makefile) From 9a91933c26e1ba67f6c5591e96c8a750f0657885 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 25 Mar 2024 07:46:00 +0800 Subject: [PATCH 22/35] More documentation tweaks. --- Doc/library/os.rst | 4 ++-- Doc/library/platform.rst | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 5149ed4bad0fd8..e1f29ae051e2fa 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -785,8 +785,8 @@ process and user. ``socket.gethostbyaddr(socket.gethostname())``. On macOS, iOS and Android, this returns the *kernel* name and version (i.e., - ``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android). :func:`platform.uname()` - can be used to get the user-facing operating system name and version on iOS and + ``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android). :func:`platform.uname()` + can be used to get the user-facing operating system name and version on iOS and Android. .. availability:: Unix. diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 11ab8f46123681..d408509f9dd20a 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -148,8 +148,8 @@ Cross Platform Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``, ``'Windows'``. An empty string is returned if the value cannot be determined. - On iOS and Android, this returns the user-facing OS name (i.e, ``'iOS``, - ``'iPadOS'`` or ``'Android'``). To obtain the kernel name (``'Darwin'`` or + On iOS and Android, this returns the user-facing OS name (i.e, ``'iOS``, + ``'iPadOS'`` or ``'Android'``). To obtain the kernel name (``'Darwin'`` or ``'Linux'``), use :func:`os.uname()`. .. function:: system_alias(system, release, version) @@ -164,7 +164,7 @@ Cross Platform Returns the system's release version, e.g. ``'#3 on degas'``. An empty string is returned if the value cannot be determined. - On iOS and Android, this is the user-facing OS version. To obtain the + On iOS and Android, this is the user-facing OS version. To obtain the Darwin or Linux kernel version, use :func:`os.uname()`. .. function:: uname() @@ -260,10 +260,10 @@ iOS Platform Get iOS version information and return it as a :func:`~collections.namedtuple` with the following attributes: - * ``system`` is the OS name; either ``iOS`` or ``iPadOS``. + * ``system`` is the OS name; either ``'iOS'`` or ``'iPadOS'``. * ``release`` is the iOS version number as a string (e.g., ``'17.2'``). * ``model`` is the device model identifier; this will be a string like - ``iPhone13,2`` for a physical device, or ``'iPhone'`` on a simulator. + ``'iPhone13,2'`` for a physical device, or ``'iPhone'`` on a simulator. * ``is_simulator`` is a boolean describing if the app is running on a simulator or a physical device. From e6550b79e91e4f93afdee87009cba066b3764fad Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 25 Mar 2024 07:46:24 +0800 Subject: [PATCH 23/35] Add protection for missing ctypes to webbrowser module. --- Lib/webbrowser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 1c0360c7780635..7ef80a8f5ace9e 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -607,7 +607,9 @@ def open(self, url, new=0, autoraise=True): # if sys.platform == "ios": from _ios_support import objc - from ctypes import c_void_p, c_char_p, c_ulong + if objc: + # If objc exists, we know ctypes is also importable. + from ctypes import c_void_p, c_char_p, c_ulong class IOSBrowser(BaseBrowser): def open(self, url, new=0, autoraise=True): From 8654376d41950a401ab08d782292a6b52337460a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 25 Mar 2024 08:08:56 +0800 Subject: [PATCH 24/35] Simplifications and clarifications to sysconfig. --- Lib/sysconfig/__init__.py | 3 --- Lib/test/test_sysconfig.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 990e426d35830e..75707c775d2e6c 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -510,9 +510,6 @@ def _init_config_vars(): import _osx_support _osx_support.customize_config_vars(_CONFIG_VARS) - if sys.platform in {'ios', 'tvos', 'watchos'}: - _CONFIG_VARS['installed_base'] = sys.executable - global _CONFIG_VARS_INITIALIZED _CONFIG_VARS_INITIALIZED = True diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 43726abe737cdd..e48eeab75dcb60 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -351,7 +351,7 @@ def test_get_platform(self): @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") @unittest.skipIf(is_apple_mobile, - f"{sys.platform} doesn't distribute config.h in the runtime environment") + f"{sys.platform} doesn't distribute header files in the runtime environment") def test_get_config_h_filename(self): config_h = sysconfig.get_config_h_filename() self.assertTrue(os.path.isfile(config_h), config_h) From 4451326e238d9035d27b34e3fbaf88cd0599ff82 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 25 Mar 2024 09:13:30 +0800 Subject: [PATCH 25/35] Correct the naming of the IPHONEOS_DEPLOYMENT_TARGET variable. --- Lib/sysconfig/__init__.py | 4 ++-- Lib/test/pythoninfo.py | 2 +- Makefile.pre.in | 2 +- configure | 18 +++++++++--------- configure.ac | 19 +++++++++---------- iOS/Resources/Info.plist.in | 2 +- 6 files changed, 23 insertions(+), 24 deletions(-) diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 75707c775d2e6c..70bdecf2138fd9 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -21,7 +21,7 @@ # Keys for get_config_var() that are never converted to Python integers. _ALWAYS_STR = { - 'IOS_DEPLOYMENT_TARGET', + 'IPHONEOS_DEPLOYMENT_TARGET', 'MACOSX_DEPLOYMENT_TARGET', } @@ -627,7 +627,7 @@ def get_platform(): release = m.group() elif osname[:6] == "darwin": if sys.platform == "ios": - release = get_config_vars().get("IOS_DEPLOYMENT_TARGET", "12.0") + release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "12.0") osname = sys.platform machine = sys.implementation._multiarch else: diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index a6b743fa7a9851..8f85f6df8917bd 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -287,7 +287,7 @@ def format_groups(groups): "HOMEDRIVE", "HOMEPATH", "IDLESTARTUP", - "IOS_DEPLOYMENT_TARGET", + "IPHONEOS_DEPLOYMENT_TARGET", "LANG", "LDFLAGS", "LDSHARED", diff --git a/Makefile.pre.in b/Makefile.pre.in index 76b0975bbbdaae..9c68b1fd6cf9d9 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -192,7 +192,7 @@ RESSRCDIR= @RESSRCDIR@ MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@ @EXPORT_MACOSX_DEPLOYMENT_TARGET@export MACOSX_DEPLOYMENT_TARGET # iOS Deployment target selected during configure -IOS_DEPLOYMENT_TARGET=@CONFIGURE_IOS_DEPLOYMENT_TARGET@ +IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@ # Option to install to strip binaries STRIPFLAG=-s diff --git a/configure b/configure index 8c717cd82f01c6..f81d1a68927c7e 100755 --- a/configure +++ b/configure @@ -976,7 +976,7 @@ LDFLAGS CFLAGS CC HAS_XCRUN -CONFIGURE_IOS_DEPLOYMENT_TARGET +IPHONEOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET _PYTHON_HOST_PLATFORM @@ -4442,15 +4442,16 @@ if test "$cross_compiling" = yes; then _host_device=`echo $host | cut -d '-' -f4` _host_device=${_host_device:=os} - IOS_DEPLOYMENT_TARGET=${_host_os:3} - IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET:=12.0} + # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version + IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} + IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=12.0} case "$host_cpu" in aarch64) - _host_ident=${IOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} + _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} ;; *) - _host_ident=${IOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} + _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} ;; esac ;; @@ -4597,8 +4598,7 @@ fi CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' -# Record the value of IOS_DEPLOYMENT_TARGET enforced by the selected host triple. -CONFIGURE_IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET} +# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple. # checks for alternative programs @@ -4636,8 +4636,8 @@ esac case $ac_sys_system in #( iOS) : - as_fn_append CFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" - as_fn_append LDFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" + as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" + as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ;; #( *) : ;; diff --git a/configure.ac b/configure.ac index c8226f86e2934c..dace8925706f21 100644 --- a/configure.ac +++ b/configure.ac @@ -715,16 +715,16 @@ if test "$cross_compiling" = yes; then _host_device=`echo $host | cut -d '-' -f4` _host_device=${_host_device:=os} - dnl IOS_DEPLOYMENT_TARGET is the minimum supported iOS version - IOS_DEPLOYMENT_TARGET=${_host_os:3} - IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET:=12.0} + # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version + IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} + IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=12.0} case "$host_cpu" in aarch64) - _host_ident=${IOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} + _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} ;; *) - _host_ident=${IOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} + _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} ;; esac ;; @@ -866,9 +866,8 @@ AC_SUBST([EXPORT_MACOSX_DEPLOYMENT_TARGET]) CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' -# Record the value of IOS_DEPLOYMENT_TARGET enforced by the selected host triple. -CONFIGURE_IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET} -AC_SUBST([CONFIGURE_IOS_DEPLOYMENT_TARGET]) +# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple. +AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET]) # checks for alternative programs @@ -905,8 +904,8 @@ AS_CASE([$host], dnl Add the compiler flag for the iOS minimum supported OS version. AS_CASE([$ac_sys_system], [iOS], [ - AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) - AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) + AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) + AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) ], ) diff --git a/iOS/Resources/Info.plist.in b/iOS/Resources/Info.plist.in index 52c0a6e7fd7a55..c3e261ecd9eff7 100644 --- a/iOS/Resources/Info.plist.in +++ b/iOS/Resources/Info.plist.in @@ -29,6 +29,6 @@ iPhoneOS MinimumOSVersion - @IOS_DEPLOYMENT_TARGET@ + @IPHONEOS_DEPLOYMENT_TARGET@ From 4386a7a2f90082f5f69a102d8cfe6c8b02e04d1e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 26 Mar 2024 05:47:01 +0800 Subject: [PATCH 26/35] Added clarifying comment around IPHONEOS_DEPLOYMENT_TARGET --- Makefile.pre.in | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 9c68b1fd6cf9d9..5535f47d65ba5d 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -186,12 +186,16 @@ PYTHONFRAMEWORKPREFIX= @PYTHONFRAMEWORKPREFIX@ PYTHONFRAMEWORKINSTALLDIR= @PYTHONFRAMEWORKINSTALLDIR@ PYTHONFRAMEWORKINSTALLNAMEPREFIX= @PYTHONFRAMEWORKINSTALLNAMEPREFIX@ RESSRCDIR= @RESSRCDIR@ -# Deployment target selected during configure, to be checked +# macOS deployment target selected during configure, to be checked # by distutils. The export statement is needed to ensure that the # deployment target is active during build. MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@ @EXPORT_MACOSX_DEPLOYMENT_TARGET@export MACOSX_DEPLOYMENT_TARGET -# iOS Deployment target selected during configure + +# iOS Deployment target selected during configure. Unlike macOS, the iOS +# deployment target is controlled using `-mios-version-min` arguments added to +# CFLAGS and LDFLAGS by the configure script. This variable is not used during +# the build, and is only listed here so it will be included in sysconfigdata. IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@ # Option to install to strip binaries From 61559ac5f52ddf8d3e527970949d9acfd77d6a35 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 27 Mar 2024 14:33:39 +0800 Subject: [PATCH 27/35] Removed the hard-coded development team. --- iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj index 4389c08ac1960d..d57cfc3dbe0304 100644 --- a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj +++ b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj @@ -441,7 +441,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 3HEZE76D99; + DEVELOPMENT_TEAM = ""; ENABLE_USER_SCRIPT_SANDBOXING = NO; HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; @@ -471,7 +471,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 3HEZE76D99; + DEVELOPMENT_TEAM = ""; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; From abc203490c78be5017e802c8b7f2564ad1f000ef Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 27 Mar 2024 14:34:29 +0800 Subject: [PATCH 28/35] Account for some testing edge cases picked up in review. --- Lib/test/test_platform.py | 8 ++++---- Lib/test/test_sysconfig.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index c05fe8a81608bd..ae0f883378e338 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -430,10 +430,10 @@ def test_ios_ver(self): # System is either iOS or iPadOS, depending on the test device self.assertIn(system, {"iOS", "iPadOS"}) - # Release is a major.minor specifier - major, minor = release.split(".") - self.assertTrue(major.isdigit()) - self.assertTrue(minor.isdigit()) + # Release is a numeric version specifier with at least 2 parts + parts = release.split(".") + self.assertGreaterEqual(len(parts), 2) + self.assertTrue(all(part.isdigit() for part in parts)) # If this is a simulator, we get a high level device descriptor # with no identifying model number. If this is a physical device, diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index e48eeab75dcb60..61c6a5a42502e7 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -430,7 +430,8 @@ def test_library(self): self.assertTrue(library.endswith('.dll')) self.assertEqual(library, ldlibrary) elif is_apple_mobile: - self.assertEqual(ldlibrary, "Python.framework/Python") + framework = sysconfig.get_config_var('PYTHONFRAMEWORK') + self.assertEqual(ldlibrary, f"{framework}.framework/{framework}") else: self.assertTrue(library.startswith(f'libpython{major}.{minor}')) self.assertTrue(library.endswith('.a')) From 096078afc922f78c81b21475fc39f296d9f1a9fd Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 27 Mar 2024 14:59:37 +0800 Subject: [PATCH 29/35] Disable --with-ensurepip for iOS. --- configure | 2 ++ configure.ac | 1 + 2 files changed, 3 insertions(+) diff --git a/configure b/configure index f81d1a68927c7e..542783e723d934 100755 --- a/configure +++ b/configure @@ -27500,6 +27500,8 @@ else $as_nop with_ensurepip=no ;; #( WASI) : with_ensurepip=no ;; #( + iOS) : + with_ensurepip=no ;; #( *) : with_ensurepip=upgrade ;; diff --git a/configure.ac b/configure.ac index dace8925706f21..fc62bfe5a1d4c4 100644 --- a/configure.ac +++ b/configure.ac @@ -6941,6 +6941,7 @@ AC_ARG_WITH([ensurepip], AS_CASE([$ac_sys_system], [Emscripten], [with_ensurepip=no], [WASI], [with_ensurepip=no], + [iOS], [with_ensurepip=no], [with_ensurepip=upgrade] ) ]) From 7419002db38fb9a463276acfdcee546d9bfddcca Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Mar 2024 07:13:08 +0800 Subject: [PATCH 30/35] Correct merge of test_platform. --- Lib/test/test_platform.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index d35058db84759e..10ed18bf0e0c30 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -228,13 +228,13 @@ def test_uname(self): if sys.platform == "android": self.assertEqual(res.system, "Android") self.assertEqual(res.release, platform.android_ver().release) + elif sys.platform == "ios": + self.assertIn(res.system, {"iOS", "iPadOS"}) + self.assertEqual(res.release, platform.ios_ver().release) else: self.assertEqual(res.system, uname.sysname) self.assertEqual(res.release, uname.release) - if sys.platform == "ios": - self.assertIn(res.system, {"iOS", "iPadOS"}) - self.assertEqual(res.release, platform.ios_ver().release) @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") def test_uname_win32_without_wmi(self): From c121d5fb7c4e9576ecebb5cf25fc4e151a496d62 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Mar 2024 12:32:14 +0800 Subject: [PATCH 31/35] Always display iOS test output. --- Makefile.pre.in | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 5535f47d65ba5d..38dc518b7b1e22 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2044,17 +2044,23 @@ testios: cp -r $(srcdir)/iOS/testbed $(XCFOLDER) # Copy the framework from the install location to the testbed project. cp -r $(PYTHONFRAMEWORKPREFIX)/* $(XCFOLDER)/Python.xcframework/ios-arm64_x86_64-simulator + # Run the test suite for the Xcode project, targeting the iOS simulator. - # If the suite fails, extract and print the console output, then re-raise the failure + # If the suite fails, touch a file in the test folder as a marker if ! xcodebuild test -project $(XCFOLDER)/iOSTestbed.xcodeproj -scheme "iOSTestbed" -destination "platform=iOS Simulator,name=iPhone SE (3rd Generation)" -resultBundlePath $(XCRESULT) ; then \ - xcrun xcresulttool get --path $(XCRESULT) \ - --id $$( \ - xcrun xcresulttool get --path $(XCRESULT) --format json | \ - $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['actions']['_values'][0]['actionResult']['logRef']['id']['_value'])" \ - ) \ - --format json | \ - $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['subsections']['_values'][1]['subsections']['_values'][0]['emittedOutput']['_value'])"; \ - echo ; \ + touch $(XCFOLDER)/failed; \ + fi + + # Regardless of success or failure, extract and print the test output + xcrun xcresulttool get --path $(XCRESULT) \ + --id $$( \ + xcrun xcresulttool get --path $(XCRESULT) --format json | \ + $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['actions']['_values'][0]['actionResult']['logRef']['id']['_value'])" \ + ) \ + --format json | \ + $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['subsections']['_values'][1]['subsections']['_values'][0]['emittedOutput']['_value'])" + + @if test -e $(XCFOLDER)/failed ; then \ exit 1; \ fi From 1ac4b26ec881ffe303feba3396fa34558edae064 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Mar 2024 13:15:37 +0800 Subject: [PATCH 32/35] Make test suite resilient to the absence of ctypes. --- Lib/platform.py | 2 +- Lib/test/test_platform.py | 24 ++++++++++++++++++++---- Lib/test/test_webbrowser.py | 1 + 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 9ec88dfd00cf45..dbcb636df64981 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -678,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 diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 10ed18bf0e0c30..40d5fb338ce563 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -10,6 +10,14 @@ from test import support from test.support import os_helper +try: + # Some of the iOS tests need ctypes to operate. + # Confirm that the ctypes module is available + # is available. + import _ctypes +except ImportError: + _ctypes = None + FEDORA_OS_RELEASE = """\ NAME=Fedora VERSION="32 (Thirty Two)" @@ -229,8 +237,15 @@ def test_uname(self): self.assertEqual(res.system, "Android") self.assertEqual(res.release, platform.android_ver().release) elif sys.platform == "ios": - self.assertIn(res.system, {"iOS", "iPadOS"}) - self.assertEqual(res.release, platform.ios_ver().release) + # Platform module needs ctypes for full operation. If ctypes + # isn't available, there's no ObjC module, and dummy values are + # returned. + if _ctypes: + self.assertIn(res.system, {"iOS", "iPadOS"}) + self.assertEqual(res.release, platform.ios_ver().release) + else: + self.assertEqual(res.system, "") + self.assertEqual(res.release, "") else: self.assertEqual(res.system, uname.sysname) self.assertEqual(res.release, uname.release) @@ -428,9 +443,10 @@ def test_mac_ver_with_fork(self): def test_ios_ver(self): result = platform.ios_ver() - if sys.platform == "ios": - system, release, model, is_simulator = result + # ios_ver is only fully available on iOS where ctypes is available. + if sys.platform == "ios" and _ctypes: + system, release, model, is_simulator = result # Result is a namedtuple self.assertEqual(result.system, system) self.assertEqual(result.release, release) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 733544bbb2aeca..603dfbfa9fa5a9 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -230,6 +230,7 @@ def _obj_ref(self, *args): # as a proxy for object instance references return "|".join(str(a) for a in args) + @unittest.skipIf(webbrowser.objc is None, "iOS Webbrowser tests require ctypes") def setUp(self): # Intercept the the objc library. Wrap the calls to get the # references to classes and selectors to return strings, and From 857a0c3afabc35a94f31defe6fa0dce8d44e8e40 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Mar 2024 13:41:35 +0800 Subject: [PATCH 33/35] Make webbrowser skip resilient across platforms. --- Lib/test/test_webbrowser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 603dfbfa9fa5a9..a1bccb5f19b60f 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -230,7 +230,8 @@ def _obj_ref(self, *args): # as a proxy for object instance references return "|".join(str(a) for a in args) - @unittest.skipIf(webbrowser.objc is None, "iOS Webbrowser tests require ctypes") + @unittest.skipIf(getattr(webbrowser, "objc", None) is None, + "iOS Webbrowser tests require ctypes") def setUp(self): # Intercept the the objc library. Wrap the calls to get the # references to classes and selectors to return strings, and From aa65dc22c1c2f12f2e177ad0446ba1df30d96898 Mon Sep 17 00:00:00 2001 From: Ned Deily Date: Thu, 28 Mar 2024 02:21:28 -0400 Subject: [PATCH 34/35] Account for ABI suffix in header directory Needed for --with-pydebug builds --- Makefile.pre.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 38dc518b7b1e22..5b89d6ba1acf71 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2795,8 +2795,8 @@ frameworkinstallmobileheaders: frameworkinstallunversionedstructure inclinstall echo "Removing old framework headers"; \ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \ fi - mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(VERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers" - $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(VERSION)" + mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers" + $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" # Build the toplevel Makefile Makefile.pre: $(srcdir)/Makefile.pre.in config.status From 61e51ff9b3cf46015badf6d9777de6bbb3038c67 Mon Sep 17 00:00:00 2001 From: Ned Deily Date: Thu, 28 Mar 2024 02:46:25 -0400 Subject: [PATCH 35/35] test_refcount_errors requires subprocess --- Lib/test/test_gc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 3bf5c9ed41ee44..fa8e50fccb2c7b 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1223,6 +1223,7 @@ def test_collect_garbage(self): self.assertEqual(len(gc.garbage), 0) + @requires_subprocess() @unittest.skipIf(BUILD_WITH_NDEBUG, 'built with -NDEBUG') def test_refcount_errors(self):