From a4c04b6434f89b5312480818fbbefadfbb9a94a1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 25 Oct 2022 06:47:39 +0800 Subject: [PATCH 01/13] Bump for Python v3.11.0 release. --- Makefile | 2 +- README.rst | 10 +++++----- patch/Python/Python.patch | 24 ++++++++++++------------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index a24ada3a..702ce082 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ BUILD_NUMBER=custom # PYTHON_VERSION is the full version number (e.g., 3.10.0b3) # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.11.0rc2 +PYTHON_VERSION=3.11.0 PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_VER=$(basename $(PYTHON_VERSION)) diff --git a/README.rst b/README.rst index 57017afe..fc46a4f1 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ Python Apple Support This is a meta-package for building a version of Python that can be embedded into a macOS, iOS, tvOS or watchOS project. -**This branch builds a packaged version of Python 3.11.0rc1**. +**This branch builds a packaged version of Python 3.11.0**. Other Python versions are available by cloning other branches of the main repository: @@ -151,10 +151,10 @@ To build these wheels, run: * `make wheels-tvOS` to build all the tvOS wheels * `make wheels-watchOS` to build all the watchOS wheels -.. _for macOS: https://briefcase-support.s3.amazonaws.com/python/3.11/macOS/Python-3.11-macOS-support.b0.tar.gz -.. _for iOS: https://briefcase-support.s3.amazonaws.com/python/3.11/iOS/Python-3.11-iOS-support.b0.tar.gz -.. _for tvOS: https://briefcase-support.s3.amazonaws.com/python/3.11/tvOS/Python-3.11-tvOS-support.b0.tar.gz -.. _for watchOS: https://briefcase-support.s3.amazonaws.com/python/3.11/watchOS/Python-3.11-watchOS-support.b0.tar.gz +.. _for macOS: https://briefcase-support.s3.amazonaws.com/python/3.11/macOS/Python-3.11-macOS-support.b1.tar.gz +.. _for iOS: https://briefcase-support.s3.amazonaws.com/python/3.11/iOS/Python-3.11-iOS-support.b1.tar.gz +.. _for tvOS: https://briefcase-support.s3.amazonaws.com/python/3.11/tvOS/Python-3.11-tvOS-support.b1.tar.gz +.. _for watchOS: https://briefcase-support.s3.amazonaws.com/python/3.11/watchOS/Python-3.11-watchOS-support.b1.tar.gz Historical support ------------------ diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index b51b93fb..e95db4bc 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -755,7 +755,7 @@ index 9b2cd201f3..e47de5d468 100644 @unittest.skipUnless(sys.platform == 'darwin', "OSX only test") def test_mac_ver_with_fork(self): diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py -index ae25ef5588..2885d5fd52 100644 +index e643d8e5a4..65e0e2d2c6 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2,6 +2,7 @@ @@ -1029,7 +1029,7 @@ index 9c6561c099..ccccfb8b0b 100644 # Same as above, but a delay gets introduced after the thread's # Python code returned but before the thread state is deleted. diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py -index 74039f59f7..36dc82f871 100644 +index decf021c64..7c824c8278 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -19,7 +19,7 @@ @@ -1038,7 +1038,7 @@ index 74039f59f7..36dc82f871 100644 skip_if_broken_multiprocessing_synchronize, verbose, - requires_subprocess, is_emscripten, is_wasi, + requires_subprocess, is_apple_mobile, is_emscripten, is_wasi, - requires_venv_with_pip) + requires_venv_with_pip, TEST_HOME_DIR) from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) import unittest @@ -40,6 +40,8 @@ @@ -1313,7 +1313,7 @@ index aa93e756c6..fcf3784c2f 100644 sin(pi*x), giving accurate results for all finite x (especially x integral or close to an integer). This is here for use in the diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c -index 378032501f..25a035acd7 100644 +index 309982af82..2afccdc969 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -69,6 +69,8 @@ @@ -1325,7 +1325,7 @@ index 378032501f..25a035acd7 100644 #if defined(__has_builtin) #if __has_builtin(__builtin_available) #define HAVE_BUILTIN_AVAILABLE 1 -@@ -325,6 +327,26 @@ +@@ -329,6 +331,26 @@ # endif /* _MSC_VER */ #endif /* ! __WATCOMC__ || __QNX__ */ @@ -1352,7 +1352,7 @@ index 378032501f..25a035acd7 100644 /*[clinic input] # one of the few times we lie about this name! module os -@@ -1573,7 +1595,9 @@ +@@ -1577,7 +1599,9 @@ */ #include #elif !defined(_MSC_VER) && (!defined(__WATCOMC__) || defined(__QNX__) || defined(__VXWORKS__)) @@ -1362,7 +1362,7 @@ index 378032501f..25a035acd7 100644 #endif /* !_MSC_VER */ static PyObject * -@@ -1589,6 +1613,7 @@ +@@ -1593,6 +1617,7 @@ d = PyDict_New(); if (d == NULL) return NULL; @@ -1370,7 +1370,7 @@ index 378032501f..25a035acd7 100644 #ifdef MS_WINDOWS /* _wenviron must be initialized in this way if the program is started through main() instead of wmain(). */ -@@ -1642,6 +1667,7 @@ +@@ -1646,6 +1671,7 @@ Py_DECREF(k); Py_DECREF(v); } @@ -1378,7 +1378,7 @@ index 378032501f..25a035acd7 100644 return d; } -@@ -4935,6 +4961,9 @@ +@@ -4939,6 +4965,9 @@ /*[clinic end generated code: output=290fc437dd4f33a0 input=86a58554ba6094af]*/ { long result; @@ -1388,7 +1388,7 @@ index 378032501f..25a035acd7 100644 const char *bytes = PyBytes_AsString(command); if (PySys_Audit("os.system", "(O)", command) < 0) { -@@ -4944,6 +4973,7 @@ +@@ -4948,6 +4977,7 @@ Py_BEGIN_ALLOW_THREADS result = system(bytes); Py_END_ALLOW_THREADS @@ -1396,7 +1396,7 @@ index 378032501f..25a035acd7 100644 return result; } #endif -@@ -13700,6 +13730,7 @@ +@@ -13736,6 +13766,7 @@ int is_symlink; int need_stat; #endif @@ -1404,7 +1404,7 @@ index 378032501f..25a035acd7 100644 #ifdef MS_WINDOWS unsigned long dir_bits; #endif -@@ -13760,6 +13791,7 @@ +@@ -13796,6 +13827,7 @@ #endif return result; From ed0df136c2f2e1a7283b52e36b76d3fd6f34b42a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 30 Aug 2023 15:21:03 +0800 Subject: [PATCH 02/13] Update patch of v3.11.5 Includes: * XZ 5.4.4 * OpenSSL 3.1.2 * LibFFI 3.4.4 plus fixes to the build system to avoid lockups due to cross-compilation autodetection issues on iOS. --- Makefile | 11 +- patch/Python/Python.patch | 168 +++++++++++----------- patch/libffi-3.4.4.patch | 174 +++++++++++++++++++++++ patch/{xz-5.4.2.patch => xz-5.4.4.patch} | 2 +- 4 files changed, 261 insertions(+), 94 deletions(-) create mode 100644 patch/libffi-3.4.4.patch rename patch/{xz-5.4.2.patch => xz-5.4.4.patch} (99%) diff --git a/Makefile b/Makefile index fe5dcff8..4b9886e8 100644 --- a/Makefile +++ b/Makefile @@ -39,23 +39,23 @@ BUILD_NUMBER=custom # PYTHON_VERSION is the full version number (e.g., 3.10.0b3) # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.11.3 +PYTHON_VERSION=3.11.5 PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_VER=$(basename $(PYTHON_VERSION)) BZIP2_VERSION=1.0.8 -XZ_VERSION=5.4.2 +XZ_VERSION=5.4.4 # Preference is to use OpenSSL 3; however, Cryptography 3.4.8 (and # probably some other packages as well) only works with 1.1.1, so # we need to preserve the ability to build the older OpenSSL (for now...) -OPENSSL_VERSION=3.1.0 +OPENSSL_VERSION=3.1.2 # OPENSSL_VERSION_NUMBER=1.1.1 -# OPENSSL_REVISION=q +# OPENSSL_REVISION=v # OPENSSL_VERSION=$(OPENSSL_VERSION_NUMBER)$(OPENSSL_REVISION) -LIBFFI_VERSION=3.4.2 +LIBFFI_VERSION=3.4.4 # Supported OS and dependencies DEPENDENCIES=BZip2 XZ OpenSSL libFFI @@ -310,6 +310,7 @@ $$(XZ_SRCDIR-$(target))/Makefile: downloads/xz-$(XZ_VERSION).tar.gz --disable-shared \ --enable-static \ --host=$$(TARGET_TRIPLE-$(target)) \ + --build=$(HOST_ARCH)-apple-darwin \ --prefix="$$(XZ_INSTALL-$(target))" \ 2>&1 | tee -a ../xz-$(XZ_VERSION).config.log diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 0ba33d6a..3909c1c8 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -266,7 +266,7 @@ index 69670d9d7f..8287267e64 100644 return f'{userbase}/lib/python{version[0]}.{version[1]}/site-packages' diff --git a/Lib/subprocess.py b/Lib/subprocess.py -index 1f203bd00d..3d9169ef81 100644 +index 6df5dd551e..396fb60c8d 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -74,8 +74,8 @@ @@ -360,7 +360,7 @@ index ebe3711827..ea00c3176d 100644 return f"{osname}-{release}-{machine}" diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index c33f90d807..041789e823 100644 +index 98be9cdd0e..baca44ea62 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -47,7 +47,7 @@ @@ -580,7 +580,7 @@ index 8e98256a62..819c8ef89c 100644 off_t = 'l' pid_t = 'i' diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py -index cbcf94136a..ae52a91f6d 100644 +index cdd1bea754..fdc1750880 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -30,6 +30,7 @@ @@ -591,7 +591,7 @@ index cbcf94136a..ae52a91f6d 100644 from test.support import os_helper from test.support import threading_helper -@@ -401,7 +402,7 @@ +@@ -422,7 +423,7 @@ with open(os.path.join(self.tempdir, filename), 'wb') as f: f.write(os_helper.TESTFN_UNDECODABLE) response = self.request(self.base_url + '/') @@ -622,7 +622,7 @@ index 79aa2da586..791b5ec018 100644 'largefile', 'test requires %s bytes and a long time to run' % self.LARGE) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py -index 350f4a57e2..54ef53a4c1 100644 +index f1f2b75fef..8d1edd79e7 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -43,6 +43,7 @@ @@ -633,7 +633,7 @@ index 350f4a57e2..54ef53a4c1 100644 from test.support import os_helper from test.support import socket_helper from test.support import threading_helper -@@ -1787,9 +1788,20 @@ +@@ -1823,9 +1824,20 @@ # just need a name - file can't be present, or we'll get an # 'address already in use' error. os.remove(fn) @@ -654,7 +654,7 @@ index 350f4a57e2..54ef53a4c1 100644 class UnixSocketHandlerTest(SocketHandlerTest): """Test for SocketHandler with unix sockets.""" -@@ -1873,6 +1885,7 @@ +@@ -1909,6 +1921,7 @@ self.assertEqual(self.log_output, "spam\neggs\n") @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") @@ -662,7 +662,7 @@ index 350f4a57e2..54ef53a4c1 100644 class UnixDatagramHandlerTest(DatagramHandlerTest): """Test for DatagramHandler using Unix sockets.""" -@@ -1967,6 +1980,7 @@ +@@ -2003,6 +2016,7 @@ self.assertEqual(self.log_output, b'<11>sp\xc3\xa4m\x00') @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") @@ -694,7 +694,7 @@ index a1a91f661b..92fde54f5a 100644 last.append([0]) last = last[-1] diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py -index 213a44d56f..87279579fe 100644 +index bab8686008..075786e463 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1,5 +1,5 @@ @@ -713,7 +713,7 @@ index 213a44d56f..87279579fe 100644 # Try incompatible flags, prot and access parameters. with open(TESTFN, "r+b") as f: self.assertRaises(ValueError, mmap.mmap, f.fileno(), mapsize, -@@ -896,7 +896,7 @@ +@@ -1002,7 +1002,7 @@ unlink(TESTFN) def _make_test_file(self, num_zeroes, tail): @@ -812,10 +812,10 @@ index e643d8e5a4..65e0e2d2c6 100644 self.assertRaises(OSError, posix.sched_get_priority_max, -23) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py -index 055c9af2cc..338cb6f61a 100644 +index 274a8b7377..06cab9848a 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py -@@ -1783,6 +1783,7 @@ +@@ -1840,6 +1840,7 @@ check_chown(dirname, uid, gid) @@ -823,7 +823,7 @@ index 055c9af2cc..338cb6f61a 100644 class TestWhich(BaseTest, unittest.TestCase): def setUp(self): -@@ -2649,6 +2650,7 @@ +@@ -2706,6 +2707,7 @@ self.assertGreaterEqual(size.lines, 0) @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty") @@ -1051,10 +1051,10 @@ index 86ce60fef1..c36e6b6096 100644 @requires_subprocess() def check_output(cmd, encoding=None): diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py -index ffb954659e..8a5bfb7a7f 100644 +index c8e0159765..639e8f2c21 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py -@@ -1167,6 +1167,7 @@ +@@ -1320,6 +1320,7 @@ self.skipTest('requires write access to the installed location') unlink(filename) @@ -1062,7 +1062,7 @@ index ffb954659e..8a5bfb7a7f 100644 def test_write_pyfile(self): self.requiresWriteAccess(os.path.dirname(__file__)) with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp: -@@ -1211,6 +1212,7 @@ +@@ -1364,6 +1365,7 @@ self.assertCompiledIn('email/__init__.py', names) self.assertCompiledIn('email/mime/text.py', names) @@ -1133,7 +1133,7 @@ index 44974d433b..ae4e18802b 100755 # # Platform support for Windows diff --git a/Makefile.pre.in b/Makefile.pre.in -index b356f6293e..50ed6e0047 100644 +index 884bea1841..a15df8a03c 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -344,6 +344,8 @@ @@ -1146,7 +1146,7 @@ index b356f6293e..50ed6e0047 100644 ########################################################################## # Parser diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c -index 9132f13e81..36e00f7bfd 100644 +index ad9daaede4..f295528d4e 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -31,10 +31,20 @@ @@ -1170,7 +1170,7 @@ index 9132f13e81..36e00f7bfd 100644 #if defined(__ANDROID__) && __ANDROID_API__ < 21 && !defined(SYS_getdents64) # include # define SYS_getdents64 __NR_getdents64 -@@ -660,11 +670,15 @@ +@@ -700,11 +710,15 @@ saved_errno = 0; for (i = 0; exec_array[i] != NULL; ++i) { const char *executable = exec_array[i]; @@ -1186,7 +1186,7 @@ index 9132f13e81..36e00f7bfd 100644 if (errno != ENOENT && errno != ENOTDIR && saved_errno == 0) { saved_errno = errno; } -@@ -730,7 +744,9 @@ +@@ -770,7 +784,9 @@ PyObject *preexec_fn, PyObject *preexec_fn_args_tuple) { @@ -1197,7 +1197,7 @@ index 9132f13e81..36e00f7bfd 100644 pid_t pid; #ifdef VFORK_USABLE -@@ -749,7 +765,7 @@ +@@ -789,7 +805,7 @@ pid = fork(); } } else @@ -1206,7 +1206,7 @@ index 9132f13e81..36e00f7bfd 100644 { pid = fork(); } -@@ -757,7 +773,6 @@ +@@ -797,7 +813,6 @@ if (pid != 0) { return pid; } @@ -1214,8 +1214,8 @@ index 9132f13e81..36e00f7bfd 100644 /* Child process. * See the comment above child_exec() for restrictions imposed on * the code below. -@@ -780,6 +795,9 @@ - py_fds_to_keep, preexec_fn, preexec_fn_args_tuple); +@@ -821,6 +836,9 @@ + preexec_fn, preexec_fn_args_tuple); _exit(255); return 0; /* Dead code to avoid a potential compiler warning. */ +#else /* HAVE_FORK */ @@ -1224,10 +1224,10 @@ index 9132f13e81..36e00f7bfd 100644 } -@@ -810,7 +828,9 @@ - int need_after_fork = 0; +@@ -852,7 +870,9 @@ int saved_errno = 0; int allow_vfork; + int *c_fds_to_keep = NULL; - +/* iOS/tvOS/watchOS define the existence of fork, but it cannot be invoked; + * so fail fast if any attempt is made to invoke fork_exec */ @@ -1235,15 +1235,7 @@ index 9132f13e81..36e00f7bfd 100644 if (!PyArg_ParseTuple( args, "OOpO!OOiiiiiiiiii" _Py_PARSE_PID "OOOiOp:fork_exec", &process_args, &executable_list, -@@ -982,7 +1002,6 @@ - goto cleanup; - #endif /* HAVE_SETREUID */ - } -- - /* This must be the last thing done before fork() because we do not - * want to call PyOS_BeforeFork() if there is any chance of another - * error leading to the cleanup: code without calling fork(). */ -@@ -1017,7 +1036,7 @@ +@@ -1069,7 +1089,7 @@ } old_sigmask = &old_sigs; } @@ -1252,7 +1244,7 @@ index 9132f13e81..36e00f7bfd 100644 pid = do_fork_exec(exec_array, argv, envp, cwd, p2cread, p2cwrite, c2pread, c2pwrite, -@@ -1049,7 +1068,7 @@ +@@ -1102,7 +1122,7 @@ * the thread signal mask. */ (void) pthread_sigmask(SIG_SETMASK, old_sigmask, NULL); } @@ -1261,7 +1253,7 @@ index 9132f13e81..36e00f7bfd 100644 if (need_after_fork) PyOS_AfterFork_Parent(); -@@ -1078,8 +1097,10 @@ +@@ -1135,8 +1155,10 @@ PyGC_Enable(); } Py_XDECREF(gc_module); @@ -1298,7 +1290,7 @@ index 04995d2e74..a57daca284 100644 and sigaction() SA_ONSTACK */ #if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c -index 0a907a0c04..f2ce439e28 100644 +index 5f5b71c4c0..20faeee4db 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -76,6 +76,10 @@ @@ -1313,7 +1305,7 @@ index 0a907a0c04..f2ce439e28 100644 sin(pi*x), giving accurate results for all finite x (especially x integral or close to an integer). This is here for use in the diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c -index 613b86f09c..56234822a8 100644 +index cfadb6a066..f9b89bc6af 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -72,6 +72,8 @@ @@ -1325,7 +1317,7 @@ index 613b86f09c..56234822a8 100644 #if defined(__has_builtin) #if __has_builtin(__builtin_available) #define HAVE_BUILTIN_AVAILABLE 1 -@@ -344,6 +346,26 @@ +@@ -353,6 +355,26 @@ # endif /* _MSC_VER */ #endif /* ! __WATCOMC__ || __QNX__ */ @@ -1352,7 +1344,7 @@ index 613b86f09c..56234822a8 100644 /*[clinic input] # one of the few times we lie about this name! module os -@@ -1585,7 +1607,9 @@ +@@ -1594,7 +1616,9 @@ */ #include #elif !defined(_MSC_VER) && (!defined(__WATCOMC__) || defined(__QNX__) || defined(__VXWORKS__)) @@ -1362,7 +1354,7 @@ index 613b86f09c..56234822a8 100644 #endif /* !_MSC_VER */ static PyObject * -@@ -1601,6 +1625,7 @@ +@@ -1610,6 +1634,7 @@ d = PyDict_New(); if (d == NULL) return NULL; @@ -1370,7 +1362,7 @@ index 613b86f09c..56234822a8 100644 #ifdef MS_WINDOWS /* _wenviron must be initialized in this way if the program is started through main() instead of wmain(). */ -@@ -1654,6 +1679,7 @@ +@@ -1663,6 +1688,7 @@ Py_DECREF(k); Py_DECREF(v); } @@ -1378,7 +1370,7 @@ index 613b86f09c..56234822a8 100644 return d; } -@@ -4948,6 +4974,9 @@ +@@ -4959,6 +4985,9 @@ /*[clinic end generated code: output=290fc437dd4f33a0 input=86a58554ba6094af]*/ { long result; @@ -1388,7 +1380,7 @@ index 613b86f09c..56234822a8 100644 const char *bytes = PyBytes_AsString(command); if (PySys_Audit("os.system", "(O)", command) < 0) { -@@ -4957,6 +4986,7 @@ +@@ -4968,6 +4997,7 @@ Py_BEGIN_ALLOW_THREADS result = system(bytes); Py_END_ALLOW_THREADS @@ -1396,7 +1388,7 @@ index 613b86f09c..56234822a8 100644 return result; } #endif -@@ -13745,6 +13775,7 @@ +@@ -13765,6 +13795,7 @@ int is_symlink; int need_stat; #endif @@ -1404,7 +1396,7 @@ index 613b86f09c..56234822a8 100644 #ifdef MS_WINDOWS unsigned long dir_bits; #endif -@@ -13805,6 +13836,7 @@ +@@ -13825,6 +13856,7 @@ #endif return result; @@ -1474,7 +1466,7 @@ index a757380bd0..5a20864a1c 100644 "getpwnam(): name not found: %R", name); } diff --git a/Modules/timemodule.c b/Modules/timemodule.c -index 18f9ddb909..2ed289a92f 100644 +index b8e0e481cb..29e7775d95 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -62,6 +62,11 @@ @@ -1543,7 +1535,7 @@ index 3a2a731808..cb2d9e53e2 100644 /* Fill buffer with size pseudo-random bytes generated by getentropy(): diff --git a/Python/marshal.c b/Python/marshal.c -index 2690f55766..a4d05fb274 100644 +index 29f3bab60a..ea3b25a978 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -14,6 +14,10 @@ @@ -1780,7 +1772,7 @@ index d74fb6deac..a30f74f03d 100755 # Blank kernel with real OS is always fine. ;; diff --git a/configure b/configure -index 9e76287725..b2cc25e158 100755 +index af4a5bbfdf..4c03d87f16 100755 --- a/configure +++ b/configure @@ -838,6 +838,8 @@ @@ -1970,7 +1962,7 @@ index 9e76287725..b2cc25e158 100755 fi -@@ -10902,6 +10958,9 @@ +@@ -10940,6 +10996,9 @@ BLDSHARED="$LDSHARED" fi ;; @@ -1980,7 +1972,7 @@ index 9e76287725..b2cc25e158 100755 Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; -@@ -11310,8 +11369,8 @@ +@@ -11348,8 +11407,8 @@ pkg_failed=no @@ -1991,7 +1983,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$LIBUUID_CFLAGS"; then pkg_cv_LIBUUID_CFLAGS="$LIBUUID_CFLAGS" -@@ -11351,7 +11410,7 @@ +@@ -11389,7 +11448,7 @@ if test $pkg_failed = yes; then @@ -2000,7 +1992,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -11494,7 +11553,7 @@ +@@ -11532,7 +11591,7 @@ elif test $pkg_failed = untried; then @@ -2009,7 +2001,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } save_CFLAGS=$CFLAGS -@@ -12145,23 +12204,35 @@ +@@ -12183,23 +12242,35 @@ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_system_ffi" >&5 $as_echo "$with_system_ffi" >&6; } else @@ -2051,7 +2043,7 @@ index 9e76287725..b2cc25e158 100755 # Check for use of the system libmpdec library { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-system-libmpdec" >&5 $as_echo_n "checking for --with-system-libmpdec... " >&6; } -@@ -12302,8 +12373,8 @@ +@@ -12340,8 +12411,8 @@ pkg_failed=no @@ -2062,7 +2054,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$LIBNSL_CFLAGS"; then pkg_cv_LIBNSL_CFLAGS="$LIBNSL_CFLAGS" -@@ -12343,7 +12414,7 @@ +@@ -12381,7 +12452,7 @@ if test $pkg_failed = yes; then @@ -2071,7 +2063,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -12444,7 +12515,7 @@ +@@ -12482,7 +12553,7 @@ LIBNSL_LIBS=${LIBNSL_LIBS-$libnsl} elif test $pkg_failed = untried; then @@ -2080,7 +2072,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } LIBNSL_CFLAGS=${LIBNSL_CFLAGS-""} -@@ -12592,8 +12663,8 @@ +@@ -12630,8 +12701,8 @@ pkg_failed=no @@ -2091,7 +2083,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$LIBSQLITE3_CFLAGS"; then pkg_cv_LIBSQLITE3_CFLAGS="$LIBSQLITE3_CFLAGS" -@@ -12633,7 +12704,7 @@ +@@ -12671,7 +12742,7 @@ if test $pkg_failed = yes; then @@ -2100,7 +2092,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -12655,7 +12726,7 @@ +@@ -12693,7 +12764,7 @@ elif test $pkg_failed = untried; then @@ -2109,7 +2101,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } LIBSQLITE3_CFLAGS=${LIBSQLITE3_CFLAGS-""} -@@ -13424,8 +13495,8 @@ +@@ -13462,8 +13533,8 @@ pkg_failed=no @@ -2120,7 +2112,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$TCLTK_CFLAGS"; then pkg_cv_TCLTK_CFLAGS="$TCLTK_CFLAGS" -@@ -13465,7 +13536,7 @@ +@@ -13503,7 +13574,7 @@ if test $pkg_failed = yes; then @@ -2129,7 +2121,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -13483,7 +13554,7 @@ +@@ -13521,7 +13592,7 @@ found_tcltk=no elif test $pkg_failed = untried; then @@ -2138,7 +2130,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } found_tcltk=no else -@@ -13519,8 +13590,8 @@ +@@ -13557,8 +13628,8 @@ pkg_failed=no @@ -2149,7 +2141,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$X11_CFLAGS"; then pkg_cv_X11_CFLAGS="$X11_CFLAGS" -@@ -13560,7 +13631,7 @@ +@@ -13598,7 +13669,7 @@ if test $pkg_failed = yes; then @@ -2158,7 +2150,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -13587,7 +13658,7 @@ +@@ -13625,7 +13696,7 @@ and X11_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then @@ -2167,7 +2159,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -@@ -16163,8 +16234,8 @@ +@@ -16201,8 +16272,8 @@ pkg_failed=no @@ -2178,7 +2170,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$ZLIB_CFLAGS"; then pkg_cv_ZLIB_CFLAGS="$ZLIB_CFLAGS" -@@ -16204,7 +16275,7 @@ +@@ -16242,7 +16313,7 @@ if test $pkg_failed = yes; then @@ -2187,7 +2179,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -16348,7 +16419,7 @@ +@@ -16386,7 +16457,7 @@ elif test $pkg_failed = untried; then @@ -2196,7 +2188,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } save_CFLAGS=$CFLAGS -@@ -16511,8 +16582,8 @@ +@@ -16549,8 +16620,8 @@ pkg_failed=no @@ -2207,7 +2199,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$BZIP2_CFLAGS"; then pkg_cv_BZIP2_CFLAGS="$BZIP2_CFLAGS" -@@ -16552,7 +16623,7 @@ +@@ -16590,7 +16661,7 @@ if test $pkg_failed = yes; then @@ -2216,7 +2208,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -16649,7 +16720,7 @@ +@@ -16687,7 +16758,7 @@ elif test $pkg_failed = untried; then @@ -2225,7 +2217,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } save_CFLAGS=$CFLAGS -@@ -16741,8 +16812,8 @@ +@@ -16779,8 +16850,8 @@ pkg_failed=no @@ -2236,7 +2228,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$LIBLZMA_CFLAGS"; then pkg_cv_LIBLZMA_CFLAGS="$LIBLZMA_CFLAGS" -@@ -16782,7 +16853,7 @@ +@@ -16820,7 +16891,7 @@ if test $pkg_failed = yes; then @@ -2245,7 +2237,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -16879,7 +16950,7 @@ +@@ -16917,7 +16988,7 @@ elif test $pkg_failed = untried; then @@ -2254,7 +2246,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } save_CFLAGS=$CFLAGS -@@ -18112,8 +18183,8 @@ +@@ -18150,8 +18221,8 @@ pkg_failed=no @@ -2265,7 +2257,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$LIBCRYPT_CFLAGS"; then pkg_cv_LIBCRYPT_CFLAGS="$LIBCRYPT_CFLAGS" -@@ -18153,7 +18224,7 @@ +@@ -18191,7 +18262,7 @@ if test $pkg_failed = yes; then @@ -2274,7 +2266,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -18250,7 +18321,7 @@ +@@ -18288,7 +18359,7 @@ elif test $pkg_failed = untried; then @@ -2283,7 +2275,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } save_CFLAGS=$CFLAGS -@@ -23025,8 +23096,8 @@ +@@ -23063,8 +23134,8 @@ if ! $found; then OPENSSL_INCLUDES= for ssldir in $ssldirs; do @@ -2294,7 +2286,7 @@ index 9e76287725..b2cc25e158 100755 if test -f "$ssldir/include/openssl/ssl.h"; then OPENSSL_INCLUDES="-I$ssldir/include" OPENSSL_LDFLAGS="-L$ssldir/lib" -@@ -23407,8 +23478,8 @@ +@@ -23445,8 +23516,8 @@ pkg_failed=no @@ -2305,7 +2297,7 @@ index 9e76287725..b2cc25e158 100755 if test -n "$LIBB2_CFLAGS"; then pkg_cv_LIBB2_CFLAGS="$LIBB2_CFLAGS" -@@ -23448,7 +23519,7 @@ +@@ -23486,7 +23557,7 @@ if test $pkg_failed = yes; then @@ -2314,7 +2306,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -23466,7 +23537,7 @@ +@@ -23504,7 +23575,7 @@ have_libb2=no elif test $pkg_failed = untried; then @@ -2323,7 +2315,7 @@ index 9e76287725..b2cc25e158 100755 $as_echo "no" >&6; } have_libb2=no else -@@ -23537,6 +23608,29 @@ +@@ -23575,6 +23646,29 @@ py_cv_module_ossaudiodev=n/a py_cv_module_spwd=n/a ;; #( @@ -2354,7 +2346,7 @@ index 9e76287725..b2cc25e158 100755 diff --git a/configure.ac b/configure.ac -index c62a565eb6..44ed124c26 100644 +index e1cbb7c7fb..9fd0afc76a 100644 --- a/configure.ac +++ b/configure.ac @@ -545,6 +545,15 @@ @@ -2490,7 +2482,7 @@ index c62a565eb6..44ed124c26 100644 fi AC_SUBST(READELF) -@@ -3167,6 +3239,9 @@ +@@ -3171,6 +3243,9 @@ BLDSHARED="$LDSHARED" fi ;; @@ -2500,7 +2492,7 @@ index c62a565eb6..44ed124c26 100644 Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; -@@ -3636,20 +3711,30 @@ +@@ -3640,20 +3715,30 @@ esac AC_MSG_RESULT($with_system_ffi) else @@ -2536,7 +2528,7 @@ index c62a565eb6..44ed124c26 100644 # Check for use of the system libmpdec library AC_MSG_CHECKING(for --with-system-libmpdec) -@@ -6830,6 +6915,30 @@ +@@ -6833,6 +6918,30 @@ [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])], [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])], diff --git a/patch/libffi-3.4.4.patch b/patch/libffi-3.4.4.patch new file mode 100644 index 00000000..6e143449 --- /dev/null +++ b/patch/libffi-3.4.4.patch @@ -0,0 +1,174 @@ +diff --git a/config.sub b/config.sub +old mode 100644 +new mode 100755 +index dba16e8..630b5d9 +--- a/config.sub ++++ b/config.sub +@@ -1126,7 +1126,7 @@ case $cpu-$vendor in + xscale-* | xscalee[bl]-*) + cpu=`echo "$cpu" | sed 's/^xscale/arm/'` + ;; +- arm64-* | aarch64le-*) ++ arm64-* | arm64_32-* | aarch64le-*) + cpu=aarch64 + ;; + +@@ -1728,7 +1728,7 @@ case $os in + | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ + | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \ + | hiux* | abug | nacl* | netware* | windows* \ +- | os9* | macos* | osx* | ios* \ ++ | os9* | macos* | osx* | ios* | watchos* | tvos* \ + | mpw* | magic* | mmixware* | mon960* | lnews* \ + | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ + | aos* | aros* | cloudabi* | sortix* | twizzler* \ +@@ -1790,6 +1790,8 @@ case $kernel-$os in + ;; + os2-emx) + ;; ++ ios*-simulator | tvos*-simulator | watchos*-simulator) ++ ;; + *-eabi* | *-gnueabi*) + ;; + -*) +diff --git a/generate-darwin-source-and-headers.py b/generate-darwin-source-and-headers.py +index 5b60ccc..c801dc0 100755 +--- a/generate-darwin-source-and-headers.py ++++ b/generate-darwin-source-and-headers.py +@@ -48,7 +48,6 @@ class armv7_platform(Platform): + + + class ios_simulator_i386_platform(i386_platform): +- triple = 'i386-apple-darwin11' + target = 'i386-apple-ios-simulator' + directory = 'darwin_ios' + sdk = 'iphonesimulator' +@@ -56,7 +55,6 @@ class ios_simulator_i386_platform(i386_platform): + + + class ios_simulator_x86_64_platform(x86_64_platform): +- triple = 'x86_64-apple-darwin13' + target = 'x86_64-apple-ios-simulator' + directory = 'darwin_ios' + sdk = 'iphonesimulator' +@@ -64,7 +62,6 @@ class ios_simulator_x86_64_platform(x86_64_platform): + + + class ios_simulator_arm64_platform(arm64_platform): +- triple = 'aarch64-apple-darwin20' + target = 'arm64-apple-ios-simulator' + directory = 'darwin_ios' + sdk = 'iphonesimulator' +@@ -72,7 +69,6 @@ class ios_simulator_arm64_platform(arm64_platform): + + + class ios_device_armv7_platform(armv7_platform): +- triple = 'arm-apple-darwin11' + target = 'armv7-apple-ios' + directory = 'darwin_ios' + sdk = 'iphoneos' +@@ -80,7 +76,6 @@ class ios_device_armv7_platform(armv7_platform): + + + class ios_device_arm64_platform(arm64_platform): +- triple = 'aarch64-apple-darwin13' + target = 'arm64-apple-ios' + directory = 'darwin_ios' + sdk = 'iphoneos' +@@ -88,7 +83,6 @@ class ios_device_arm64_platform(arm64_platform): + + + class desktop_x86_64_platform(x86_64_platform): +- triple = 'x86_64-apple-darwin10' + target = 'x86_64-apple-macos' + directory = 'darwin_osx' + sdk = 'macosx' +@@ -96,7 +90,6 @@ class desktop_x86_64_platform(x86_64_platform): + + + class desktop_arm64_platform(arm64_platform): +- triple = 'aarch64-apple-darwin20' + target = 'arm64-apple-macos' + directory = 'darwin_osx' + sdk = 'macosx' +@@ -104,7 +97,6 @@ class desktop_arm64_platform(arm64_platform): + + + class tvos_simulator_x86_64_platform(x86_64_platform): +- triple = 'x86_64-apple-darwin13' + target = 'x86_64-apple-tvos-simulator' + directory = 'darwin_tvos' + sdk = 'appletvsimulator' +@@ -112,7 +104,6 @@ class tvos_simulator_x86_64_platform(x86_64_platform): + + + class tvos_simulator_arm64_platform(arm64_platform): +- triple = 'aarch64-apple-darwin20' + target = 'arm64-apple-tvos-simulator' + directory = 'darwin_tvos' + sdk = 'appletvsimulator' +@@ -120,7 +111,6 @@ class tvos_simulator_arm64_platform(arm64_platform): + + + class tvos_device_arm64_platform(arm64_platform): +- triple = 'aarch64-apple-darwin13' + target = 'arm64-apple-tvos' + directory = 'darwin_tvos' + sdk = 'appletvos' +@@ -128,7 +118,6 @@ class tvos_device_arm64_platform(arm64_platform): + + + class watchos_simulator_i386_platform(i386_platform): +- triple = 'i386-apple-darwin11' + target = 'i386-apple-watchos-simulator' + directory = 'darwin_watchos' + sdk = 'watchsimulator' +@@ -136,7 +125,6 @@ class watchos_simulator_i386_platform(i386_platform): + + + class watchos_simulator_x86_64_platform(x86_64_platform): +- triple = 'x86_64-apple-darwin13' + target = 'x86_64-apple-watchos-simulator' + directory = 'darwin_watchos' + sdk = 'watchsimulator' +@@ -144,7 +132,6 @@ class watchos_simulator_x86_64_platform(x86_64_platform): + + + class watchos_simulator_arm64_platform(arm64_platform): +- triple = 'aarch64-apple-darwin20' + target = 'arm64-apple-watchos-simulator' + directory = 'darwin_watchos' + sdk = 'watchsimulator' +@@ -152,7 +139,6 @@ class watchos_simulator_arm64_platform(arm64_platform): + + + class watchos_device_armv7k_platform(armv7_platform): +- triple = 'arm-apple-darwin11' + target = 'armv7k-apple-watchos' + directory = 'darwin_watchos' + sdk = 'watchos' +@@ -161,7 +147,6 @@ class watchos_device_armv7k_platform(armv7_platform): + + + class watchos_device_arm64_32_platform(arm64_platform): +- triple = 'aarch64-apple-darwin13' + target = 'arm64_32-apple-watchos' + directory = 'darwin_watchos' + sdk = 'watchos' +@@ -229,7 +214,15 @@ def build_target(platform, platform_headers): + working_dir = os.getcwd() + try: + os.chdir(build_dir) +- subprocess.check_call(['../configure', '-host', platform.triple], env=env) ++ subprocess.check_call( ++ [ ++ "../configure", ++ f"--host={platform.target}", ++ ] + ( ++ [] if platform.sdk == "macosx" else [f"--build={os.uname().machine}-apple-darwin"] ++ ), ++ env=env ++ ) + finally: + os.chdir(working_dir) + diff --git a/patch/xz-5.4.2.patch b/patch/xz-5.4.4.patch similarity index 99% rename from patch/xz-5.4.2.patch rename to patch/xz-5.4.4.patch index 35dfe187..a964e51c 100644 --- a/patch/xz-5.4.2.patch +++ b/patch/xz-5.4.4.patch @@ -9,7 +9,7 @@ diff -ur xz-5.4.2-orig/build-aux/config.sub xz-5.4.2/build-aux/config.sub + arm64-* | arm64_32-* | aarch64le-*) cpu=aarch64 ;; - + @@ -1728,7 +1728,7 @@ | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \ From 5f0c652e0127d02fa0ef1ec8ab5c71431434cb3d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 31 Aug 2023 09:45:49 +0800 Subject: [PATCH 03/13] Add version separation for Python support files. --- Makefile | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 4b9886e8..f8bd42e5 100644 --- a/Makefile +++ b/Makefile @@ -913,8 +913,8 @@ os=$1 SDKS-$(os)=$$(sort $$(basename $$(TARGETS-$(os)))) # Predeclare the Python XCFramework files so they can be referenced in SDK targets -PYTHON_XCFRAMEWORK-$(os)=support/$(os)/Python.xcframework -PYTHON_STDLIB-$(os)=support/$(os)/python-stdlib +PYTHON_XCFRAMEWORK-$(os)=support/$(PYTHON_VER)/$(os)/Python.xcframework +PYTHON_STDLIB-$(os)=support/$(PYTHON_VER)/$(os)/python-stdlib # Expand the build-sdk macro for all the sdks on this OS (e.g., iphoneos, iphonesimulator) $$(foreach sdk,$$(SDKS-$(os)),$$(eval $$(call build-sdk,$$(sdk),$(os)))) @@ -1036,7 +1036,7 @@ $$(PYTHON_XCFRAMEWORK-$(os)): \ mkdir -p $$(dir $$(PYTHON_XCFRAMEWORK-$(os))) xcodebuild -create-xcframework \ -output $$@ $$(foreach sdk,$$(SDKS-$(os)),-library $$(PYTHON_FATLIB-$$(sdk)) -headers $$(PYTHON_FATINCLUDE-$$(sdk))) \ - 2>&1 | tee -a support/python-$(os).xcframework.log + 2>&1 | tee -a support/$(PYTHON_VER)/python-$(os).xcframework.log $$(PYTHON_STDLIB-$(os)): \ $$(PYTHON_XCFRAMEWORK-$(os)) \ @@ -1062,34 +1062,34 @@ $$(PYTHON_STDLIB-$(os)): \ dist/Python-$(PYTHON_VER)-$(os)-support.$(BUILD_NUMBER).tar.gz: $$(PYTHON_XCFRAMEWORK-$(os)) $$(PYTHON_STDLIB-$(os)) @echo ">>> Create VERSIONS file for $(os)" - echo "Python version: $(PYTHON_VERSION) " > support/$(os)/VERSIONS - echo "Build: $(BUILD_NUMBER)" >> support/$(os)/VERSIONS - echo "Min $(os) version: $$(VERSION_MIN-$(os))" >> support/$(os)/VERSIONS - echo "---------------------" >> support/$(os)/VERSIONS + echo "Python version: $(PYTHON_VERSION) " > support/$(PYTHON_VER)/$(os)/VERSIONS + echo "Build: $(BUILD_NUMBER)" >> support/$(PYTHON_VER)/$(os)/VERSIONS + echo "Min $(os) version: $$(VERSION_MIN-$(os))" >> support/$(PYTHON_VER)/$(os)/VERSIONS + echo "---------------------" >> support/$(PYTHON_VER)/$(os)/VERSIONS ifeq ($(os),macOS) - echo "libFFI: macOS native" >> support/$(os)/VERSIONS + echo "libFFI: macOS native" >> support/$(PYTHON_VER)/$(os)/VERSIONS else - echo "libFFI: $(LIBFFI_VERSION)" >> support/$(os)/VERSIONS + echo "libFFI: $(LIBFFI_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS endif - echo "BZip2: $(BZIP2_VERSION)" >> support/$(os)/VERSIONS - echo "OpenSSL: $(OPENSSL_VERSION)" >> support/$(os)/VERSIONS - echo "XZ: $(XZ_VERSION)" >> support/$(os)/VERSIONS + echo "BZip2: $(BZIP2_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS + echo "OpenSSL: $(OPENSSL_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS + echo "XZ: $(XZ_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS ifneq ($(os),macOS) @echo ">>> Create cross-platform site sitecustomize.py for $(os)" - mkdir -p support/$(os)/platform-site + mkdir -p support/$(PYTHON_VER)/$(os)/platform-site cat $(PROJECT_DIR)/patch/Python/sitecustomize.py \ | sed -e "s/{{os}}/$(os)/g" \ | sed -e "s/{{tag}}/$$(shell echo $(os) | tr '[:upper:]' '[:lower:]')_$$(shell echo $$(VERSION_MIN-$(os)) | sed "s/\./_/g")/g" \ - > support/$(os)/platform-site/sitecustomize.py + > support/$(PYTHON_VER)/$(os)/platform-site/sitecustomize.py endif @echo ">>> Create final distribution artefact for $(os)" mkdir -p dist # Build a "full" tarball with all content for test purposes - tar zcvf dist/Python-$(PYTHON_VER)-$(os)-support.test-$(BUILD_NUMBER).tar.gz -X patch/Python/test.exclude -C support/$(os) `ls -A support/$(os)` + tar zcvf dist/Python-$(PYTHON_VER)-$(os)-support.test-$(BUILD_NUMBER).tar.gz -X patch/Python/test.exclude -C support/$(PYTHON_VER)/$(os) `ls -A support/$(PYTHON_VER)/$(os)/` # Build a distributable tarball - tar zcvf $$@ -X patch/Python/release.common.exclude -X patch/Python/release.$(os).exclude -C support/$(os) `ls -A support/$(os)` + tar zcvf $$@ -X patch/Python/release.common.exclude -X patch/Python/release.$(os).exclude -C support/$(PYTHON_VER)/$(os) `ls -A support/$(PYTHON_VER)/$(os)/` Python-$(os): dist/Python-$(PYTHON_VER)-$(os)-support.$(BUILD_NUMBER).tar.gz @@ -1102,8 +1102,7 @@ clean-Python-$(os): install/$(os)/*/python-$(PYTHON_VER)*.*.log \ merge/$(os)/*/python-$(PYTHON_VER)* \ merge/$(os)/*/python-$(PYTHON_VER)*.*.log \ - support/$(os) \ - support/*-$(os).*.log \ + support/$(PYTHON_VER)/$(os) \ dist/Python-$(PYTHON_VER)-$(os)-* dev-clean-Python-$(os): @@ -1115,8 +1114,7 @@ dev-clean-Python-$(os): install/$(os)/*/python-$(PYTHON_VERSION).*.log \ merge/$(os)/*/python-$(PYTHON_VERSION) \ merge/$(os)/*/python-$(PYTHON_VERSION).*.log \ - support/$(os) \ - support/*-$(os).*.log \ + support/$(PYTHON_VER)/$(os) \ dist/Python-$(PYTHON_VER)-$(os)-* merge-clean-Python-$(os): @@ -1124,8 +1122,7 @@ merge-clean-Python-$(os): rm -rf \ merge/$(os)/*/python-$(PYTHON_VERSION) \ merge/$(os)/*/python-$(PYTHON_VERSION).*.log \ - support/$(os) \ - support/*-$(os).*.log \ + support/$(PYTHON_VER)/$(os) \ dist/Python-$(PYTHON_VER)-$(os)-* ########################################################################### From 83ae7c19e56b56909c622798b2213c54a58da575 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Sep 2023 14:53:56 +0800 Subject: [PATCH 04/13] Add link for 3.12 branch. --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 36baf978..87a9b9cb 100644 --- a/README.rst +++ b/README.rst @@ -11,6 +11,7 @@ repository: * `Python 3.8 `__ * `Python 3.9 `__ * `Python 3.10 `__ +* `Python 3.12 `__ It works by downloading, patching, and building a fat binary of Python and selected pre-requisites, and packaging them as static libraries that can be incorporated into an From 52c43969ee62d92804d4d9abbe2a915912be5565 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 4 Oct 2023 10:21:57 +0800 Subject: [PATCH 05/13] Bumped Python to 3.11.6. Also includes fix for platform.processor() issue. --- Makefile | 2 +- patch/Python/Python.patch | 106 +++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/Makefile b/Makefile index f7b7b5ed..b7f2d299 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ BUILD_NUMBER=custom # PYTHON_VERSION is the full version number (e.g., 3.10.0b3) # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.11.5 +PYTHON_VERSION=3.11.6 PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_VER=$(basename $(PYTHON_VERSION)) diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index a22d08a7..0cd550ba 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -213,7 +213,7 @@ index f603a89f7f..48463efcfc 100644 + _bootstrap._verbose_message('Adding Apple Framework dylib finder at {}', frameworks_folder) + sys.meta_path.append(AppleFrameworkFinder(frameworks_folder)) diff --git a/Lib/platform.py b/Lib/platform.py -index 9b9d88bf58..6fe084a3a5 100755 +index 9b9d88bf58..764fd6d186 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -452,6 +452,26 @@ @@ -260,17 +260,17 @@ index 9b9d88bf58..6fe084a3a5 100755 + # as uname.machine. On device it doesn't; but there's only + # on CPU architecture on device + def get_ios(): -+ if sys.implementation._multiarch.endswith('simulator'): ++ if getattr(sys.implementation, "_simulator", False): + return os.uname().machine + return 'arm64' + + def get_tvos(): -+ if sys.implementation._multiarch.endswith('simulator'): ++ if getattr(sys.implementation, "_simulator", False): + return os.uname().machine + return 'arm64' + + def get_watchos(): -+ if sys.implementation._multiarch.endswith('simulator'): ++ if getattr(sys.implementation, "_simulator", False): + return os.uname().machine + return 'arm64_32' + @@ -497,10 +497,10 @@ index ebe3711827..5f2456b5db 100644 return f"{osname}-{release}-{machine}" diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 98be9cdd0e..baca44ea62 100644 +index 2e6518cf24..cc08015a73 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py -@@ -47,7 +47,7 @@ +@@ -48,7 +48,7 @@ "check__all__", "skip_if_buggy_ucrt_strfptime", "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", # sys @@ -509,7 +509,7 @@ index 98be9cdd0e..baca44ea62 100644 "check_impl_detail", "unix_shell", "setswitchinterval", # network "open_urlresource", -@@ -507,7 +507,7 @@ +@@ -511,7 +511,7 @@ is_android = hasattr(sys, 'getandroidapilevel') @@ -518,7 +518,7 @@ index 98be9cdd0e..baca44ea62 100644 unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None -@@ -517,12 +517,25 @@ +@@ -521,12 +521,25 @@ is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" @@ -547,18 +547,18 @@ index 98be9cdd0e..baca44ea62 100644 def requires_subprocess(): """Used for subprocess, os.spawn calls, fd inheritance""" diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py -index 5728d25254..76e62d4738 100644 +index d7871d3e53..6966ee77af 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py -@@ -33,6 +33,7 @@ - from asyncio import selector_events +@@ -34,6 +34,7 @@ + from multiprocessing.util import _cleanup_tests as multiprocessing_cleanup_tests from test.test_asyncio import utils as test_utils from test import support +from test.support import is_apple_mobile from test.support import socket_helper from test.support import threading_helper from test.support import ALWAYS_EQ, LARGEST, SMALLEST -@@ -542,6 +543,7 @@ +@@ -544,6 +545,7 @@ self._basetest_create_connection(conn_fut) @socket_helper.skip_unless_bind_unix_socket @@ -566,7 +566,7 @@ index 5728d25254..76e62d4738 100644 def test_create_unix_connection(self): # Issue #20682: On Mac OS X Tiger, getsockname() returns a # zero-length address for UNIX socket. -@@ -634,6 +636,7 @@ +@@ -636,6 +638,7 @@ self.assertEqual(cm.exception.reason, 'CERTIFICATE_VERIFY_FAILED') @unittest.skipIf(ssl is None, 'No ssl module') @@ -574,7 +574,7 @@ index 5728d25254..76e62d4738 100644 def test_create_ssl_connection(self): with test_utils.run_test_server(use_ssl=True) as httpd: create_connection = functools.partial( -@@ -645,6 +648,7 @@ +@@ -647,6 +650,7 @@ @socket_helper.skip_unless_bind_unix_socket @unittest.skipIf(ssl is None, 'No ssl module') @@ -582,7 +582,7 @@ index 5728d25254..76e62d4738 100644 def test_create_ssl_unix_connection(self): # Issue #20682: On Mac OS X Tiger, getsockname() returns a # zero-length address for UNIX socket. -@@ -901,6 +905,7 @@ +@@ -905,6 +909,7 @@ return server, path @socket_helper.skip_unless_bind_unix_socket @@ -590,7 +590,7 @@ index 5728d25254..76e62d4738 100644 def test_create_unix_server(self): proto = MyProto(loop=self.loop) server, path = self._make_unix_server(lambda: proto) -@@ -929,6 +934,7 @@ +@@ -933,6 +938,7 @@ server.close() @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') @@ -598,7 +598,7 @@ index 5728d25254..76e62d4738 100644 def test_create_unix_server_path_socket_error(self): proto = MyProto(loop=self.loop) sock = socket.socket() -@@ -994,6 +1000,7 @@ +@@ -998,6 +1004,7 @@ @socket_helper.skip_unless_bind_unix_socket @unittest.skipIf(ssl is None, 'No ssl module') @@ -606,7 +606,7 @@ index 5728d25254..76e62d4738 100644 def test_create_unix_server_ssl(self): proto = MyProto(loop=self.loop) server, path = self._make_ssl_unix_server( -@@ -1024,6 +1031,7 @@ +@@ -1028,6 +1035,7 @@ server.close() @unittest.skipIf(ssl is None, 'No ssl module') @@ -614,7 +614,7 @@ index 5728d25254..76e62d4738 100644 def test_create_server_ssl_verify_failed(self): proto = MyProto(loop=self.loop) server, host, port = self._make_ssl_server( -@@ -1054,6 +1062,7 @@ +@@ -1058,6 +1066,7 @@ @socket_helper.skip_unless_bind_unix_socket @unittest.skipIf(ssl is None, 'No ssl module') @@ -622,7 +622,7 @@ index 5728d25254..76e62d4738 100644 def test_create_unix_server_ssl_verify_failed(self): proto = MyProto(loop=self.loop) server, path = self._make_ssl_unix_server( -@@ -1114,6 +1123,7 @@ +@@ -1118,6 +1127,7 @@ @socket_helper.skip_unless_bind_unix_socket @unittest.skipIf(ssl is None, 'No ssl module') @@ -717,7 +717,7 @@ index 8e98256a62..819c8ef89c 100644 off_t = 'l' pid_t = 'i' diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py -index cdd1bea754..fdc1750880 100644 +index cfd8a101dc..c350e87eec 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -30,6 +30,7 @@ @@ -738,7 +738,7 @@ index cdd1bea754..fdc1750880 100644 # UTF-8 into a percent-encoded value. for name in os.listdir(self.tempdir): diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py -index 79aa2da586..791b5ec018 100644 +index e54a13c2d8..55dac3c3c6 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -40,6 +40,7 @@ @@ -759,7 +759,7 @@ index 79aa2da586..791b5ec018 100644 'largefile', 'test requires %s bytes and a long time to run' % self.LARGE) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py -index f1f2b75fef..8d1edd79e7 100644 +index 55c5cd5658..87453f29c2 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -43,6 +43,7 @@ @@ -770,7 +770,7 @@ index f1f2b75fef..8d1edd79e7 100644 from test.support import os_helper from test.support import socket_helper from test.support import threading_helper -@@ -1823,9 +1824,20 @@ +@@ -1824,9 +1825,20 @@ # just need a name - file can't be present, or we'll get an # 'address already in use' error. os.remove(fn) @@ -791,7 +791,7 @@ index f1f2b75fef..8d1edd79e7 100644 class UnixSocketHandlerTest(SocketHandlerTest): """Test for SocketHandler with unix sockets.""" -@@ -1909,6 +1921,7 @@ +@@ -1910,6 +1922,7 @@ self.assertEqual(self.log_output, "spam\neggs\n") @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") @@ -799,7 +799,7 @@ index f1f2b75fef..8d1edd79e7 100644 class UnixDatagramHandlerTest(DatagramHandlerTest): """Test for DatagramHandler using Unix sockets.""" -@@ -2003,6 +2016,7 @@ +@@ -2004,6 +2017,7 @@ self.assertEqual(self.log_output, b'<11>sp\xc3\xa4m\x00') @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") @@ -831,7 +831,7 @@ index a1a91f661b..92fde54f5a 100644 last.append([0]) last = last[-1] diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py -index bab8686008..075786e463 100644 +index dfcf303942..5aabfac885 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1,5 +1,5 @@ @@ -850,7 +850,7 @@ index bab8686008..075786e463 100644 # Try incompatible flags, prot and access parameters. with open(TESTFN, "r+b") as f: self.assertRaises(ValueError, mmap.mmap, f.fileno(), mapsize, -@@ -1002,7 +1002,7 @@ +@@ -1007,7 +1007,7 @@ unlink(TESTFN) def _make_test_file(self, num_zeroes, tail): @@ -969,7 +969,7 @@ index 274a8b7377..06cab9848a 100644 'need os.get_terminal_size()') def test_stty_match(self): diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py -index 13cb2a7945..ccd0ca4d19 100644 +index af0d2a4e6f..3b96cc476f 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1,5 +1,6 @@ @@ -1137,7 +1137,7 @@ index d96371d242..5634962453 100644 wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py -index 9c6561c099..ccccfb8b0b 100644 +index a4192acd6d..8c00d34e0b 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -3,7 +3,7 @@ @@ -1149,7 +1149,7 @@ index 9c6561c099..ccccfb8b0b 100644 from test.support import verbose, cpython_only, os_helper from test.support.import_helper import import_module from test.support.script_helper import assert_python_ok, assert_python_failure -@@ -1171,6 +1171,7 @@ +@@ -1184,6 +1184,7 @@ os.set_blocking(r, False) return (r, w) @@ -1157,7 +1157,7 @@ index 9c6561c099..ccccfb8b0b 100644 def test_threads_join(self): # Non-daemon threads should be joined at subinterpreter shutdown # (issue #18808) -@@ -1199,6 +1200,7 @@ +@@ -1212,6 +1213,7 @@ # The thread was joined properly. self.assertEqual(os.read(r, 1), b"x") @@ -1166,7 +1166,7 @@ index 9c6561c099..ccccfb8b0b 100644 # Same as above, but a delay gets introduced after the thread's # Python code returned but before the thread state is deleted. diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py -index 86ce60fef1..c36e6b6096 100644 +index eb9227a3b7..19f93263c5 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -19,7 +19,7 @@ @@ -1175,10 +1175,10 @@ index 86ce60fef1..c36e6b6096 100644 skip_if_broken_multiprocessing_synchronize, verbose, - requires_subprocess, is_emscripten, is_wasi, + requires_subprocess, is_apple_mobile, is_emscripten, is_wasi, - requires_venv_with_pip, TEST_HOME_DIR) + requires_venv_with_pip, TEST_HOME_DIR, + requires_resource, copy_python_src_ignore) from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) - import unittest -@@ -40,6 +40,8 @@ +@@ -41,6 +41,8 @@ if is_emscripten or is_wasi: raise unittest.SkipTest("venv is not available on Emscripten/WASI.") @@ -1270,7 +1270,7 @@ index 44974d433b..ae4e18802b 100755 # # Platform support for Windows diff --git a/Makefile.pre.in b/Makefile.pre.in -index 884bea1841..a15df8a03c 100644 +index 0d0ac1315b..50dde04558 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -344,6 +344,8 @@ @@ -1283,7 +1283,7 @@ index 884bea1841..a15df8a03c 100644 ########################################################################## # Parser diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c -index ad9daaede4..f295528d4e 100644 +index 072519c91a..14424c46d2 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -31,10 +31,20 @@ @@ -1334,7 +1334,7 @@ index ad9daaede4..f295528d4e 100644 pid_t pid; #ifdef VFORK_USABLE -@@ -789,7 +805,7 @@ +@@ -805,7 +821,7 @@ pid = fork(); } } else @@ -1343,15 +1343,15 @@ index ad9daaede4..f295528d4e 100644 { pid = fork(); } -@@ -797,7 +813,6 @@ - if (pid != 0) { +@@ -814,7 +830,6 @@ + // Parent process. return pid; } - /* Child process. * See the comment above child_exec() for restrictions imposed on * the code below. -@@ -821,6 +836,9 @@ +@@ -838,6 +853,9 @@ preexec_fn, preexec_fn_args_tuple); _exit(255); return 0; /* Dead code to avoid a potential compiler warning. */ @@ -1361,7 +1361,7 @@ index ad9daaede4..f295528d4e 100644 } -@@ -852,7 +870,9 @@ +@@ -869,7 +887,9 @@ int saved_errno = 0; int allow_vfork; int *c_fds_to_keep = NULL; @@ -1372,7 +1372,7 @@ index ad9daaede4..f295528d4e 100644 if (!PyArg_ParseTuple( args, "OOpO!OOiiiiiiiiii" _Py_PARSE_PID "OOOiOp:fork_exec", &process_args, &executable_list, -@@ -1069,7 +1089,7 @@ +@@ -1086,7 +1106,7 @@ } old_sigmask = &old_sigs; } @@ -1381,7 +1381,7 @@ index ad9daaede4..f295528d4e 100644 pid = do_fork_exec(exec_array, argv, envp, cwd, p2cread, p2cwrite, c2pread, c2pwrite, -@@ -1102,7 +1122,7 @@ +@@ -1119,7 +1139,7 @@ * the thread signal mask. */ (void) pthread_sigmask(SIG_SETMASK, old_sigmask, NULL); } @@ -1390,7 +1390,7 @@ index ad9daaede4..f295528d4e 100644 if (need_after_fork) PyOS_AfterFork_Parent(); -@@ -1135,8 +1155,10 @@ +@@ -1152,8 +1172,10 @@ PyGC_Enable(); } Py_XDECREF(gc_module); @@ -1403,7 +1403,7 @@ index ad9daaede4..f295528d4e 100644 diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c -index 04995d2e74..a57daca284 100644 +index 520b7b603e..0177632cd5 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -1,3 +1,7 @@ @@ -1442,7 +1442,7 @@ index 5f5b71c4c0..20faeee4db 100644 sin(pi*x), giving accurate results for all finite x (especially x integral or close to an integer). This is here for use in the diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c -index cfadb6a066..f9b89bc6af 100644 +index 2e4a66ab1b..673e26b3b2 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -72,6 +72,8 @@ @@ -1507,7 +1507,7 @@ index cfadb6a066..f9b89bc6af 100644 return d; } -@@ -4959,6 +4985,9 @@ +@@ -4981,6 +5007,9 @@ /*[clinic end generated code: output=290fc437dd4f33a0 input=86a58554ba6094af]*/ { long result; @@ -1517,7 +1517,7 @@ index cfadb6a066..f9b89bc6af 100644 const char *bytes = PyBytes_AsString(command); if (PySys_Audit("os.system", "(O)", command) < 0) { -@@ -4968,6 +4997,7 @@ +@@ -4990,6 +5019,7 @@ Py_BEGIN_ALLOW_THREADS result = system(bytes); Py_END_ALLOW_THREADS @@ -1525,7 +1525,7 @@ index cfadb6a066..f9b89bc6af 100644 return result; } #endif -@@ -13765,6 +13795,7 @@ +@@ -13821,6 +13851,7 @@ int is_symlink; int need_stat; #endif @@ -1533,7 +1533,7 @@ index cfadb6a066..f9b89bc6af 100644 #ifdef MS_WINDOWS unsigned long dir_bits; #endif -@@ -13825,6 +13856,7 @@ +@@ -13881,6 +13912,7 @@ #endif return result; @@ -1749,7 +1749,7 @@ index 29f3bab60a..ea3b25a978 100644 #define TYPE_NULL '0' diff --git a/Python/sysmodule.c b/Python/sysmodule.c -index 6e9ec90c9d..16b0a6ab11 100644 +index d0941e8cfe..7e07a6ae55 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -49,6 +49,10 @@ From b4140e822f3f1f6ba90df0bf23f3cc6f0a3b2b54 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 5 Aug 2024 15:06:15 +0800 Subject: [PATCH 06/13] Update patch to 3.11.9. --- Makefile | 21 +- patch/Python/Python.patch | 10662 +++++++++++++++++++++++++++++------- 2 files changed, 8602 insertions(+), 2081 deletions(-) diff --git a/Makefile b/Makefile index cb6f11a3..93524f9c 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ BUILD_NUMBER=custom # PYTHON_VERSION is the full version number (e.g., 3.10.0b3) # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.11.6 +PYTHON_VERSION=3.11.9 PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_VER=$(basename $(PYTHON_VERSION)) @@ -25,9 +25,10 @@ PYTHON_VER=$(basename $(PYTHON_VERSION)) # iOS, tvOS, watchOS: # https://github.com/beeware/cpython-apple-source-deps/releases BZIP2_VERSION=1.0.8-1 -XZ_VERSION=5.4.4-1 -OPENSSL_VERSION=3.0.12-1 -LIBFFI_VERSION=3.4.4-1 +MPDECIMAL_VERSION=4.0.0-1 +OPENSSL_VERSION=3.0.14-1 +LIBFFI_VERSION=3.4.6-1 +XZ_VERSION=5.4.7-1 # Supported OS OS_LIST=macOS iOS tvOS watchOS @@ -41,12 +42,12 @@ CFLAGS-macOS=-mmacosx-version-min=$(VERSION_MIN-macOS) # iOS targets TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64 -VERSION_MIN-iOS=12.0 +VERSION_MIN-iOS=13.0 CFLAGS-iOS=-mios-version-min=$(VERSION_MIN-iOS) # tvOS targets TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64 -VERSION_MIN-tvOS=9.0 +VERSION_MIN-tvOS=12.0 CFLAGS-tvOS=-mtvos-version-min=$(VERSION_MIN-tvOS) PYTHON_CONFIGURE-tvOS=ac_cv_func_sigaltstack=no @@ -132,10 +133,8 @@ ARCH-$(target)=$$(subst .,,$$(suffix $(target))) ifneq ($(os),macOS) ifeq ($$(findstring simulator,$$(SDK-$(target))),) TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os)) -TARGET_TOOL_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target)) else TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os))-simulator -TARGET_TOOL_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))-simulator endif endif @@ -288,10 +287,6 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \ cd $$(PYTHON_SRCDIR-$(target)) && \ PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ ./configure \ - AR=$$(TARGET_TOOL_TRIPLE-$(target))-ar \ - CC=$$(TARGET_TOOL_TRIPLE-$(target))-clang \ - CPP=$$(TARGET_TOOL_TRIPLE-$(target))-cpp \ - CXX=$$(TARGET_TOOL_TRIPLE-$(target))-clang \ LIBLZMA_CFLAGS="-I$$(XZ_INSTALL-$(target))/include" \ LIBLZMA_LIBS="-L$$(XZ_INSTALL-$(target))/lib -llzma" \ BZIP2_CFLAGS="-I$$(BZIP2_INSTALL-$(target))/include" \ @@ -551,7 +546,7 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ tar zxf build/macOS/macosx/python-$(PYTHON_VERSION)/Python_Framework.pkgPython_Framework.pkg/PayloadPython_Framework.pkgPython_Framework.pkg/PayloadPython_Framework.pkgPython_Framework.pkg/Payload -C $$(PYTHON_FRAMEWORK-macosx) -X patch/Python/release.macOS.exclude # Apply the App Store compliance patch - patch --strip 2 --directory $$(PYTHON_INSTALL_VERSION-macosx)/lib/python$(PYTHON_VER) --input $(PROJECT_DIR)/patch/Python/app-store-compliance.patch + # patch --strip 2 --directory $$(PYTHON_INSTALL_VERSION-macosx)/lib/python$(PYTHON_VER) --input $(PROJECT_DIR)/patch/Python/app-store-compliance.patch # Rewrite the framework to make it standalone patch/make-relocatable.sh $$(PYTHON_INSTALL_VERSION-macosx) 2>&1 > /dev/null diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 833df31d..2bdad51b 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,94 +1,1510 @@ --- /dev/null -+++ b/Lib/_ios_support.py -@@ -0,0 +1,36 @@ -+from ctypes import cdll, c_void_p, c_char_p -+from ctypes import util ++++ b/Doc/includes/wasm-ios-notavail.rst +@@ -0,0 +1,8 @@ ++.. include for modules that don't work on WASM or iOS + ++.. availability:: not WASI, not iOS. + -+def get_platform_ios(): -+ objc = cdll.LoadLibrary(util.find_library(b'objc')) ++ This module does not work or is not available on WebAssembly platforms, or ++ on iOS. See :ref:`wasm-availability` for more information on WASM ++ availability; see :ref:`iOS-availability` for more information on iOS ++ availability. +diff --git a/Doc/includes/wasm-notavail.rst b/Doc/includes/wasm-notavail.rst +index e680e1f9b43..c1b79d2a4a0 100644 +--- a/Doc/includes/wasm-notavail.rst ++++ b/Doc/includes/wasm-notavail.rst +@@ -1,7 +1,6 @@ + .. include for modules that don't work on WASM + +-.. availability:: not Emscripten, not WASI. ++.. availability:: not WASI. + +- This module does not work or is not available on WebAssembly platforms +- ``wasm32-emscripten`` and ``wasm32-wasi``. See ++ This module does not work or is not available on WebAssembly. See + :ref:`wasm-availability` for more information. +diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst +index 0d743efd28e..dfd71112231 100644 +--- a/Doc/library/curses.rst ++++ b/Doc/library/curses.rst +@@ -21,6 +21,8 @@ + designed to match the API of ncurses, an open-source curses library hosted on + Linux and the BSD variants of Unix. + ++.. include:: ../includes/wasm-ios-notavail.rst ++ + .. note:: + + Whenever the documentation mentions a *character* it can be specified +diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst +index 74f96b6c433..30b7738a08e 100644 +--- a/Doc/library/dbm.rst ++++ b/Doc/library/dbm.rst +@@ -14,6 +14,7 @@ + is a `third party interface `_ to + the Oracle Berkeley DB. + ++.. include:: ../includes/wasm-ios-notavail.rst + + .. exception:: error + +@@ -398,4 +399,3 @@ + .. method:: dumbdbm.close() + + Close the database. +- +diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst +index de3b93f5e61..168e45cfd6f 100644 +--- a/Doc/library/ensurepip.rst ++++ b/Doc/library/ensurepip.rst +@@ -38,7 +38,7 @@ + :pep:`453`: Explicit bootstrapping of pip in Python installations + The original rationale and specification for this module. + +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst + + Command line interface + ---------------------- +diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst +index 1836f60ca5c..a36974b1594 100644 +--- a/Doc/library/fcntl.rst ++++ b/Doc/library/fcntl.rst +@@ -18,7 +18,7 @@ + See the :manpage:`fcntl(2)` and :manpage:`ioctl(2)` Unix manual pages + for full details. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI. + + All functions in this module take a file descriptor *fd* as their first + argument. This can be an integer file descriptor, such as returned by +diff --git a/Doc/library/grp.rst b/Doc/library/grp.rst +index ee55b12ea86..80260fec8d3 100644 +--- a/Doc/library/grp.rst ++++ b/Doc/library/grp.rst +@@ -10,7 +10,7 @@ + This module provides access to the Unix group database. It is available on all + Unix versions. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI, not iOS. + + Group database entries are reported as a tuple-like object, whose attributes + correspond to the members of the ``group`` structure (Attribute field below, see +diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst +index e9200dd1e2d..5f57362e557 100644 +--- a/Doc/library/importlib.rst ++++ b/Doc/library/importlib.rst +@@ -1202,6 +1202,69 @@ + and how the module's :attr:`__file__` is populated. + + ++.. class:: AppleFrameworkLoader(name, path) ++ ++ A specialization of :class:`importlib.machinery.ExtensionFileLoader` that ++ is able to load extension modules in Framework format. ++ ++ For compatibility with the iOS App Store, *all* binary modules in an iOS app ++ must be dynamic libraries, contained in a framework with appropriate ++ metadata, stored in the ``Frameworks`` folder of the packaged app. There can ++ be only a single binary per framework, and there can be no executable binary ++ material outside the Frameworks folder. ++ ++ To accomodate this requirement, when running on iOS, extension module ++ binaries are *not* packaged as ``.so`` files on ``sys.path``, but as ++ individual standalone frameworks. To discover those frameworks, this loader ++ is be registered against the ``.fwork`` file extension, with a ``.fwork`` ++ file acting as a placeholder in the original location of the binary on ++ ``sys.path``. The ``.fwork`` file contains the path of the actual binary in ++ the ``Frameworks`` folder, relative to the app bundle. To allow for ++ resolving a framework-packaged binary back to the original location, the ++ framework is expected to contain a ``.origin`` file that contains the ++ location of the ``.fwork`` file, relative to the app bundle. ++ ++ For example, consider the case of an import ``from foo.bar import _whiz``, ++ where ``_whiz`` is implemented with the binary module ++ ``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location ++ registered on ``sys.path``, relative to the application bundle. This module ++ *must* be distributed as ++ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` (creating the framework ++ name from the full import path of the module), with an ``Info.plist`` file ++ in the ``.framework`` directory identifying the binary as a framework. The ++ ``foo.bar._whiz`` module would be represented in the original location with ++ a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing the path ++ ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also contain ++ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing the ++ path to the ``.fwork`` file. ++ ++ When a module is loaded with this loader, the ``__file__`` for the module ++ will report as the location of the ``.fwork`` file. This allows code to use ++ the ``__file__`` of a module as an anchor for file system traveral. ++ However, the spec origin will reference the location of the *actual* binary ++ in the ``.framework`` folder. ++ ++ The Xcode project building the app is responsible for converting any ``.so`` ++ files from wherever they exist in the ``PYTHONPATH`` into frameworks in the ++ ``Frameworks`` folder (including stripping extensions from the module file, ++ the addition of framework metadata, and signing the resulting framework), ++ and creating the ``.fwork`` and ``.origin`` files. This will usually be done ++ with a build step in the Xcode project; see the iOS documentation for ++ details on how to construct this build step. ++ ++ .. versionadded:: 3.13 ++ ++ .. availability:: iOS. ++ ++ .. attribute:: name ++ ++ Name of the module the loader supports. ++ ++ .. attribute:: path ++ ++ Path to the ``.fwork`` file for the extension module. ++ ++ + :mod:`importlib.util` -- Utility code for importers + --------------------------------------------------- + +diff --git a/Doc/library/intro.rst b/Doc/library/intro.rst +index 5a4c9b8b16a..ffc8939d211 100644 +--- a/Doc/library/intro.rst ++++ b/Doc/library/intro.rst +@@ -58,7 +58,7 @@ + operating system. + + * If not separately noted, all functions that claim "Availability: Unix" are +- supported on macOS, which builds on a Unix core. ++ supported on macOS and iOS, both of which build on a Unix core. + + * If an availability note contains both a minimum Kernel version and a minimum + libc version, then both conditions must hold. For example a feature with note +@@ -119,3 +119,44 @@ + .. _wasmtime: https://wasmtime.dev/ + .. _Pyodide: https://pyodide.org/ + .. _PyScript: https://pyscript.net/ ++ ++.. _iOS-availability: ++ ++iOS ++--- ++ ++iOS is, in most respects, a POSIX operating system. File I/O, socket handling, ++and threading all behave as they would on any POSIX operating system. However, ++there are several major differences between iOS and other POSIX systems. ++ ++* iOS can only use Python in "embedded" mode. There is no Python REPL, and no ++ ability to execute binaries that are part of the normal Python developer ++ experience, such as :program:`pip`. To add Python code to your iOS app, you must use ++ the :ref:`Python embedding API ` to add a Python interpreter to an ++ iOS app created with Xcode. See the :ref:`iOS usage guide ` for ++ more details. ++ ++* An iOS app cannot use any form of subprocessing, background processing, or ++ inter-process communication. If an iOS app attempts to create a subprocess, ++ the process creating the subprocess will either lock up, or crash. An iOS app ++ has no visibility of other applications that are running, nor any ability to ++ communicate with other running applications, outside of the iOS-specific APIs ++ that exist for this purpose. ++ ++* iOS apps have limited access to modify system resources (such as the system ++ clock). These resources will often be *readable*, but attempts to modify ++ those resources will usually fail. ++ ++* iOS apps have a limited concept of console input and output. ``stdout`` and ++ ``stderr`` *exist*, and content written to ``stdout`` and ``stderr`` will be ++ visible in logs when running in Xcode, but this content *won't* be recorded ++ in the system log. If a user who has installed your app provides their app ++ logs as a diagnostic aid, they will not include any detail written to ++ ``stdout`` or ``stderr``. ++ ++ iOS apps have no concept of ``stdin`` at all. While iOS apps can have a ++ keyboard, this is a software feature, not something that is attached to ++ ``stdin``. ++ ++ As a result, Python library that involve console manipulation (such as ++ :mod:`curses` and :mod:`readline`) are not available on iOS. +diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst +index ffd690148f1..4173d76d4fb 100644 +--- a/Doc/library/multiprocessing.rst ++++ b/Doc/library/multiprocessing.rst +@@ -8,7 +8,7 @@ + + -------------- + +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst + + Introduction + ------------ +diff --git a/Doc/library/os.rst b/Doc/library/os.rst +index 5fefee382fd..5dda3fc6882 100644 +--- a/Doc/library/os.rst ++++ b/Doc/library/os.rst +@@ -34,12 +34,13 @@ + + * On VxWorks, os.popen, os.fork, os.execv and os.spawn*p* are not supported. + +-* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, large +- parts of the :mod:`os` module are not available or behave differently. API +- related to processes (e.g. :func:`~os.fork`, :func:`~os.execve`), signals +- (e.g. :func:`~os.kill`, :func:`~os.wait`), and resources +- (e.g. :func:`~os.nice`) are not available. Others like :func:`~os.getuid` +- and :func:`~os.getpid` are emulated or stubs. ++* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, and on ++ iOS, large parts of the :mod:`os` module are not available or behave ++ differently. API related to processes (e.g. :func:`~os.fork`, ++ :func:`~os.execve`) and resources (e.g. :func:`~os.nice`) are not available. ++ Others like :func:`~os.getuid` and :func:`~os.getpid` are emulated or stubs. ++ WebAssembly platforms also lack support for signals (e.g. :func:`~os.kill`, ++ :func:`~os.wait`). + + + .. note:: +@@ -735,6 +736,11 @@ + :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). :func:`platform.uname()` ++ can be used to get the user-facing operating system name and version on iOS and ++ Android. ++ + .. availability:: Unix. + + .. versionchanged:: 3.3 +@@ -3761,7 +3767,7 @@ + + .. audit-event:: os.exec path,args,env os.execl + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionchanged:: 3.3 + Added support for specifying *path* as an open file descriptor +@@ -3941,7 +3947,16 @@ + + See :mod:`ssl` for applications that use the SSL module with fork(). + +- .. availability:: Unix, not Emscripten, not WASI. ++ Users of macOS or users of libc or malloc implementations other ++ than those typically found in glibc to date are among those ++ already more likely to experience deadlocks running such code. ++ ++ See `this discussion on fork being incompatible with threads ++ `_ ++ for technical details of why we're surfacing this longstanding ++ platform compatibility problem to developers. ++ ++ .. availability:: POSIX, not Emscripten, not WASI, not iOS. + + + .. function:: forkpty() +@@ -3963,7 +3978,12 @@ + Calling ``forkpty()`` in a subinterpreter is no longer supported + (:exc:`RuntimeError` is raised). + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. versionchanged:: 3.12 ++ If Python is able to detect that your process has multiple ++ threads, this now raises a :exc:`DeprecationWarning`. See the ++ longer explanation on :func:`os.fork`. ++ ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: kill(pid, sig, /) +@@ -3987,7 +4007,7 @@ + + .. audit-event:: os.kill pid,sig os.kill + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionchanged:: 3.2 + Added Windows support. +@@ -4003,7 +4023,7 @@ + + .. audit-event:: os.killpg pgid,sig os.killpg + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: nice(increment, /) +@@ -4031,7 +4051,7 @@ + Lock program segments into memory. The value of *op* (defined in + ````) determines which segments are locked. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: popen(cmd, mode='r', buffering=-1) +@@ -4063,7 +4083,7 @@ + documentation for more powerful ways to manage and communicate with + subprocesses. + +- .. availability:: not Emscripten, not WASI. ++ .. availability:: not Emscripten, not WASI, not iOS. + + .. note:: + The :ref:`Python UTF-8 Mode ` affects encodings used +@@ -4158,7 +4178,7 @@ + + .. versionadded:: 3.8 + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ + setpgroup=None, resetids=False, setsid=False, setsigmask=(), \ +@@ -4174,7 +4194,7 @@ + + .. versionadded:: 3.8 + +- .. availability:: POSIX, not Emscripten, not WASI. ++ .. availability:: POSIX, not Emscripten, not WASI, not iOS. + + See :func:`posix_spawn` documentation. + +@@ -4207,7 +4227,7 @@ + + There is no way to unregister a function. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.7 + +@@ -4276,7 +4296,7 @@ + + .. audit-event:: os.spawn mode,path,args,env os.spawnl + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + :func:`spawnlp`, :func:`spawnlpe`, :func:`spawnvp` + and :func:`spawnvpe` are not available on Windows. :func:`spawnle` and +@@ -4400,7 +4420,7 @@ + + .. audit-event:: os.system command os.system + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + + .. function:: times() +@@ -4444,7 +4464,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exit code. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. seealso:: + +@@ -4478,7 +4498,10 @@ + Otherwise, if there are no matching children + that could be waited for, :exc:`ChildProcessError` is raised. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. ++ ++ .. note:: ++ This function is not available on macOS. + + .. note:: + This function is not available on macOS. +@@ -4519,7 +4542,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exit code. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionchanged:: 3.5 + If the system call is interrupted and the signal handler does not raise an +@@ -4539,7 +4562,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exitcode. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: wait4(pid, options) +@@ -4553,7 +4576,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exitcode. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: P_PID +@@ -4570,7 +4593,7 @@ + * :data:`!P_PIDFD` - wait for the child identified by the file descriptor + *id* (a process file descriptor created with :func:`pidfd_open`). + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. note:: :data:`!P_PIDFD` is only available on Linux >= 5.4. + +@@ -4585,7 +4608,7 @@ + :func:`waitid` causes child processes to be reported if they have been + continued from a job control stop since they were last reported. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: WEXITED +@@ -4596,7 +4619,7 @@ + The other ``wait*`` functions always report children that have terminated, + so this option is not available for them. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.3 + +@@ -4608,7 +4631,7 @@ + + This option is not available for the other ``wait*`` functions. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.3 + +@@ -4621,7 +4644,7 @@ + + This option is not available for :func:`waitid`. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: WNOHANG +@@ -4630,7 +4653,7 @@ + :func:`waitid` to return right away if no child process status is available + immediately. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: WNOWAIT +@@ -4640,7 +4663,7 @@ + + This option is not available for the other ``wait*`` functions. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: CLD_EXITED +@@ -4653,7 +4676,7 @@ + These are the possible values for :attr:`!si_code` in the result returned by + :func:`waitid`. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.3 + +@@ -4688,7 +4711,7 @@ + :func:`WIFEXITED`, :func:`WEXITSTATUS`, :func:`WIFSIGNALED`, + :func:`WTERMSIG`, :func:`WIFSTOPPED`, :func:`WSTOPSIG` functions. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.9 + +@@ -4704,7 +4727,7 @@ + + This function should be employed only if :func:`WIFSIGNALED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WIFCONTINUED(status) +@@ -4715,7 +4738,7 @@ + + See :data:`WCONTINUED` option. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WIFSTOPPED(status) +@@ -4727,14 +4750,14 @@ + done using :data:`WUNTRACED` option or when the process is being traced (see + :manpage:`ptrace(2)`). + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. function:: WIFSIGNALED(status) + + Return ``True`` if the process was terminated by a signal, otherwise return + ``False``. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WIFEXITED(status) +@@ -4743,7 +4766,7 @@ + by calling ``exit()`` or ``_exit()``, or by returning from ``main()``; + otherwise return ``False``. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WEXITSTATUS(status) +@@ -4752,7 +4775,7 @@ + + This function should be employed only if :func:`WIFEXITED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WSTOPSIG(status) +@@ -4761,7 +4784,7 @@ + + This function should be employed only if :func:`WIFSTOPPED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WTERMSIG(status) +@@ -4770,7 +4793,7 @@ + + This function should be employed only if :func:`WIFSIGNALED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + Interface to the scheduler +diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst +index 60cd6b88084..316b693eece 100644 +--- a/Doc/library/platform.rst ++++ b/Doc/library/platform.rst +@@ -148,6 +148,9 @@ + 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 ++ ``'Linux'``), use :func:`os.uname()`. + + .. function:: system_alias(system, release, version) + +@@ -161,6 +164,8 @@ + 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 ++ Darwin or Linux kernel version, use :func:`os.uname()`. + + .. function:: uname() + +@@ -230,7 +235,6 @@ + macOS Platform + -------------- + +- + .. function:: mac_ver(release='', versioninfo=('','',''), machine='') + + Get macOS version information and return it as tuple ``(release, versioninfo, +@@ -240,6 +244,24 @@ + Entries which cannot be determined are set to ``''``. All tuple entries are + strings. + ++iOS Platform ++------------ ++ ++.. function:: ios_ver(system='', release='', model='', is_simulator=False) ++ ++ 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'``). ++ * ``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 the defaults given as ++ parameters. ++ + + Unix Platforms + -------------- +diff --git a/Doc/library/pwd.rst b/Doc/library/pwd.rst +index 755f0d29ac7..b6d212e93b5 100644 +--- a/Doc/library/pwd.rst ++++ b/Doc/library/pwd.rst +@@ -10,7 +10,7 @@ + This module provides access to the Unix user account and password database. It + is available on all Unix versions. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI, not iOS. + + Password database entries are reported as a tuple-like object, whose attributes + correspond to the members of the ``passwd`` structure (Attribute field below, +diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst +index 3fb5ceef086..758b89b4612 100644 +--- a/Doc/library/readline.rst ++++ b/Doc/library/readline.rst +@@ -24,6 +24,8 @@ + allowable constructs of that file, and the capabilities of the + Readline library in general. + ++.. include:: ../includes/wasm-ios-notavail.rst ++ + .. note:: + + The underlying Readline library API may be implemented by +diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst +index 389a63f089d..4fea8d5cb71 100644 +--- a/Doc/library/resource.rst ++++ b/Doc/library/resource.rst +@@ -13,7 +13,7 @@ + This module provides basic mechanisms for measuring and controlling system + resources utilized by a program. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI. + + Symbolic constants are used to specify particular system resources and to + request usage information about either the current process or its children. +diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst +index 85a073aad23..05ef45c123b 100644 +--- a/Doc/library/signal.rst ++++ b/Doc/library/signal.rst +@@ -26,9 +26,9 @@ + underlying implementation), with the exception of the handler for + :const:`SIGCHLD`, which follows the underlying implementation. + +-On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, signals +-are emulated and therefore behave differently. Several functions and signals +-are not available on these platforms. ++On WebAssembly platforms, signals are emulated and therefore behave ++differently. Several functions and signals are not available on these ++platforms. + + Execution of Python signal handlers + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst +index 3a5368717ac..de47e1f6e5f 100644 +--- a/Doc/library/socket.rst ++++ b/Doc/library/socket.rst +@@ -1109,7 +1109,7 @@ + buffer. Raises :exc:`OverflowError` if *length* is outside the + permissible range of values. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not WASI. + + Most Unix platforms. + +@@ -1132,7 +1132,7 @@ + amount of ancillary data that can be received, since additional + data may be able to fit into the padding area. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not WASI. + + most Unix platforms. + +@@ -1172,7 +1172,7 @@ + (index int, name string) tuples. + :exc:`OSError` if the system call fails. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + .. versionadded:: 3.3 + +@@ -1199,7 +1199,7 @@ + interface name. + :exc:`OSError` if no interface with the given name exists. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + .. versionadded:: 3.3 + +@@ -1216,7 +1216,7 @@ + interface index number. + :exc:`OSError` if no interface with the given index exists. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + .. versionadded:: 3.3 + +@@ -1233,7 +1233,7 @@ + The *fds* parameter is a sequence of file descriptors. + Consult :meth:`~socket.sendmsg` for the documentation of these parameters. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + Unix platforms supporting :meth:`~socket.sendmsg` + and :const:`SCM_RIGHTS` mechanism. +@@ -1247,7 +1247,7 @@ + Return ``(msg, list(fds), flags, addr)``. + Consult :meth:`~socket.recvmsg` for the documentation of these parameters. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + Unix platforms supporting :meth:`~socket.sendmsg` + and :const:`SCM_RIGHTS` mechanism. +diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst +index f2d0f83bb68..8d39c94660b 100644 +--- a/Doc/library/subprocess.rst ++++ b/Doc/library/subprocess.rst +@@ -25,7 +25,7 @@ + + :pep:`324` -- PEP proposing the subprocess module + +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst + + Using the :mod:`subprocess` Module + ---------------------------------- +diff --git a/Doc/library/syslog.rst b/Doc/library/syslog.rst +index 889bbb39d58..6c7c0907143 100644 +--- a/Doc/library/syslog.rst ++++ b/Doc/library/syslog.rst +@@ -11,7 +11,7 @@ + Refer to the Unix manual pages for a detailed description of the ``syslog`` + facility. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI, not iOS. + + This module wraps the system ``syslog`` family of routines. A pure Python + library that can speak to a syslog server is available in the +diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst +index 9f90418bc96..c198c5798e9 100644 +--- a/Doc/library/urllib.parse.rst ++++ b/Doc/library/urllib.parse.rst +@@ -22,11 +22,19 @@ + + The module has been designed to match the internet RFC on Relative Uniform + Resource Locators. It supports the following URL schemes: ``file``, ``ftp``, +-``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``mailto``, ``mms``, ++``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``itms-services``, ``mailto``, ``mms``, + ``news``, ``nntp``, ``prospero``, ``rsync``, ``rtsp``, ``rtsps``, ``rtspu``, + ``sftp``, ``shttp``, ``sip``, ``sips``, ``snews``, ``svn``, ``svn+ssh``, + ``telnet``, ``wais``, ``ws``, ``wss``. + ++.. impl-detail:: ++ ++ The inclusion of the ``itms-services`` URL scheme can prevent an app from ++ passing Apple's App Store review process for the macOS and iOS App Stores. ++ Handling for the ``itms-services`` scheme is always removed on iOS; on ++ macOS, it *may* be removed if CPython has been built with the ++ :option:`--with-app-store-compliance` option. ++ + The :mod:`urllib.parse` module defines functions that fall into two broad + categories: URL parsing and URL quoting. These are covered in detail in + the following sections. +diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst +index ce6d093b741..822cead8050 100644 +--- a/Doc/library/venv.rst ++++ b/Doc/library/venv.rst +@@ -56,7 +56,7 @@ + `Python Packaging User Guide: Creating and using virtual environments + `__ + +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst + + Creating virtual environments + ----------------------------- +diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst +index 734b6321e5a..b709e511afa 100644 +--- a/Doc/library/webbrowser.rst ++++ b/Doc/library/webbrowser.rst +@@ -33,6 +33,13 @@ + 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; +@@ -157,6 +164,8 @@ + +------------------------+-----------------------------------------+-------+ + | ``'chromium-browser'`` | :class:`Chromium('chromium-browser')` | | + +------------------------+-----------------------------------------+-------+ ++| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) | +++------------------------+-----------------------------------------+-------+ + + Notes: + +@@ -171,7 +180,11 @@ + Only on Windows platforms. + + (3) +- Only on macOS platform. ++ Only on macOS. ++ ++(4) ++ Only on iOS. ++ + + .. versionadded:: 3.3 + Support for Chrome/Chromium has been added. +@@ -179,6 +192,9 @@ + .. deprecated-removed:: 3.11 3.13 + :class:`MacOSX` is deprecated, use :class:`MacOSXOSAScript` instead. + ++.. versionchanged:: 3.13 ++ Support for iOS has been added. ++ + Here are some simple examples:: + + url = 'https://docs.python.org/' +diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py +index c849a4d3875..4b8252cb590 100644 +--- a/Doc/tools/extensions/pyspecific.py ++++ b/Doc/tools/extensions/pyspecific.py +@@ -138,7 +138,7 @@ + known_platforms = frozenset({ + "AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD", + "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", "Unix", "VxWorks", +- "WASI", "Windows", "macOS", ++ "WASI", "Windows", "macOS", "iOS", + # libc + "BSD libc", "glibc", "musl", + # POSIX platforms with pthreads +@@ -169,7 +169,7 @@ + + Example:: + +- .. availability:: Windows, Linux >= 4.2, not Emscripten, not WASI ++ .. availability:: Windows, Linux >= 4.2, not WASI + + Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not + parsed into separate tokens. +diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst +index e45a5a4791e..26ac341bc98 100644 +--- a/Doc/using/configure.rst ++++ b/Doc/using/configure.rst +@@ -527,7 +527,7 @@ + macOS Options + ------------- + +-See ``Mac/README.rst``. ++See :source:`Mac/README.rst`. + + .. option:: --enable-universalsdk + .. option:: --enable-universalsdk=SDKDIR +@@ -562,6 +562,31 @@ + Specify the name for the python framework on macOS only valid when + :option:`--enable-framework` is set (default: ``Python``). + ++.. option:: --with-app-store-compliance ++.. option:: --with-app-store-compliance=PATCH-FILE ++ ++ The Python standard library contains strings that are known to trigger ++ automated inspection tool errors when submitted for distribution by ++ the macOS and iOS App Stores. If enabled, this option will apply the list of ++ patches that are known to correct app store compliance. A custom patch ++ file can also be specified. This option is disabled by default. ++ ++ .. versionadded:: 3.13 ++ ++iOS Options ++----------- ++ ++See :source:`iOS/README.rst`. ++ ++.. option:: --enable-framework=INSTALLDIR ++ ++ Create a Python.framework. Unlike macOS, the *INSTALLDIR* argument ++ specifying the installation path is mandatory. ++ ++.. option:: --with-framework-name=FRAMEWORK ++ ++ Specify the name for the framework (default: ``Python``). ++ + + Cross Compiling Options + ----------------------- +diff --git a/Doc/using/index.rst b/Doc/using/index.rst +index e1a3111f36a..f55a12f1ab8 100644 +--- a/Doc/using/index.rst ++++ b/Doc/using/index.rst +@@ -18,4 +18,5 @@ + configure.rst + windows.rst + mac.rst ++ ios.rst + editors.rst +--- /dev/null ++++ b/Doc/using/ios.rst +@@ -0,0 +1,343 @@ ++.. _using-ios: ++ ++=================== ++Using Python on iOS ++=================== ++ ++:Authors: ++ Russell Keith-Magee (2024-03) ++ ++Python on iOS is unlike Python on desktop platforms. On a desktop platform, ++Python is generally installed as a system resource that can be used by any user ++of that computer. Users then interact with Python by running a :program:`python` ++executable and entering commands at an interactive prompt, or by running a ++Python script. ++ ++On iOS, there is no concept of installing as a system resource. The only unit ++of software distribution is an "app". There is also no console where you could ++run a :program:`python` executable, or interact with a Python REPL. ++ ++As a result, the only way you can use Python on iOS is in embedded mode - that ++is, by writing a native iOS application, and embedding a Python interpreter ++using ``libPython``, and invoking Python code using the :ref:`Python embedding ++API `. The full Python interpreter, the standard library, and all ++your Python code is then packaged as a standalone bundle that can be ++distributed via the iOS App Store. ++ ++If you're looking to experiment for the first time with writing an iOS app in ++Python, projects such as `BeeWare `__ and `Kivy ++`__ will provide a much more approachable user experience. ++These projects manage the complexities associated with getting an iOS project ++running, so you only need to deal with the Python code itself. ++ ++Python at runtime on iOS ++======================== ++ ++iOS version compatibility ++------------------------- ++ ++The minimum supported iOS version is specified at compile time, using the ++:option:`--host` option to ``configure``. By default, when compiled for iOS, ++Python will be compiled with a minimum supported iOS version of 13.0. To use a ++different miniumum iOS version, provide the version number as part of the ++:option:`!--host` argument - for example, ++``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 simulator build ++with a deployment target of 15.4. ++ ++Platform identification ++----------------------- ++ ++When executing on iOS, ``sys.platform`` will report as ``ios``. This value will ++be returned on an iPhone or iPad, regardless of whether the app is running on ++the simulator or a physical device. ++ ++Information about the specific runtime environment, including the iOS version, ++device model, and whether the device is a simulator, can be obtained using ++:func:`platform.ios_ver()`. :func:`platform.system()` will report ``iOS`` or ++``iPadOS``, depending on the device. ++ ++:func:`os.uname()` reports kernel-level details; it will report a name of ++``Darwin``. ++ ++Standard library availability ++----------------------------- ++ ++The Python standard library has some notable omissions and restrictions on ++iOS. See the :ref:`API availability guide for iOS ` for ++details. ++ ++Binary extension modules ++------------------------ ++ ++One notable difference about iOS as a platform is that App Store distribution ++imposes hard requirements on the packaging of an application. One of these ++requirements governs how binary extension modules are distributed. ++ ++The iOS App Store requires that *all* binary modules in an iOS app must be ++dynamic libraries, contained in a framework with appropriate metadata, stored ++in the ``Frameworks`` folder of the packaged app. There can be only a single ++binary per framework, and there can be no executable binary material outside ++the ``Frameworks`` folder. ++ ++This conflicts with the usual Python approach for distributing binaries, which ++allows a binary extension module to be loaded from any location on ++``sys.path``. To ensure compliance with App Store policies, an iOS project must ++post-process any Python packages, converting ``.so`` binary modules into ++individual standalone frameworks with appropriate metadata and signing. For ++details on how to perform this post-processing, see the guide for :ref:`adding ++Python to your project `. ++ ++To help Python discover binaries in their new location, the original ``.so`` ++file on ``sys.path`` is replaced with a ``.fwork`` file. This file is a text ++file containing the location of the framework binary, relative to the app ++bundle. To allow the framework to resolve back to the original location, the ++framework must contain a ``.origin`` file that contains the location of the ++``.fwork`` file, relative to the app bundle. ++ ++For example, consider the case of an import ``from foo.bar import _whiz``, ++where ``_whiz`` is implemented with the binary module ++``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location ++registered on ``sys.path``, relative to the application bundle. This module ++*must* be distributed as ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` ++(creating the framework name from the full import path of the module), with an ++``Info.plist`` file in the ``.framework`` directory identifying the binary as a ++framework. The ``foo.bar._whiz`` module would be represented in the original ++location with a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing ++the path ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also ++contain ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing ++the path to the ``.fwork`` file. ++ ++When running on iOS, the Python interpreter will install an ++:class:`~importlib.machinery.AppleFrameworkLoader` that is able to read and ++import ``.fwork`` files. Once imported, the ``__file__`` attribute of the ++binary module will report as the location of the ``.fwork`` file. However, the ++:class:`~importlib.machinery.ModuleSpec` for the loaded module will report the ++``origin`` as the location of the binary in the framework folder. ++ ++Compiler stub binaries ++---------------------- ++ ++Xcode doesn't expose explicit compilers for iOS; instead, it uses an ``xcrun`` ++script that resolves to a full compiler path (e.g., ``xcrun --sdk iphoneos ++clang`` to get the ``clang`` for an iPhone device). However, using this script ++poses two problems: ++ ++* The output of ``xcrun`` includes paths that are machine specific, resulting ++ in a sysconfig module that cannot be shared between users; and ++ ++* It results in ``CC``/``CPP``/``LD``/``AR`` definitions that include spaces. ++ There is a lot of C ecosystem tooling that assumes that you can split a ++ command line at the first space to get the path to the compiler executable; ++ this isn't the case when using ``xcrun``. ++ ++To avoid these problems, Python provided stubs for these tools. These stubs are ++shell script wrappers around the underingly ``xcrun`` tools, distributed in a ++``bin`` folder distributed alongside the compiled iOS framework. These scripts ++are relocatable, and will always resolve to the appropriate local system paths. ++By including these scripts in the bin folder that accompanies a framework, the ++contents of the ``sysconfig`` module becomes useful for end-users to compile ++their own modules. When compiling third-party Python modules for iOS, you ++should ensure these stub binaries are on your path. ++ ++Installing Python on iOS ++======================== ++ ++Tools for building iOS apps ++--------------------------- ++ ++Building for iOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode. This will ++require the use of the most (or second-most) recently released macOS version, ++as Apple does not maintain Xcode for older macOS versions. The Xcode Command ++Line Tools are not sufficient for iOS development; you need a *full* Xcode ++install. ++ ++If you want to run your code on the iOS simulator, you'll also need to install ++an iOS Simulator Platform. You should be prompted to select an iOS Simulator ++Platform when you first run Xcode. Alternatively, you can add an iOS Simulator ++Platform by selecting from the Platforms tab of the Xcode Settings panel. ++ ++.. _adding-ios: ++ ++Adding Python to an iOS project ++------------------------------- ++ ++Python can be added to any iOS project, using either Swift or Objective C. The ++following examples will use Objective C; if you are using Swift, you may find a ++library like `PythonKit `__ to be ++helpful. ++ ++To add Python to an iOS Xcode project: ++ ++1. Build or obtain a Python ``XCFramework``. See the instructions in ++ :source:`iOS/README.rst` (in the CPython source distribution) for details on ++ how to build a Python ``XCFramework``. At a minimum, you will need a build ++ that supports ``arm64-apple-ios``, plus one of either ++ ``arm64-apple-ios-simulator`` or ``x86_64-apple-ios-simulator``. ++ ++2. Drag the ``XCframework`` into your iOS project. In the following ++ instructions, we'll assume you've dropped the ``XCframework`` into the root ++ of your project; however, you can use any other location that you want by ++ adjusting paths as needed. ++ ++3. Drag the ``iOS/Resources/dylib-Info-template.plist`` file into your project, ++ and ensure it is associated with the app target. ++ ++4. Add your application code as a folder in your Xcode project. In the ++ following instructions, we'll assume that your user code is in a folder ++ named ``app`` in the root of your project; you can use any other location by ++ adjusting paths as needed. Ensure that this folder is associated with your ++ app target. ++ ++5. Select the app target by selecting the root node of your Xcode project, then ++ the target name in the sidebar that appears. ++ ++6. In the "General" settings, under "Frameworks, Libraries and Embedded ++ Content", add ``Python.xcframework``, with "Embed & Sign" selected. ++ ++7. In the "Build Settings" tab, modify the following: ++ ++ - Build Options ++ ++ * User Script Sandboxing: No ++ * Enable Testability: Yes ++ ++ - Search Paths ++ ++ * Framework Search Paths: ``$(PROJECT_DIR)`` ++ * Header Search Paths: ``"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"`` ++ ++ - Apple Clang - Warnings - All languages ++ ++ * Quoted Include In Framework Header: No ++ ++8. Add a build step that copies the Python standard library into your app. In ++ the "Build Phases" tab, add a new "Run Script" build step *before* the ++ "Embed Frameworks" step, but *after* the "Copy Bundle Resources" step. Name ++ the step "Install Target Specific Python Standard Library", disable the ++ "Based on dependency analysis" checkbox, and set the script content to: ++ ++ .. code-block:: bash ++ ++ set -e ++ ++ mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" ++ if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then ++ echo "Installing Python modules for iOS Simulator" ++ rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" ++ else ++ echo "Installing Python modules for iOS Device" ++ rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" ++ fi ++ ++ Note that the name of the simulator "slice" in the XCframework may be ++ different, depending the CPU architectures your ``XCFramework`` supports. ++ ++9. Add a second build step that processes the binary extension modules in the ++ standard library into "Framework" format. Add a "Run Script" build step ++ *directly after* the one you added in step 8, named "Prepare Python Binary ++ Modules". It should also have "Based on dependency analysis" unchecked, with ++ the following script content: ++ ++ .. code-block:: bash ++ ++ set -e ++ ++ install_dylib () { ++ INSTALL_BASE=$1 ++ FULL_EXT=$2 ++ ++ # The name of the extension file ++ EXT=$(basename "$FULL_EXT") ++ # The location of the extension file, relative to the bundle ++ RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} ++ # The path to the extension file, relative to the install base ++ PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} ++ # The full dotted name of the extension module, constructed from the file path. ++ FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); ++ # A bundle identifier; not actually used, but required by Xcode framework packaging ++ FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") ++ # The name of the framework folder. ++ FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" ++ ++ # If the framework folder doesn't exist, create it. ++ if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then ++ echo "Creating framework for $RELATIVE_EXT" ++ mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ++ cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ fi ++ ++ echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ # Create a placeholder .fwork file where the .so was ++ echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork ++ # Create a back reference to the .so file location in the framework ++ echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" ++ } ++ ++ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") ++ echo "Install Python $PYTHON_VER standard library extension modules..." ++ find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do ++ install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" ++ done ++ ++ # Clean up dylib template ++ rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" ++ ++ echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." ++ find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \; ++ ++10. Add Objective C code to initialize and use a Python interpreter in embedded ++ mode. You should ensure that: ++ ++ * :c:member:`UTF-8 mode ` is *enabled*; ++ * :c:member:`Buffered stdio ` is *disabled*; ++ * :c:member:`Writing bytecode ` is *disabled*; ++ * :c:member:`Signal handlers ` are *enabled*; ++ * ``PYTHONHOME`` for the interpreter is configured to point at the ++ ``python`` subfolder of your app's bundle; and ++ * The ``PYTHONPATH`` for the interpreter includes: ++ ++ - the ``python/lib/python3.X`` subfolder of your app's bundle, ++ - the ``python/lib/python3.X/lib-dynload`` subfolder of your app's bundle, and ++ - the ``app`` subfolder of your app's bundle ++ ++ Your app's bundle location can be determined using ``[[NSBundle mainBundle] ++ resourcePath]``. ++ ++Steps 8, 9 and 10 of these instructions assume that you have a single folder of ++pure Python application code, named ``app``. If you have third-party binary ++modules in your app, some additional steps will be required: ++ ++* You need to ensure that any folders containing third-party binaries are ++ either associated with the app target, or copied in as part of step 8. Step 8 ++ should also purge any binaries that are not appropriate for the platform a ++ specific build is targetting (i.e., delete any device binaries if you're ++ building app app targeting the simulator). ++ ++* Any folders that contain third-party binaries must be processed into ++ framework form by step 9. The invocation of ``install_dylib`` that processes ++ the ``lib-dynload`` folder can be copied and adapted for this purpose. + ++* If you're using a separate folder for third-party packages, ensure that folder ++ is included as part of the ``PYTHONPATH`` configuration in step 10. ++ ++App Store Compliance ++==================== ++ ++The only mechanism for distributing apps to third-party iOS devices is to ++submit the app to the iOS App Store; apps submitted for distribution must pass ++Apple's app review process. This process includes a set of automated validation ++rules that inspect the submitted application bundle for problematic code. ++ ++The Python standard library contains some code that is known to violate these ++automated rules. While these violations appear to be false positives, Apple's ++review rules cannot be challenged; so, it is necessary to modify the Python ++standard library for an app to pass App Store review. ++ ++The Python source tree contains ++:source:`a patch file ` that will remove ++all code that is known to cause issues with the App Store review process. This ++patch is applied automatically when building for iOS. +diff --git a/Doc/using/mac.rst b/Doc/using/mac.rst +index 986d693d03f..da45aabca28 100644 +--- a/Doc/using/mac.rst ++++ b/Doc/using/mac.rst +@@ -188,6 +188,28 @@ + * `PyInstaller `__: A cross-platform packaging tool that creates + a single file or folder as a distributable artifact. + ++App Store Compliance ++-------------------- ++ ++Apps submitted for distribution through the macOS App Store must pass Apple's ++app review process. This process includes a set of automated validation rules ++that inspect the submitted application bundle for problematic code. ++ ++The Python standard library contains some code that is known to violate these ++automated rules. While these violations appear to be false positives, Apple's ++review rules cannot be challenged. Therefore, it is necessary to modify the ++Python standard library for an app to pass App Store review. ++ ++The Python source tree contains ++:source:`a patch file ` that will remove ++all code that is known to cause issues with the App Store review process. This ++patch is applied automatically when CPython is configured with the ++:option:`--with-app-store-compliance` option. ++ ++This patch is not normally required to use CPython on a Mac; nor is it required ++if you are distributing an app *outside* the macOS App Store. It is *only* ++required if you are using the macOS App Store as a distribution channel. ++ + Other Resources + =============== + +--- /dev/null ++++ b/Lib/_ios_support.py +@@ -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.objc_msgSend.restype = c_void_p -+ objc.objc_msgSend.argtypes = [c_void_p, c_void_p] + objc.sel_registerName.restype = c_void_p + objc.sel_registerName.argtypes = [c_char_p] + -+ UIDevice = c_void_p(objc.objc_getClass(b'UIDevice')) -+ SEL_currentDevice = c_void_p(objc.sel_registerName(b'currentDevice')) -+ device = c_void_p(objc.objc_msgSend(UIDevice, SEL_currentDevice)) + -+ SEL_systemVersion = c_void_p(objc.sel_registerName(b'systemVersion')) -+ systemVersion = c_void_p(objc.objc_msgSend(device, SEL_systemVersion)) ++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") ++ device = objc.objc_msgSend(UIDevice, SEL_currentDevice) + -+ SEL_systemName = c_void_p(objc.sel_registerName(b'systemName')) -+ systemName = c_void_p(objc.objc_msgSend(device, SEL_systemName)) ++ # Equivalent of: ++ # device_systemVersion = [device systemVersion] ++ SEL_systemVersion = objc.sel_registerName(b"systemVersion") ++ device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion) + -+ SEL_model = c_void_p(objc.sel_registerName(b'model')) -+ systemModel = c_void_p(objc.objc_msgSend(device, SEL_model)) ++ # 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 = c_void_p(objc.sel_registerName(b'UTF8String')) ++ SEL_UTF8String = objc.sel_registerName(b"UTF8String") + objc.objc_msgSend.restype = c_char_p + -+ system = objc.objc_msgSend(systemName, SEL_UTF8String).decode() -+ release = objc.objc_msgSend(systemVersion, SEL_UTF8String).decode() -+ model = objc.objc_msgSend(systemModel, SEL_UTF8String).decode() ++ # 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/ctypes/__init__.py b/Lib/ctypes/__init__.py +index 26135ad9629..315c725e547 100644 +--- a/Lib/ctypes/__init__.py ++++ b/Lib/ctypes/__init__.py +@@ -343,6 +343,19 @@ + use_errno=False, + use_last_error=False, + winmode=None): ++ if name: ++ name = _os.fspath(name) + -+ return system, release, model ++ # If the filename that has been provided is an iOS/tvOS/watchOS ++ # .fwork file, dereference the location to the true origin of the ++ # binary. ++ if name.endswith(".fwork"): ++ with open(name) as f: ++ name = _os.path.join( ++ _os.path.dirname(_sys.executable), ++ f.read().strip() ++ ) ++ + self._name = name + flags = self._func_flags_ + if use_errno: diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py -index 0c2510e161..6c3c43f11d 100644 +index c550883e7c7..12d7428fe9a 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -67,7 +67,7 @@ return fname return None - + -elif os.name == "posix" and sys.platform == "darwin": -+elif os.name == "posix" and sys.platform in ('darwin', 'ios', 'tvos', 'watchos'): ++elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}: from ctypes.macholib.dyld import dyld_find as _dyld_find def find_library(name): possible = ['lib%s.dylib' % name, diff --git a/Lib/distutils/tests/test_cygwinccompiler.py b/Lib/distutils/tests/test_cygwinccompiler.py -index 0912ffd15c..959aa90522 100644 +index 633d3041a1b..a91ee9e0f37 100644 --- a/Lib/distutils/tests/test_cygwinccompiler.py +++ b/Lib/distutils/tests/test_cygwinccompiler.py -@@ -5,11 +5,14 @@ +@@ -4,6 +4,9 @@ + import os from io import BytesIO - from test.support import run_unittest - --from distutils import cygwinccompiler --from distutils.cygwinccompiler import (check_config_h, -- CONFIG_H_OK, CONFIG_H_NOTOK, -- CONFIG_H_UNCERTAIN, get_versions, -- get_msvcr) -+# Importing cygwinccompiler attempts to import other tools -+# that may not exist unless you're on win32. -+if sys.platform == 'win32': -+ from distutils import cygwinccompiler -+ from distutils.cygwinccompiler import (check_config_h, -+ CONFIG_H_OK, CONFIG_H_NOTOK, -+ CONFIG_H_UNCERTAIN, get_versions, -+ get_msvcr) + ++if sys.platform != 'win32': ++ raise unittest.SkipTest("Cygwin tests only needed on Windows") ++ + from distutils import cygwinccompiler + from distutils.cygwinccompiler import (check_config_h, + CONFIG_H_OK, CONFIG_H_NOTOK, +diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py +index 363834fe8b3..c67b7a130b5 100644 +--- a/Lib/distutils/tests/test_sysconfig.py ++++ b/Lib/distutils/tests/test_sysconfig.py +@@ -10,7 +10,7 @@ + from distutils import sysconfig + from distutils.ccompiler import get_default_compiler from distutils.tests import support - - class FakePopen(object): -@@ -25,6 +28,7 @@ - self.stdout = os.popen(cmd, 'r') - - -+@unittest.skipUnless(sys.platform == "win32", "These tests are only for win32") - class CygwinCCompilerTestCase(support.TempdirManager, - unittest.TestCase): - +-from test.support import swap_item, requires_subprocess, is_wasi ++from test.support import swap_item, requires_subprocess, is_apple_mobile, is_wasi + from test.support.os_helper import TESTFN + from test.support.warnings_helper import check_warnings + +@@ -33,6 +33,7 @@ + shutil.rmtree(TESTFN) + + @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") ++ @unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile") + def test_get_config_h_filename(self): + config_h = sysconfig.get_config_h_filename() + self.assertTrue(os.path.isfile(config_h), config_h) +@@ -50,6 +51,7 @@ + self.assertTrue(cvars) + + @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") ++ @unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile") + def test_srcdir(self): + # See Issues #15322, #15364. + srcdir = sysconfig.get_config_var('srcdir') diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py -index d00c48981e..5d12b4779d 100644 +index d00c48981eb..5d12b4779db 100644 --- a/Lib/distutils/unixccompiler.py +++ b/Lib/distutils/unixccompiler.py @@ -270,9 +270,9 @@ static_f = self.library_filename(lib, lib_type='static') - + if sys.platform == 'darwin': - # On OSX users can specify an alternate SDK using - # '-isysroot', calculate the SDK root if it is specified @@ -111,233 +1527,412 @@ index d00c48981e..5d12b4779d 100644 + sysroot = m.group(1) else: sysroot = m.group(1) - + - - for dir in dirs: shared = os.path.join(dir, shared_f) dylib = os.path.join(dir, dylib_f) diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py -index 2ce5c5b64d..6e10a0c4a5 100644 +index 2ce5c5b64d6..e927f4af938 100644 --- a/Lib/distutils/util.py +++ b/Lib/distutils/util.py -@@ -170,7 +170,7 @@ +@@ -89,10 +89,25 @@ + if m: + release = m.group() + elif osname[:6] == "darwin": +- import _osx_support, distutils.sysconfig +- osname, release, machine = _osx_support.get_platform_osx( +- distutils.sysconfig.get_config_vars(), +- osname, release, machine) ++ import distutils.sysconfig ++ config_vars = distutils.sysconfig.get_config_vars() ++ if sys.platform == "ios": ++ release = config_vars.get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "tvos": ++ release = config_vars.get("TVOS_DEPLOYMENT_TARGET", "9.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "watchos": ++ release = config_vars.get("WATCHOS_DEPLOYMENT_TARGET", "4.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ else: ++ import _osx_support ++ osname, release, machine = _osx_support.get_platform_osx( ++ config_vars, ++ osname, release, machine) + + return "%s-%s-%s" % (osname, release, machine) + +@@ -170,7 +185,7 @@ if _environ_checked: return - + - if os.name == 'posix' and 'HOME' not in os.environ: -+ if os.name == 'posix' and 'HOME' not in os.environ and sys.platform not in ('ios', 'tvos', 'watchos'): ++ if os.name == 'posix' and 'HOME' not in os.environ and sys.platform not in {"ios", "tvos", "watchos"}: try: import pwd os.environ['HOME'] = pwd.getpwuid(os.getuid())[5] diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py -index f603a89f7f..92af10f21a 100644 +index e53f6acf38f..64aa8444410 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -52,7 +52,7 @@ - + # Bootstrap-related code ###################################################### _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', -_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin' +_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos' _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) - -@@ -1705,6 +1705,59 @@ + +@@ -1708,6 +1708,46 @@ return 'FileFinder({!r})'.format(self.path) - - + + +class AppleFrameworkLoader(ExtensionFileLoader): -+ """A loader for modules that have been packaged as Apple Frameworks for -+ compatibility with Apple's App Store policies. -+ -+ For compatibility with the App Store, *all* binary modules must be in .dylibs, -+ contained in a Framework, in the ``Frameworks`` folder of the packaged app. If -+ you're trying to run "from foo import _bar", and _bar is implemented with the binary -+ module "foo/_bar.abi3.dylib" (or any other .dylib extension), this loader will look -+ for "{sys.executable}/Frameworks/foo__bar.framework/_bar.abi3.dylib" (forming the -+ package name by taking the full path of the library, and replacing ``/`` with -+ ``_``). The app packaging tool is responsible for putting the library in this -+ location. -+ -+ However, the ``__file__`` attribute of the _bar module will report as the original -+ location inside the ``foo`` directory. This so that code that depends on walking -+ directory trees will continue to work as expected based on the *original* file -+ location. ++ """A loader for modules that have been packaged as frameworks for ++ compatibility with Apple's iOS App Store policies. + """ -+ def __init__(self, fullname, dylib_file, path): -+ super().__init__(fullname, dylib_file) -+ self.parent_paths = path -+ + def create_module(self, spec): -+ mod = super().create_module(spec) -+ if self.parent_paths: -+ for parent_path in self.parent_paths: -+ if _path_isdir(parent_path): -+ mod.__file__ = _path_join(parent_path, _path_split(self.path)[-1]) -+ continue -+ return mod ++ # If the ModuleSpec has been created by the FileFinder, it will have ++ # been created with an origin pointing to the .fwork file. We need to ++ # redirect this to the location in the Frameworks folder, using the ++ # content of the .fwork file. ++ if spec.origin.endswith(".fwork"): ++ with _io.FileIO(spec.origin, 'r') as file: ++ framework_binary = file.read().decode().strip() ++ bundle_path = _path_split(sys.executable)[0] ++ spec.origin = _path_join(bundle_path, framework_binary) + ++ # If the loader is created based on the spec for a loaded module, the ++ # path will be pointing at the Framework location. If this occurs, ++ # get the original .fwork location to use as the module's __file__. ++ if self.path.endswith(".fwork"): ++ path = self.path ++ else: ++ with _io.FileIO(self.path + ".origin", 'r') as file: ++ origin = file.read().decode().strip() ++ bundle_path = _path_split(sys.executable)[0] ++ path = _path_join(bundle_path, origin) + -+class AppleFrameworkFinder: -+ """A finder for modules that have been packaged as Apple Frameworks -+ for compatibility with Apple's App Store policies. -+ -+ See AppleFrameworkLoader for details. -+ """ -+ def __init__(self, path): -+ self.frameworks_path = path ++ module = _bootstrap._call_with_frames_removed(_imp.create_dynamic, spec) + -+ def find_spec(self, fullname, path, target=None): -+ name = fullname.split(".")[-1] ++ _bootstrap._verbose_message( ++ "Apple framework extension module {!r} loaded from {!r} (path {!r})", ++ spec.name, ++ spec.origin, ++ path, ++ ) + -+ for extension in EXTENSION_SUFFIXES: -+ dylib_file = _path_join(self.frameworks_path, f"{fullname}.framework", f"{name}{extension}") -+ _bootstrap._verbose_message('Looking for Apple Framework dylib {}', dylib_file) -+ if _path_isfile(dylib_file): -+ loader = AppleFrameworkLoader(fullname, dylib_file, path) -+ return _bootstrap.spec_from_loader(fullname, loader) ++ # Ensure that the __file__ points at the .fwork location ++ module.__file__ = path + -+ return None ++ return module + # Import setup ############################################################### - + def _fix_up_module(ns, name, pathname, cpathname=None): -@@ -1752,3 +1805,7 @@ - supported_loaders = _get_supported_file_loaders() - sys.path_hooks.extend([FileFinder.path_hook(*supported_loaders)]) - sys.meta_path.append(PathFinder) +@@ -1738,10 +1778,17 @@ + + Each item is a tuple (loader, suffixes). + """ +- extensions = ExtensionFileLoader, _imp.extension_suffixes() + if sys.platform in {"ios", "tvos", "watchos"}: -+ frameworks_folder = _path_join(_path_split(sys.executable)[0], "Frameworks") -+ _bootstrap._verbose_message('Adding Apple Framework dylib finder at {}', frameworks_folder) -+ sys.meta_path.append(AppleFrameworkFinder(frameworks_folder)) ++ extension_loaders = [(AppleFrameworkLoader, [ ++ suffix.replace(".so", ".fwork") ++ for suffix in _imp.extension_suffixes() ++ ])] ++ else: ++ extension_loaders = [] ++ extension_loaders.append((ExtensionFileLoader, _imp.extension_suffixes())) + source = SourceFileLoader, SOURCE_SUFFIXES + bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES +- return [extensions, source, bytecode] ++ return extension_loaders + [source, bytecode] + + + def _set_bootstrap_module(_bootstrap_module): +diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py +index 3fa151f390b..47a6a5ed5a6 100644 +--- a/Lib/importlib/abc.py ++++ b/Lib/importlib/abc.py +@@ -261,7 +261,11 @@ + else: + return self.source_to_code(source, path) + +-_register(ExecutionLoader, machinery.ExtensionFileLoader) ++_register( ++ ExecutionLoader, ++ machinery.ExtensionFileLoader, ++ machinery.AppleFrameworkLoader, ++) + + + class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader): +diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py +index d9a19a13f7b..fbd30b159fb 100644 +--- a/Lib/importlib/machinery.py ++++ b/Lib/importlib/machinery.py +@@ -12,6 +12,7 @@ + from ._bootstrap_external import SourceFileLoader + from ._bootstrap_external import SourcelessFileLoader + from ._bootstrap_external import ExtensionFileLoader ++from ._bootstrap_external import AppleFrameworkLoader + from ._bootstrap_external import NamespaceLoader + + +diff --git a/Lib/inspect.py b/Lib/inspect.py +index 09e6a23652f..0dbe90f470a 100644 +--- a/Lib/inspect.py ++++ b/Lib/inspect.py +@@ -972,6 +972,7 @@ + return object + if hasattr(object, '__module__'): + return sys.modules.get(object.__module__) ++ + # Try the filename to modulename cache + if _filename is not None and _filename in modulesbyfile: + return sys.modules.get(modulesbyfile[_filename]) +@@ -1065,7 +1066,7 @@ + # Allow filenames in form of "" to pass through. + # `doctest` monkeypatches `linecache` module to enable + # inspection, so let `linecache.getlines` to be called. +- if not (file.startswith('<') and file.endswith('>')): ++ if (not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'): + raise OSError('source code not available') + + module = getmodule(object, file) +diff --git a/Lib/lib2to3/tests/test_parser.py b/Lib/lib2to3/tests/test_parser.py +index 8e7773bcae1..7df3e354f6d 100644 +--- a/Lib/lib2to3/tests/test_parser.py ++++ b/Lib/lib2to3/tests/test_parser.py +@@ -62,9 +62,7 @@ + shutil.rmtree(tmpdir) + + @unittest.skipIf(sys.executable is None, 'sys.executable required') +- @unittest.skipIf( +- sys.platform in {'emscripten', 'wasi'}, 'requires working subprocess' +- ) ++ @test.support.requires_subprocess() + def test_load_grammar_from_subprocess(self): + tmpdir = tempfile.mkdtemp() + tmpsubdir = os.path.join(tmpdir, 'subdir') +diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py +index a0a020f9eeb..ac478ee7f51 100644 +--- a/Lib/modulefinder.py ++++ b/Lib/modulefinder.py +@@ -72,7 +72,12 @@ + if isinstance(spec.loader, importlib.machinery.SourceFileLoader): + kind = _PY_SOURCE + +- elif isinstance(spec.loader, importlib.machinery.ExtensionFileLoader): ++ elif isinstance( ++ spec.loader, ( ++ importlib.machinery.ExtensionFileLoader, ++ importlib.machinery.AppleFrameworkLoader, ++ ) ++ ): + kind = _C_EXTENSION + + elif isinstance(spec.loader, importlib.machinery.SourcelessFileLoader): diff --git a/Lib/platform.py b/Lib/platform.py -index 9b9d88bf58..764fd6d186 100755 +index 9b9d88bf584..1bb5fcb96d6 100755 --- a/Lib/platform.py +++ b/Lib/platform.py -@@ -452,6 +452,26 @@ +@@ -452,6 +452,78 @@ # If that also doesn't work return the default values return release, versioninfo, machine - -+def iOS_ver(): -+ """Get iOS/tvOS version information, and return it as a -+ tuple (system, release, model). All tuple entries are strings. + ++ ++# 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) ++ ++ ++# A namedtuple for tvOS version information. ++TVOSVersionInfo = collections.namedtuple( ++ "TVOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) ++ ++ ++def tvos_ver(system="", release="", model="", is_simulator=False): ++ """Get tvOS 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. + """ -+ import _ios_support -+ return _ios_support.get_platform_ios() ++ if sys.platform == "tvos": ++ # TODO: Can the iOS implementation be used here? ++ import _ios_support ++ result = _ios_support.get_platform_ios() ++ if result is not None: ++ return TVOSVersionInfo(*result) ++ ++ return TVOSVersionInfo(system, release, model, is_simulator) + -+def is_simulator(): -+ """Determine if the current platform is a device simulator. + -+ Only useful when working with iOS, tvOS or watchOS, because -+ Apple provides simulator platforms for those devices. ++# A namedtuple for watchOS version information. ++WatchOSVersionInfo = collections.namedtuple( ++ "WatchOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) ++ + -+ If the platform is actual hardware, returns False. Will also -+ return False for device *emulators*, which are indistinguishable -+ from actual devices because they are reproducing actual device -+ properties. ++def watchos_ver(system="", release="", model="", is_simulator=False): ++ """Get watchOS 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. + """ -+ return getattr(sys.implementation, "_simulator", False) ++ if sys.platform == "watchos": ++ # TODO: Can the iOS implementation be used here? ++ import _ios_support ++ result = _ios_support.get_platform_ios() ++ if result is not None: ++ return WatchOSVersionInfo(*result) ++ ++ return WatchOSVersionInfo(system, release, model, is_simulator) ++ + def _java_getprop(name, default): - + from java.lang import System -@@ -608,7 +628,7 @@ +@@ -567,7 +639,7 @@ + if cleaned == platform: + break + platform = cleaned +- while platform[-1] == '-': ++ while platform and platform[-1] == '-': + platform = platform[:-1] + + return platform +@@ -608,7 +680,7 @@ default in case the command should fail. - + """ - if sys.platform in ('dos', 'win32', 'win16'): -+ if sys.platform in ('dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'): ++ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}: # XXX Others too ? return default - -@@ -750,6 +770,24 @@ + +@@ -750,6 +822,25 @@ csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' - -+ # On iOS, tvOS and watchOS, os.uname returns the architecture -+ # as uname.machine. On device it doesn't; but there's only -+ # on CPU architecture on device + ++ # On the iOS/tvOS/watchOS 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 devices, so we know the right ++ # answer. + def get_ios(): -+ if getattr(sys.implementation, "_simulator", False): ++ if sys.implementation._multiarch.endswith("simulator"): + return os.uname().machine + return 'arm64' + + def get_tvos(): -+ if getattr(sys.implementation, "_simulator", False): ++ if sys.implementation._multiarch.endswith("simulator"): + return os.uname().machine + return 'arm64' + + def get_watchos(): -+ if getattr(sys.implementation, "_simulator", False): ++ if sys.implementation._multiarch.endswith("simulator"): + return os.uname().machine + return 'arm64_32' + def from_subprocess(): """ Fall back to `uname -p` -@@ -904,6 +942,15 @@ +@@ -904,6 +995,14 @@ system = 'Windows' release = 'Vista' - + + # Normalize responses on Apple mobile platforms -+ if sys.platform in ('ios', 'tvos'): -+ system, release, model = iOS_ver() -+ -+ # On iOS/tvOS simulators, os.uname() reports the machine as something -+ # like "arm64" or "x86_64". -+ if getattr(sys.implementation, "_simulator", False): -+ machine = f'{model}Simulator' ++ if sys.platform == 'ios': ++ system, release, _, _ = ios_ver() ++ if sys.platform == 'tvos': ++ system, release, _, _ = tvos_ver() ++ if sys.platform == 'watchos': ++ system, release, _, _ = watchos_ver() + vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' _uname_cache = uname_result(*map(_unknown_as_blank, vals)) -@@ -1216,11 +1263,13 @@ +@@ -1216,11 +1315,18 @@ 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() ++ # macOS and iOS both report as a "Darwin" kernel ++ if sys.platform == "ios": ++ system, release, _, _ = ios_ver() ++ elif sys.platform == "tvos": ++ system, release, _, _ = tvos_ver() ++ elif sys.platform == "watchos": ++ system, release, _, _ = watchos_ver() + else: + macos_release = mac_ver()[0] + if macos_release: + system = 'macOS' + release = macos_release - + if system == 'Windows': # MS platforms diff --git a/Lib/site.py b/Lib/site.py -index 69670d9d7f..8287267e64 100644 +index 2904e44cffd..371a89ff717 100644 --- a/Lib/site.py +++ b/Lib/site.py -@@ -294,6 +294,9 @@ - - if sys.platform == 'darwin' and sys._framework: - return f'{userbase}/lib/python/site-packages' -+ elif sys.platform in ('ios', 'tvos', 'watchos'): -+ from sysconfig import get_path -+ return get_path('purelib', sys.platform) - - return f'{userbase}/lib/python{version[0]}.{version[1]}/site-packages' - +@@ -276,8 +276,8 @@ + 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): diff --git a/Lib/subprocess.py b/Lib/subprocess.py -index 6df5dd551e..597da09643 100644 +index 1d17ae3608a..34dfa0019a5 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -74,8 +74,8 @@ else: _mswindows = True - + -# wasm32-emscripten and wasm32-wasi do not support processes -_can_fork_exec = sys.platform not in {"emscripten", "wasi"} -+# some platforms do not support processes ++# some platforms do not support subprocesses +_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"} - + if _mswindows: import _winapi @@ -103,18 +103,22 @@ @@ -351,8 +1946,8 @@ index 6df5dd551e..597da09643 100644 - _WNOHANG = os.WNOHANG + class _del_safe: + waitpid = os.waitpid -+ waitstatus_to_exitcode = os.waitstatus_to_exitcode -+ WIFSTOPPED = os.WIFSTOPPED ++ waitstatus_to_exitcode = os.waitstatus_to_exitcode ++ WIFSTOPPED = os.WIFSTOPPED + WSTOPSIG = os.WSTOPSIG + WNOHANG = os.WNOHANG + ECHILD = errno.ECHILD @@ -373,11 +1968,11 @@ index 6df5dd551e..597da09643 100644 + import select import selectors - -@@ -1951,20 +1955,16 @@ + +@@ -1958,20 +1962,16 @@ raise child_exception_type(err_msg) - - + + - def _handle_exitstatus(self, sts, - _waitstatus_to_exitcode=_waitstatus_to_exitcode, - _WIFSTOPPED=_WIFSTOPPED, @@ -393,14 +1988,14 @@ index 6df5dd551e..597da09643 100644 else: - self.returncode = _waitstatus_to_exitcode(sts) + self.returncode = _del_safe.waitstatus_to_exitcode(sts) - + - def _internal_poll(self, _deadstate=None, _waitpid=_waitpid, - _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD): + def _internal_poll(self, _deadstate=None, _del_safe=_del_safe): """Check if child process has terminated. Returns returncode attribute. - -@@ -1980,13 +1980,13 @@ + +@@ -1987,13 +1987,13 @@ try: if self.returncode is not None: return self.returncode # Another thread waited. @@ -417,56 +2012,37 @@ index 6df5dd551e..597da09643 100644 # waiting for child processes has otherwise been # disabled for our process. This child is dead, we diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py -index ebe3711827..5f2456b5db 100644 +index ebe37118274..edf12d57068 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py -@@ -95,6 +95,33 @@ +@@ -20,6 +20,7 @@ + + # Keys for get_config_var() that are never converted to Python integers. + _ALWAYS_STR = { ++ 'IPHONEOS_DEPLOYMENT_TARGET', + 'MACOSX_DEPLOYMENT_TARGET', + } + +@@ -56,6 +57,7 @@ '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', -+ 'scripts': '{installed_base}/bin', -+ 'data': '{installed_base}/Resources', -+ }, -+ 'tvos': { -+ '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', -+ 'scripts': '{installed_base}/bin', -+ 'data': '{installed_base}/Resources', -+ }, -+ 'watchos': { -+ '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', -+ 'scripts': '{installed_base}/bin', -+ 'data': '{installed_base}/Resources', -+ }, - } - - # For the OS-native venv scheme, we essentially provide an alias: -@@ -283,12 +310,19 @@ - 'home': 'posix_home', - 'user': 'nt_user', - } -+ if sys.platform in ('ios', 'tvos', 'watchos'): -+ return { -+ 'prefix': sys.platform, -+ 'home': sys.platform, -+ 'user': sys.platform, -+ } - if sys.platform == 'darwin' and sys._framework: - return { - 'prefix': 'posix_prefix', ++ + # 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). +@@ -111,8 +113,8 @@ + 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): +@@ -289,6 +291,7 @@ 'home': 'posix_home', 'user': 'osx_framework_user', } @@ -474,7 +2050,7 @@ index ebe3711827..5f2456b5db 100644 return { 'prefix': 'posix_prefix', 'home': 'posix_home', -@@ -788,10 +822,16 @@ +@@ -788,10 +791,23 @@ if m: release = m.group() elif osname[:6] == "darwin": @@ -482,9 +2058,16 @@ index ebe3711827..5f2456b5db 100644 - 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() ++ if sys.platform == "ios": ++ release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "tvos": ++ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "9.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "watchos": ++ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "4.0") + osname = sys.platform + machine = sys.implementation._multiarch + else: @@ -492,209 +2075,482 @@ index ebe3711827..5f2456b5db 100644 + osname, release, machine = _osx_support.get_platform_osx( + get_config_vars(), + osname, release, machine) - + return f"{osname}-{release}-{machine}" - + +diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py +index 74ebb5e5b8a..d3e5fddf69b 100644 +--- a/Lib/test/pythoninfo.py ++++ b/Lib/test/pythoninfo.py +@@ -287,6 +287,7 @@ + "HOMEDRIVE", + "HOMEPATH", + "IDLESTARTUP", ++ "IPHONEOS_DEPLOYMENT_TARGET", + "LANG", + "LDFLAGS", + "LDSHARED", diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 2e6518cf24..cc08015a73 100644 +index 059542c2433..532dff848a4 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py -@@ -48,7 +48,7 @@ - "check__all__", "skip_if_buggy_ucrt_strfptime", +@@ -45,7 +45,7 @@ "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", # sys -- "is_jython", "is_android", "is_emscripten", "is_wasi", -+ "is_jython", "is_android", "is_emscripten", "is_wasi", "is_apple_mobile", - "check_impl_detail", "unix_shell", "setswitchinterval", + "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", +- "check_impl_detail", "unix_shell", "setswitchinterval", ++ "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval", # network "open_urlresource", -@@ -511,7 +511,7 @@ - + # processes +@@ -517,7 +517,7 @@ + is_android = hasattr(sys, 'getandroidapilevel') - + -if sys.platform not in ('win32', 'vxworks'): -+if sys.platform not in ('win32', 'vxworks', 'ios', 'tvos', 'watchos'): ++if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None -@@ -521,12 +521,25 @@ +@@ -527,19 +527,35 @@ is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" - + -has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi +# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not +# have subprocess or fork support. -+is_apple_mobile = sys.platform in ('ios', 'tvos', 'watchos') ++is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} ++is_apple = is_apple_mobile or sys.platform == "darwin" + -+has_fork_support = ( -+ hasattr(os, "fork") -+ and not is_emscripten -+ and not is_wasi -+ and not is_apple_mobile ++has_fork_support = hasattr(os, "fork") and not ( ++ is_emscripten ++ or is_wasi ++ or is_apple_mobile +) - + def requires_fork(): return unittest.skipUnless(has_fork_support, "requires working os.fork()") - + -has_subprocess_support = not is_emscripten and not is_wasi -+has_subprocess_support = ( -+ not is_emscripten -+ and not is_wasi -+ and not is_apple_mobile ++has_subprocess_support = not ( ++ is_emscripten ++ or is_wasi ++ or is_apple_mobile +) - + def requires_subprocess(): """Used for subprocess, os.spawn calls, fd inheritance""" + return unittest.skipUnless(has_subprocess_support, "requires subprocess support") + + # Emscripten's socket emulation and WASI sockets have limitations. +-has_socket_support = not is_emscripten and not is_wasi ++has_socket_support = not ( ++ is_emscripten ++ or is_wasi ++) + + def requires_working_socket(*, module=False): + """Skip tests or modules that require working sockets +diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py +index c1b2995ef37..353a3f589ac 100644 +--- a/Lib/test/support/os_helper.py ++++ b/Lib/test/support/os_helper.py +@@ -9,6 +9,8 @@ + import unittest + import warnings + ++from test import support ++ + + # Filename used for testing + if os.name == 'java': +@@ -23,8 +25,8 @@ + + # TESTFN_UNICODE is a non-ascii filename + TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f" +-if sys.platform == 'darwin': +- # In Mac OS X's VFS API file names are, by definition, canonically ++if support.is_apple: ++ # On Apple's VFS API file names are, by definition, canonically + # decomposed Unicode, encoded using UTF-8. See QA1173: + # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html + import unicodedata +@@ -49,8 +51,8 @@ + 'encoding (%s). Unicode filename tests may not be effective' + % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) + TESTFN_UNENCODABLE = None +-# macOS and Emscripten deny unencodable filenames (invalid utf-8) +-elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: ++# Apple and Emscripten deny unencodable filenames (invalid utf-8) ++elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}: + try: + # ascii and utf-8 cannot encode the byte 0xff + b'\xff'.decode(sys.getfilesystemencoding()) +@@ -611,7 +613,8 @@ + if hasattr(os, 'sysconf'): + try: + MAXFD = os.sysconf("SC_OPEN_MAX") +- except OSError: ++ except (OSError, ValueError): ++ # gh-118201: ValueError is raised intermittently on iOS + pass + + old_modes = None +diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py +index ec6d1dee4e9..48900f50508 100644 +--- a/Lib/test/support/socket_helper.py ++++ b/Lib/test/support/socket_helper.py +@@ -1,7 +1,9 @@ + import contextlib + import errno ++import os.path + import socket + import subprocess ++import tempfile + import sys + import unittest + +@@ -273,6 +275,16 @@ + socket.setdefaulttimeout(old_timeout) + + ++def create_unix_domain_name(): ++ """ ++ Create a UNIX domain name: socket.bind() argument of a AF_UNIX socket. ++ ++ Return a path relative to the current directory to get a short path ++ (around 27 ASCII characters). ++ """ ++ return tempfile.mktemp(prefix="test_python_", suffix='.sock', ++ dir=os.path.curdir) ++ + # consider that sysctl values should not change while tests are running + _sysctl_cache = {} + diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py -index d7871d3e53..6966ee77af 100644 +index 8e3c68db550..0e4028a31ae 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py -@@ -34,6 +34,7 @@ - from multiprocessing.util import _cleanup_tests as multiprocessing_cleanup_tests - from test.test_asyncio import utils as test_utils - from test import support -+from test.support import is_apple_mobile - from test.support import socket_helper - from test.support import threading_helper - from test.support import ALWAYS_EQ, LARGEST, SMALLEST -@@ -544,6 +545,7 @@ - self._basetest_create_connection(conn_fut) - - @socket_helper.skip_unless_bind_unix_socket -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_unix_connection(self): - # Issue #20682: On Mac OS X Tiger, getsockname() returns a - # zero-length address for UNIX socket. -@@ -636,6 +638,7 @@ - self.assertEqual(cm.exception.reason, 'CERTIFICATE_VERIFY_FAILED') - - @unittest.skipIf(ssl is None, 'No ssl module') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_ssl_connection(self): - with test_utils.run_test_server(use_ssl=True) as httpd: - create_connection = functools.partial( -@@ -647,6 +650,7 @@ - - @socket_helper.skip_unless_bind_unix_socket - @unittest.skipIf(ssl is None, 'No ssl module') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_ssl_unix_connection(self): - # Issue #20682: On Mac OS X Tiger, getsockname() returns a - # zero-length address for UNIX socket. -@@ -905,6 +909,7 @@ - return server, path - - @socket_helper.skip_unless_bind_unix_socket -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_unix_server(self): - proto = MyProto(loop=self.loop) - server, path = self._make_unix_server(lambda: proto) -@@ -933,6 +938,7 @@ - server.close() - - @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_unix_server_path_socket_error(self): - proto = MyProto(loop=self.loop) - sock = socket.socket() -@@ -998,6 +1004,7 @@ - - @socket_helper.skip_unless_bind_unix_socket - @unittest.skipIf(ssl is None, 'No ssl module') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_unix_server_ssl(self): - proto = MyProto(loop=self.loop) - server, path = self._make_ssl_unix_server( -@@ -1028,6 +1035,7 @@ - server.close() - - @unittest.skipIf(ssl is None, 'No ssl module') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_server_ssl_verify_failed(self): - proto = MyProto(loop=self.loop) - server, host, port = self._make_ssl_server( -@@ -1058,6 +1066,7 @@ - - @socket_helper.skip_unless_bind_unix_socket - @unittest.skipIf(ssl is None, 'No ssl module') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_unix_server_ssl_verify_failed(self): - proto = MyProto(loop=self.loop) - server, path = self._make_ssl_unix_server( -@@ -1118,6 +1127,7 @@ - - @socket_helper.skip_unless_bind_unix_socket - @unittest.skipIf(ssl is None, 'No ssl module') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_create_unix_server_ssl_verified(self): - proto = MyProto(loop=self.loop) - server, path = self._make_ssl_unix_server( +@@ -1867,6 +1867,7 @@ + else: + self.assertEqual(-signal.SIGKILL, returncode) + ++ @support.requires_subprocess() + def test_subprocess_exec(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1888,6 +1889,7 @@ + self.check_killed(proto.returncode) + self.assertEqual(b'Python The Winner', proto.data[1]) + ++ @support.requires_subprocess() + def test_subprocess_interactive(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1915,6 +1917,7 @@ + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) + ++ @support.requires_subprocess() + def test_subprocess_shell(self): + connect = self.loop.subprocess_shell( + functools.partial(MySubprocessProtocol, self.loop), +@@ -1931,6 +1934,7 @@ + self.assertEqual(proto.data[2], b'') + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_exitcode(self): + connect = self.loop.subprocess_shell( + functools.partial(MySubprocessProtocol, self.loop), +@@ -1942,6 +1946,7 @@ + self.assertEqual(7, proto.returncode) + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_close_after_finish(self): + connect = self.loop.subprocess_shell( + functools.partial(MySubprocessProtocol, self.loop), +@@ -1956,6 +1961,7 @@ + self.assertEqual(7, proto.returncode) + self.assertIsNone(transp.close()) + ++ @support.requires_subprocess() + def test_subprocess_kill(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1972,6 +1978,7 @@ + self.check_killed(proto.returncode) + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_terminate(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1989,6 +1996,7 @@ + transp.close() + + @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP") ++ @support.requires_subprocess() + def test_subprocess_send_signal(self): + # bpo-31034: Make sure that we get the default signal handler (killing + # the process). The parent process may have decided to ignore SIGHUP, +@@ -2013,6 +2021,7 @@ + finally: + signal.signal(signal.SIGHUP, old_handler) + ++ @support.requires_subprocess() + def test_subprocess_stderr(self): + prog = os.path.join(os.path.dirname(__file__), 'echo2.py') + +@@ -2034,6 +2043,7 @@ + self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2]) + self.assertEqual(0, proto.returncode) + ++ @support.requires_subprocess() + def test_subprocess_stderr_redirect_to_stdout(self): + prog = os.path.join(os.path.dirname(__file__), 'echo2.py') + +@@ -2059,6 +2069,7 @@ + transp.close() + self.assertEqual(0, proto.returncode) + ++ @support.requires_subprocess() + def test_subprocess_close_client_stream(self): + prog = os.path.join(os.path.dirname(__file__), 'echo3.py') + +@@ -2093,6 +2104,7 @@ + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) + ++ @support.requires_subprocess() + def test_subprocess_wait_no_same_group(self): + # start the new process in a new session + connect = self.loop.subprocess_shell( +@@ -2105,6 +2117,7 @@ + self.assertEqual(7, proto.returncode) + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_exec_invalid_args(self): + async def connect(**kwds): + await self.loop.subprocess_exec( +@@ -2118,6 +2131,7 @@ + with self.assertRaises(ValueError): + self.loop.run_until_complete(connect(shell=True)) + ++ @support.requires_subprocess() + def test_subprocess_shell_invalid_args(self): + + async def connect(cmd=None, **kwds): diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py -index 95fc7a159b..837046cd86 100644 +index 59ff89e7734..320affdfdec 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py -@@ -17,6 +17,7 @@ - - import asyncio - from test.test_asyncio import utils as test_utils -+from test.support import is_apple_mobile - - - def tearDownModule(): -@@ -60,6 +61,7 @@ - self._basetest_open_connection(conn_fut) - - @socket_helper.skip_unless_bind_unix_socket -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_open_unix_connection(self): - with test_utils.run_test_unix_server() as httpd: - conn_fut = asyncio.open_unix_connection(httpd.address) -@@ -91,6 +93,7 @@ - - @socket_helper.skip_unless_bind_unix_socket - @unittest.skipIf(ssl is None, 'No ssl module') -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_open_unix_connection_no_loop_ssl(self): - with test_utils.run_test_unix_server(use_ssl=True) as httpd: - conn_fut = asyncio.open_unix_connection( -@@ -119,6 +122,7 @@ - self._basetest_open_connection_error(conn_fut) - - @socket_helper.skip_unless_bind_unix_socket -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_open_unix_connection_error(self): - with test_utils.run_test_unix_server() as httpd: - conn_fut = asyncio.open_unix_connection(httpd.address) -@@ -637,6 +641,7 @@ - self.assertEqual(messages, []) - - @socket_helper.skip_unless_bind_unix_socket -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_start_unix_server(self): - - class MyServer: +@@ -9,7 +9,7 @@ + import threading + import unittest + from unittest import mock +-from test.support import socket_helper ++from test.support import requires_subprocess, socket_helper + try: + import ssl + except ImportError: +@@ -769,6 +769,7 @@ + self.assertEqual(msg2, b"hello world 2!\n") + + @unittest.skipIf(sys.platform == 'win32', "Don't have pipes") ++ @requires_subprocess() + def test_read_all_from_pipe_reader(self): + # See asyncio issue 168. This test is derived from the example + # subprocess_attach_read_pipe.py, but we configure the +diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py +index f0d1d95d2c1..b07afb97adb 100644 +--- a/Lib/test/test_asyncio/test_subprocess.py ++++ b/Lib/test/test_asyncio/test_subprocess.py +@@ -48,6 +48,7 @@ + self._proc.pid = -1 + + ++@support.requires_subprocess() + class SubprocessTransportTests(test_utils.TestCase): + def setUp(self): + super().setUp() +@@ -111,6 +112,7 @@ + transport.close() + + ++@support.requires_subprocess() + class SubprocessMixin: + + def test_stdin_stdout(self): diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py -index 01c1214c7f..e861c1d1e0 100644 +index 01c1214c7f7..7e16e2a369d 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py -@@ -13,6 +13,7 @@ +@@ -315,11 +315,15 @@ + self.loop.run_until_complete(coro) + + def test_create_unix_server_existing_path_nonsock(self): +- with tempfile.NamedTemporaryFile() as file: +- coro = self.loop.create_unix_server(lambda: None, file.name) +- with self.assertRaisesRegex(OSError, +- 'Address.*is already in use'): +- self.loop.run_until_complete(coro) ++ path = test_utils.gen_unix_socket_path() ++ self.addCleanup(os_helper.unlink, path) ++ # create the file ++ open(path, "wb").close() ++ ++ coro = self.loop.create_unix_server(lambda: None, path) ++ with self.assertRaisesRegex(OSError, ++ 'Address.*is already in use'): ++ self.loop.run_until_complete(coro) + + def test_create_unix_server_ssl_bool(self): + coro = self.loop.create_unix_server(lambda: None, path='spam', +diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py +index 045e385511b..c39fb20ed7c 100644 +--- a/Lib/test/test_asyncio/utils.py ++++ b/Lib/test/test_asyncio/utils.py +@@ -11,7 +11,6 @@ + import socket + import socketserver + import sys +-import tempfile import threading + import time import unittest - from unittest import mock +@@ -34,7 +33,7 @@ + from asyncio import tasks + from asyncio.log import logger + from test import support +-from test.support import threading_helper ++from test.support import threading_helper, socket_helper + + + # Use the maximum known clock resolution (gh-75191, gh-110088): Windows +@@ -256,8 +255,7 @@ + + + def gen_unix_socket_path(): +- with tempfile.NamedTemporaryFile() as file: +- return file.name ++ return socket_helper.create_unix_domain_name() + + + @contextlib.contextmanager +diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py +index 98ccd3a9304..d7860853eba 100644 +--- a/Lib/test/test_asyncore.py ++++ b/Lib/test/test_asyncore.py +@@ -9,6 +9,7 @@ + import threading + + from test import support +from test.support import is_apple_mobile from test.support import os_helper from test.support import socket_helper - -@@ -276,6 +277,7 @@ - - @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), - 'UNIX Sockets are not supported') -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - class SelectorEventLoopUnixSocketTests(test_utils.TestCase): - + from test.support import threading_helper +@@ -657,6 +658,7 @@ + + @unittest.skipIf(sys.platform.startswith("sunos"), + "OOB support is broken on Solaris") ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case in removed test module") + def test_handle_expt(self): + # Make sure handle_expt is called on OOB data received. + # Note: this might fail on some platforms as OOB data is +diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py +index cf28cf35159..54600eafefc 100644 +--- a/Lib/test/test_capi/test_misc.py ++++ b/Lib/test/test_capi/test_misc.py +@@ -1099,6 +1099,13 @@ + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if support.is_apple_mobile: ++ loader = "AppleFrameworkLoader" ++ else: ++ loader = "ExtensionFileLoader" ++ + script = textwrap.dedent(f""" + import importlib.machinery + import importlib.util +@@ -1106,7 +1113,7 @@ + + fullname = '_test_module_state_shared' + origin = importlib.util.find_spec('_testmultiphase').origin +- loader = importlib.machinery.ExtensionFileLoader(fullname, origin) ++ loader = importlib.machinery.{loader}(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + attr_id = str(id(module.Error)).encode() +@@ -1311,7 +1318,12 @@ def setUp(self): + fullname = '_testmultiphase_meth_state_access' # XXX + origin = importlib.util.find_spec('_testmultiphase').origin +- loader = importlib.machinery.ExtensionFileLoader(fullname, origin) ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if support.is_apple_mobile: ++ loader = importlib.machinery.AppleFrameworkLoader(fullname, origin) ++ else: ++ loader = importlib.machinery.ExtensionFileLoader(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) +diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py +index 7fcd563be27..2945b3f56b5 100644 +--- a/Lib/test/test_cmd_line_script.py ++++ b/Lib/test/test_cmd_line_script.py +@@ -14,8 +14,7 @@ + + import textwrap + from test import support +-from test.support import import_helper +-from test.support import os_helper ++from test.support import import_helper, is_apple, os_helper + from test.support.script_helper import ( + make_pkg, make_script, make_zip_pkg, make_zip_script, + assert_python_ok, assert_python_failure, spawn_python, kill_python) +@@ -555,12 +554,17 @@ + self.assertTrue(text[3].startswith('NameError')) + + def test_non_ascii(self): +- # Mac OS X denies the creation of a file with an invalid UTF-8 name. ++ # Apple platforms deny the creation of a file with an invalid UTF-8 name. + # Windows allows creating a name with an arbitrary bytes name, but + # Python cannot a undecodable bytes argument to a subprocess. +- # WASI does not permit invalid UTF-8 names. +- if (os_helper.TESTFN_UNDECODABLE +- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): ++ # Emscripten/WASI does not permit invalid UTF-8 names. ++ if ( ++ os_helper.TESTFN_UNDECODABLE ++ and sys.platform not in { ++ "win32", "emscripten", "wasi" ++ } ++ and not is_apple ++ ): + name = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + elif os_helper.TESTFN_NONASCII: + name = os_helper.TESTFN_NONASCII +diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py +index 812f989d8f3..dfcf9e16e40 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 @@ + 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): diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py -index 8e98256a62..819c8ef89c 100644 +index 8e98256a62c..7681e829d8b 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -6,7 +6,7 @@ @@ -702,63 +2558,459 @@ index 8e98256a62..819c8ef89c 100644 import unittest from multiprocessing import Process -from test.support import verbose, cpython_only -+from test.support import verbose, cpython_only, is_apple_mobile ++from test.support import cpython_only, is_apple, requires_subprocess, verbose from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink - + @@ -25,7 +25,7 @@ start_len = "qq" - + if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd')) - or sys.platform == 'darwin'): -+ or sys.platform == 'darwin' or is_apple_mobile): ++ or is_apple): if struct.calcsize('l') == 8: off_t = 'l' pid_t = 'i' +@@ -156,6 +156,7 @@ + self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH) + + @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") ++ @requires_subprocess() + def test_lockf_exclusive(self): + self.f = open(TESTFN, 'wb+') + cmd = fcntl.LOCK_EX | fcntl.LOCK_NB +@@ -167,6 +168,7 @@ + self.assertEqual(p.exitcode, 0) + + @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") ++ @requires_subprocess() + def test_lockf_share(self): + self.f = open(TESTFN, 'wb+') + cmd = fcntl.LOCK_SH | fcntl.LOCK_NB +diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py +index d813ecdcd6f..38e405933b2 100644 +--- a/Lib/test/test_ftplib.py ++++ b/Lib/test/test_ftplib.py +@@ -18,6 +18,7 @@ + + from unittest import TestCase, skipUnless + from test import support ++from test.support import requires_subprocess + from test.support import threading_helper + from test.support import socket_helper + from test.support import warnings_helper +@@ -902,6 +903,7 @@ + + + @skipUnless(ssl, "SSL not available") ++@requires_subprocess() + class TestTLS_FTPClassMixin(TestFTPClass): + """Repeat TestFTPClass tests starting the TLS layer for both control + and data connections first. +@@ -918,6 +920,7 @@ + + + @skipUnless(ssl, "SSL not available") ++@requires_subprocess() + class TestTLS_FTPClass(TestCase): + """Specific TLS_FTP class tests.""" + +diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py +index 01578539eb3..90669f5ff45 100644 +--- a/Lib/test/test_gc.py ++++ b/Lib/test/test_gc.py +@@ -1188,6 +1188,7 @@ + self.assertEqual(len(gc.garbage), 0) + + ++ @requires_subprocess() + @unittest.skipIf(BUILD_WITH_NDEBUG, + 'built with -NDEBUG') + def test_refcount_errors(self): +diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py +index 4f311c2d498..b77cd4c67d6 100644 +--- a/Lib/test/test_genericpath.py ++++ b/Lib/test/test_genericpath.py +@@ -7,9 +7,9 @@ + import sys + import unittest + import warnings +-from test.support import is_emscripten +-from test.support import os_helper +-from test.support import warnings_helper ++from test.support import ( ++ is_apple, is_emscripten, os_helper, warnings_helper ++) + from test.support.script_helper import assert_python_ok + from test.support.os_helper import FakePath + +@@ -483,12 +483,16 @@ + self.assertIsInstance(abspath(path), str) + + def test_nonascii_abspath(self): +- if (os_helper.TESTFN_UNDECODABLE +- # macOS and Emscripten deny the creation of a directory with an +- # invalid UTF-8 name. Windows allows creating a directory with an +- # arbitrary bytes name, but fails to enter this directory +- # (when the bytes name is used). +- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): ++ if ( ++ os_helper.TESTFN_UNDECODABLE ++ # Apple platforms and Emscripten/WASI deny the creation of a ++ # directory with an invalid UTF-8 name. Windows allows creating a ++ # directory with an arbitrary bytes name, but fails to enter this ++ # directory (when the bytes name is used). ++ and sys.platform not in { ++ "win32", "emscripten", "wasi" ++ } and not is_apple ++ ): + name = os_helper.TESTFN_UNDECODABLE + elif os_helper.TESTFN_NONASCII: + name = os_helper.TESTFN_NONASCII diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py -index cfd8a101dc..c350e87eec 100644 +index cfd8a101dcc..b8ac2544be5 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py -@@ -30,6 +30,7 @@ - +@@ -30,8 +30,9 @@ + import unittest from test import support -+from test.support import is_apple_mobile - from test.support import os_helper - from test.support import threading_helper - -@@ -422,7 +423,7 @@ +-from test.support import os_helper +-from test.support import threading_helper ++from test.support import ( ++ is_apple, os_helper, requires_subprocess, threading_helper ++) + + support.requires_working_socket(module=True) + +@@ -410,8 +411,8 @@ + reader.close() + return body + +- @unittest.skipIf(sys.platform == 'darwin', +- 'undecodable name cannot always be decoded on macOS') ++ @unittest.skipIf(is_apple, ++ 'undecodable name cannot always be decoded on Apple platforms') + @unittest.skipIf(sys.platform == 'win32', + 'undecodable name cannot be decoded on win32') + @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, +@@ -422,11 +423,11 @@ with open(os.path.join(self.tempdir, filename), 'wb') as f: f.write(os_helper.TESTFN_UNDECODABLE) response = self.request(self.base_url + '/') - if sys.platform == 'darwin': -+ if sys.platform == 'darwin' or is_apple_mobile: - # On Mac OS the HFS+ filesystem replaces bytes that aren't valid - # UTF-8 into a percent-encoded value. +- # On Mac OS the HFS+ filesystem replaces bytes that aren't valid +- # UTF-8 into a percent-encoded value. ++ if is_apple: ++ # On Apple platforms the HFS+ filesystem replaces bytes that ++ # aren't valid UTF-8 into a percent-encoded value. for name in os.listdir(self.tempdir): +- if name != 'test': # Ignore a filename created in setUp(). ++ if name != 'test': # Ignore a filename created in setUp(). + filename = name + break + body = self.check_status_and_reason(response, HTTPStatus.OK) +@@ -697,6 +698,7 @@ + + @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, + "This test can't be run reliably as root (issue #13308).") ++@requires_subprocess() + class CGIHTTPServerTestCase(BaseTestCase): + class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler): + pass +diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py +index aa67cc3514e..7c684c5799f 100644 +--- a/Lib/test/test_imp.py ++++ b/Lib/test/test_imp.py +@@ -10,6 +10,7 @@ + from test.support import os_helper + from test.support import script_helper + from test.support import warnings_helper ++from test.support import is_apple_mobile + from test.support import is_wasi + import unittest + import warnings +@@ -234,6 +235,7 @@ + self.assertIsNot(orig_getenv, new_os.getenv) + + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_issue15828_load_extensions(self): + # Issue 15828 picked up that the adapter between the old imp API + # and importlib couldn't handle C extensions +@@ -246,6 +248,7 @@ + self.assertEqual(mod.__name__, example) + + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_issue16421_multiple_modules_in_one_dll(self): + # Issue 16421: loading several modules from the same compiled file fails + m = '_testimportmultiple' +@@ -273,6 +276,7 @@ + self.assertEqual(name, err.exception.name) + + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_load_module_extension_file_is_None(self): + # When loading an extension module and the file is None, open one + # on the behalf of imp.load_dynamic(). +@@ -286,6 +290,7 @@ + imp.load_module(name, None, *found[1:]) + + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_issue24748_load_module_skips_sys_modules_check(self): + name = 'test.imp_dummy' + try: +diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py +index f991f323573..b80a29f697c 100644 +--- a/Lib/test/test_import/__init__.py ++++ b/Lib/test/test_import/__init__.py +@@ -20,7 +20,7 @@ + + from test.support import os_helper + from test.support import ( +- STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only, is_emscripten, ++ STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only, is_apple_mobile, is_emscripten, + is_wasi) + from test.support.import_helper import ( + forget, make_legacy_pyc, unlink, unload, ready_to_import, +@@ -86,7 +86,7 @@ + self.assertEqual(cm.exception.path, _testcapi.__file__) + self.assertRegex( + str(cm.exception), +- r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|pyd)\)" ++ r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|fwork|pyd)\)" + ) + else: + self.assertEqual( +diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py +index 3de120958fd..cdc8884d668 100644 +--- a/Lib/test/test_importlib/extension/test_finder.py ++++ b/Lib/test/test_importlib/extension/test_finder.py +@@ -1,3 +1,4 @@ ++from test.support import is_apple_mobile + from test.test_importlib import abc, util + + machinery = util.import_importlib('importlib.machinery') +@@ -19,9 +20,27 @@ + ) + + def find_spec(self, fullname): +- importer = self.machinery.FileFinder(util.EXTENSIONS.path, +- (self.machinery.ExtensionFileLoader, +- self.machinery.EXTENSION_SUFFIXES)) ++ if is_apple_mobile: ++ # Apple mobile platforms require a specialist loader that uses ++ # .fwork files as placeholders for the true `.so` files. ++ loaders = [ ++ ( ++ self.machinery.AppleFrameworkLoader, ++ [ ++ ext.replace(".so", ".fwork") ++ for ext in self.machinery.EXTENSION_SUFFIXES ++ ] ++ ) ++ ] ++ else: ++ loaders = [ ++ ( ++ self.machinery.ExtensionFileLoader, ++ self.machinery.EXTENSION_SUFFIXES ++ ) ++ ] ++ ++ importer = self.machinery.FileFinder(util.EXTENSIONS.path, *loaders) + + return importer.find_spec(fullname) + +diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py +index 2519149c071..828b6f0dfd3 100644 +--- a/Lib/test/test_importlib/extension/test_loader.py ++++ b/Lib/test/test_importlib/extension/test_loader.py +@@ -1,3 +1,4 @@ ++from test.support import is_apple_mobile + from warnings import catch_warnings + from test.test_importlib import abc, util + +@@ -25,8 +26,15 @@ + raise unittest.SkipTest( + f"{util.EXTENSIONS.name} is a builtin module" + ) +- self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name, +- util.EXTENSIONS.file_path) ++ ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if is_apple_mobile: ++ self.LoaderClass = self.machinery.AppleFrameworkLoader ++ else: ++ self.LoaderClass = self.machinery.ExtensionFileLoader ++ ++ self.loader = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path) + + def load_module(self, fullname): + with warnings.catch_warnings(): +@@ -43,13 +51,11 @@ + self.load_module('XXX') + + def test_equality(self): +- other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name, +- util.EXTENSIONS.file_path) ++ other = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path) + self.assertEqual(self.loader, other) + + def test_inequality(self): +- other = self.machinery.ExtensionFileLoader('_' + util.EXTENSIONS.name, +- util.EXTENSIONS.file_path) ++ other = self.LoaderClass('_' + util.EXTENSIONS.name, util.EXTENSIONS.file_path) + self.assertNotEqual(self.loader, other) + + def test_module(self): +@@ -60,8 +66,7 @@ + ('__package__', '')]: + self.assertEqual(getattr(module, attr), value) + self.assertIn(util.EXTENSIONS.name, sys.modules) +- self.assertIsInstance(module.__loader__, +- self.machinery.ExtensionFileLoader) ++ self.assertIsInstance(module.__loader__, self.LoaderClass) + + # No extension module as __init__ available for testing. + test_package = None +@@ -88,7 +93,7 @@ + self.assertFalse(self.loader.is_package(util.EXTENSIONS.name)) + for suffix in self.machinery.EXTENSION_SUFFIXES: + path = os.path.join('some', 'path', 'pkg', '__init__' + suffix) +- loader = self.machinery.ExtensionFileLoader('pkg', path) ++ loader = self.LoaderClass('pkg', path) + self.assertTrue(loader.is_package('pkg')) + + (Frozen_LoaderTests, +@@ -101,6 +106,14 @@ + def setUp(self): + if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS: + raise unittest.SkipTest("Requires dynamic loading support.") ++ ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if is_apple_mobile: ++ self.LoaderClass = self.machinery.AppleFrameworkLoader ++ else: ++ self.LoaderClass = self.machinery.ExtensionFileLoader ++ + self.name = '_testmultiphase' + if self.name in sys.builtin_module_names: + raise unittest.SkipTest( +@@ -109,8 +122,7 @@ + finder = self.machinery.FileFinder(None) + self.spec = importlib.util.find_spec(self.name) + assert self.spec +- self.loader = self.machinery.ExtensionFileLoader( +- self.name, self.spec.origin) ++ self.loader = self.LoaderClass(self.name, self.spec.origin) + + def load_module(self): + # Load the module from the test extension. +@@ -121,7 +133,7 @@ + def load_module_by_name(self, fullname): + # Load a module from the test extension by name. + origin = self.spec.origin +- loader = self.machinery.ExtensionFileLoader(fullname, origin) ++ loader = self.LoaderClass(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) +@@ -147,8 +159,7 @@ + with self.assertRaises(AttributeError): + module.__path__ + self.assertIs(module, sys.modules[self.name]) +- self.assertIsInstance(module.__loader__, +- self.machinery.ExtensionFileLoader) ++ self.assertIsInstance(module.__loader__, self.LoaderClass) + + def test_functionality(self): + # Test basic functionality of stuff defined in an extension module. +diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py +index 4f1b190f4cc..12eb544912f 100644 +--- a/Lib/test/test_importlib/util.py ++++ b/Lib/test/test_importlib/util.py +@@ -8,6 +8,7 @@ + import os.path + from test import support + from test.support import import_helper ++from test.support import is_apple_mobile + from test.support import os_helper + import unittest + import sys +@@ -43,6 +44,11 @@ + global EXTENSIONS + for path in sys.path: + for ext in machinery.EXTENSION_SUFFIXES: ++ # Apple mobile platforms mechanically load .so files, ++ # but the findable files are labelled .fwork ++ if is_apple_mobile: ++ ext = ext.replace(".so", ".fwork") ++ + filename = EXTENSIONS.name + ext + file_path = os.path.join(path, filename) + if os.path.exists(file_path): +diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py +index 5abb66c7ae4..f69f9bd38b8 100644 +--- a/Lib/test/test_inspect/test_inspect.py ++++ b/Lib/test/test_inspect/test_inspect.py +@@ -25,7 +25,7 @@ + except ImportError: + ThreadPoolExecutor = None + +-from test.support import cpython_only ++from test.support import cpython_only, is_apple_mobile + from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ + from test.support.import_helper import DirsOnSysPath, ready_to_import + from test.support.os_helper import TESTFN +@@ -783,6 +783,7 @@ + @unittest.skipIf(not hasattr(unicodedata, '__file__') or + unicodedata.__file__.endswith('.py'), + "unicodedata is not an external binary module") ++ @unittest.skipIf(is_apple_mobile, "FIXME: Edge case of module loader") + def test_findsource_binary(self): + self.assertRaises(OSError, inspect.getsource, unicodedata) + self.assertRaises(OSError, inspect.findsource, unicodedata) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py -index e54a13c2d8..55dac3c3c6 100644 +index 537c9fa7b98..bc356219f9a 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py -@@ -40,6 +40,7 @@ +@@ -39,11 +39,9 @@ + from test import support from test.support.script_helper import ( assert_python_ok, assert_python_failure, run_python_until_end) - from test.support import import_helper -+from test.support import is_apple_mobile - from test.support import os_helper - from test.support import threading_helper - from test.support import warnings_helper -@@ -609,7 +610,7 @@ - # On Windows and Mac OSX this test consumes large resources; It takes - # a long time to build the >2 GiB file and takes >2 GiB of disk space - # therefore the resource must be enabled to run this test. +-from test.support import import_helper +-from test.support import os_helper +-from test.support import threading_helper +-from test.support import warnings_helper +-from test.support import skip_if_sanitizer ++from test.support import ( ++ import_helper, is_apple, os_helper, skip_if_sanitizer, threading_helper, warnings_helper ++) + from test.support.os_helper import FakePath + + import codecs +@@ -631,10 +629,10 @@ + self.read_ops(f, True) + + def test_large_file_ops(self): +- # On Windows and Mac OSX this test consumes large resources; It takes +- # a long time to build the >2 GiB file and takes >2 GiB of disk space +- # therefore the resource must be enabled to run this test. - if sys.platform[:3] == 'win' or sys.platform == 'darwin': -+ if sys.platform[:3] == 'win' or sys.platform == 'darwin' or is_apple_mobile: ++ # On Windows and Apple platforms this test consumes large resources; It ++ # takes a long time to build the >2 GiB file and takes >2 GiB of disk ++ # space therefore the resource must be enabled to run this test. ++ if sys.platform[:3] == 'win' or is_apple: support.requires( 'largefile', 'test requires %s bytes and a long time to run' % self.LARGE) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py -index 55c5cd5658..87453f29c2 100644 +index fbaafde881c..7117b160949 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -43,6 +43,7 @@ @@ -769,340 +3021,531 @@ index 55c5cd5658..87453f29c2 100644 from test.support import os_helper from test.support import socket_helper from test.support import threading_helper -@@ -1824,9 +1825,20 @@ - # just need a name - file can't be present, or we'll get an - # 'address already in use' error. - os.remove(fn) -+ # Check the size of the socket file name. If it exceeds 108 -+ # characters (UNIX_PATH_MAX), it can't be used as a UNIX socket. -+ # In this case, fall back to a path constructed somewhere that -+ # is known to be short. -+ if len(fn) > 108: -+ fd, fn = tempfile.mkstemp(prefix='test_logging_', suffix='.sock', dir='/tmp') -+ os.close(fd) -+ # just need a name - file can't be present, or we'll get an -+ # 'address already in use' error. -+ os.remove(fn) - return fn - - @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - class UnixSocketHandlerTest(SocketHandlerTest): - - """Test for SocketHandler with unix sockets.""" -@@ -1910,6 +1922,7 @@ - self.assertEqual(self.log_output, "spam\neggs\n") - - @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - class UnixDatagramHandlerTest(DatagramHandlerTest): - - """Test for DatagramHandler using Unix sockets.""" -@@ -2004,6 +2017,7 @@ - self.assertEqual(self.log_output, b'<11>sp\xc3\xa4m\x00') - - @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - class UnixSysLogHandlerTest(SysLogHandlerTest): - - """Test for SysLogHandler with Unix sockets.""" +@@ -2003,6 +2004,7 @@ + self.handled.wait(support.LONG_TIMEOUT) + self.assertEqual(self.log_output, b'<11>h\xc3\xa4m-sp\xc3\xa4m') + ++ @unittest.skipIf(is_apple_mobile, "FIXME: Edge case of logging setup") + def test_udp_reconnection(self): + logger = logging.getLogger("slh") + self.sl_hdlr.close() diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py -index a1a91f661b..92fde54f5a 100644 +index a1a91f661ba..dde44aeffb6 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -1,5 +1,5 @@ from test import support -from test.support import os_helper, requires_debug_ranges -+from test.support import os_helper, requires_debug_ranges, is_apple_mobile ++from test.support import is_apple_mobile, os_helper, requires_debug_ranges from test.support.script_helper import assert_python_ok import array import io -@@ -262,7 +262,10 @@ - elif sys.platform == 'wasi': +@@ -259,7 +259,7 @@ + #if os.name == 'nt' and hasattr(sys, 'gettotalrefcount'): + if os.name == 'nt': + MAX_MARSHAL_STACK_DEPTH = 1000 +- elif sys.platform == 'wasi': ++ elif sys.platform == 'wasi' or is_apple_mobile: MAX_MARSHAL_STACK_DEPTH = 1500 else: -- MAX_MARSHAL_STACK_DEPTH = 2000 -+ if is_apple_mobile: -+ MAX_MARSHAL_STACK_DEPTH = 1500 -+ else: -+ MAX_MARSHAL_STACK_DEPTH = 2000 - for i in range(MAX_MARSHAL_STACK_DEPTH - 2): - last.append([0]) - last = last[-1] + MAX_MARSHAL_STACK_DEPTH = 2000 diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py -index dfcf303942..5aabfac885 100644 +index 1867e8c957f..f75e40940e4 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1,5 +1,5 @@ from test.support import ( - requires, _2G, _4G, gc_collect, cpython_only, is_emscripten -+ requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple_mobile ++ requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple, ) from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink -@@ -245,7 +245,7 @@ - with open(TESTFN, "r+b") as f: - self.assertRaises(ValueError, mmap.mmap, f.fileno(), mapsize, access=4) - -- if os.name == "posix": -+ if os.name == "posix" and not is_apple_mobile: - # Try incompatible flags, prot and access parameters. - with open(TESTFN, "r+b") as f: - self.assertRaises(ValueError, mmap.mmap, f.fileno(), mapsize, -@@ -1007,7 +1007,7 @@ +@@ -1009,7 +1009,7 @@ unlink(TESTFN) - + def _make_test_file(self, num_zeroes, tail): - if sys.platform[:3] == 'win' or sys.platform == 'darwin': -+ if sys.platform[:3] == 'win' or sys.platform == 'darwin' or is_apple_mobile: ++ if sys.platform[:3] == 'win' or is_apple: requires('largefile', 'test requires %s bytes and a long time to run' % str(0x180000000)) f = open(TESTFN, 'w+b') +diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py +index 3dab98d4577..2058c54e156 100644 +--- a/Lib/test/test_os.py ++++ b/Lib/test/test_os.py +@@ -2286,6 +2286,7 @@ + support.is_emscripten or support.is_wasi, + "musl libc issue on Emscripten/WASI, bpo-46390" + ) ++ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS") + def test_fpathconf(self): + self.check(os.pathconf, "PC_NAME_MAX") + self.check(os.fpathconf, "PC_NAME_MAX") +@@ -3750,6 +3751,7 @@ + self.assertGreaterEqual(size.columns, 0) + self.assertGreaterEqual(size.lines, 0) + ++ @support.requires_subprocess() + def test_stty_match(self): + """Check if stty returns the same results + diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py -index c9f27575b5..40a66e8613 100644 +index 4569e30ce86..fceeb4cbfa3 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py -@@ -8,7 +8,7 @@ - from unittest import mock - +@@ -10,6 +10,14 @@ from test import support --from test.support import os_helper -+from test.support import os_helper, is_apple_mobile - + 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 -@@ -323,7 +323,7 @@ - def test_mac_ver(self): - res = platform.mac_ver() - -- if platform.uname().system == 'Darwin': -+ if platform.uname().system == 'Darwin' and not is_apple_mobile: - # We are on a macOS system, check that the right version - # information is returned - output = subprocess.check_output(['sw_vers'], text=True) -@@ -355,6 +355,9 @@ - else: - self.assertEqual(res[2], 'PowerPC') - -+ @unittest.skipUnless(is_apple_mobile, 'iOS/tvOS/watchOS only test') + VERSION="32 (Thirty Two)" +@@ -229,6 +237,29 @@ + self.assertEqual(res[-1], res.processor) + self.assertEqual(len(res), 6) + ++ if os.name == "posix": ++ uname = os.uname() ++ self.assertEqual(res.node, uname.nodename) ++ self.assertEqual(res.version, uname.version) ++ self.assertEqual(res.machine, uname.machine) ++ ++ if sys.platform == "android": ++ self.assertEqual(res.system, "Android") ++ self.assertEqual(res.release, platform.android_ver().release) ++ elif sys.platform == "ios": ++ # 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) ++ + def test_uname_cast_to_tuple(self): + res = platform.uname() + expected = ( +@@ -400,6 +431,56 @@ + # parent + support.wait_process(pid, exitcode=0) + + def test_ios_ver(self): -+ res = platform.ios_ver() - - @unittest.skipUnless(sys.platform == 'darwin', "OSX only test") - def test_mac_ver_with_fork(self): ++ result = platform.ios_ver() ++ ++ # 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) ++ 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. ++ ++ # System is either iOS or iPadOS, depending on the test device ++ self.assertIn(system, {"iOS", "iPadOS"}) ++ ++ # 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, ++ # 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(result.system, "") ++ self.assertEqual(result.release, "") ++ 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 +@@ -495,7 +576,8 @@ + 'root:xnu-4570.71.2~1/RELEASE_X86_64'), + 'x86_64', 'i386') + arch = ('64bit', '') +- with mock.patch.object(platform, 'uname', return_value=uname), \ ++ with mock.patch.object(sys, "platform", "darwin"), \ ++ mock.patch.object(platform, 'uname', return_value=uname), \ + mock.patch.object(platform, 'architecture', return_value=arch): + for mac_ver, expected_terse, expected in [ + # darwin: mac_ver() returns empty strings diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py -index e643d8e5a4..65e0e2d2c6 100644 +index 221d25a4d62..a4f473b58ec 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py -@@ -2,6 +2,7 @@ - +@@ -1,7 +1,7 @@ + "Test posix functions" + from test import support - from test.support import import_helper -+from test.support import is_apple_mobile +-from test.support import import_helper ++from test.support import is_apple from test.support import os_helper from test.support import warnings_helper from test.support.script_helper import assert_python_ok -@@ -69,12 +70,19 @@ - "getpid", "getpgrp", "getppid", "getuid", "sync", - ] - -+ # getgroups can't be invoked on iOS/tvOS/watchOS. -+ if is_apple_mobile: -+ NO_ARG_FUNCTIONS.append("getgroups") -+ - for name in NO_ARG_FUNCTIONS: - posix_func = getattr(posix, name, None) - if posix_func is not None: - with self.subTest(name): -- posix_func() -- self.assertRaises(TypeError, posix_func, 1) -+ try: -+ posix_func() -+ self.assertRaises(TypeError, posix_func, 1) -+ except Exception as e: -+ self.fail('Problem invoking %s: %s' % (name, e)) - - @unittest.skipUnless(hasattr(posix, 'getresuid'), - 'test needs posix.getresuid()') -@@ -776,9 +784,10 @@ +@@ -561,6 +561,7 @@ + + @unittest.skipUnless(hasattr(posix, 'confstr'), + 'test needs posix.confstr()') ++ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS") + def test_confstr(self): + self.assertRaises(ValueError, posix.confstr, "CS_garbage") + self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) +@@ -778,9 +779,10 @@ check_stat(uid, gid) self.assertRaises(OSError, chown_func, first_param, 0, -1) check_stat(uid, gid) - if 0 not in os.getgroups(): - self.assertRaises(OSError, chown_func, first_param, -1, 0) - check_stat(uid, gid) -+ if hasattr(os, 'getgroups') and not is_apple_mobile: ++ if hasattr(os, 'getgroups'): + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) # test illegal types for t in str, float: self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) -@@ -1129,7 +1138,7 @@ +@@ -1249,8 +1251,8 @@ + self.assertIsInstance(lo, int) self.assertIsInstance(hi, int) self.assertGreaterEqual(hi, lo) - # OSX evidently just returns 15 without checking the argument. +- # OSX evidently just returns 15 without checking the argument. - if sys.platform != "darwin": -+ if sys.platform != 'darwin' and not is_apple_mobile: ++ # Apple plaforms return 15 without checking the argument. ++ if not is_apple: self.assertRaises(OSError, posix.sched_get_priority_min, -23) self.assertRaises(OSError, posix.sched_get_priority_max, -23) - + +@@ -2036,11 +2038,13 @@ + + + @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") ++@support.requires_subprocess() + class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): + spawn_func = getattr(posix, 'posix_spawn', None) + + + @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") ++@support.requires_subprocess() + class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): + spawn_func = getattr(posix, 'posix_spawnp', None) + +diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py +index 51e3a46d0df..3f2bac0155f 100644 +--- a/Lib/test/test_pty.py ++++ b/Lib/test/test_pty.py +@@ -1,12 +1,17 @@ +-from test.support import verbose, reap_children +-from test.support.os_helper import TESTFN, unlink ++import sys ++import unittest ++from test.support import ( ++ is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose ++) + from test.support.import_helper import import_module ++from test.support.os_helper import TESTFN, unlink + +-# Skip these tests if termios or fcntl are not available ++# Skip these tests if termios is not available + import_module('termios') +-# fcntl is a proxy for not being one of the wasm32 platforms even though we +-# don't use this module... a proper check for what crashes those is needed. +-import_module("fcntl") ++ ++# Skip tests on WASM platforms, plus iOS/tvOS/watchOS ++if is_apple_mobile or is_emscripten or is_wasi: ++ raise unittest.SkipTest(f"pty tests not required on {sys.platform}") + + import errno + import os +@@ -17,7 +22,6 @@ + import signal + import socket + import io # readline +-import unittest + import warnings + + TEST_STRING_1 = b"I wish to buy a fish license.\n" +diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py +index 31757205ca3..6b88b121580 100644 +--- a/Lib/test/test_selectors.py ++++ b/Lib/test/test_selectors.py +@@ -6,8 +6,7 @@ + import socket + import sys + from test import support +-from test.support import os_helper +-from test.support import socket_helper ++from test.support import is_apple, os_helper, socket_helper + from time import sleep + import unittest + import unittest.mock +@@ -520,7 +519,7 @@ + try: + fds = s.select() + except OSError as e: +- if e.errno == errno.EINVAL and sys.platform == 'darwin': ++ if e.errno == errno.EINVAL and is_apple: + # unexplainable errors on macOS don't need to fail the test + self.skipTest("Invalid argument error calling poll()") + raise diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py -index 274a8b7377..06cab9848a 100644 +index 6728d309589..e85ff0fdcfa 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py -@@ -1840,6 +1840,7 @@ +@@ -1906,6 +1906,7 @@ check_chown(dirname, uid, gid) - - -+@unittest.skipIf(support.has_subprocess_support, 'Test requires support for subprocesses.') + + ++@support.requires_subprocess() class TestWhich(BaseTest, unittest.TestCase): - + def setUp(self): -@@ -2706,6 +2707,7 @@ +@@ -2801,6 +2802,7 @@ self.assertGreaterEqual(size.lines, 0) - + @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty") -+ @unittest.skipUnless(support.has_subprocess_support, 'Test requires support for subprocesses.') ++ @support.requires_subprocess() @unittest.skipUnless(hasattr(os, 'get_terminal_size'), 'need os.get_terminal_size()') def test_stty_match(self): +diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py +index 3711e7d1c6c..8003f3a226c 100644 +--- a/Lib/test/test_signal.py ++++ b/Lib/test/test_signal.py +@@ -13,9 +13,10 @@ + import time + import unittest + from test import support +-from test.support import os_helper ++from test.support import ( ++ is_apple, is_apple_mobile, os_helper, threading_helper ++) + from test.support.script_helper import assert_python_ok, spawn_python +-from test.support import threading_helper + try: + import _testcapi + except ImportError: +@@ -832,7 +833,7 @@ + self.assertEqual(self.hndl_called, True) + + # Issue 3864, unknown if this affects earlier versions of freebsd also +- @unittest.skipIf(sys.platform in ('netbsd5',), ++ @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile, + 'itimer not reliable (does not mix well with threading) on some BSDs.') + def test_itimer_virtual(self): + self.itimer = signal.ITIMER_VIRTUAL +@@ -1352,7 +1353,7 @@ + # Python handler + self.assertEqual(len(sigs), N, "Some signals were lost") + +- @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)") ++ @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)") + @unittest.skipUnless(hasattr(signal, "SIGUSR1"), + "test needs SIGUSR1") + @threading_helper.requires_working_threading() diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py -index af0d2a4e6f..3b96cc476f 100644 +index 42adc573ecc..96b51d32c25 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py -@@ -1,5 +1,6 @@ - import unittest - from test import support -+from test.support import is_apple_mobile +@@ -3,6 +3,7 @@ from test.support import os_helper from test.support import socket_helper from test.support import threading_helper -@@ -1157,7 +1158,7 @@ ++from test.support import is_apple + + import errno + import io +@@ -676,7 +677,7 @@ + super().setUp() + + def bindSock(self, sock): +- path = tempfile.mktemp(dir=self.dir_path) ++ path = socket_helper.create_unix_domain_name() + socket_helper.bind_unix_socket(sock, path) + self.addCleanup(os_helper.unlink, path) + +@@ -1153,8 +1154,11 @@ + # Find one service that exists, then check all the related interfaces. # I've ordered this by protocols that have both a tcp and udp # protocol, at least for modern Linuxes. - if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) +- if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) - or sys.platform in ('linux', 'darwin')): -+ or sys.platform in ('linux', 'darwin') or is_apple_mobile): ++ if ( ++ sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) ++ or sys.platform == 'linux' ++ or is_apple ++ ): # avoid the 'echo' service on this platform, as there is an # assumption breaking non-standard port/protocol entry services = ('daytime', 'qotd', 'domain') -@@ -3567,7 +3568,7 @@ +@@ -1916,12 +1920,13 @@ + self._test_socket_fileno(s, socket.AF_INET6, socket.SOCK_STREAM) + + if hasattr(socket, "AF_UNIX"): +- tmpdir = tempfile.mkdtemp() +- self.addCleanup(shutil.rmtree, tmpdir) ++ unix_name = socket_helper.create_unix_domain_name() ++ self.addCleanup(os_helper.unlink, unix_name) ++ + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(s.close) + try: +- s.bind(os.path.join(tmpdir, 'socket')) ++ s.bind(unix_name) + except PermissionError: + pass + else: +@@ -3564,7 +3569,7 @@ def _testFDPassCMSG_LEN(self): self.createAndSendFDs(1) - + - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(sys.platform == "darwin" or is_apple_mobile, "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparate(self): -@@ -3578,7 +3579,7 @@ +@@ -3575,7 +3580,7 @@ maxcmsgs=2) - + @testFDPassSeparate.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(sys.platform == "darwin" or is_apple_mobile, "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparate(self): fd0, fd1 = self.newFDs(2) -@@ -3591,7 +3592,7 @@ +@@ -3588,7 +3593,7 @@ array.array("i", [fd1]))]), len(MSG)) - + - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(sys.platform == "darwin" or is_apple_mobile, "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparateMinSpace(self): -@@ -3605,7 +3606,7 @@ +@@ -3602,7 +3607,7 @@ maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC) - + @testFDPassSeparateMinSpace.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(sys.platform == "darwin" or is_apple_mobile, "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparateMinSpace(self): fd0, fd1 = self.newFDs(2) -@@ -3629,7 +3630,7 @@ +@@ -3626,7 +3631,7 @@ nbytes = self.sendmsgToServer([msg]) self.assertEqual(nbytes, len(msg)) - + - @unittest.skipIf(sys.platform == "darwin", "see issue #24725") -+ @unittest.skipIf(sys.platform == "darwin" or is_apple_mobile, "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") def testFDPassEmpty(self): # Try to pass an empty FD array. Can receive either no array # or an empty array. -@@ -4449,28 +4450,33 @@ - pass - - @requireAttrs(socket.socket, "sendmsg") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - @requireAttrs(socket, "AF_UNIX") - class SendmsgUnixStreamTest(SendmsgStreamTests, SendrecvmsgUnixStreamTestBase): - pass - - @requireAttrs(socket.socket, "recvmsg") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - @requireAttrs(socket, "AF_UNIX") - class RecvmsgUnixStreamTest(RecvmsgTests, RecvmsgGenericStreamTests, - SendrecvmsgUnixStreamTestBase): - pass - - @requireAttrs(socket.socket, "recvmsg_into") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - @requireAttrs(socket, "AF_UNIX") - class RecvmsgIntoUnixStreamTest(RecvmsgIntoTests, RecvmsgGenericStreamTests, - SendrecvmsgUnixStreamTestBase): - pass - - @requireAttrs(socket.socket, "sendmsg", "recvmsg") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - @requireAttrs(socket, "AF_UNIX", "SOL_SOCKET", "SCM_RIGHTS") - class RecvmsgSCMRightsStreamTest(SCMRightsTest, SendrecvmsgUnixStreamTestBase): - pass - - @requireAttrs(socket.socket, "sendmsg", "recvmsg_into") -+@unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - @requireAttrs(socket, "AF_UNIX", "SOL_SOCKET", "SCM_RIGHTS") - class RecvmsgIntoSCMRightsStreamTest(RecvmsgIntoMixin, SCMRightsTest, - SendrecvmsgUnixStreamTestBase): diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py -index 2edb1e0c0e..62307168f6 100644 +index 80e1968c0bf..529b6405f28 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py -@@ -8,13 +8,14 @@ - import select - import signal - import socket -+import sys - import tempfile - import threading - import unittest - import socketserver - - import test.support --from test.support import reap_children, verbose -+from test.support import reap_children, verbose, is_apple_mobile - from test.support import os_helper - from test.support import socket_helper +@@ -91,8 +91,7 @@ + else: + # XXX: We need a way to tell AF_UNIX to pick its own name + # like AF_INET provides port==0. +- dir = None +- fn = tempfile.mktemp(prefix='unix_socket.', dir=dir) ++ fn = socket_helper.create_unix_domain_name() + self.test_files.append(fn) + return fn + +diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py +index ff86291bc57..59370264ab3 100644 +--- a/Lib/test/test_sqlite3/test_dbapi.py ++++ b/Lib/test/test_sqlite3/test_dbapi.py +@@ -31,7 +31,7 @@ + + from test.support import ( + SHORT_TIMEOUT, bigmemtest, check_disallow_instantiation, requires_subprocess, +- is_emscripten, is_wasi ++ is_apple, is_emscripten, is_wasi + ) from test.support import threading_helper -@@ -198,12 +199,14 @@ - self.stream_examine) - - @requires_unix_sockets -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_UnixStreamServer(self): - self.run_server(socketserver.UnixStreamServer, - socketserver.StreamRequestHandler, - self.stream_examine) - - @requires_unix_sockets -+ @unittest.skipIf(is_apple_mobile, "%s doesn't fully support UNIX sockets." % sys.platform) - def test_ThreadingUnixStreamServer(self): - self.run_server(socketserver.ThreadingUnixStreamServer, - socketserver.StreamRequestHandler, + from _testcapi import INT_MAX, ULLONG_MAX +@@ -335,7 +335,7 @@ + + # sqlite3_enable_shared_cache() is deprecated on macOS and calling it may raise + # OperationalError on some buildbots. +- @unittest.skipIf(sys.platform == "darwin", "shared cache is deprecated on macOS") ++ @unittest.skipIf(is_apple, "shared cache is deprecated on Apple platforms") + def test_shared_cache_deprecated(self): + for enable in (True, False): + with self.assertWarns(DeprecationWarning) as cm: +@@ -659,7 +659,7 @@ + cx.execute(self._sql) + + @unittest.skipIf(sys.platform == "win32", "skipped on Windows") +- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") ++ @unittest.skipIf(is_apple, "skipped on Apple platforms") + @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") + @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") + def test_open_with_undecodable_path(self): +@@ -705,7 +705,7 @@ + cx.execute(self._sql) + + @unittest.skipIf(sys.platform == "win32", "skipped on Windows") +- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") ++ @unittest.skipIf(is_apple, "skipped on Apple platforms") + @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") + @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") + def test_open_undecodable_uri(self): +diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py +index c77fec3d39d..ca55d429aec 100644 +--- a/Lib/test/test_stat.py ++++ b/Lib/test/test_stat.py +@@ -2,8 +2,7 @@ + import os + import socket + import sys +-from test.support import os_helper +-from test.support import socket_helper ++from test.support import is_apple, os_helper, socket_helper + from test.support.import_helper import import_fresh_module + from test.support.os_helper import TESTFN + diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py -index de2e7305cc..ccf240397d 100644 +index de2e7305ccc..3b247e06442 100644 --- a/Lib/test/test_sundry.py +++ b/Lib/test/test_sundry.py @@ -1,5 +1,6 @@ @@ -1113,614 +3556,703 @@ index de2e7305cc..ccf240397d 100644 from test.support import import_helper from test.support import warnings_helper @@ -20,7 +21,8 @@ - + import distutils.bcppcompiler import distutils.ccompiler - import distutils.cygwinccompiler -+ if sys.platform.startswith('win'): ++ if sys.platform.startswith("win"): + import distutils.cygwinccompiler import distutils.filelist import distutils.text_file import distutils.unixccompiler diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py -index d96371d242..5634962453 100644 +index d3bb0d25cac..f487fb5313b 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py -@@ -336,7 +336,7 @@ - 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 = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv', 'tvos', 'watchos'] - if HAS_USER_BASE: - wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) - self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) -diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py -index a4192acd6d..8c00d34e0b 100644 ---- a/Lib/test/test_threading.py -+++ b/Lib/test/test_threading.py -@@ -3,7 +3,7 @@ - """ - - import test.support --from test.support import threading_helper, requires_subprocess -+from test.support import threading_helper, requires_subprocess, is_apple_mobile - from test.support import verbose, cpython_only, os_helper +@@ -6,7 +6,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.script_helper import assert_python_ok, assert_python_failure -@@ -1184,6 +1184,7 @@ - os.set_blocking(r, False) - return (r, w) - -+ @unittest.skipIf(is_apple_mobile, "%s doesn't have os.pipe" % sys.platform) - def test_threads_join(self): - # Non-daemon threads should be joined at subinterpreter shutdown - # (issue #18808) -@@ -1212,6 +1213,7 @@ - # The thread was joined properly. - self.assertEqual(os.read(r, 1), b"x") - -+ @unittest.skipIf(is_apple_mobile, "%s doesn't have os.pipe" % sys.platform) - def test_threads_join_2(self): - # Same as above, but a delay gets introduced after the thread's - # Python code returned but before the thread state is deleted. + from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink, +@@ -340,6 +344,8 @@ + # 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 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) +@@ -449,6 +455,8 @@ + 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 include config folder at runtime") + def test_srcdir(self): + # See Issues #15322, #15364. + srcdir = sysconfig.get_config_var('srcdir') +@@ -517,6 +525,8 @@ + @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 include config folder at runtime") + def test_get_makefile_filename(self): + makefile = sysconfig.get_makefile_filename() + self.assertTrue(os.path.isfile(makefile), makefile) +diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py +index 47619c8807b..25c16e3a0b7 100644 +--- a/Lib/test/test_unicode_file_functions.py ++++ b/Lib/test/test_unicode_file_functions.py +@@ -5,7 +5,7 @@ + import unittest + import warnings + from unicodedata import normalize +-from test.support import os_helper ++from test.support import is_apple, os_helper + from test import support + + +@@ -23,13 +23,13 @@ + '10_\u1fee\u1ffd', + ] + +-# Mac OS X decomposes Unicode names, using Normal Form D. ++# Apple platforms decompose Unicode names, using Normal Form D. + # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html + # "However, most volume formats do not follow the exact specification for + # these normal forms. For example, HFS Plus uses a variant of Normal Form D + # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through + # U+2FAFF are not decomposed." +-if sys.platform != 'darwin': ++if not is_apple: + filenames.extend([ + # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different + '11_\u0385\u03d3\u03d4', +@@ -119,11 +119,11 @@ + os.stat(name) + self._apply_failure(os.listdir, name, self._listdir_failure) + +- # Skip the test on darwin, because darwin does normalize the filename to ++ # Skip the test on Apple platforms, because they don't normalize the filename to + # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC, + # NFKD in Python is useless, because darwin will normalize it later and so + # open(), os.stat(), etc. don't raise any exception. +- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') ++ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') + @unittest.skipIf( + support.is_emscripten or support.is_wasi, + "test fails on Emscripten/WASI when host platform is macOS." +@@ -142,10 +142,10 @@ + self._apply_failure(os.remove, name) + self._apply_failure(os.listdir, name) + +- # Skip the test on darwin, because darwin uses a normalization different ++ # Skip the test on Apple platforms, because they use a normalization different + # than Python NFD normalization: filenames are different even if we use + # Python NFD normalization. +- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') ++ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') + def test_listdir(self): + sf0 = set(self.files) + with warnings.catch_warnings(): +diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py +index 94cbc8a62f9..06a553f093f 100644 +--- a/Lib/test/test_urllib2.py ++++ b/Lib/test/test_urllib2.py +@@ -1,7 +1,7 @@ + import unittest + from test import support + from test.support import os_helper +-from test.support import socket_helper ++from test.support import requires_subprocess + from test.support import warnings_helper + from test import test_urllib + +@@ -987,6 +987,7 @@ + + file_obj.close() + ++ @requires_subprocess() + def test_http_body_pipe(self): + # A file reading from a pipe. + # A pipe cannot be seek'ed. There is no way to determine the diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py -index eb9227a3b7..19f93263c5 100644 +index a60f4cd3111..1ea960e7e33 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py -@@ -19,7 +19,7 @@ +@@ -19,8 +19,8 @@ import tempfile from test.support import (captured_stdout, captured_stderr, requires_zlib, skip_if_broken_multiprocessing_synchronize, verbose, - requires_subprocess, is_emscripten, is_wasi, -+ requires_subprocess, is_apple_mobile, is_emscripten, is_wasi, - requires_venv_with_pip, TEST_HOME_DIR, +- requires_venv_with_pip, TEST_HOME_DIR, ++ requires_subprocess, is_apple_mobile, is_emscripten, ++ is_wasi, requires_venv_with_pip, TEST_HOME_DIR, requires_resource, copy_python_src_ignore) from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) -@@ -41,6 +41,8 @@ - - if is_emscripten or is_wasi: - raise unittest.SkipTest("venv is not available on Emscripten/WASI.") -+if is_apple_mobile: -+ raise unittest.SkipTest("venv is not available on mobile Apple platforms.") - + import unittest +@@ -39,8 +39,10 @@ + or sys._base_executable != sys.executable, + 'cannot run venv.create from within a venv on this platform') + +-if is_emscripten or is_wasi: +- raise unittest.SkipTest("venv is not available on Emscripten/WASI.") ++# Skip tests on WASM platforms, plus iOS/tvOS/watchOS ++if is_apple_mobile or is_emscripten or is_wasi: ++ raise unittest.SkipTest(f"venv tests not required on {sys.platform}") ++ + @requires_subprocess() def check_output(cmd, encoding=None): -diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py -index c8e0159765..639e8f2c21 100644 ---- a/Lib/test/test_zipfile.py -+++ b/Lib/test/test_zipfile.py -@@ -1320,6 +1320,7 @@ - self.skipTest('requires write access to the installed location') - unlink(filename) - -+ @unittest.skipIf(sys.dont_write_bytecode, "Test requires ability to write bytecode") - def test_write_pyfile(self): - self.requiresWriteAccess(os.path.dirname(__file__)) - with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp: -@@ -1364,6 +1365,7 @@ - self.assertCompiledIn('email/__init__.py', names) - self.assertCompiledIn('email/mime/text.py', names) - -+ @unittest.skipIf(sys.dont_write_bytecode, "Test requires ability to write bytecode") - def test_write_filtered_python_package(self): - import test - packagedir = os.path.dirname(test.__file__) +diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py +index 9d608d63a01..24b1103f500 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 = 'http://www.example.com' + CMD_NAME = 'test' +@@ -24,6 +27,7 @@ + return 0 + + ++@requires_subprocess() + class CommandTestMixin: + + def _test(self, meth, *, args=[URL], kw={}, options, arguments): +@@ -219,6 +223,73 @@ + 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) ++ ++ @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 ++ # 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'http://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): +@@ -302,6 +373,10 @@ + 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: +@@ -313,6 +388,10 @@ + 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 44974d433b..ae4e18802b 100755 +index 5d72524c087..48976e4e11f 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py -@@ -596,6 +596,57 @@ - - # what to do if _tryorder is now empty? - +@@ -534,6 +534,9 @@ + # 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")) +@@ -707,6 +710,70 @@ + rc = osapipe.close() + return not rc + +# +# Platform support for iOS +# -+if sys.platform == 'ios': -+ class MobileSafari(BaseBrowser): ++if sys.platform == "ios": ++ from _ios_support import objc ++ 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): -+ # This code is the equivalent of: -+ # NSURL *nsurl = [NSURL URLWithString:url]; -+ # [[UIApplication sharedApplication] openURL:nsurl]; -+ from ctypes import cdll, c_void_p, c_char_p, c_uint32 -+ from ctypes import util -+ objc = cdll.LoadLibrary(util.find_library(b'objc')) -+ cf = cdll.LoadLibrary(util.find_library(b'CoreFoundation')) -+ 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] -+ cf.CFStringCreateWithCString.restype = c_void_p -+ cf.CFStringCreateWithCString.argtypes = [c_void_p, c_char_p, c_uint32] -+ -+ # Get an NSString describing the URL -+ kCFStringEncodingUTF8 = 0x08000100 -+ url = c_void_p(cf.CFStringCreateWithCString(None, url.encode('utf-8'), kCFStringEncodingUTF8)) -+ autorelease = c_void_p(objc.sel_registerName(b'autorelease')) -+ objc.objc_msgSend.argtypes = [c_void_p, c_void_p] -+ objc.objc_msgSend.restype = c_void_p -+ objc.objc_msgSend(url, autorelease) ++ sys.audit("webbrowser.open", url) ++ # If ctypes isn't available, we can't open a browser ++ if objc is None: ++ return False + -+ # Get an NSURL object representing the URL -+ NSURL = c_void_p(objc.objc_getClass(b'NSURL')) -+ urlWithString_ = c_void_p(objc.sel_registerName(b'URLWithString:')) ++ # 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") ++ # 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] -+ nsurl = c_void_p(objc.objc_msgSend(NSURL, urlWithString_, url)) ++ ns_url = objc.objc_msgSend(NSURL, urlWithString_, url_string) + + # Get the shared UIApplication instance -+ UIApplication = c_void_p(objc.objc_getClass(b'UIApplication')) -+ sharedApplication = c_void_p(objc.sel_registerName(b'sharedApplication')) ++ # 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] -+ objc.objc_msgSend.restype = c_void_p -+ shared_app = c_void_p(objc.objc_msgSend(UIApplication, sharedApplication)) ++ shared_app = objc.objc_msgSend(UIApplication, sharedApplication) + + # Open the URL on the shared application -+ openURL_ = c_void_p(objc.sel_registerName(b'openURL:')) -+ objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_void_p] ++ # 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 ++ ] ++ # Method returns void + objc.objc_msgSend.restype = None -+ objc.objc_msgSend(shared_app, openURL_, nsurl) ++ objc.objc_msgSend(shared_app, openURL_, ns_url, None, None) + + return True + -+ register("mobilesafari", None, MobileSafari(), preferred=True) - - # - # Platform support for Windows + + def main(): + import getopt +--- /dev/null ++++ b/Mac/Resources/app-store-compliance.patch +@@ -0,0 +1 @@ ++# No compliance patching required. diff --git a/Makefile.pre.in b/Makefile.pre.in -index 0d0ac1315b..50dde04558 100644 +index 81d4d50f82f..9e7e0cd0ac0 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in -@@ -344,6 +344,8 @@ +@@ -175,18 +175,29 @@ + EXE= @EXEEXT@ + BUILDEXE= @BUILDEXEEXT@ + ++# Name of the patch file to apply for app store compliance ++APP_STORE_COMPLIANCE_PATCH=@APP_STORE_COMPLIANCE_PATCH@ ++ + # Short name and location for Mac OS X Python framework + UNIVERSALSDK=@UNIVERSALSDK@ + PYTHONFRAMEWORK= @PYTHONFRAMEWORK@ + PYTHONFRAMEWORKDIR= @PYTHONFRAMEWORKDIR@ + PYTHONFRAMEWORKPREFIX= @PYTHONFRAMEWORKPREFIX@ + PYTHONFRAMEWORKINSTALLDIR= @PYTHONFRAMEWORKINSTALLDIR@ +-# Deployment target selected during configure, to be checked ++PYTHONFRAMEWORKINSTALLNAMEPREFIX= @PYTHONFRAMEWORKINSTALLNAMEPREFIX@ ++RESSRCDIR= @RESSRCDIR@ ++# 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. 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 + STRIPFLAG=-s + +@@ -344,6 +355,8 @@ ########################################################################## - + LIBFFI_INCLUDEDIR= @LIBFFI_INCLUDEDIR@ +LIBFFI_LIBDIR= @LIBFFI_LIBDIR@ -+LIBFFI_LIB=@LIBFFI_LIB@ - ++LIBFFI_LIB= @LIBFFI_LIB@ + ########################################################################## # Parser -diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c -index 072519c91a..14424c46d2 100644 ---- a/Modules/_posixsubprocess.c -+++ b/Modules/_posixsubprocess.c -@@ -31,10 +31,20 @@ - - #include "posixmodule.h" - -+#if defined(__APPLE__) -+#include "TargetConditionals.h" -+#endif +@@ -585,7 +598,7 @@ + + # Default target + all: @DEF_MAKE_ALL_RULE@ +-build_all: check-clean-src $(BUILDPYTHON) platform oldsharedmods sharedmods \ ++build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform oldsharedmods sharedmods \ + gdbhooks Programs/_testembed python-config + build_wasm: check-clean-src $(BUILDPYTHON) platform oldsharedmods python-config + +@@ -604,6 +617,16 @@ + exit 1; \ + fi + ++# Check that the app store compliance patch can be applied (if configured). ++# This is checked as a dry-run against the original library sources; ++# the patch will be actually applied during the install phase. ++.PHONY: check-app-store-compliance ++check-app-store-compliance: ++ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \ ++ patch --dry-run --quiet --force --strip 1 --directory "$(abs_srcdir)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)"; \ ++ echo "App store compliance patch can be applied."; \ ++ fi + - #ifdef _Py_MEMORY_SANITIZER - # include - #endif - -+// iOS/tvOS/watchOS *define* a number of POSIX functions, but you can't use them -+// because they aren't conventional multiprocess environments. -+#if TARGET_OS_IPHONE -+# undef HAVE_FORK -+#endif -+ - #if defined(__ANDROID__) && __ANDROID_API__ < 21 && !defined(SYS_getdents64) - # include - # define SYS_getdents64 __NR_getdents64 -@@ -700,11 +710,15 @@ - saved_errno = 0; - for (i = 0; exec_array[i] != NULL; ++i) { - const char *executable = exec_array[i]; -+#if defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) || defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) -+ errno = ENOTSUP; -+#else - if (envp) { - execve(executable, argv, envp); - } else { - execv(executable, argv); - } -+#endif - if (errno != ENOENT && errno != ENOTDIR && saved_errno == 0) { - saved_errno = errno; - } -@@ -770,7 +784,9 @@ - PyObject *preexec_fn, - PyObject *preexec_fn_args_tuple) - { -- -+/* iOS/tvOS/watchOS define the existence of fork, but it cannot be invoked; -+ * so fail fast if any attempt is made to invoke fork_exec */ -+#ifdef HAVE_FORK - pid_t pid; - - #ifdef VFORK_USABLE -@@ -805,7 +821,7 @@ - pid = fork(); - } - } else --#endif -+#endif /* VFORK_USABLE */ - { - pid = fork(); - } -@@ -814,7 +830,6 @@ - // Parent process. - return pid; - } -- - /* Child process. - * See the comment above child_exec() for restrictions imposed on - * the code below. -@@ -838,6 +853,9 @@ - preexec_fn, preexec_fn_args_tuple); - _exit(255); - return 0; /* Dead code to avoid a potential compiler warning. */ -+#else /* HAVE_FORK */ -+ return -1; -+#endif /* HAVE_FORK */ - } - - -@@ -869,7 +887,9 @@ - int saved_errno = 0; - int allow_vfork; - int *c_fds_to_keep = NULL; -- -+/* iOS/tvOS/watchOS define the existence of fork, but it cannot be invoked; -+ * so fail fast if any attempt is made to invoke fork_exec */ -+#ifdef HAVE_FORK - if (!PyArg_ParseTuple( - args, "OOpO!OOiiiiiiiiii" _Py_PARSE_PID "OOOiOp:fork_exec", - &process_args, &executable_list, -@@ -1086,7 +1106,7 @@ - } - old_sigmask = &old_sigs; - } --#endif -+#endif /* VFORK_USABLE */ - - pid = do_fork_exec(exec_array, argv, envp, cwd, - p2cread, p2cwrite, c2pread, c2pwrite, -@@ -1119,7 +1139,7 @@ - * the thread signal mask. */ - (void) pthread_sigmask(SIG_SETMASK, old_sigmask, NULL); - } --#endif -+#endif /* VFORK_USABLE */ - - if (need_after_fork) - PyOS_AfterFork_Parent(); -@@ -1152,8 +1172,10 @@ - PyGC_Enable(); - } - Py_XDECREF(gc_module); -- - return pid == -1 ? NULL : PyLong_FromPid(pid); -+#else /* HAVE_FORK */ -+ return NULL; -+#endif - } - - -diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c -index 520b7b603e..0177632cd5 100644 ---- a/Modules/faulthandler.c -+++ b/Modules/faulthandler.c -@@ -1,3 +1,7 @@ -+#ifdef __APPLE__ -+# include "TargetConditionals.h" -+#endif /* __APPLE__ */ + # Profile generation build must start from a clean tree. + profile-clean-stamp: + $(MAKE) clean +@@ -765,7 +788,7 @@ + $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ + + libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) +- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + + + libpython$(VERSION).sl: $(LIBRARY_OBJS) +@@ -789,14 +812,13 @@ + # This rule is here for OPENSTEP/Rhapsody/MacOSX. It builds a temporary + # minimal framework (not including the Lib directory and such) in the current + # directory. +-RESSRCDIR=Mac/Resources/framework + $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \ + $(LIBRARY) \ + $(RESSRCDIR)/Info.plist + $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION) + $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ +- -all_load $(LIBRARY) -Wl,-single_module \ +- -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK) \ ++ -all_load $(LIBRARY) \ ++ -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ + -compatibility_version $(VERSION) \ + -current_version $(VERSION) \ + -framework CoreFoundation $(LIBS); +@@ -808,6 +830,21 @@ + $(LN) -fsn Versions/Current/$(PYTHONFRAMEWORK) $(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK) + $(LN) -fsn Versions/Current/Resources $(PYTHONFRAMEWORKDIR)/Resources + ++# This rule is for iOS, which requires an annoyingly just slighly different ++# format for frameworks to macOS. It *doesn't* use a versioned framework, and ++# the Info.plist must be in the root of the framework. ++$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK): \ ++ $(LIBRARY) \ ++ $(RESSRCDIR)/Info.plist ++ $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR) ++ $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ ++ -all_load $(LIBRARY) \ ++ -install_name $(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ ++ -compatibility_version $(VERSION) \ ++ -current_version $(VERSION) \ ++ -framework CoreFoundation $(LIBS); ++ $(INSTALL_DATA) $(RESSRCDIR)/Info.plist $(PYTHONFRAMEWORKDIR)/Info.plist + - #include "Python.h" - #include "pycore_initconfig.h" // _PyStatus_ERR - #include "pycore_pyerrors.h" // _Py_DumpExtensionModules -@@ -19,6 +23,11 @@ - # include - #endif - -+// tvOS and watchOS don't provide a number of important POSIX functions. -+#if defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) || defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) -+# undef HAVE_SIGALTSTACK -+#endif /* TVOS || WATCHOS */ -+ - /* Using an alternative stack requires sigaltstack() - and sigaction() SA_ONSTACK */ - #if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) -diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c -index 5f5b71c4c0..20faeee4db 100644 ---- a/Modules/mathmodule.c -+++ b/Modules/mathmodule.c -@@ -76,6 +76,10 @@ - /*[clinic end generated code: output=da39a3ee5e6b4b0d input=76bc7002685dd942]*/ - - -+#ifdef __APPLE__ -+# include "TargetConditionals.h" -+#endif /* __APPLE__ */ + # This rule builds the Cygwin Python DLL and import library if configured + # for a shared core library; otherwise, this rule is a noop. + $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS) +@@ -1711,6 +1748,54 @@ + $(RUNSHARED) /usr/libexec/oah/translate \ + ./$(BUILDPYTHON) -E -m test -j 0 -u all $(TESTOPTS) + ++# Run the test suite on the iOS simulator. Must be run on a macOS machine with ++# a full Xcode install that has an iPhone SE (3rd edition) simulator available. ++# This must be run *after* a `make install` has completed the build. The ++# `--with-framework-name` argument *cannot* be used when configuring the build. ++XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s) ++XCRESULT=$(XCFOLDER)/$(MULTIARCH).xcresult ++.PHONY: testios ++testios: ++ @if test "$(MACHDEP)" != "ios"; then \ ++ echo "Cannot run the iOS testbed for a non-iOS build."; \ ++ exit 1;\ ++ fi ++ @if test "$(findstring -iphonesimulator,$(MULTIARCH))" != "-iphonesimulator"; then \ ++ echo "Cannot run the iOS testbed for non-simulator builds."; \ ++ exit 1;\ ++ fi ++ @if test $(PYTHONFRAMEWORK) != "Python"; then \ ++ echo "Cannot run the iOS testbed with a non-default framework name."; \ ++ exit 1;\ ++ fi ++ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \ ++ echo "Cannot find a finalized iOS Python.framework. Have you run 'make install' to finalize the framework build?"; \ ++ exit 1;\ ++ fi ++ # Copy the testbed project into the build folder ++ 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 + - /* - sin(pi*x), giving accurate results for all finite x (especially x - integral or close to an integer). This is here for use in the -diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c -index 2e4a66ab1b..673e26b3b2 100644 ---- a/Modules/posixmodule.c -+++ b/Modules/posixmodule.c -@@ -72,6 +72,8 @@ - */ - #if defined(__APPLE__) - -+#include "TargetConditionals.h" -+ - #if defined(__has_builtin) - #if __has_builtin(__builtin_available) - #define HAVE_BUILTIN_AVAILABLE 1 -@@ -353,6 +355,26 @@ - # endif /* _MSC_VER */ - #endif /* ! __WATCOMC__ || __QNX__ */ - -+// iOS/tvOS/watchOS *define* a number of POSIX functions, but you can't use them -+// because they aren't conventional multiprocess environment. -+#if TARGET_OS_IPHONE -+# undef HAVE_EXECV -+# undef HAVE_FORK -+# undef HAVE_FORK1 -+# undef HAVE_FORKPTY -+# undef HAVE_GETGROUPS -+# undef HAVE_POSIX_SPAWN -+# undef HAVE_POSIX_SPAWNP -+# undef HAVE_SCHED_H -+# undef HAVE_SENDFILE -+# undef HAVE_SETPRIORITY -+# undef HAVE_SPAWNV -+# undef HAVE_WAIT -+# undef HAVE_WAIT3 -+# undef HAVE_WAIT4 -+# undef HAVE_WAITPID -+#endif ++ # Run the test suite for the Xcode project, targeting the iOS simulator. ++ # 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) -derivedDataPath $(XCFOLDER)/DerivedData ; then \ ++ touch $(XCFOLDER)/failed; \ ++ fi + - /*[clinic input] - # one of the few times we lie about this name! - module os -@@ -1594,7 +1616,9 @@ - */ - #include - #elif !defined(_MSC_VER) && (!defined(__WATCOMC__) || defined(__QNX__) || defined(__VXWORKS__)) -+# if !TARGET_OS_TV && !TARGET_OS_WATCH - extern char **environ; -+# endif - #endif /* !_MSC_VER */ - - static PyObject * -@@ -1610,6 +1634,7 @@ - d = PyDict_New(); - if (d == NULL) - return NULL; -+#if !TARGET_OS_TV && !TARGET_OS_WATCH - #ifdef MS_WINDOWS - /* _wenviron must be initialized in this way if the program is started - through main() instead of wmain(). */ -@@ -1663,6 +1688,7 @@ - Py_DECREF(k); - Py_DECREF(v); - } -+#endif - return d; - } - -@@ -4981,6 +5007,9 @@ - /*[clinic end generated code: output=290fc437dd4f33a0 input=86a58554ba6094af]*/ - { - long result; -+#if TARGET_OS_IPHONE -+ result = -1; -+#else - const char *bytes = PyBytes_AsString(command); - - if (PySys_Audit("os.system", "(O)", command) < 0) { -@@ -4990,6 +5019,7 @@ - Py_BEGIN_ALLOW_THREADS - result = system(bytes); - Py_END_ALLOW_THREADS -+#endif - return result; - } - #endif -@@ -13821,6 +13851,7 @@ - int is_symlink; - int need_stat; - #endif -+#if !TARGET_OS_TV && !TARGET_OS_WATCH - #ifdef MS_WINDOWS - unsigned long dir_bits; - #endif -@@ -13881,6 +13912,7 @@ - #endif - - return result; -+#endif - - error: - Py_XDECREF(st_mode); -diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c -index a757380bd0..5a20864a1c 100644 ---- a/Modules/pwdmodule.c -+++ b/Modules/pwdmodule.c -@@ -1,6 +1,10 @@ - - /* UNIX password file access module */ - -+#ifdef __APPLE__ -+# include "TargetConditionals.h" -+#endif /* __APPLE__ */ ++ # 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'])" + - #include "Python.h" - #include "posixmodule.h" - -@@ -182,6 +186,22 @@ - if (nomem == 1) { - return PyErr_NoMemory(); - } ++ @if test -e $(XCFOLDER)/failed ; then \ ++ exit 1; \ ++ fi + -+// iPhone has a "user" with UID 501, username "mobile"; but the simulator -+// doesn't reflect this. Generate a simulated response. -+#if TARGET_IPHONE_SIMULATOR -+ if (uid == 501) { -+ struct passwd mp; -+ mp.pw_name = "mobile"; -+ mp.pw_passwd = "/smx7MYTQIi2M"; -+ mp.pw_uid = 501; -+ mp.pw_gid = 501; -+ mp.pw_gecos = "Mobile User"; -+ mp.pw_dir = "/var/mobile"; -+ mp.pw_shell = "/bin/sh"; -+ return mkpwent(module, &mp); -+ } -+#endif - PyObject *uid_obj = _PyLong_FromUid(uid); - if (uid_obj == NULL) - return NULL; -@@ -265,6 +285,22 @@ - PyErr_NoMemory(); - } - else { -+// iPhone has a "user" with UID 501, username "mobile"; but the simulator -+// doesn't reflect this. Generate a simulated response. -+#if TARGET_IPHONE_SIMULATOR -+ if (strcmp(name, "mobile") == 0) { -+ struct passwd mp; -+ mp.pw_name = "mobile"; -+ mp.pw_passwd = "/smx7MYTQIi2M"; -+ mp.pw_uid = 501; -+ mp.pw_gid = 501; -+ mp.pw_gecos = "Mobile User"; -+ mp.pw_dir = "/var/mobile"; -+ mp.pw_shell = "/bin/sh"; -+ retval = mkpwent(module, &mp); -+ goto out; -+ } -+#endif - PyErr_Format(PyExc_KeyError, - "getpwnam(): name not found: %R", name); - } -diff --git a/Modules/timemodule.c b/Modules/timemodule.c -index b8e0e481cb..29e7775d95 100644 ---- a/Modules/timemodule.c -+++ b/Modules/timemodule.c -@@ -62,6 +62,11 @@ - #define SEC_TO_NS (1000 * 1000 * 1000) - - -+#ifdef __APPLE__ -+# include "TargetConditionals.h" -+#endif /* __APPLE__ */ + # Like testall, but with only one pass and without multiple processes. + # Run an optional script to include information about the build environment. + buildbottest: all +@@ -1747,7 +1832,7 @@ + # prevent race conditions with PGO builds. PGO builds use recursive make, + # which can lead to two parallel `./python setup.py build` processes that + # step on each others toes. +-install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKINSTALLLAST@ ++install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@ + if test "x$(ENSUREPIP)" != "xno" ; then \ + case $(ENSUREPIP) in \ + upgrade) ensurepip="--upgrade" ;; \ +@@ -2144,7 +2229,16 @@ + $(INSTALL_DATA) $(srcdir)/Modules/xxmodule.c \ + $(DESTDIR)$(LIBDEST)/distutils/tests ; \ + fi +- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \ ++ @ # If app store compliance has been configured, apply the patch to the ++ @ # installed library code. The patch has been previously validated against ++ @ # the original source tree, so we can ignore any errors that are raised ++ @ # due to files that are missing because of --disable-test-modules etc. ++ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \ ++ echo "Applying app store compliance patch"; \ ++ patch --force --reject-file "$(abs_builddir)/app-store-compliance.rej" --strip 2 --directory "$(DESTDIR)$(LIBDEST)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)" || true ; \ ++ fi ++ @ # Build PYC files for the 3 optimization levels (0, 1, 2) ++ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \ + $(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \ + -j0 -d $(LIBDEST) -f \ + -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \ +@@ -2316,9 +2410,11 @@ + # automatically set prefix to the location deep down in the framework, so we + # only have to cater for the structural bits of the framework. + +-frameworkinstallframework: frameworkinstallstructure install frameworkinstallmaclib ++frameworkinstallframework: @FRAMEWORKINSTALLFIRST@ install frameworkinstallmaclib + +-frameworkinstallstructure: $(LDLIBRARY) ++# macOS uses a versioned frameworks structure that includes a full install ++.PHONY: frameworkinstallversionedstructure ++frameworkinstallversionedstructure: $(LDLIBRARY) + @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ + echo Not configured with --enable-framework; \ + exit 1; \ +@@ -2339,6 +2435,27 @@ + $(LN) -fsn Versions/Current/Resources $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Resources + $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) + ++# iOS/tvOS/watchOS uses a non-versioned framework with Info.plist in the ++# framework root, no .lproj data, and only stub compilation assistance binaries ++.PHONY: frameworkinstallunversionedstructure ++frameworkinstallunversionedstructure: $(LDLIBRARY) ++ @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ ++ echo Not configured with --enable-framework; \ ++ exit 1; \ ++ else true; \ ++ fi ++ if test -d $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; then \ ++ echo "Clearing stale header symlink directory"; \ ++ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; \ ++ fi ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) ++ sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist ++ $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) ++ for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ ++ $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ ++ done + + # This installs Mac/Lib into the framework + # Install a number of symlinks to keep software that expects a normal unix + # install (which includes python-config) happy. +@@ -2373,6 +2490,19 @@ + frameworkinstallextras: + cd Mac && $(MAKE) installextras DESTDIR="$(DESTDIR)" + ++# On iOS, bin/lib can't live inside the framework; include needs to be called ++# "Headers", but *must* be in the framework, and *not* include the `python3.X` ++# subdirectory. The install has put these folders in the same folder as ++# Python.framework; Move the headers to their final framework-compatible home. ++.PHONY: frameworkinstallmobileheaders ++frameworkinstallmobileheaders: frameworkinstallunversionedstructure inclinstall ++ if test -d $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; then \ ++ echo "Removing old framework headers"; \ ++ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \ ++ fi ++ mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers" ++ $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" + - /* Forward declarations */ - static int pysleep(_PyTime_t timeout); - -@@ -263,11 +268,13 @@ - if (_PyTime_AsTimespec(t, &tp) == -1) - return NULL; - -+#if !TARGET_OS_IPHONE - ret = clock_settime((clockid_t)clk_id, &tp); - if (ret != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } -+#endif - Py_RETURN_NONE; - } - -@@ -296,11 +303,13 @@ - return NULL; - } - -+#if !TARGET_OS_IPHONE - ret = clock_settime((clockid_t)clk_id, &ts); - if (ret != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } -+#endif - Py_RETURN_NONE; - } - -diff --git a/Python/bootstrap_hash.c b/Python/bootstrap_hash.c -index 3a2a731808..cb2d9e53e2 100644 ---- a/Python/bootstrap_hash.c -+++ b/Python/bootstrap_hash.c -@@ -35,6 +35,10 @@ + # Build the toplevel Makefile + Makefile.pre: $(srcdir)/Makefile.pre.in config.status + CONFIG_FILES=Makefile.pre CONFIG_HEADERS= $(SHELL) config.status +@@ -2477,6 +2607,10 @@ + -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' + -rm -f Include/pydtrace_probes.h + -rm -f profile-gen-stamp ++ -rm -rf iOS/testbed/Python.xcframework/ios-*/bin ++ -rm -rf iOS/testbed/Python.xcframework/ios-*/lib ++ -rm -rf iOS/testbed/Python.xcframework/ios-*/include ++ -rm -rf iOS/testbed/Python.xcframework/ios-*/Python.framework + + profile-removal: + find . -name '*.gc??' -exec rm -f {} ';' +@@ -2498,6 +2632,8 @@ + config.cache config.log pyconfig.h Modules/config.c + -rm -rf build platform + -rm -rf $(PYTHONFRAMEWORKDIR) ++ -rm -rf iOS/Frameworks ++ -rm -rf iOSTestbed.* + -rm -f python-config.py python-config + + # Make things extra clean, before making a distribution: +@@ -2578,7 +2714,7 @@ + .PHONY: all build_all build_wasm sharedmods check-clean-src oldsharedmods test quicktest + .PHONY: install altinstall oldsharedinstall bininstall altbininstall + .PHONY: maninstall libinstall inclinstall libainstall sharedinstall +-.PHONY: frameworkinstall frameworkinstallframework frameworkinstallstructure ++.PHONY: frameworkinstall frameworkinstallframework + .PHONY: frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools + .PHONY: frameworkaltinstallunixtools recheck clean clobber distclean + .PHONY: smelly funny patchcheck touch altmaninstall commoninstall +diff --git a/Modules/getpath.c b/Modules/getpath.c +index 61d654065fd..8bd844d6459 100644 +--- a/Modules/getpath.c ++++ b/Modules/getpath.c +@@ -15,6 +15,7 @@ #endif - - -+#ifdef __APPLE__ + + #ifdef __APPLE__ +# include "TargetConditionals.h" -+#endif /* __APPLE__ */ -+ - #ifdef Py_DEBUG - int _Py_HashSecret_Initialized = 0; - #else -@@ -180,6 +184,9 @@ - } - - #elif defined(HAVE_GETENTROPY) -+// iOS, tvOS and watchOS have an incomplete definitions of getentropy -+// so it is *found* by configure, but doesn't actually exist. -+#elif defined(HAVE_GETENTROPY) && !TARGET_OS_IPHONE - #define PY_GETENTROPY 1 - - /* Fill buffer with size pseudo-random bytes generated by getentropy(): -diff --git a/Python/dynload_shlib.c b/Python/dynload_shlib.c -index 3c5fd83df5..77b26d2d85 100644 ---- a/Python/dynload_shlib.c -+++ b/Python/dynload_shlib.c -@@ -28,6 +28,10 @@ - #define LEAD_UNDERSCORE "" + # include #endif - -+#ifdef __APPLE__ -+# include "TargetConditionals.h" -+#endif /* __APPLE__ */ -+ - /* The .so extension module ABI tag, supplied by the Makefile via - Makefile.pre.in and configure. This is used to discriminate between - incompatible .so files so that extensions for different Python builds can -@@ -38,12 +42,21 @@ - #ifdef __CYGWIN__ - ".dll", - #else /* !__CYGWIN__ */ -- "." SOABI ".so", --#ifdef ALT_SOABI -- "." ALT_SOABI ".so", --#endif -- ".abi" PYTHON_ABI_STRING ".so", -- ".so", -+# ifdef __APPLE__ -+# if TARGET_OS_IPHONE -+# define SHLIB_SUFFIX ".dylib" -+# else -+# define SHLIB_SUFFIX ".so" -+# endif -+# else -+# define SHLIB_SUFFIX ".so" -+# endif -+ "." SOABI SHLIB_SUFFIX, -+# ifdef ALT_SOABI -+ "." ALT_SOABI SHLIB_SUFFIX, -+# endif -+ ".abi" PYTHON_ABI_STRING SHLIB_SUFFIX, -+ SHLIB_SUFFIX, - #endif /* __CYGWIN__ */ - NULL, - }; + +@@ -767,7 +768,7 @@ + if (PyWin_DLLhModule) { + return winmodule_to_dict(dict, key, PyWin_DLLhModule); + } +-#elif defined(WITH_NEXT_FRAMEWORK) ++#elif defined(WITH_NEXT_FRAMEWORK) && !defined(TARGET_OS_IPHONE) + static char modPath[MAXPATHLEN + 1]; + static int modPathInitialized = -1; + if (modPathInitialized < 0) { +@@ -961,4 +962,3 @@ + + return _PyStatus_OK(); + } +- diff --git a/Python/marshal.c b/Python/marshal.c -index 29f3bab60a..ea3b25a978 100644 +index 29f3bab60a5..7d32bb3661d 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -14,6 +14,10 @@ #include "pycore_hashtable.h" // _Py_hashtable_t #include "marshal.h" // Py_MARSHAL_VERSION - + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#endif /* __APPLE__ */ @@ -1728,7 +4260,7 @@ index 29f3bab60a..ea3b25a978 100644 /*[clinic input] module marshal [clinic start generated code]*/ -@@ -33,11 +37,15 @@ +@@ -33,11 +37,14 @@ * #if defined(MS_WINDOWS) && defined(_DEBUG) */ #if defined(MS_WINDOWS) @@ -1736,234 +4268,393 @@ index 29f3bab60a..ea3b25a978 100644 +# 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 -+# if TARGET_OS_IPHONE -+# define MAX_MARSHAL_STACK_DEPTH 1500 -+# else -+# define MAX_MARSHAL_STACK_DEPTH 2000 -+# endif ++# define MAX_MARSHAL_STACK_DEPTH 2000 #endif - + #define TYPE_NULL '0' -diff --git a/Python/sysmodule.c b/Python/sysmodule.c -index d0941e8cfe..7e07a6ae55 100644 ---- a/Python/sysmodule.c -+++ b/Python/sysmodule.c -@@ -49,6 +49,10 @@ - extern const char *PyWin_DLLVersionString; - #endif - -+#if defined(__APPLE__) -+#include "TargetConditionals.h" -+#endif -+ - #ifdef __EMSCRIPTEN__ - #include - #endif -@@ -2739,6 +2743,15 @@ - goto error; - #endif - -+#if TARGET_OS_IPHONE -+# if TARGET_OS_SIMULATOR -+ res = PyDict_SetItemString(impl_info, "_simulator", Py_True); -+# else -+ res = PyDict_SetItemString(impl_info, "_simulator", Py_False); -+# endif -+ if (res < 0) -+ goto error; -+#endif - /* dict ready */ - - ns = _PyNamespace_New(impl_info); -diff --git a/aclocal.m4 b/aclocal.m4 -index 6a33c0cc9d..09ae5d1aa8 100644 ---- a/aclocal.m4 -+++ b/aclocal.m4 -@@ -1,6 +1,6 @@ --# generated automatically by aclocal 1.16.3 -*- Autoconf -*- -+# generated automatically by aclocal 1.16.5 -*- Autoconf -*- - --# Copyright (C) 1996-2020 Free Software Foundation, Inc. -+# Copyright (C) 1996-2021 Free Software Foundation, Inc. - - # This file is free software; the Free Software Foundation - # gives unlimited permission to copy and/or distribute it, -@@ -184,7 +184,7 @@ - # and this notice are preserved. This file is offered as-is, without any - # warranty. - --#serial 10 -+#serial 11 - - AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) - AC_DEFUN([AX_CHECK_OPENSSL], [ -@@ -227,7 +227,7 @@ - if ! $found; then - OPENSSL_INCLUDES= - for ssldir in $ssldirs; do -- AC_MSG_CHECKING([for openssl/ssl.h in $ssldir]) -+ AC_MSG_CHECKING([for include/openssl/ssl.h in $ssldir]) - if test -f "$ssldir/include/openssl/ssl.h"; then - OPENSSL_INCLUDES="-I$ssldir/include" - OPENSSL_LDFLAGS="-L$ssldir/lib" -@@ -276,7 +276,7 @@ - ]) - - # pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- --# serial 11 (pkg-config-0.29.1) -+# serial 12 (pkg-config-0.29.2) - - dnl Copyright © 2004 Scott James Remnant . - dnl Copyright © 2012-2015 Dan Nicholson -@@ -318,7 +318,7 @@ - dnl See the "Since" comment for each macro you use to see what version - dnl of the macros you require. - m4_defun([PKG_PREREQ], --[m4_define([PKG_MACROS_VERSION], [0.29.1]) -+[m4_define([PKG_MACROS_VERSION], [0.29.2]) - m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, - [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) - ])dnl PKG_PREREQ -@@ -419,7 +419,7 @@ - AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl - - pkg_failed=no --AC_MSG_CHECKING([for $1]) -+AC_MSG_CHECKING([for $2]) - - _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) - _PKG_CONFIG([$1][_LIBS], [libs], [$2]) -@@ -429,11 +429,11 @@ - See the pkg-config man page for more details.]) - - if test $pkg_failed = yes; then -- AC_MSG_RESULT([no]) -+ AC_MSG_RESULT([no]) - _PKG_SHORT_ERRORS_SUPPORTED - if test $_pkg_short_errors_supported = yes; then - $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` -- else -+ else - $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` - fi - # Put the nasty error message in config.log where it belongs -@@ -450,7 +450,7 @@ - _PKG_TEXT])[]dnl - ]) - elif test $pkg_failed = untried; then -- AC_MSG_RESULT([no]) -+ AC_MSG_RESULT([no]) - m4_default([$4], [AC_MSG_FAILURE( - [The pkg-config script could not be found or is too old. Make sure it - is in your PATH or set the PKG_CONFIG environment variable to the full -@@ -551,77 +551,9 @@ - AS_VAR_IF([$1], [""], [$5], [$4])dnl - ])dnl PKG_CHECK_VAR - --dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, --dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], --dnl [DESCRIPTION], [DEFAULT]) --dnl ------------------------------------------ --dnl --dnl Prepare a "--with-" configure option using the lowercase --dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and --dnl PKG_CHECK_MODULES in a single macro. --AC_DEFUN([PKG_WITH_MODULES], --[ --m4_pushdef([with_arg], m4_tolower([$1])) -- --m4_pushdef([description], -- [m4_default([$5], [build with ]with_arg[ support])]) -- --m4_pushdef([def_arg], [m4_default([$6], [auto])]) --m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) --m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) -- --m4_case(def_arg, -- [yes],[m4_pushdef([with_without], [--without-]with_arg)], -- [m4_pushdef([with_without],[--with-]with_arg)]) -- --AC_ARG_WITH(with_arg, -- AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, -- [AS_TR_SH([with_]with_arg)=def_arg]) -- --AS_CASE([$AS_TR_SH([with_]with_arg)], -- [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], -- [auto],[PKG_CHECK_MODULES([$1],[$2], -- [m4_n([def_action_if_found]) $3], -- [m4_n([def_action_if_not_found]) $4])]) -- --m4_popdef([with_arg]) --m4_popdef([description]) --m4_popdef([def_arg]) -- --])dnl PKG_WITH_MODULES -- --dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, --dnl [DESCRIPTION], [DEFAULT]) --dnl ----------------------------------------------- --dnl --dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES --dnl check._[VARIABLE-PREFIX] is exported as make variable. --AC_DEFUN([PKG_HAVE_WITH_MODULES], --[ --PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) -- --AM_CONDITIONAL([HAVE_][$1], -- [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) --])dnl PKG_HAVE_WITH_MODULES -- --dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, --dnl [DESCRIPTION], [DEFAULT]) --dnl ------------------------------------------------------ --dnl --dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after --dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make --dnl and preprocessor variable. --AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], --[ --PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) -- --AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], -- [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) --])dnl PKG_HAVE_DEFINE_WITH_MODULES -- - # AM_CONDITIONAL -*- Autoconf -*- - --# Copyright (C) 1997-2020 Free Software Foundation, Inc. -+# Copyright (C) 1997-2021 Free Software Foundation, Inc. - # - # This file is free software; the Free Software Foundation - # gives unlimited permission to copy and/or distribute it, -@@ -652,7 +584,7 @@ - Usually this means the macro was only invoked conditionally.]]) - fi])]) - --# Copyright (C) 2006-2020 Free Software Foundation, Inc. -+# Copyright (C) 2006-2021 Free Software Foundation, Inc. - # - # This file is free software; the Free Software Foundation - # gives unlimited permission to copy and/or distribute it, +diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h +index 553585a76a3..2223b9a9a55 100644 +--- a/Python/stdlib_module_names.h ++++ b/Python/stdlib_module_names.h +@@ -40,6 +40,7 @@ + "_heapq", + "_imp", + "_io", ++"_ios_support", + "_json", + "_locale", + "_lsprof", diff --git a/config.sub b/config.sub -index d74fb6deac..a30f74f03d 100755 +index d74fb6deac9..1bb6a05dc11 100755 --- a/config.sub +++ b/config.sub -@@ -1121,10 +1121,9 @@ +@@ -1,14 +1,15 @@ + #! /bin/sh + # Configuration validation subroutine script. +-# Copyright 1992-2021 Free Software Foundation, Inc. ++# Copyright 1992-2024 Free Software Foundation, Inc. + + # shellcheck disable=SC2006,SC2268 # see below for rationale + +-timestamp='2021-08-14' ++# Patched 2024-02-03 to include support for arm64_32 and iOS/tvOS/watchOS simulators ++timestamp='2024-01-01' + + # This file is free software; you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by +-# the Free Software Foundation; either version 3 of the License, or ++# the Free Software Foundation, either version 3 of the License, or + # (at your option) any later version. + # + # This program is distributed in the hope that it will be useful, but +@@ -76,13 +77,13 @@ + version="\ + GNU config.sub ($timestamp) + +-Copyright 1992-2021 Free Software Foundation, Inc. ++Copyright 1992-2024 Free Software Foundation, Inc. + + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + + help=" +-Try \`$me --help' for more information." ++Try '$me --help' for more information." + + # Parse command line + while test $# -gt 0 ; do +@@ -130,7 +131,7 @@ + # Separate into logical components for further validation + case $1 in + *-*-*-*-*) +- echo Invalid configuration \`"$1"\': more than four components >&2 ++ echo "Invalid configuration '$1': more than four components" >&2 + exit 1 + ;; + *-*-*-*) +@@ -145,7 +146,8 @@ + nto-qnx* | linux-* | uclinux-uclibc* \ + | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ + | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ +- | storm-chaos* | os2-emx* | rtmk-nova*) ++ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \ ++ | windows-* ) + basic_machine=$field1 + basic_os=$maybe_os + ;; +@@ -943,7 +945,7 @@ + EOF + IFS=$saved_IFS + ;; +- # We use `pc' rather than `unknown' ++ # We use 'pc' rather than 'unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) +@@ -1020,6 +1022,11 @@ + ;; + + # Here we normalize CPU types with a missing or matching vendor ++ armh-unknown | armh-alt) ++ cpu=armv7l ++ vendor=alt ++ basic_os=${basic_os:-linux-gnueabihf} ++ ;; + dpx20-unknown | dpx20-bull) + cpu=rs6000 + vendor=bull +@@ -1070,7 +1077,7 @@ + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + cpu=i586 + ;; +- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*) ++ pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*) + cpu=i686 + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) +@@ -1121,7 +1128,7 @@ xscale-* | xscalee[bl]-*) cpu=`echo "$cpu" | sed 's/^xscale/arm/'` ;; - arm64-*) -+ arm64-* | arm64_32-*) ++ arm64-* | aarch64le-* | arm64_32-*) cpu=aarch64 ;; -- - # Recognize the canonical CPU Types that limit and/or modify the - # company names they are paired with. - cr16-*) -@@ -1723,7 +1722,7 @@ + +@@ -1175,7 +1182,7 @@ + case $cpu in + 1750a | 580 \ + | a29k \ +- | aarch64 | aarch64_be \ ++ | aarch64 | aarch64_be | aarch64c | arm64ec \ + | abacus \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \ +@@ -1194,50 +1201,29 @@ + | d10v | d30v | dlx | dsp16xx \ + | e2k | elxsi | epiphany \ + | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \ ++ | javascript \ + | h8300 | h8500 \ + | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i*86 | i860 | i960 | ia16 | ia64 \ + | ip2k | iq2000 \ + | k1om \ ++ | kvx \ + | le32 | le64 \ + | lm32 \ +- | loongarch32 | loongarch64 | loongarchx32 \ ++ | loongarch32 | loongarch64 \ + | m32c | m32r | m32rle \ + | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \ + | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \ + | m88110 | m88k | maxq | mb | mcore | mep | metag \ + | microblaze | microblazeel \ +- | mips | mipsbe | mipseb | mipsel | mipsle \ +- | mips16 \ +- | mips64 | mips64eb | mips64el \ +- | mips64octeon | mips64octeonel \ +- | mips64orion | mips64orionel \ +- | mips64r5900 | mips64r5900el \ +- | mips64vr | mips64vrel \ +- | mips64vr4100 | mips64vr4100el \ +- | mips64vr4300 | mips64vr4300el \ +- | mips64vr5000 | mips64vr5000el \ +- | mips64vr5900 | mips64vr5900el \ +- | mipsisa32 | mipsisa32el \ +- | mipsisa32r2 | mipsisa32r2el \ +- | mipsisa32r3 | mipsisa32r3el \ +- | mipsisa32r5 | mipsisa32r5el \ +- | mipsisa32r6 | mipsisa32r6el \ +- | mipsisa64 | mipsisa64el \ +- | mipsisa64r2 | mipsisa64r2el \ +- | mipsisa64r3 | mipsisa64r3el \ +- | mipsisa64r5 | mipsisa64r5el \ +- | mipsisa64r6 | mipsisa64r6el \ +- | mipsisa64sb1 | mipsisa64sb1el \ +- | mipsisa64sr71k | mipsisa64sr71kel \ +- | mipsr5900 | mipsr5900el \ +- | mipstx39 | mipstx39el \ ++ | mips* \ + | mmix \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ ++ | nanomips* \ + | nds32 | nds32le | nds32be \ + | nfp \ + | nios | nios2 | nios2eb | nios2el \ +@@ -1269,6 +1255,7 @@ + | ubicom32 \ + | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \ + | vax \ ++ | vc4 \ + | visium \ + | w65 \ + | wasm32 | wasm64 \ +@@ -1280,7 +1267,7 @@ + ;; + + *) +- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2 ++ echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2 + exit 1 + ;; + esac +@@ -1301,11 +1288,12 @@ + + # Decode manufacturer-specific aliases for certain operating systems. + +-if test x$basic_os != x ++if test x"$basic_os" != x + then + +-# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just ++# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just + # set os. ++obj= + case $basic_os in + gnu/linux*) + kernel=linux +@@ -1336,6 +1324,10 @@ + kernel=linux + os=`echo "$basic_os" | sed -e 's|linux|gnu|'` + ;; ++ managarm*) ++ kernel=managarm ++ os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'` ++ ;; + *) + kernel= + os=$basic_os +@@ -1501,10 +1493,16 @@ + os=eabi + ;; + *) +- os=elf ++ os= ++ obj=elf + ;; + esac + ;; ++ aout* | coff* | elf* | pe*) ++ # These are machine code file formats, not OSes ++ obj=$os ++ os= ++ ;; + *) + # No normalization, but not necessarily accepted, that comes below. + ;; +@@ -1523,12 +1521,15 @@ + # system, and we'll never get to this point. + + kernel= ++obj= + case $cpu-$vendor in + score-*) +- os=elf ++ os= ++ obj=elf + ;; + spu-*) +- os=elf ++ os= ++ obj=elf + ;; + *-acorn) + os=riscix1.2 +@@ -1538,28 +1539,35 @@ + os=gnu + ;; + arm*-semi) +- os=aout ++ os= ++ obj=aout + ;; + c4x-* | tic4x-*) +- os=coff ++ os= ++ obj=coff + ;; + c8051-*) +- os=elf ++ os= ++ obj=elf + ;; + clipper-intergraph) + os=clix + ;; + hexagon-*) +- os=elf ++ os= ++ obj=elf + ;; + tic54x-*) +- os=coff ++ os= ++ obj=coff + ;; + tic55x-*) +- os=coff ++ os= ++ obj=coff + ;; + tic6x-*) +- os=coff ++ os= ++ obj=coff + ;; + # This must come before the *-dec entry. + pdp10-*) +@@ -1581,19 +1589,24 @@ + os=sunos3 + ;; + m68*-cisco) +- os=aout ++ os= ++ obj=aout + ;; + mep-*) +- os=elf ++ os= ++ obj=elf + ;; + mips*-cisco) +- os=elf ++ os= ++ obj=elf + ;; +- mips*-*) +- os=elf ++ mips*-*|nanomips*-*) ++ os= ++ obj=elf + ;; + or32-*) +- os=coff ++ os= ++ obj=coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=sysv3 +@@ -1602,7 +1615,8 @@ + os=sunos4.1.1 + ;; + pru-*) +- os=elf ++ os= ++ obj=elf + ;; + *-be) + os=beos +@@ -1683,10 +1697,12 @@ + os=uxpv + ;; + *-rom68k) +- os=coff ++ os= ++ obj=coff + ;; + *-*bug) +- os=coff ++ os= ++ obj=coff + ;; + *-apple) + os=macos +@@ -1704,10 +1720,11 @@ + + fi + +-# Now, validate our (potentially fixed-up) OS. ++# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ). ++ + case $os in + # Sometimes we do "kernel-libc", so those need to count as OSes. +- musl* | newlib* | relibc* | uclibc*) ++ llvm* | musl* | newlib* | relibc* | uclibc*) + ;; + # Likewise for "kernel-abi" + eabi* | gnueabi*) +@@ -1715,6 +1732,9 @@ + # VxWorks passes extra cpu info in the 4th filed. + simlinux | simwindows | spe) + ;; ++ # See `case $cpu-$os` validation below ++ ghcjs) ++ ;; + # Now accept the basic system types. + # The portable systems comes first. + # Each alternative MUST end in a * to match a version number. +@@ -1723,7 +1743,7 @@ | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \ | hiux* | abug | nacl* | netware* | windows* \ @@ -1972,17 +4663,168 @@ index d74fb6deac..a30f74f03d 100755 | mpw* | magic* | mmixware* | mon960* | lnews* \ | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ | aos* | aros* | cloudabi* | sortix* | twizzler* \ -@@ -1786,6 +1785,8 @@ +@@ -1732,11 +1752,11 @@ + | mirbsd* | netbsd* | dicos* | openedition* | ose* \ + | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \ + | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \ +- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ +- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ ++ | bosx* | nextstep* | cxux* | oabi* \ ++ | ptx* | ecoff* | winnt* | domain* | vsta* \ + | udi* | lites* | ieee* | go32* | aux* | hcos* \ + | chorusrdb* | cegcc* | glidix* | serenity* \ +- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \ ++ | cygwin* | msys* | moss* | proelf* | rtems* \ + | midipix* | mingw32* | mingw64* | mint* \ + | uxpv* | beos* | mpeix* | udk* | moxiebox* \ + | interix* | uwin* | mks* | rhapsody* | darwin* \ +@@ -1748,49 +1768,119 @@ + | skyos* | haiku* | rdos* | toppers* | drops* | es* \ + | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ + | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ +- | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr*) ++ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ ++ | fiwix* | mlibc* | cos* | mbr* | ironclad* ) + ;; + # This one is extra strict with allowed versions + sco3.2v2 | sco3.2v[4-9]* | sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + ;; ++ # This refers to builds using the UEFI calling convention ++ # (which depends on the architecture) and PE file format. ++ # Note that this is both a different calling convention and ++ # different file format than that of GNU-EFI ++ # (x86_64-w64-mingw32). ++ uefi) ++ ;; + none) + ;; ++ kernel* | msvc* ) ++ # Restricted further below ++ ;; ++ '') ++ if test x"$obj" = x ++ then ++ echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2 ++ fi ++ ;; + *) +- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2 ++ echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2 ++ exit 1 ++ ;; ++esac ++ ++case $obj in ++ aout* | coff* | elf* | pe*) ++ ;; ++ '') ++ # empty is fine ++ ;; ++ *) ++ echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2 ++ exit 1 ++ ;; ++esac ++ ++# Here we handle the constraint that a (synthetic) cpu and os are ++# valid only in combination with each other and nowhere else. ++case $cpu-$os in ++ # The "javascript-unknown-ghcjs" triple is used by GHC; we ++ # accept it here in order to tolerate that, but reject any ++ # variations. ++ javascript-ghcjs) ++ ;; ++ javascript-* | *-ghcjs) ++ echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2 + exit 1 + ;; + esac + + # As a final step for OS-related things, validate the OS-kernel combination + # (given a valid OS), if there is a kernel. +-case $kernel-$os in +- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ +- | linux-musl* | linux-relibc* | linux-uclibc* ) ++case $kernel-$os-$obj in ++ linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \ ++ | linux-mlibc*- | linux-musl*- | linux-newlib*- \ ++ | linux-relibc*- | linux-uclibc*- ) ++ ;; ++ uclinux-uclibc*- ) + ;; +- uclinux-uclibc* ) ++ managarm-mlibc*- | managarm-kernel*- ) ;; - *-eabi* | *-gnueabi*) +- -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* ) ++ windows*-msvc*-) ++ ;; ++ -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \ ++ | -uclibc*- ) + # These are just libc implementations, not actual OSes, and thus + # require a kernel. +- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 ++ echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2 + exit 1 + ;; +- kfreebsd*-gnu* | kopensolaris*-gnu*) ++ -kernel*- ) ++ echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2 ++ exit 1 + ;; +- vxworks-simlinux | vxworks-simwindows | vxworks-spe) ++ *-kernel*- ) ++ echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2 ++ exit 1 + ;; +- nto-qnx*) ++ *-msvc*- ) ++ echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2 ++ exit 1 ;; -+ ios*-simulator | tvos*-simulator | watchos*-simulator) +- os2-emx) ++ kfreebsd*-gnu*- | kopensolaris*-gnu*-) + ;; +- *-eabi* | *-gnueabi*) ++ vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-) + ;; +- -*) ++ nto-qnx*-) ++ ;; ++ os2-emx-) ++ ;; ++ *-eabi*- | *-gnueabi*-) + ;; - -*) ++ ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) ++ ;; ++ none--*) ++ # None (no kernel, i.e. freestanding / bare metal), ++ # can be paired with an machine code file format ++ ;; ++ -*-) # Blank kernel with real OS is always fine. ;; +- *-*) +- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 ++ --*) ++ # Blank kernel and OS with real machine code file format is always fine. ++ ;; ++ *-*-*) ++ echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 + exit 1 + ;; + esac +@@ -1873,7 +1963,7 @@ + ;; + esac + +-echo "$cpu-$vendor-${kernel:+$kernel-}$os" ++echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" + exit + + # Local variables: diff --git a/configure b/configure -index af4a5bbfdf..03f864cc80 100755 +index a1ad0ae2510..89b894f6551 100755 --- a/configure +++ b/configure @@ -838,6 +838,8 @@ @@ -1994,7 +4836,40 @@ index af4a5bbfdf..03f864cc80 100755 LIBFFI_INCLUDEDIR LIBEXPAT_INTERNAL LIBEXPAT_LDFLAGS -@@ -991,7 +993,6 @@ +@@ -925,10 +927,14 @@ + LDFLAGS + CFLAGS + CC ++WATCHOS_DEPLOYMENT_TARGET ++TVOS_DEPLOYMENT_TARGET ++IPHONEOS_DEPLOYMENT_TARGET + EXPORT_MACOSX_DEPLOYMENT_TARGET + CONFIGURE_MACOSX_DEPLOYMENT_TARGET + _PYTHON_HOST_PLATFORM +-MACHDEP ++APP_STORE_COMPLIANCE_PATCH ++INSTALLTARGETS + FRAMEWORKINSTALLAPPSPREFIX + FRAMEWORKUNIXTOOLSPREFIX + FRAMEWORKPYTHONW +@@ -936,6 +942,8 @@ + FRAMEWORKALTINSTALLFIRST + FRAMEWORKINSTALLLAST + FRAMEWORKINSTALLFIRST ++RESSRCDIR ++PYTHONFRAMEWORKINSTALLNAMEPREFIX + PYTHONFRAMEWORKINSTALLDIR + PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKDIR +@@ -945,6 +953,7 @@ + LIPO_32BIT_FLAGS + ARCH_RUN_32BIT + UNIVERSALSDK ++MACHDEP + PKG_CONFIG_LIBDIR + PKG_CONFIG_PATH + PKG_CONFIG +@@ -991,7 +1000,6 @@ docdir oldincludedir includedir @@ -2002,7 +4877,15 @@ index af4a5bbfdf..03f864cc80 100755 localstatedir sharedstatedir sysconfdir -@@ -1142,7 +1143,6 @@ +@@ -1020,6 +1028,7 @@ + with_universal_archs + with_framework_name + enable_framework ++with_app_store_compliance + with_cxx_main + with_emscripten_target + enable_wasm_dynamic_linking +@@ -1142,7 +1151,6 @@ sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' @@ -2010,10 +4893,10 @@ index af4a5bbfdf..03f864cc80 100755 includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' -@@ -1395,15 +1395,6 @@ +@@ -1395,15 +1403,6 @@ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; - + - -runstatedir | --runstatedir | --runstatedi | --runstated \ - | --runstate | --runstat | --runsta | --runst | --runs \ - | --run | --ru | --r) @@ -2026,7 +4909,7 @@ index af4a5bbfdf..03f864cc80 100755 -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ -@@ -1541,7 +1532,7 @@ +@@ -1541,7 +1540,7 @@ for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ @@ -2035,7 +4918,7 @@ index af4a5bbfdf..03f864cc80 100755 do eval ac_val=\$$ac_var # Remove trailing slashes. -@@ -1694,7 +1685,6 @@ +@@ -1694,7 +1693,6 @@ --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] @@ -2043,10 +4926,42 @@ index af4a5bbfdf..03f864cc80 100755 --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] -@@ -3823,6 +3813,15 @@ - *-*-cygwin*) - ac_sys_system=Cygwin - ;; +@@ -1779,6 +1777,10 @@ + specify the name for the python framework on macOS + only valid when --enable-framework is set. see + Mac/README.rst (default is 'Python') ++ --with-app-store-compliance=[PATCH-FILE] ++ Enable any patches required for compiliance with app ++ stores. Optional PATCH-FILE specifies the custom ++ patch to apply. + --with-cxx-main[=COMPILER] + compile main() and link Python executable with C++ + compiler specified in COMPILER (default is $CXX) +@@ -3566,6 +3568,166 @@ + as_fn_error $? "pkg-config is required" "$LINENO" 5] + fi + ++# Set name for machine-dependent library files ++ ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 ++$as_echo_n "checking MACHDEP... " >&6; } ++if test -z "$MACHDEP" ++then ++ # avoid using uname for cross builds ++ if test "$cross_compiling" = yes; then ++ # ac_sys_system and ac_sys_release are used for setting ++ # a lot of different things including 'define_xopen_source' ++ # in the case statement below. ++ case "$host" in ++ *-*-linux-android*) ++ ac_sys_system=Linux-android ++ ;; ++ *-*-linux*) ++ ac_sys_system=Linux ++ ;; ++ *-*-cygwin*) ++ ac_sys_system=Cygwin ++ ;; + *-apple-ios*) + ac_sys_system=iOS + ;; @@ -2056,10 +4971,540 @@ index af4a5bbfdf..03f864cc80 100755 + *-apple-watchos*) + ac_sys_system=watchOS + ;; - *-*-vxworks*) - ac_sys_system=VxWorks - ;; -@@ -3870,27 +3869,96 @@ ++ *-*-vxworks*) ++ ac_sys_system=VxWorks ++ ;; ++ *-*-emscripten) ++ ac_sys_system=Emscripten ++ ;; ++ *-*-wasi) ++ ac_sys_system=WASI ++ ;; ++ *) ++ # for now, limit cross builds to known configurations ++ MACHDEP="unknown" ++ as_fn_error $? "cross build not supported for $host" "$LINENO" 5 ++ esac ++ ac_sys_release= ++ else ++ ac_sys_system=`uname -s` ++ if test "$ac_sys_system" = "AIX" \ ++ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then ++ ac_sys_release=`uname -v` ++ else ++ ac_sys_release=`uname -r` ++ fi ++ fi ++ ac_md_system=`echo $ac_sys_system | ++ tr -d '/ ' | tr '[A-Z]' '[a-z]'` ++ ac_md_release=`echo $ac_sys_release | ++ tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` ++ MACHDEP="$ac_md_system$ac_md_release" ++ ++ case $MACHDEP in ++ aix*) MACHDEP="aix";; ++ linux*) MACHDEP="linux";; ++ cygwin*) MACHDEP="cygwin";; ++ darwin*) MACHDEP="darwin";; ++ '') MACHDEP="unknown";; ++ esac ++ ++ if test "$ac_sys_system" = "SunOS"; then ++ # For Solaris, there isn't an OS version specific macro defined ++ # in most compilers, so we define one here. ++ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` ++ ++cat >>confdefs.h <<_ACEOF ++#define Py_SUNOS_VERSION $SUNOS_VERSION ++_ACEOF ++ ++ fi ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 ++$as_echo "\"$MACHDEP\"" >&6; } ++ ++# On cross-compile builds, configure will look for a host-specific compiler by ++# prepending the user-provided host triple to the required binary name. ++# ++# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", ++# which isn't a binary that exists, and isn't very convenient, as it contains the ++# iOS version. As the default cross-compiler name won't exist, configure falls ++# back to gcc, which *definitely* won't work. We're providing wrapper scripts for ++# these tools; the binary names of these scripts are better defaults than "gcc". ++# This only requires that the user put the platform scripts folder (e.g., ++# "iOS/Resources/bin") in their path, rather than defining platform-specific ++# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to ++# either put the platform scripts folder in the path, or specify CC etc, ++# configure will fail. ++if test -z "$AR"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; ++ aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; ++ x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; ++ ++ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;; ++ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;; ++ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;; ++ ++ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;; ++ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; ++ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; ++ *) ++ esac ++fi ++if test -z "$CC"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; ++ aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; ++ x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++ ++ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;; ++ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;; ++ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;; ++ ++ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;; ++ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;; ++ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;; ++ *) ++ esac ++fi ++if test -z "$CPP"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; ++ aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; ++ x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++ ++ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;; ++ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;; ++ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;; ++ ++ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;; ++ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;; ++ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;; ++ *) ++ esac ++fi ++if test -z "$CXX"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang ;; ++ aarch64-apple-ios*) CXX=arm64-apple-ios-clang ;; ++ x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang ;; ++ ++ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang ;; ++ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang ;; ++ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang ;; ++ ++ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang ;; ++ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang ;; ++ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang ;; ++ *) ++ esac ++fi ++ + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --enable-universalsdk" >&5 + $as_echo_n "checking for --enable-universalsdk... " >&6; } + # Check whether --enable-universalsdk was given. +@@ -3677,111 +3839,195 @@ + enableval=$enable_framework; + case $enableval in + yes) +- enableval=/Library/Frameworks ++ case $ac_sys_system in ++ Darwin) enableval=/Library/Frameworks ;; ++ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; ++ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;; ++ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;; ++ *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 ++ esac + esac ++ + case $enableval in + no) +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE"; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= ++ case $ac_sys_system in ++ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; ++ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; ++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ ++ if test "x${prefix}" = "xNONE"; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= ++ esac + ;; + *) + PYTHONFRAMEWORKPREFIX="${enableval}" + PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR +- FRAMEWORKINSTALLFIRST="frameworkinstallstructure" +- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " +- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" +- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" +- FRAMEWORKPYTHONW="frameworkpythonw" +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi + +- case "${enableval}" in +- /System*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- if test "${prefix}" = "NONE" ; then +- # See below +- FRAMEWORKUNIXTOOLSPREFIX="/usr" +- fi +- ;; ++ case $ac_sys_system in #( ++ Darwin) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" ++ FRAMEWORKPYTHONW="frameworkpythonw" ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi + +- /Library*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ case "${enableval}" in ++ /System*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ if test "${prefix}" = "NONE" ; then ++ # See below ++ FRAMEWORKUNIXTOOLSPREFIX="/usr" ++ fi ++ ;; ++ ++ /Library*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; ++ ++ */Library/Frameworks) ++ MDIR="`dirname "${enableval}"`" ++ MDIR="`dirname "${MDIR}"`" ++ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" ++ ++ if test "${prefix}" = "NONE"; then ++ # User hasn't specified the ++ # --prefix option, but wants to install ++ # the framework in a non-default location, ++ # ensure that the compatibility links get ++ # installed relative to that prefix as well ++ # instead of in /usr/local. ++ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" ++ fi ++ ;; + +- */Library/Frameworks) +- MDIR="`dirname "${enableval}"`" +- MDIR="`dirname "${MDIR}"`" +- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" +- +- if test "${prefix}" = "NONE"; then +- # User hasn't specified the +- # --prefix option, but wants to install +- # the framework in a non-default location, +- # ensure that the compatibility links get +- # installed relative to that prefix as well +- # instead of in /usr/local. +- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" +- fi +- ;; ++ *) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; ++ esac + +- *) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} ++ RESSRCDIR=Mac/Resources/framework ++ ++ # Add files for Mac specific code to the list of output ++ # files: ++ ac_config_files="$ac_config_files Mac/Makefile" ++ ++ ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" ++ ++ ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" ++ ++ ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" ++ ++ ;; ++ iOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=iOS/Resources ++ ++ ac_config_files="$ac_config_files iOS/Resources/Info.plist" ++ ++ ;; ++ tvOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=tvOS/Resources ++ ++ ac_config_files="$ac_config_files tvOS/Resources/Info.plist" ++ ++ ;; ++ watchOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=watchOS/Resources ++ ++ ac_config_files="$ac_config_files watchOS/Resources/Info.plist" ++ ++ ;; ++ *) ++ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 ++ ;; ++ esac + esac + +- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION +- +- # Add files for Mac specific code to the list of output +- # files: +- ac_config_files="$ac_config_files Mac/Makefile" +- +- ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" +- +- ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" +- +- ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" ++else + ++ case $ac_sys_system in ++ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; ++ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; ++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= + esac + +-else ++fi + +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= + + +-fi + + + +@@ -3802,78 +4048,51 @@ + _ACEOF + + +-# Set name for machine-dependent library files ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-app-store-compliance" >&5 ++$as_echo_n "checking for --with-app-store-compliance... " >&6; } + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 +-$as_echo_n "checking MACHDEP... " >&6; } +-if test -z "$MACHDEP" +-then +- # avoid using uname for cross builds +- if test "$cross_compiling" = yes; then +- # ac_sys_system and ac_sys_release are used for setting +- # a lot of different things including 'define_xopen_source' +- # in the case statement below. +- case "$host" in +- *-*-linux-android*) +- ac_sys_system=Linux-android +- ;; +- *-*-linux*) +- ac_sys_system=Linux +- ;; +- *-*-cygwin*) +- ac_sys_system=Cygwin +- ;; +- *-*-vxworks*) +- ac_sys_system=VxWorks +- ;; +- *-*-emscripten) +- ac_sys_system=Emscripten +- ;; +- *-*-wasi) +- ac_sys_system=WASI +- ;; +- *) +- # for now, limit cross builds to known configurations +- MACHDEP="unknown" +- as_fn_error $? "cross build not supported for $host" "$LINENO" 5 +- esac +- ac_sys_release= +- else +- ac_sys_system=`uname -s` +- if test "$ac_sys_system" = "AIX" \ +- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then +- ac_sys_release=`uname -v` +- else +- ac_sys_release=`uname -r` +- fi +- fi +- ac_md_system=`echo $ac_sys_system | +- tr -d '/ ' | tr '[A-Z]' '[a-z]'` +- ac_md_release=`echo $ac_sys_release | +- tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` +- MACHDEP="$ac_md_system$ac_md_release" +- +- case $MACHDEP in +- aix*) MACHDEP="aix";; +- linux*) MACHDEP="linux";; +- cygwin*) MACHDEP="cygwin";; +- darwin*) MACHDEP="darwin";; +- '') MACHDEP="unknown";; ++# Check whether --with-app_store_compliance was given. ++if test "${with_app_store_compliance+set}" = set; then : ++ withval=$with_app_store_compliance; ++ case "$withval" in ++ yes) ++ case $ac_sys_system in ++ Darwin|iOS|tvOS|watchOS) ++ # iOS/tvOS/watchOS is able to share the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ ;; ++ *) as_fn_error $? "no default app store compliance patch available for $ac_sys_system" "$LINENO" 5 ;; ++ esac ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 ++$as_echo "applying default app store compliance patch" >&6; } ++ ;; ++ *) ++ APP_STORE_COMPLIANCE_PATCH="${withval}" ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying custom app store compliance patch" >&5 ++$as_echo "applying custom app store compliance patch" >&6; } ++ ;; + esac + +- if test "$ac_sys_system" = "SunOS"; then +- # For Solaris, there isn't an OS version specific macro defined +- # in most compilers, so we define one here. +- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` ++else + +-cat >>confdefs.h <<_ACEOF +-#define Py_SUNOS_VERSION $SUNOS_VERSION +-_ACEOF ++ case $ac_sys_system in ++ iOS|tvOS|watchOS) ++ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 ++$as_echo "applying default app store compliance patch" >&6; } ++ ;; ++ *) ++ # No default app compliance patching on any other platform ++ APP_STORE_COMPLIANCE_PATCH= ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not patching for app store compliance" >&5 ++$as_echo "not patching for app store compliance" >&6; } ++ ;; ++ esac + +- fi + fi +-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 +-$as_echo "\"$MACHDEP\"" >&6; } ++ ++ + + + if test "$cross_compiling" = yes; then +@@ -3881,27 +4100,93 @@ *-*-linux*) case "$host_cpu" in arm*) @@ -2075,73 +5520,70 @@ index af4a5bbfdf..03f864cc80 100755 - _host_cpu= + _host_ident= + ;; -+ *-apple-ios*-simulator) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-iphonesimulator-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-iphonesimulator-$host_cpu -+ esac -+ ;; -+ *-apple-ios*) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-iphoneos-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-iphoneos-$host_cpu -+ esac -+ ;; -+ *-apple-tvos*-simulator) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-appletvsimulator-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-appletvsimulator-$host_cpu -+ esac -+ ;; -+ *-apple-tvos*) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-appletvos-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-appletvos-$host_cpu -+ esac -+ ;; -+ *-apple-watchos*-simulator) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` ++ *-apple-ios*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking iOS deployment target" >&5 ++$as_echo_n "checking iOS deployment target... " >&6; } ++ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} ++ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0} ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $IPHONEOS_DEPLOYMENT_TARGET" >&5 ++$as_echo "$IPHONEOS_DEPLOYMENT_TARGET" >&6; } ++ + case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-watchsimualtor-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-watchsimualtor-$host_cpu -+ esac -+ ;; -+ *-apple-watchos*) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` ++ aarch64) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} ++ ;; ++ *) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-tvos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking tvOS deployment target" >&5 ++$as_echo_n "checking tvOS deployment target... " >&6; } ++ TVOS_DEPLOYMENT_TARGET=${_host_os:4} ++ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0} ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TVOS_DEPLOYMENT_TARGET" >&5 ++$as_echo "$TVOS_DEPLOYMENT_TARGET" >&6; } ++ + case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-watchosos-arm64_32 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-watchosos-$host_cpu -+ esac -+ ;; -+ *-apple-*) ++ aarch64) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device} ++ ;; ++ *) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-watchos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking watchOS deployment target" >&5 ++$as_echo_n "checking watchOS deployment target... " >&6; } ++ WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5 ++$as_echo "$WATCHOS_DEPLOYMENT_TARGET" >&6; } ++ + case "$host_cpu" in -+ arm*) -+ _host_ident=arm -+ ;; -+ *) -+ _host_ident=$host_cpu ++ aarch64) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device} ++ ;; ++ *) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device} ++ ;; + esac ;; *-*-vxworks*) @@ -2160,13 +5602,13 @@ index af4a5bbfdf..03f864cc80 100755 - _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" + _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" fi - + # Some systems cannot stand _XOPEN_SOURCE being defined at all; they -@@ -3957,6 +4025,13 @@ +@@ -3968,6 +4253,13 @@ define_xopen_source=no;; Darwin/[12][0-9].*) define_xopen_source=no;; -+ # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. ++ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; + tvOS/*) @@ -2176,51 +5618,90 @@ index af4a5bbfdf..03f864cc80 100755 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -6159,7 +6234,42 @@ +@@ -4030,6 +4322,12 @@ + CONFIGURE_MACOSX_DEPLOYMENT_TARGET= + EXPORT_MACOSX_DEPLOYMENT_TARGET='#' + ++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / ++# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. ++ ++ ++ ++ + # checks for alternative programs + + # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just +@@ -4062,6 +4360,26 @@ + ;; + esac + ++case $ac_sys_system in #( ++ iOS) : ++ ++ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ++ as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ++ ;; #( ++ tvOS) : ++ ++ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" ++ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" ++ ;; #( ++ watchOS) : ++ ++ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" ++ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" ++ ;; #( ++ *) : ++ ;; ++esac ++ + if test "$ac_sys_system" = "Darwin" + then + # Compiler selection on MacOSX is more complicated than +@@ -6170,7 +6488,42 @@ #elif defined(__gnu_hurd__) i386-gnu #elif defined(__APPLE__) -- darwin +# include "TargetConditionals.h" +# if TARGET_OS_IOS +# if TARGET_OS_SIMULATOR +# if __x86_64__ -+ iphonesimulator-x86_64 ++ x86_64-iphonesimulator +# else -+ iphonesimulator-arm64 ++ arm64-iphonesimulator +# endif +# else -+ iphoneos-arm64 ++ arm64-iphoneos +# endif +# elif TARGET_OS_TV +# if TARGET_OS_SIMULATOR +# if __x86_64__ -+ appletvsimulator-x86_64 ++ x86_64-appletvsimulator +# else -+ appletvsimulator-arm64 ++ arm64-appletvsimulator +# endif +# else -+ appletvos-arm64 ++ arm64-appletvos +# endif +# elif TARGET_OS_WATCH +# if TARGET_OS_SIMULATOR +# if __x86_64__ -+ watchsimulator-x86_64 ++ x86_64-watchsimulator +# else -+ watchsimulator-arm64 ++ arm64-watchsimulator +# endif +# else -+ watchos-arm64_32 ++ arm64_32-watchos +# endif +# elif TARGET_OS_OSX -+ darwin + darwin +# else +# error unknown Apple platform +# endif #elif defined(__VXWORKS__) vxworks #elif defined(__wasm32__) -@@ -6204,6 +6314,12 @@ +@@ -6215,6 +6568,12 @@ case $ac_sys_system in #( Darwin*) : MULTIARCH="" ;; #( @@ -2233,399 +5714,499 @@ index af4a5bbfdf..03f864cc80 100755 FreeBSD*) : MULTIARCH="" ;; #( *) : -@@ -6211,8 +6327,6 @@ +@@ -6222,8 +6581,6 @@ ;; esac - + -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 -$as_echo "$MULTIARCH" >&6; } - + if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then if test x$PLATFORM_TRIPLET != x$MULTIARCH; then -@@ -6222,6 +6336,16 @@ +@@ -6233,6 +6590,16 @@ MULTIARCH=$PLATFORM_TRIPLET fi - + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 +$as_echo "$MULTIARCH" >&6; } + +case $ac_sys_system in #( + iOS|tvOS|watchOS) : -+ SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f1` ;; #( ++ SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( + *) : + SOABI_PLATFORM=$PLATFORM_TRIPLET + ;; +esac - + if test x$MULTIARCH != x; then MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" -@@ -6265,8 +6389,14 @@ +@@ -6276,6 +6643,18 @@ PY_SUPPORT_TIER=3 ;; #( x86_64-*-freebsd*/clang) : PY_SUPPORT_TIER=3 ;; #( + aarch64-apple-ios*-simulator/clang) : + PY_SUPPORT_TIER=3 ;; #( -+ x86_64-apple-ios*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( + aarch64-apple-ios*/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-tvos*-simulator/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-tvos*/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-watchos*-simulator/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ arm64_32-apple-watchos*/clang) : + PY_SUPPORT_TIER=3 ;; #( *) : -- PY_SUPPORT_TIER=0 -+ PY_SUPPORT_TIER=0 + PY_SUPPORT_TIER=0 ;; - esac - -@@ -7210,11 +7340,23 @@ +@@ -6718,17 +7097,25 @@ + { $as_echo "$as_me:${as_lineno-$LINENO}: checking LDLIBRARY" >&5 + $as_echo_n "checking LDLIBRARY... " >&6; } + +-# MacOSX framework builds need more magic. LDLIBRARY is the dynamic ++# Apple framework builds need more magic. LDLIBRARY is the dynamic + # library that we build, but we do not want to link against it (we + # will find it with a -framework option). For this reason there is an + # extra variable BLDLIBRARY against which Python and the extension + # modules are linked, BLDLIBRARY. This is normally the same as +-# LDLIBRARY, but empty for MacOSX framework builds. ++# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, ++# but uses a non-versioned framework layout. + if test "$enable_framework" + then +- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' +- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} ++ case $ac_sys_system in ++ Darwin) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; ++ *) ++ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; ++ esac + BLDLIBRARY='' ++ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} + else + BLDLIBRARY='$(LDLIBRARY)' + fi +@@ -6741,64 +7128,70 @@ + + case $ac_sys_system in + CYGWIN*) +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- DLLLIBRARY='libpython$(LDVERSION).dll' +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ DLLLIBRARY='libpython$(LDVERSION).dll' ++ ;; + SunOS*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + hp*|HP*) +- case `uname -m` in +- ia64) +- LDLIBRARY='libpython$(LDVERSION).so' +- ;; +- *) +- LDLIBRARY='libpython$(LDVERSION).sl' +- ;; +- esac +- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} +- ;; ++ case `uname -m` in ++ ia64) ++ LDLIBRARY='libpython$(LDVERSION).so' ++ ;; ++ *) ++ LDLIBRARY='libpython$(LDVERSION).sl' ++ ;; ++ esac ++ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} ++ ;; + Darwin*) +- LDLIBRARY='libpython$(LDVERSION).dylib' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ++ ;; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ ;; + AIX*) +- LDLIBRARY='libpython$(LDVERSION).so' +- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} ++ ;; + + esac + else # shared is disabled + PY_ENABLE_SHARED=0 + case $ac_sys_system in + CYGWIN*) +- BLDLIBRARY='$(LIBRARY)' +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- ;; ++ BLDLIBRARY='$(LIBRARY)' ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ ;; + esac + fi + ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 ++$as_echo "$LDLIBRARY" >&6; } ++ + if test "$cross_compiling" = yes; then +- RUNSHARED= ++ RUNSHARED= + fi + + +@@ -6977,9 +7370,6 @@ + PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" + fi + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 +-$as_echo "$LDLIBRARY" >&6; } +- + # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable + case $ac_sys_system/$ac_sys_emscripten_target in #( + Emscripten/browser*) : +@@ -7221,11 +7611,16 @@ fi - + if test "$cross_compiling" = yes; then - case "$READELF" in - readelf|:) - as_fn_error $? "readelf for the host is required for cross builds" "$LINENO" 5 - ;; - esac -+ case "$host" in -+ *-apple-ios*) -+ # readelf not required for iOS cross builds. -+ ;; -+ *-apple-tvos*) -+ # readelf not required for tvOS cross builds. -+ ;; -+ *-apple-watchos*) -+ # readelf not required for watchOS cross builds. -+ ;; -+ *) -+ case "$READELF" in -+ readelf|:) -+ as_fn_error $? "readelf for the host is required for cross builds" "$LINENO" 5 -+ ;; -+ esac -+ esac - fi - - -@@ -10858,6 +11000,7 @@ - esac - ;; - CYGWIN*) SHLIB_SUFFIX=.dll;; -+ iOS|tvOS|watchOS) SHLIB_SUFFIX=.dylib;; - *) SHLIB_SUFFIX=.so;; - esac ++ case "$ac_sys_system" in ++ iOS|tvOS|watchOS) ;; ++ *) ++ case "$READELF" in ++ readelf|:) ++ as_fn_error $? "readelf for the host is required for cross builds" "$LINENO" 5 ++ ;; ++ esac ++ ;; ++ esac fi -@@ -10940,6 +11083,9 @@ + + +@@ -10951,6 +11346,11 @@ BLDSHARED="$LDSHARED" fi ;; + iOS/*|tvOS/*|watchOS/*) -+ LDSHARED='$(CC) -dynamiclib -undefined dynamic_lookup' -+ LDCXXSHARED='$(CXX) -dynamiclib -undefined dynamic_lookup';; ++ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ BLDSHARED="$LDSHARED" ++ ;; Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; -@@ -11348,8 +11494,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBUUID" >&5 --$as_echo_n "checking for LIBUUID... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for uuid >= 2.20" >&5 -+$as_echo_n "checking for uuid >= 2.20... " >&6; } - - if test -n "$LIBUUID_CFLAGS"; then - pkg_cv_LIBUUID_CFLAGS="$LIBUUID_CFLAGS" -@@ -11389,7 +11535,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -11532,7 +11678,7 @@ - - - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - save_CFLAGS=$CFLAGS -@@ -12183,23 +12329,35 @@ - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_system_ffi" >&5 - $as_echo "$with_system_ffi" >&6; } - else -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 --$as_echo "yes" >&6; } -- if test "$with_system_ffi" != "" -+ if test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "watchOS" - then -- { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with(out)-system-ffi is ignored on this platform" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+$as_echo "no" >&6; } -+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Using user-provided libffi configuration" >&5 -+$as_echo "$as_me: WARNING: Using user-provided libffi configuration" >&2;} -+ LIBFFI_LIBDIR="${LIBFFI_LIBDIR}" -+ LIBFFI_LIB="${LIBFFI_LIB}" -+ else -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -+$as_echo "yes" >&6; } -+ if test "$with_system_ffi" != "" -+ then -+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with(out)-system-ffi is ignored on this platform" >&5 - $as_echo "$as_me: WARNING: --with(out)-system-ffi is ignored on this platform" >&2;} -+ fi -+ with_system_ffi="yes" - fi -- with_system_ffi="yes" +@@ -11078,20 +11478,18 @@ + Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; + Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; + # -u libsys_s pulls in all symbols in libsys +- Darwin/*) ++ Darwin/*|iOS/*|tvOS/*|watchOS/*) + LINKFORSHARED="$extra_undefs -framework CoreFoundation" + + # Issue #18075: the default maximum stack size (8MBytes) is too + # small for the default recursion limit. Increase the stack size + # to ensure that tests don't crash +- stack_size="1000000" # 16 MB +- if test "$with_ubsan" = "yes" +- then +- # Undefined behavior sanitizer requires an even deeper stack +- stack_size="4000000" # 64 MB +- fi +- +- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" ++ stack_size="1000000" # 16 MB ++ if test "$with_ubsan" = "yes" ++ then ++ # Undefined behavior sanitizer requires an even deeper stack ++ stack_size="4000000" # 64 MB ++ fi + + + cat >>confdefs.h <<_ACEOF +@@ -11099,11 +11497,17 @@ + _ACEOF + + +- if test "$enable_framework" +- then +- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ if test $ac_sys_system = "Darwin"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" ++ ++ if test "$enable_framework"; then ++ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ fi ++ LINKFORSHARED="$LINKFORSHARED" ++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi +- LINKFORSHARED="$LINKFORSHARED";; ++ ;; + OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; + SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; + ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; +@@ -12179,7 +12583,7 @@ fi - + + +-if test "$ac_sys_system" = "Darwin" ++if test "$ac_sys_system" = "Darwin" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" + then + case "$with_system_ffi" in + "") +@@ -12207,10 +12611,12 @@ if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`" else - LIBFFI_INCLUDEDIR="" + LIBFFI_INCLUDEDIR="${LIBFFI_INCLUDEDIR}" fi - - + + + + # Check for use of the system libmpdec library { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-system-libmpdec" >&5 $as_echo_n "checking for --with-system-libmpdec... " >&6; } -@@ -12340,8 +12498,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBNSL" >&5 --$as_echo_n "checking for LIBNSL... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libnsl" >&5 -+$as_echo_n "checking for libnsl... " >&6; } - - if test -n "$LIBNSL_CFLAGS"; then - pkg_cv_LIBNSL_CFLAGS="$LIBNSL_CFLAGS" -@@ -12381,7 +12539,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -12482,7 +12640,7 @@ - LIBNSL_LIBS=${LIBNSL_LIBS-$libnsl} - - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - LIBNSL_CFLAGS=${LIBNSL_CFLAGS-""} -@@ -12630,8 +12788,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBSQLITE3" >&5 --$as_echo_n "checking for LIBSQLITE3... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3 >= 3.7.15" >&5 -+$as_echo_n "checking for sqlite3 >= 3.7.15... " >&6; } - - if test -n "$LIBSQLITE3_CFLAGS"; then - pkg_cv_LIBSQLITE3_CFLAGS="$LIBSQLITE3_CFLAGS" -@@ -12671,7 +12829,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -12693,7 +12851,7 @@ - - - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - LIBSQLITE3_CFLAGS=${LIBSQLITE3_CFLAGS-""} -@@ -13462,8 +13620,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for TCLTK" >&5 --$as_echo_n "checking for TCLTK... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $_QUERY" >&5 -+$as_echo_n "checking for $_QUERY... " >&6; } - - if test -n "$TCLTK_CFLAGS"; then - pkg_cv_TCLTK_CFLAGS="$TCLTK_CFLAGS" -@@ -13503,7 +13661,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -13521,7 +13679,7 @@ - - found_tcltk=no - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - found_tcltk=no +@@ -15209,27 +15615,27 @@ + # checks for library functions + for ac_func in \ + accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ +- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ ++ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ + faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ +- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ +- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ +- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ ++ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ ++ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \ ++ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ + getpeername getpgid getpid getppid getpriority _getpty \ + getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ + getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ + lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ + mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ +- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ ++ pipe2 plock poll posix_fadvise posix_fallocate \ + pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ + pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ + rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ + sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ + sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ + setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ +- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ ++ setresuid setreuid setsid setuid setvbuf shutdown sigaction \ + sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ + sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ +- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ ++ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ + tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ + wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ + +@@ -15262,8 +15668,46 @@ + + fi + ++# iOS/tvOS/watchOS define some system methods that can be linked (so they are ++# found by configure), but either raise a compilation error (because the ++# header definition prevents usage - autoconf doesn't use the headers), or ++# raise an error if used at runtime. Force these symbols off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ for ac_func in getentropy getgroups system ++do : ++ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ++ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" ++if eval test \"x\$"$as_ac_var"\" = x"yes"; then : ++ cat >>confdefs.h <<_ACEOF ++#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 ++_ACEOF ++ ++fi ++done ++ ++fi ++ ++# tvOS/watchOS have some additional methods that can be found, but not used. ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ for ac_func in \ ++ execv fork fork1 posix_spawn posix_spawnp \ ++ sigaltstack \ ++ ++do : ++ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ++ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" ++if eval test \"x\$"$as_ac_var"\" = x"yes"; then : ++ cat >>confdefs.h <<_ACEOF ++#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 ++_ACEOF ++ ++fi ++done ++ ++fi ++ + ac_fn_c_check_decl "$LINENO" "dirfd" "ac_cv_have_decl_dirfd" "#include +- #include ++ #include + " + if test "x$ac_cv_have_decl_dirfd" = xyes; then : + +@@ -17817,8 +18261,9 @@ + + + # check for openpty, login_tty, and forkpty +- +-for ac_func in openpty ++# tvOS/watchOS have functions for tty, but can't use them ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ for ac_func in openpty + do : + ac_fn_c_check_func "$LINENO" "openpty" "ac_cv_func_openpty" + if test "x$ac_cv_func_openpty" = xyes; then : +@@ -17908,14 +18353,12 @@ + LIBS="$LIBS -lbsd" + fi + +- + fi + +- + fi + done + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5 + $as_echo_n "checking for library containing login_tty... " >&6; } + if ${ac_cv_search_login_tty+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -17974,7 +18417,7 @@ + + fi + +-for ac_func in forkpty ++ for ac_func in forkpty + do : + ac_fn_c_check_func "$LINENO" "forkpty" "ac_cv_func_forkpty" + if test "x$ac_cv_func_forkpty" = xyes; then : +@@ -18064,13 +18507,12 @@ + LIBS="$LIBS -lbsd" + fi + +- + fi + +- + fi + done + ++fi + + # check for long file support functions + for ac_func in fseek64 fseeko fstatvfs ftell64 ftello statvfs +@@ -18568,7 +19010,12 @@ + done + + +-for ac_func in clock_settime ++# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by ++# configure), but when used in an unprivileged process, it crashes rather than ++# returning an error. Force the symbol off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ++then ++ for ac_func in clock_settime + do : + ac_fn_c_check_func "$LINENO" "clock_settime" "ac_cv_func_clock_settime" + if test "x$ac_cv_func_clock_settime" = xyes; then : +@@ -18578,7 +19025,7 @@ + + else + +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 + $as_echo_n "checking for clock_settime in -lrt... " >&6; } + if ${ac_cv_lib_rt_clock_settime+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -18616,7 +19063,7 @@ + $as_echo "$ac_cv_lib_rt_clock_settime" >&6; } + if test "x$ac_cv_lib_rt_clock_settime" = xyes; then : + +- $as_echo "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h ++ $as_echo "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h + + + fi +@@ -18625,6 +19072,7 @@ + fi + done + ++fi + + for ac_func in clock_nanosleep + do : +@@ -18838,7 +19286,9 @@ + else + if test "$cross_compiling" = yes; then : + +-if test "${enable_ipv6+set}" = set; then ++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ ac_cv_buggy_getaddrinfo="no" ++elif test "${enable_ipv6+set}" = set; then + ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" else -@@ -13557,8 +13715,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for X11" >&5 --$as_echo_n "checking for X11... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for x11" >&5 -+$as_echo_n "checking for x11... " >&6; } - - if test -n "$X11_CFLAGS"; then - pkg_cv_X11_CFLAGS="$X11_CFLAGS" -@@ -13598,7 +13756,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -13625,7 +13783,7 @@ - and X11_LIBS to avoid the need to call pkg-config. - See the pkg-config man page for more details." "$LINENO" 5 - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 - $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -@@ -16201,8 +16359,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ZLIB" >&5 --$as_echo_n "checking for ZLIB... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for zlib >= 1.2.0" >&5 -+$as_echo_n "checking for zlib >= 1.2.0... " >&6; } - - if test -n "$ZLIB_CFLAGS"; then - pkg_cv_ZLIB_CFLAGS="$ZLIB_CFLAGS" -@@ -16242,7 +16400,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -16386,7 +16544,7 @@ - - - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - save_CFLAGS=$CFLAGS -@@ -16549,8 +16707,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BZIP2" >&5 --$as_echo_n "checking for BZIP2... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for bzip2" >&5 -+$as_echo_n "checking for bzip2... " >&6; } - - if test -n "$BZIP2_CFLAGS"; then - pkg_cv_BZIP2_CFLAGS="$BZIP2_CFLAGS" -@@ -16590,7 +16748,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -16687,7 +16845,7 @@ - - - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - save_CFLAGS=$CFLAGS -@@ -16779,8 +16937,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBLZMA" >&5 --$as_echo_n "checking for LIBLZMA... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblzma" >&5 -+$as_echo_n "checking for liblzma... " >&6; } - - if test -n "$LIBLZMA_CFLAGS"; then - pkg_cv_LIBLZMA_CFLAGS="$LIBLZMA_CFLAGS" -@@ -16820,7 +16978,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -16917,7 +17075,7 @@ - - - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - save_CFLAGS=$CFLAGS -@@ -18150,8 +18308,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBCRYPT" >&5 --$as_echo_n "checking for LIBCRYPT... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libxcrypt >= 3.1.1" >&5 -+$as_echo_n "checking for libxcrypt >= 3.1.1... " >&6; } - - if test -n "$LIBCRYPT_CFLAGS"; then - pkg_cv_LIBCRYPT_CFLAGS="$LIBCRYPT_CFLAGS" -@@ -18191,7 +18349,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -18288,7 +18446,7 @@ - - - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - save_CFLAGS=$CFLAGS -@@ -20750,7 +20908,7 @@ + ac_cv_buggy_getaddrinfo=yes +@@ -20762,7 +21212,7 @@ $as_echo "$ABIFLAGS" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking SOABI" >&5 $as_echo_n "checking SOABI... " >&6; } @@ -2633,57 +6214,96 @@ index af4a5bbfdf..03f864cc80 100755 +SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SOABI" >&5 $as_echo "$SOABI" >&6; } - -@@ -20758,7 +20916,7 @@ + +@@ -20770,7 +21220,7 @@ if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then # Similar to SOABI but remove "d" flag from ABIFLAGS - + - ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} + ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} - + cat >>confdefs.h <<_ACEOF #define ALT_SOABI "${ALT_SOABI}" -@@ -23063,8 +23221,8 @@ - if ! $found; then - OPENSSL_INCLUDES= - for ssldir in $ssldirs; do -- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for openssl/ssl.h in $ssldir" >&5 --$as_echo_n "checking for openssl/ssl.h in $ssldir... " >&6; } -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for include/openssl/ssl.h in $ssldir" >&5 -+$as_echo_n "checking for include/openssl/ssl.h in $ssldir... " >&6; } - if test -f "$ssldir/include/openssl/ssl.h"; then - OPENSSL_INCLUDES="-I$ssldir/include" - OPENSSL_LDFLAGS="-L$ssldir/lib" -@@ -23445,8 +23603,8 @@ - - - pkg_failed=no --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBB2" >&5 --$as_echo_n "checking for LIBB2... " >&6; } -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libb2" >&5 -+$as_echo_n "checking for libb2... " >&6; } - - if test -n "$LIBB2_CFLAGS"; then - pkg_cv_LIBB2_CFLAGS="$LIBB2_CFLAGS" -@@ -23486,7 +23644,7 @@ - - - if test $pkg_failed = yes; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - - if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then -@@ -23504,7 +23662,7 @@ - - have_libb2=no - elif test $pkg_failed = untried; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 - $as_echo "no" >&6; } - have_libb2=no - else -@@ -23575,6 +23733,29 @@ +@@ -22152,24 +22602,28 @@ + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 + $as_echo "$as_me: checking for device files" >&6;} + +-if test "x$cross_compiling" = xyes; then +- if test "${ac_cv_file__dev_ptmx+set}" != set; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then ++ ac_cv_file__dev_ptmx=no ++ ac_cv_file__dev_ptc=no ++else ++ if test "x$cross_compiling" = xyes; then ++ if test "${ac_cv_file__dev_ptmx+set}" != set; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 + $as_echo_n "checking for /dev/ptmx... " >&6; } +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + $as_echo "not set" >&6; } +- as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 +- fi +- if test "${ac_cv_file__dev_ptc+set}" != set; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 ++ as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 ++ fi ++ if test "${ac_cv_file__dev_ptc+set}" != set; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + $as_echo_n "checking for /dev/ptc... " >&6; } +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + $as_echo "not set" >&6; } +- as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 ++ as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 ++ fi + fi +-fi + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 + $as_echo_n "checking for /dev/ptmx... " >&6; } + if ${ac_cv_file__dev_ptmx+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -22188,12 +22642,12 @@ + + fi + +-if test "x$ac_cv_file__dev_ptmx" = xyes; then ++ if test "x$ac_cv_file__dev_ptmx" = xyes; then + + $as_echo "#define HAVE_DEV_PTMX 1" >>confdefs.h + +-fi +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 ++ fi ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + $as_echo_n "checking for /dev/ptc... " >&6; } + if ${ac_cv_file__dev_ptc+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -22212,10 +22666,11 @@ + + fi + +-if test "x$ac_cv_file__dev_ptc" = xyes; then ++ if test "x$ac_cv_file__dev_ptc" = xyes; then + + $as_echo "#define HAVE_DEV_PTC 1" >>confdefs.h + ++ fi + fi + + if test $ac_sys_system = Darwin +@@ -22701,6 +23156,8 @@ + with_ensurepip=no ;; #( + WASI) : + with_ensurepip=no ;; #( ++ iOS|tvOS|watchOS) : ++ with_ensurepip=no ;; #( + *) : + with_ensurepip=upgrade + ;; +@@ -23604,6 +24061,27 @@ py_cv_module_ossaudiodev=n/a py_cv_module_spwd=n/a ;; #( @@ -2699,10 +6319,8 @@ index af4a5bbfdf..03f864cc80 100755 + py_cv_module__posixsubprocess=n/a + py_cv_module__scproxy=n/a + py_cv_module__tkinter=n/a -+ py_cv_module__xxsubinterpreters=n/a + py_cv_module_grp=n/a + py_cv_module_nis=n/a -+ py_cv_module_ossaudiodev=n/a + py_cv_module_readline=n/a + py_cv_module_pwd=n/a + py_cv_module_spwd=n/a @@ -2711,16 +6329,46 @@ index af4a5bbfdf..03f864cc80 100755 + + ;; #( CYGWIN*) : - - + + +@@ -26886,6 +27364,9 @@ + "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;; + "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; + "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; ++ "iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES iOS/Resources/Info.plist" ;; ++ "tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES tvOS/Resources/Info.plist" ;; ++ "watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES watchOS/Resources/Info.plist" ;; + "Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;; + "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; + "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; diff --git a/configure.ac b/configure.ac -index e1cbb7c7fb..34cf532a87 100644 +index 7b4000fa9c3..3c6d293a0d3 100644 --- a/configure.ac +++ b/configure.ac -@@ -545,6 +545,15 @@ - *-*-cygwin*) - ac_sys_system=Cygwin - ;; +@@ -303,6 +303,161 @@ + AC_MSG_ERROR([pkg-config is required])] + fi + ++# Set name for machine-dependent library files ++AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) ++AC_MSG_CHECKING([MACHDEP]) ++if test -z "$MACHDEP" ++then ++ # avoid using uname for cross builds ++ if test "$cross_compiling" = yes; then ++ # ac_sys_system and ac_sys_release are used for setting ++ # a lot of different things including 'define_xopen_source' ++ # in the case statement below. ++ case "$host" in ++ *-*-linux-android*) ++ ac_sys_system=Linux-android ++ ;; ++ *-*-linux*) ++ ac_sys_system=Linux ++ ;; ++ *-*-cygwin*) ++ ac_sys_system=Cygwin ++ ;; + *-apple-ios*) + ac_sys_system=iOS + ;; @@ -2730,10 +6378,541 @@ index e1cbb7c7fb..34cf532a87 100644 + *-apple-watchos*) + ac_sys_system=watchOS + ;; - *-*-vxworks*) - ac_sys_system=VxWorks - ;; -@@ -591,27 +600,96 @@ ++ *-*-vxworks*) ++ ac_sys_system=VxWorks ++ ;; ++ *-*-emscripten) ++ ac_sys_system=Emscripten ++ ;; ++ *-*-wasi) ++ ac_sys_system=WASI ++ ;; ++ *) ++ # for now, limit cross builds to known configurations ++ MACHDEP="unknown" ++ AC_MSG_ERROR([cross build not supported for $host]) ++ esac ++ ac_sys_release= ++ else ++ ac_sys_system=`uname -s` ++ if test "$ac_sys_system" = "AIX" \ ++ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then ++ ac_sys_release=`uname -v` ++ else ++ ac_sys_release=`uname -r` ++ fi ++ fi ++ ac_md_system=`echo $ac_sys_system | ++ tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` ++ ac_md_release=`echo $ac_sys_release | ++ tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` ++ MACHDEP="$ac_md_system$ac_md_release" ++ ++ case $MACHDEP in ++ aix*) MACHDEP="aix";; ++ linux*) MACHDEP="linux";; ++ cygwin*) MACHDEP="cygwin";; ++ darwin*) MACHDEP="darwin";; ++ '') MACHDEP="unknown";; ++ esac ++ ++ if test "$ac_sys_system" = "SunOS"; then ++ # For Solaris, there isn't an OS version specific macro defined ++ # in most compilers, so we define one here. ++ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` ++ AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], ++ [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) ++ fi ++fi ++AC_MSG_RESULT(["$MACHDEP"]) ++ ++# On cross-compile builds, configure will look for a host-specific compiler by ++# prepending the user-provided host triple to the required binary name. ++# ++# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", ++# which isn't a binary that exists, and isn't very convenient, as it contains the ++# iOS version. As the default cross-compiler name won't exist, configure falls ++# back to gcc, which *definitely* won't work. We're providing wrapper scripts for ++# these tools; the binary names of these scripts are better defaults than "gcc". ++# This only requires that the user put the platform scripts folder (e.g., ++# "iOS/Resources/bin") in their path, rather than defining platform-specific ++# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to ++# either put the platform scripts folder in the path, or specify CC etc, ++# configure will fail. ++if test -z "$AR"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; ++ aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; ++ x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; ++ ++ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;; ++ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;; ++ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;; ++ ++ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;; ++ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; ++ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; ++ *) ++ esac ++fi ++if test -z "$CC"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; ++ aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; ++ x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++ ++ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;; ++ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;; ++ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;; ++ ++ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;; ++ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;; ++ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;; ++ *) ++ esac ++fi ++if test -z "$CPP"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; ++ aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; ++ x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++ ++ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;; ++ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;; ++ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;; ++ ++ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;; ++ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;; ++ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;; ++ *) ++ esac ++fi ++if test -z "$CXX"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang ;; ++ aarch64-apple-ios*) CXX=arm64-apple-ios-clang ;; ++ x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang ;; ++ ++ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang ;; ++ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang ;; ++ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang ;; ++ ++ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang ;; ++ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang ;; ++ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang ;; ++ *) ++ esac ++fi ++ + AC_MSG_CHECKING([for --enable-universalsdk]) + AC_ARG_ENABLE(universalsdk, + AS_HELP_STRING([--enable-universalsdk@<:@=SDKDIR@:>@], +@@ -412,214 +567,328 @@ + [ + case $enableval in + yes) +- enableval=/Library/Frameworks ++ case $ac_sys_system in ++ Darwin) enableval=/Library/Frameworks ;; ++ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; ++ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;; ++ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;; ++ *) AC_MSG_ERROR([Unknown platform for framework build]) ++ esac + esac ++ + case $enableval in + no) +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE"; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= ++ case $ac_sys_system in ++ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; ++ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; ++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ ++ if test "x${prefix}" = "xNONE"; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= ++ esac + ;; + *) + PYTHONFRAMEWORKPREFIX="${enableval}" + PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR +- FRAMEWORKINSTALLFIRST="frameworkinstallstructure" +- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " +- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" +- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" +- FRAMEWORKPYTHONW="frameworkpythonw" +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi + +- case "${enableval}" in +- /System*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- if test "${prefix}" = "NONE" ; then +- # See below +- FRAMEWORKUNIXTOOLSPREFIX="/usr" +- fi +- ;; ++ case $ac_sys_system in #( ++ Darwin) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" ++ FRAMEWORKPYTHONW="frameworkpythonw" ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi + +- /Library*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ case "${enableval}" in ++ /System*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ if test "${prefix}" = "NONE" ; then ++ # See below ++ FRAMEWORKUNIXTOOLSPREFIX="/usr" ++ fi ++ ;; ++ ++ /Library*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; ++ ++ */Library/Frameworks) ++ MDIR="`dirname "${enableval}"`" ++ MDIR="`dirname "${MDIR}"`" ++ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" ++ ++ if test "${prefix}" = "NONE"; then ++ # User hasn't specified the ++ # --prefix option, but wants to install ++ # the framework in a non-default location, ++ # ensure that the compatibility links get ++ # installed relative to that prefix as well ++ # instead of in /usr/local. ++ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" ++ fi ++ ;; + +- */Library/Frameworks) +- MDIR="`dirname "${enableval}"`" +- MDIR="`dirname "${MDIR}"`" +- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" +- +- if test "${prefix}" = "NONE"; then +- # User hasn't specified the +- # --prefix option, but wants to install +- # the framework in a non-default location, +- # ensure that the compatibility links get +- # installed relative to that prefix as well +- # instead of in /usr/local. +- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" +- fi +- ;; ++ *) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; ++ esac + +- *) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} ++ RESSRCDIR=Mac/Resources/framework ++ ++ # Add files for Mac specific code to the list of output ++ # files: ++ AC_CONFIG_FILES([Mac/Makefile]) ++ AC_CONFIG_FILES([Mac/PythonLauncher/Makefile]) ++ AC_CONFIG_FILES([Mac/Resources/framework/Info.plist]) ++ AC_CONFIG_FILES([Mac/Resources/app/Info.plist]) ++ ;; ++ iOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=iOS/Resources ++ ++ AC_CONFIG_FILES([iOS/Resources/Info.plist]) ++ ;; ++ tvOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=tvOS/Resources ++ ++ AC_CONFIG_FILES([tvOS/Resources/Info.plist]) ++ ;; ++ watchOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=watchOS/Resources ++ ++ AC_CONFIG_FILES([watchOS/Resources/Info.plist]) ++ ;; ++ *) ++ AC_MSG_ERROR([Unknown platform for framework build]) ++ ;; ++ esac + esac +- +- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION +- +- # Add files for Mac specific code to the list of output +- # files: +- AC_CONFIG_FILES(Mac/Makefile) +- AC_CONFIG_FILES(Mac/PythonLauncher/Makefile) +- AC_CONFIG_FILES(Mac/Resources/framework/Info.plist) +- AC_CONFIG_FILES(Mac/Resources/app/Info.plist) +- esac + ],[ +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= +- +-]) +-AC_SUBST(PYTHONFRAMEWORK) +-AC_SUBST(PYTHONFRAMEWORKIDENTIFIER) +-AC_SUBST(PYTHONFRAMEWORKDIR) +-AC_SUBST(PYTHONFRAMEWORKPREFIX) +-AC_SUBST(PYTHONFRAMEWORKINSTALLDIR) +-AC_SUBST(FRAMEWORKINSTALLFIRST) +-AC_SUBST(FRAMEWORKINSTALLLAST) +-AC_SUBST(FRAMEWORKALTINSTALLFIRST) +-AC_SUBST(FRAMEWORKALTINSTALLLAST) +-AC_SUBST(FRAMEWORKPYTHONW) +-AC_SUBST(FRAMEWORKUNIXTOOLSPREFIX) +-AC_SUBST(FRAMEWORKINSTALLAPPSPREFIX) ++ case $ac_sys_system in ++ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; ++ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; ++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= ++ esac ++]) ++AC_SUBST([PYTHONFRAMEWORK]) ++AC_SUBST([PYTHONFRAMEWORKIDENTIFIER]) ++AC_SUBST([PYTHONFRAMEWORKDIR]) ++AC_SUBST([PYTHONFRAMEWORKPREFIX]) ++AC_SUBST([PYTHONFRAMEWORKINSTALLDIR]) ++AC_SUBST([PYTHONFRAMEWORKINSTALLNAMEPREFIX]) ++AC_SUBST([RESSRCDIR]) ++AC_SUBST([FRAMEWORKINSTALLFIRST]) ++AC_SUBST([FRAMEWORKINSTALLLAST]) ++AC_SUBST([FRAMEWORKALTINSTALLFIRST]) ++AC_SUBST([FRAMEWORKALTINSTALLLAST]) ++AC_SUBST([FRAMEWORKPYTHONW]) ++AC_SUBST([FRAMEWORKUNIXTOOLSPREFIX]) ++AC_SUBST([FRAMEWORKINSTALLAPPSPREFIX]) ++AC_SUBST([INSTALLTARGETS]) + + AC_DEFINE_UNQUOTED(_PYTHONFRAMEWORK, "${PYTHONFRAMEWORK}", [framework name]) + +-# Set name for machine-dependent library files +-AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) +-AC_MSG_CHECKING(MACHDEP) +-if test -z "$MACHDEP" +-then +- # avoid using uname for cross builds +- if test "$cross_compiling" = yes; then +- # ac_sys_system and ac_sys_release are used for setting +- # a lot of different things including 'define_xopen_source' +- # in the case statement below. +- case "$host" in +- *-*-linux-android*) +- ac_sys_system=Linux-android +- ;; +- *-*-linux*) +- ac_sys_system=Linux +- ;; +- *-*-cygwin*) +- ac_sys_system=Cygwin +- ;; +- *-*-vxworks*) +- ac_sys_system=VxWorks +- ;; +- *-*-emscripten) +- ac_sys_system=Emscripten +- ;; +- *-*-wasi) +- ac_sys_system=WASI +- ;; +- *) +- # for now, limit cross builds to known configurations +- MACHDEP="unknown" +- AC_MSG_ERROR([cross build not supported for $host]) +- esac +- ac_sys_release= +- else +- ac_sys_system=`uname -s` +- if test "$ac_sys_system" = "AIX" \ +- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then +- ac_sys_release=`uname -v` +- else +- ac_sys_release=`uname -r` +- fi +- fi +- ac_md_system=`echo $ac_sys_system | +- tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` +- ac_md_release=`echo $ac_sys_release | +- tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` +- MACHDEP="$ac_md_system$ac_md_release" +- +- case $MACHDEP in +- aix*) MACHDEP="aix";; +- linux*) MACHDEP="linux";; +- cygwin*) MACHDEP="cygwin";; +- darwin*) MACHDEP="darwin";; +- '') MACHDEP="unknown";; ++dnl quadrigraphs "@<:@" and "@:>@" produce "[" and "]" in the output ++AC_MSG_CHECKING([for --with-app-store-compliance]) ++AC_ARG_WITH( ++ [app_store_compliance], ++ [AS_HELP_STRING( ++ [--with-app-store-compliance=@<:@PATCH-FILE@:>@], ++ [Enable any patches required for compiliance with app stores. ++ Optional PATCH-FILE specifies the custom patch to apply.] ++ )],[ ++ case "$withval" in ++ yes) ++ case $ac_sys_system in ++ Darwin|iOS|tvOS|watchOS) ++ # iOS/tvOS/watchOS is able to share the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ ;; ++ *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;; ++ esac ++ AC_MSG_RESULT([applying default app store compliance patch]) ++ ;; ++ *) ++ APP_STORE_COMPLIANCE_PATCH="${withval}" ++ AC_MSG_RESULT([applying custom app store compliance patch]) ++ ;; + esac ++ ],[ ++ case $ac_sys_system in ++ iOS|tvOS|watchOS) ++ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ AC_MSG_RESULT([applying default app store compliance patch]) ++ ;; ++ *) ++ # No default app compliance patching on any other platform ++ APP_STORE_COMPLIANCE_PATCH= ++ AC_MSG_RESULT([not patching for app store compliance]) ++ ;; ++ esac ++]) ++AC_SUBST([APP_STORE_COMPLIANCE_PATCH]) + +- if test "$ac_sys_system" = "SunOS"; then +- # For Solaris, there isn't an OS version specific macro defined +- # in most compilers, so we define one here. +- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` +- AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], +- [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) +- fi +-fi +-AC_MSG_RESULT("$MACHDEP") +- +-AC_SUBST(_PYTHON_HOST_PLATFORM) ++AC_SUBST([_PYTHON_HOST_PLATFORM]) + if test "$cross_compiling" = yes; then + case "$host" in *-*-linux*) case "$host_cpu" in arm*) @@ -2749,73 +6928,64 @@ index e1cbb7c7fb..34cf532a87 100644 - _host_cpu= + _host_ident= + ;; -+ *-apple-ios*-simulator) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-iphonesimulator-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-iphonesimulator-$host_cpu -+ esac -+ ;; -+ *-apple-ios*) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-iphoneos-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-iphoneos-$host_cpu -+ esac -+ ;; -+ *-apple-tvos*-simulator) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-appletvsimulator-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-appletvsimulator-$host_cpu -+ esac -+ ;; -+ *-apple-tvos*) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-appletvos-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-appletvos-$host_cpu -+ esac -+ ;; -+ *-apple-watchos*-simulator) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` ++ *-apple-ios*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version ++ AC_MSG_CHECKING([iOS deployment target]) ++ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} ++ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0} ++ AC_MSG_RESULT([$IPHONEOS_DEPLOYMENT_TARGET]) ++ + case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-watchsimualtor-arm64 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-watchsimualtor-$host_cpu -+ esac -+ ;; -+ *-apple-watchos*) -+ _host_os_min_version=`echo $host | cut -d '-' -f3` ++ aarch64) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} ++ ;; ++ *) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-tvos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version ++ AC_MSG_CHECKING([tvOS deployment target]) ++ TVOS_DEPLOYMENT_TARGET=${_host_os:4} ++ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0} ++ AC_MSG_RESULT([$TVOS_DEPLOYMENT_TARGET]) ++ + case "$host_cpu" in -+ aarch64) -+ _host_ident=${_host_os_min_version:3}-watchosos-arm64_32 -+ ;; -+ *) -+ _host_ident=${_host_os_min_version:3}-watchosos-$host_cpu -+ esac -+ ;; -+ *-apple-*) ++ aarch64) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device} ++ ;; ++ *) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-watchos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version ++ AC_MSG_CHECKING([watchOS deployment target]) ++ WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET]) ++ + case "$host_cpu" in -+ arm*) -+ _host_ident=arm -+ ;; -+ *) -+ _host_ident=$host_cpu ++ aarch64) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device} ++ ;; ++ *) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device} ++ ;; + esac ;; *-*-vxworks*) @@ -2834,13 +7004,13 @@ index e1cbb7c7fb..34cf532a87 100644 - _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" + _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" fi - + # Some systems cannot stand _XOPEN_SOURCE being defined at all; they -@@ -675,6 +753,13 @@ +@@ -683,6 +952,13 @@ define_xopen_source=no;; Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) define_xopen_source=no;; -+ # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. ++ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; + tvOS/*) @@ -2850,51 +7020,95 @@ index e1cbb7c7fb..34cf532a87 100644 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -1054,7 +1139,42 @@ +@@ -739,6 +1015,12 @@ + CONFIGURE_MACOSX_DEPLOYMENT_TARGET= + EXPORT_MACOSX_DEPLOYMENT_TARGET='#' + ++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / ++# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. ++AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET]) ++AC_SUBST([TVOS_DEPLOYMENT_TARGET]) ++AC_SUBST([WATCHOS_DEPLOYMENT_TARGET]) ++ + # checks for alternative programs + + # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just +@@ -771,6 +1053,20 @@ + ], + ) + ++dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS version. ++AS_CASE([$ac_sys_system], ++ [iOS], [ ++ AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) ++ AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) ++ ],[tvOS], [ ++ AS_VAR_APPEND([CFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"]) ++ AS_VAR_APPEND([LDFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"]) ++ ],[watchOS], [ ++ AS_VAR_APPEND([CFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"]) ++ AS_VAR_APPEND([LDFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"]) ++ ], ++) ++ + if test "$ac_sys_system" = "Darwin" + then + # Compiler selection on MacOSX is more complicated than +@@ -1062,7 +1358,42 @@ #elif defined(__gnu_hurd__) i386-gnu #elif defined(__APPLE__) -- darwin +# include "TargetConditionals.h" +# if TARGET_OS_IOS +# if TARGET_OS_SIMULATOR +# if __x86_64__ -+ iphonesimulator-x86_64 ++ x86_64-iphonesimulator +# else -+ iphonesimulator-arm64 ++ arm64-iphonesimulator +# endif +# else -+ iphoneos-arm64 ++ arm64-iphoneos +# endif +# elif TARGET_OS_TV +# if TARGET_OS_SIMULATOR +# if __x86_64__ -+ appletvsimulator-x86_64 ++ x86_64-appletvsimulator +# else -+ appletvsimulator-arm64 ++ arm64-appletvsimulator +# endif +# else -+ appletvos-arm64 ++ arm64-appletvos +# endif +# elif TARGET_OS_WATCH +# if TARGET_OS_SIMULATOR +# if __x86_64__ -+ watchsimulator-x86_64 ++ x86_64-watchsimulator +# else -+ watchsimulator-arm64 ++ arm64-watchsimulator +# endif +# else -+ watchos-arm64_32 ++ arm64_32-watchos +# endif +# elif TARGET_OS_OSX -+ darwin + darwin +# else +# error unknown Apple platform +# endif #elif defined(__VXWORKS__) vxworks #elif defined(__wasm32__) -@@ -1095,11 +1215,13 @@ +@@ -1100,14 +1431,24 @@ + fi + rm -f conftest.c conftest.out + ++dnl On some platforms, using a true "triplet" for MULTIARCH would be redundant. ++dnl For example, `arm64-apple-darwin` is redundant, because there isn't a ++dnl non-Apple Darwin. Including the CPU architecture can also be potentially ++dnl redundant - on macOS, for example, it's possible to do a single compile ++dnl pass that includes multiple architectures, so it would be misleading for ++dnl MULTIARCH (and thus the sysconfigdata module name) to include a single CPU ++dnl architecture. PLATFORM_TRIPLET will be a pair or single value for these ++dnl platforms. AC_MSG_CHECKING([for multiarch]) AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], @@ -2906,40 +7120,196 @@ index e1cbb7c7fb..34cf532a87 100644 ) AC_SUBST([MULTIARCH]) -AC_MSG_RESULT([$MULTIARCH]) - + if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then if test x$PLATFORM_TRIPLET != x$MULTIARCH; then -@@ -1109,6 +1231,12 @@ +@@ -1116,7 +1457,18 @@ + elif test x$PLATFORM_TRIPLET != x && test x$MULTIARCH = x; then MULTIARCH=$PLATFORM_TRIPLET fi - AC_SUBST(PLATFORM_TRIPLET) +-AC_SUBST(PLATFORM_TRIPLET) ++AC_SUBST([PLATFORM_TRIPLET]) +AC_MSG_RESULT([$MULTIARCH]) + ++dnl Even if we *do* include the CPU architecture in the MULTIARCH value, some ++dnl platforms don't need the CPU architecture in the SOABI tag. These platforms ++dnl will have multiple sysconfig modules (one for each CPU architecture), but ++dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of ++dnl the PLATFORM_TRIPLET that will be used in binary module extensions. +AS_CASE([$ac_sys_system], -+ [iOS|tvOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f1`], ++ [iOS|tvOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], + [SOABI_PLATFORM=$PLATFORM_TRIPLET] +) - + if test x$MULTIARCH != x; then MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" -@@ -1139,6 +1267,15 @@ +@@ -1147,6 +1499,12 @@ [wasm32-unknown-emscripten/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly Emscripten [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly System Interface [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 + [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64 -+ [x86_64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on x86_64 + [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64 -+ dnl [aarch64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl Apple tvOS Simulator on ARM64 -+ dnl [x86_64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl Apple tvOS Simulator on x86_64 -+ dnl [aarch64-apple-tvos*/clang], [PY_SUPPORT_TIER=3], dnl Apple tvOS on ARM64 -+ dnl [aarch64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl Apple watchOS Simulator on ARM64 -+ dnl [x86_64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl Apple watchOS Simulator on x86_64 -+ dnl [arm64_32-apple-watchos*/clang], [PY_SUPPORT_TIER=3], dnl Apple watchOS on ARM64_32 ++ [aarch64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl tvOS Simulator on arm64 ++ [aarch64-apple-tvos*/clang], [PY_SUPPORT_TIER=3], dnl tvOS on ARM64 ++ [aarch64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl watchOS Simulator on arm64 ++ [arm64_32-apple-watchos*/clang], [PY_SUPPORT_TIER=3], dnl watchOS on ARM64 [PY_SUPPORT_TIER=0] ) - -@@ -1627,11 +1764,23 @@ - + +@@ -1459,17 +1817,25 @@ + + AC_MSG_CHECKING(LDLIBRARY) + +-# MacOSX framework builds need more magic. LDLIBRARY is the dynamic ++# Apple framework builds need more magic. LDLIBRARY is the dynamic + # library that we build, but we do not want to link against it (we + # will find it with a -framework option). For this reason there is an + # extra variable BLDLIBRARY against which Python and the extension + # modules are linked, BLDLIBRARY. This is normally the same as +-# LDLIBRARY, but empty for MacOSX framework builds. ++# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, ++# but uses a non-versioned framework layout. + if test "$enable_framework" + then +- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' +- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} ++ case $ac_sys_system in ++ Darwin) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; ++ *) ++ AC_MSG_ERROR([Unknown platform for framework build]);; ++ esac + BLDLIBRARY='' ++ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} + else + BLDLIBRARY='$(LDLIBRARY)' + fi +@@ -1480,64 +1846,69 @@ + AC_DEFINE(Py_ENABLE_SHARED, 1, [Defined if Python is built as a shared library.]) + case $ac_sys_system in + CYGWIN*) +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- DLLLIBRARY='libpython$(LDVERSION).dll' +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ DLLLIBRARY='libpython$(LDVERSION).dll' ++ ;; + SunOS*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + hp*|HP*) +- case `uname -m` in +- ia64) +- LDLIBRARY='libpython$(LDVERSION).so' +- ;; +- *) +- LDLIBRARY='libpython$(LDVERSION).sl' +- ;; +- esac +- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} +- ;; ++ case `uname -m` in ++ ia64) ++ LDLIBRARY='libpython$(LDVERSION).so' ++ ;; ++ *) ++ LDLIBRARY='libpython$(LDVERSION).sl' ++ ;; ++ esac ++ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} ++ ;; + Darwin*) +- LDLIBRARY='libpython$(LDVERSION).dylib' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ++ ;; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ ;; + AIX*) +- LDLIBRARY='libpython$(LDVERSION).so' +- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} ++ ;; + + esac + else # shared is disabled + PY_ENABLE_SHARED=0 + case $ac_sys_system in + CYGWIN*) +- BLDLIBRARY='$(LIBRARY)' +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- ;; ++ BLDLIBRARY='$(LIBRARY)' ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ ;; + esac + fi + ++AC_MSG_RESULT($LDLIBRARY) ++ + if test "$cross_compiling" = yes; then +- RUNSHARED= ++ RUNSHARED= + fi + + AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform]) +@@ -1593,8 +1964,6 @@ + PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" + fi + +-AC_MSG_RESULT($LDLIBRARY) +- + # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable + AS_CASE([$ac_sys_system/$ac_sys_emscripten_target], + [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js'], +@@ -1635,11 +2004,16 @@ + AC_CHECK_TOOLS([READELF], [readelf], [:]) if test "$cross_compiling" = yes; then - case "$READELF" in @@ -2947,68 +7317,91 @@ index e1cbb7c7fb..34cf532a87 100644 - AC_MSG_ERROR([readelf for the host is required for cross builds]) - ;; - esac -+ case "$host" in -+ *-apple-ios*) -+ # readelf not required for iOS cross builds. -+ ;; -+ *-apple-tvos*) -+ # readelf not required for tvOS cross builds. -+ ;; -+ *-apple-watchos*) -+ # readelf not required for watchOS cross builds. -+ ;; -+ *) -+ case "$READELF" in -+ readelf|:) -+ AC_MSG_ERROR([readelf for the host is required for cross builds]) -+ ;; -+ esac -+ esac ++ case "$ac_sys_system" in ++ iOS|tvOS|watchOS) ;; ++ *) ++ case "$READELF" in ++ readelf|:) ++ AC_MSG_ERROR([readelf for the host is required for cross builds]) ++ ;; ++ esac ++ ;; ++ esac fi AC_SUBST(READELF) - -@@ -3091,6 +3240,7 @@ - esac - ;; - CYGWIN*) SHLIB_SUFFIX=.dll;; -+ iOS|tvOS|watchOS) SHLIB_SUFFIX=.dylib;; - *) SHLIB_SUFFIX=.so;; - esac - fi -@@ -3171,6 +3321,9 @@ + +@@ -3179,6 +3553,11 @@ BLDSHARED="$LDSHARED" fi ;; + iOS/*|tvOS/*|watchOS/*) -+ LDSHARED='$(CC) -dynamiclib -undefined dynamic_lookup' -+ LDCXXSHARED='$(CXX) -dynamiclib -undefined dynamic_lookup';; ++ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ BLDSHARED="$LDSHARED" ++ ;; Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; -@@ -3640,20 +3793,30 @@ - esac - AC_MSG_RESULT($with_system_ffi) - else -- AC_MSG_RESULT(yes) -- if test "$with_system_ffi" != "" -+ if test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "watchOS" - then -- AC_MSG_WARN([--with(out)-system-ffi is ignored on this platform]) -+ AC_MSG_RESULT(no) -+ AC_MSG_WARN([Using user-provided libffi configuration]) -+ LIBFFI_LIBDIR="${LIBFFI_LIBDIR}" -+ LIBFFI_LIB="${LIBFFI_LIB}" -+ else -+ AC_MSG_RESULT(yes) -+ if test "$with_system_ffi" != "" -+ then -+ AC_MSG_WARN([--with(out)-system-ffi is ignored on this platform]) -+ fi -+ with_system_ffi="yes" - fi -- with_system_ffi="yes" - fi - +@@ -3299,30 +3678,34 @@ + Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; + Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; + # -u libsys_s pulls in all symbols in libsys +- Darwin/*) ++ Darwin/*|iOS/*|tvOS/*|watchOS/*) + LINKFORSHARED="$extra_undefs -framework CoreFoundation" + + # Issue #18075: the default maximum stack size (8MBytes) is too + # small for the default recursion limit. Increase the stack size + # to ensure that tests don't crash +- stack_size="1000000" # 16 MB +- if test "$with_ubsan" = "yes" +- then +- # Undefined behavior sanitizer requires an even deeper stack +- stack_size="4000000" # 64 MB +- fi ++ stack_size="1000000" # 16 MB ++ if test "$with_ubsan" = "yes" ++ then ++ # Undefined behavior sanitizer requires an even deeper stack ++ stack_size="4000000" # 64 MB ++ fi + +- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" ++ AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE], ++ [0x$stack_size], ++ [Custom thread stack size depending on chosen sanitizer runtimes.]) + +- AC_DEFINE_UNQUOTED(THREAD_STACK_SIZE, +- 0x$stack_size, +- [Custom thread stack size depending on chosen sanitizer runtimes.]) ++ if test $ac_sys_system = "Darwin"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + +- if test "$enable_framework" +- then +- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ if test "$enable_framework"; then ++ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ fi ++ LINKFORSHARED="$LINKFORSHARED" ++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi +- LINKFORSHARED="$LINKFORSHARED";; ++ ;; + OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; + SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; + ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; +@@ -3634,7 +4017,7 @@ + AC_ARG_WITH(system_ffi, + AS_HELP_STRING([--with-system-ffi], [build _ctypes module using an installed ffi library, see Doc/library/ctypes.rst (default is system-dependent)]),,,) + +-if test "$ac_sys_system" = "Darwin" ++if test "$ac_sys_system" = "Darwin" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" + then + case "$with_system_ffi" in + "") +@@ -3659,9 +4042,11 @@ if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`" else @@ -3018,34 +7411,252 @@ index e1cbb7c7fb..34cf532a87 100644 AC_SUBST(LIBFFI_INCLUDEDIR) +AC_SUBST(LIBFFI_LIBDIR) +AC_SUBST(LIBFFI_LIB) - + # Check for use of the system libmpdec library AC_MSG_CHECKING(for --with-system-libmpdec) -@@ -5687,14 +5850,14 @@ - AC_MSG_CHECKING(ABIFLAGS) - AC_MSG_RESULT($ABIFLAGS) - AC_MSG_CHECKING(SOABI) +@@ -4594,27 +4979,27 @@ + # checks for library functions + AC_CHECK_FUNCS([ \ + accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ +- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ ++ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ + faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ +- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ +- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ +- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ ++ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ ++ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \ ++ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ + getpeername getpgid getpid getppid getpriority _getpty \ + getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ + getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ + lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ + mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ +- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ ++ pipe2 plock poll posix_fadvise posix_fallocate \ + pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ + pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ + rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ + sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ + sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ + setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ +- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ ++ setresuid setreuid setsid setuid setvbuf shutdown sigaction \ + sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ + sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ +- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ ++ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ + tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ + wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ + ]) +@@ -4626,11 +5011,28 @@ + AC_CHECK_FUNCS(lchmod) + fi + +-AC_CHECK_DECL(dirfd, +- AC_DEFINE(HAVE_DIRFD, 1, +- Define if you have the 'dirfd' function or macro.), , +- [#include +- #include ]) ++# iOS/tvOS/watchOS define some system methods that can be linked (so they are ++# found by configure), but either raise a compilation error (because the ++# header definition prevents usage - autoconf doesn't use the headers), or ++# raise an error if used at runtime. Force these symbols off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ AC_CHECK_FUNCS([ getentropy getgroups system ]) ++fi ++ ++# tvOS/watchOS have some additional methods that can be found, but not used. ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ AC_CHECK_FUNCS([ \ ++ execv fork fork1 posix_spawn posix_spawnp \ ++ sigaltstack \ ++ ]) ++fi ++ ++AC_CHECK_DECL([dirfd], ++ [AC_DEFINE([HAVE_DIRFD], [1], ++ [Define if you have the 'dirfd' function or macro.])], ++ [], ++ [@%:@include ++ @%:@include ]) + + dnl PY_CHECK_FUNC(FUNCTION, [INCLUDES], [AC_DEFINE-VAR]) + AC_DEFUN([PY_CHECK_FUNC], +@@ -4883,22 +5285,22 @@ + ]) + + # check for openpty, login_tty, and forkpty +- +-AC_CHECK_FUNCS(openpty,, +- AC_CHECK_LIB(util,openpty, +- [AC_DEFINE(HAVE_OPENPTY) LIBS="$LIBS -lutil"], +- AC_CHECK_LIB(bsd,openpty, [AC_DEFINE(HAVE_OPENPTY) LIBS="$LIBS -lbsd"]) +- ) +-) +-AC_SEARCH_LIBS([login_tty], [util], +- [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])] +-) +-AC_CHECK_FUNCS(forkpty,, +- AC_CHECK_LIB(util,forkpty, +- [AC_DEFINE(HAVE_FORKPTY) LIBS="$LIBS -lutil"], +- AC_CHECK_LIB(bsd,forkpty, [AC_DEFINE(HAVE_FORKPTY) LIBS="$LIBS -lbsd"]) +- ) +-) ++# tvOS/watchOS have functions for tty, but can't use them ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ AC_CHECK_FUNCS([openpty], [], ++ [AC_CHECK_LIB([util], [openpty], ++ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"], ++ [AC_CHECK_LIB([bsd], [openpty], ++ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])]) ++ AC_SEARCH_LIBS([login_tty], [util], ++ [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])] ++ ) ++ AC_CHECK_FUNCS([forkpty], [], ++ [AC_CHECK_LIB([util], [forkpty], ++ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"], ++ [AC_CHECK_LIB([bsd], [forkpty], ++ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])]) ++fi + + # check for long file support functions + AC_CHECK_FUNCS(fseek64 fseeko fstatvfs ftell64 ftello statvfs) +@@ -4972,11 +5374,17 @@ + ]) + ]) + +-AC_CHECK_FUNCS(clock_settime, [], [ +- AC_CHECK_LIB(rt, clock_settime, [ +- AC_DEFINE(HAVE_CLOCK_SETTIME, 1) +- ]) +-]) ++# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by ++# configure), but when used in an unprivileged process, it crashes rather than ++# returning an error. Force the symbol off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ++then ++ AC_CHECK_FUNCS([clock_settime], [], [ ++ AC_CHECK_LIB([rt], [clock_settime], [ ++ AC_DEFINE([HAVE_CLOCK_SETTIME], [1]) ++ ]) ++ ]) ++fi + + AC_CHECK_FUNCS(clock_nanosleep, [], [ + AC_CHECK_LIB(rt, clock_nanosleep, [ +@@ -5122,7 +5530,9 @@ + [ac_cv_buggy_getaddrinfo=no], + [ac_cv_buggy_getaddrinfo=yes], + [ +-if test "${enable_ipv6+set}" = set; then ++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ ac_cv_buggy_getaddrinfo="no" ++elif test "${enable_ipv6+set}" = set; then + ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" + else + ac_cv_buggy_getaddrinfo=yes +@@ -5692,19 +6102,19 @@ + # + # In Python 3.2 and older, --with-wide-unicode added a 'u' flag. + # In Python 3.7 and older, --with-pymalloc added a 'm' flag. +-AC_SUBST(SOABI) +-AC_MSG_CHECKING(ABIFLAGS) +-AC_MSG_RESULT($ABIFLAGS) +-AC_MSG_CHECKING(SOABI) -SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} +-AC_MSG_RESULT($SOABI) ++AC_SUBST([SOABI]) ++AC_MSG_CHECKING([ABIFLAGS]) ++AC_MSG_RESULT([$ABIFLAGS]) ++AC_MSG_CHECKING([SOABI]) +SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} - AC_MSG_RESULT($SOABI) - ++AC_MSG_RESULT([$SOABI]) + # Release and debug (Py_DEBUG) ABI are compatible, but not Py_TRACE_REFS ABI if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then # Similar to SOABI but remove "d" flag from ABIFLAGS - AC_SUBST(ALT_SOABI) +- AC_SUBST(ALT_SOABI) - ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} +- AC_DEFINE_UNQUOTED(ALT_SOABI, "${ALT_SOABI}", ++ AC_SUBST([ALT_SOABI]) + ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} - AC_DEFINE_UNQUOTED(ALT_SOABI, "${ALT_SOABI}", ++ AC_DEFINE_UNQUOTED([ALT_SOABI], ["${ALT_SOABI}"], [Alternative SOABI used in debug build to load C extensions built in release mode]) fi -@@ -6833,6 +6996,30 @@ + +@@ -6193,28 +6603,35 @@ + AC_MSG_NOTICE([checking for device files]) + + dnl NOTE: Inform user how to proceed with files when cross compiling. +-if test "x$cross_compiling" = xyes; then +- if test "${ac_cv_file__dev_ptmx+set}" != set; then +- AC_MSG_CHECKING([for /dev/ptmx]) +- AC_MSG_RESULT([not set]) +- AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) +- fi +- if test "${ac_cv_file__dev_ptc+set}" != set; then +- AC_MSG_CHECKING([for /dev/ptc]) +- AC_MSG_RESULT([not set]) +- AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) ++dnl iOS cross-compile builds are predictable; they won't ever ++dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then ++ ac_cv_file__dev_ptmx=no ++ ac_cv_file__dev_ptc=no ++else ++ if test "x$cross_compiling" = xyes; then ++ if test "${ac_cv_file__dev_ptmx+set}" != set; then ++ AC_MSG_CHECKING([for /dev/ptmx]) ++ AC_MSG_RESULT([not set]) ++ AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) ++ fi ++ if test "${ac_cv_file__dev_ptc+set}" != set; then ++ AC_MSG_CHECKING([for /dev/ptc]) ++ AC_MSG_RESULT([not set]) ++ AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) ++ fi + fi +-fi + +-AC_CHECK_FILE(/dev/ptmx, [], []) +-if test "x$ac_cv_file__dev_ptmx" = xyes; then +- AC_DEFINE(HAVE_DEV_PTMX, 1, +- [Define to 1 if you have the /dev/ptmx device file.]) +-fi +-AC_CHECK_FILE(/dev/ptc, [], []) +-if test "x$ac_cv_file__dev_ptc" = xyes; then +- AC_DEFINE(HAVE_DEV_PTC, 1, +- [Define to 1 if you have the /dev/ptc device file.]) ++ AC_CHECK_FILE([/dev/ptmx], [], []) ++ if test "x$ac_cv_file__dev_ptmx" = xyes; then ++ AC_DEFINE([HAVE_DEV_PTMX], [1], ++ [Define to 1 if you have the /dev/ptmx device file.]) ++ fi ++ AC_CHECK_FILE([/dev/ptc], [], []) ++ if test "x$ac_cv_file__dev_ptc" = xyes; then ++ AC_DEFINE([HAVE_DEV_PTC], [1], ++ [Define to 1 if you have the /dev/ptc device file.]) ++ fi + fi + + if test $ac_sys_system = Darwin +@@ -6523,6 +6940,7 @@ + AS_CASE([$ac_sys_system], + [Emscripten], [with_ensurepip=no], + [WASI], [with_ensurepip=no], ++ [iOS|tvOS|watchOS], [with_ensurepip=no], + [with_ensurepip=upgrade] + ) + ]) +@@ -6856,6 +7274,28 @@ [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])], [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])], + [iOS|tvOS|watchOS], [ + dnl subprocess and multiprocessing are not supported (no fork syscall). + dnl curses and tkinter user interface are not available. -+ dnl gdbm, and nis aren't available ++ dnl gdbm and nis aren't available + dnl Stub implementations are provided for pwd, grp etc APIs + PY_STDLIB_MOD_SET_NA( + [_curses], @@ -3056,10 +7667,8 @@ index e1cbb7c7fb..34cf532a87 100644 + [_posixsubprocess], + [_scproxy], + [_tkinter], -+ [_xxsubinterpreters], + [grp], + [nis], -+ [ossaudiodev], + [readline], + [pwd], + [spwd], @@ -3069,8 +7678,1457 @@ index e1cbb7c7fb..34cf532a87 100644 [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], +--- /dev/null ++++ b/iOS/README.rst +@@ -0,0 +1,385 @@ ++==================== ++Python on iOS README ++==================== ++ ++:Authors: ++ Russell Keith-Magee (2023-11) ++ ++This document provides a quick overview of some iOS specific features in the ++Python distribution. ++ ++These instructions are only needed if you're planning to compile Python for iOS ++yourself. Most users should *not* need to do this. If you're looking to ++experiment with writing an iOS app in Python, tools such as `BeeWare's Briefcase ++`__ and `Kivy's Buildozer ++`__ will provide a much more approachable ++user experience. ++ ++Compilers for building on iOS ++============================= ++ ++Building for iOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode. This will ++require the use of the most (or second-most) recently released macOS version, ++as Apple does not maintain Xcode for older macOS versions. The Xcode Command ++Line Tools are not sufficient for iOS development; you need a *full* Xcode ++install. ++ ++If you want to run your code on the iOS simulator, you'll also need to install ++an iOS Simulator Platform. You should be prompted to select an iOS Simulator ++Platform when you first run Xcode. Alternatively, you can add an iOS Simulator ++Platform by selecting an open the Platforms tab of the Xcode Settings panel. ++ ++iOS specific arguments to configure ++=================================== ++ ++* ``--enable-framework[=DIR]`` ++ ++ This argument specifies the location where the Python.framework will be ++ installed. If ``DIR`` is not specified, the framework will be installed into ++ a subdirectory of the ``iOS/Frameworks`` folder. ++ ++ This argument *must* be provided when configuring iOS builds. iOS does not ++ support non-framework builds. ++ ++* ``--with-framework-name=NAME`` ++ ++ Specify the name for the Python framework; defaults to ``Python``. ++ ++ .. admonition:: Use this option with care! ++ ++ Unless you know what you're doing, changing the name of the Python ++ framework on iOS is not advised. If you use this option, you won't be able ++ to run the ``make testios`` target without making signficant manual ++ alterations, and you won't be able to use any binary packages unless you ++ compile them yourself using your own framework name. ++ ++Building Python on iOS ++====================== ++ ++ABIs and Architectures ++---------------------- ++ ++iOS apps can be deployed on physical devices, and on the iOS simulator. Although ++the API used on these devices is identical, the ABI is different - you need to ++link against different libraries for an iOS device build (``iphoneos``) or an ++iOS simulator build (``iphonesimulator``). ++ ++Apple uses the ``XCframework`` format to allow specifying a single dependency ++that supports multiple ABIs. An ``XCframework`` is a wrapper around multiple ++ABI-specific frameworks that share a common API. ++ ++iOS can also support different CPU architectures within each ABI. At present, ++there is only a single supported architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines). ++ ++To support multiple CPU architectures on a single platform, Apple uses a "fat ++binary" format - a single physical file that contains support for multiple ++architectures. It is possible to compile and use a "thin" single architecture ++version of a binary for testing purposes; however, the "thin" binary will not be ++portable to machines using other architectures. ++ ++Building a single-architecture framework ++---------------------------------------- ++ ++The Python build system will create a ``Python.framework`` that supports a ++*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a ++framework to contain non-library content, so the iOS build will produce a ++``bin`` and ``lib`` folder in the same output folder as ``Python.framework``. ++The ``lib`` folder will be needed at runtime to support the Python library. ++ ++If you want to use Python in a real iOS project, you need to produce multiple ++``Python.framework`` builds, one for each ABI and architecture. iOS builds of ++Python *must* be constructed as framework builds. To support this, you must ++provide the ``--enable-framework`` flag when configuring the build. The build ++also requires the use of cross-compilation. The minimal commands for building ++Python for the ARM64 iOS simulator will look something like:: ++ ++ $ export PATH="$(pwd)/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" ++ $ ./configure \ ++ --enable-framework \ ++ --host=arm64-apple-ios-simulator \ ++ --build=arm64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++ $ make ++ $ make install ++ ++In this invocation: ++ ++* ``iOS/Resources/bin`` has been added to the path, providing some shims for the ++ compilers and linkers needed by the build. Xcode requires the use of ``xcrun`` ++ to invoke compiler tooling. However, if ``xcrun`` is pre-evaluated and the ++ result passed to ``configure``, these results can embed user- and ++ version-specific paths into the sysconfig data, which limits the portability ++ of the compiled Python. Alternatively, if ``xcrun`` is used *as* the compiler, ++ it requires that compiler variables like ``CC`` include spaces, which can ++ cause significant problems with many C configuration systems which assume that ++ ``CC`` will be a single executable. ++ ++ To work around this problem, the ``iOS/Resources/bin`` folder contains some ++ wrapper scripts that present as simple compilers and linkers, but wrap ++ underlying calls to ``xcrun``. This allows configure to use a ``CC`` ++ definition without spaces, and without user- or version-specific paths, while ++ retaining the ability to adapt to the local Xcode install. These scripts are ++ included in the ``bin`` directory of an iOS install. ++ ++ These scripts will, by default, use the currently active Xcode installation. ++ If you want to use a different Xcode installation, you can use ++ ``xcode-select`` to set a new default Xcode globally, or you can use the ++ ``DEVELOPER_DIR`` environment variable to specify an Xcode install. The ++ scripts will use the default ``iphoneos``/``iphonesimulator`` SDK version for ++ the select Xcode install; if you want to use a different SDK, you can set the ++ ``IOS_SDK_VERSION`` environment variable. (e.g, setting ++ ``IOS_SDK_VERSION=17.1`` would cause the scripts to use the ``iphoneos17.1`` ++ and ``iphonesimulator17.1`` SDKs, regardless of the Xcode default.) ++ ++ The path has also been cleared of any user customizations. A common source of ++ bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS ++ build. Resetting the path to a known "bare bones" value is the easiest way to ++ avoid these problems. ++ ++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: ++ ++ - ``arm64-apple-ios`` for ARM64 iOS devices. ++ - ``arm64-apple-ios-simulator`` for the iOS simulator running on Apple ++ Silicon devices. ++ - ``x86_64-apple-ios-simulator`` for the iOS simulator running on Intel ++ devices. ++ ++* ``--build`` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: ++ ++ - ``arm64-apple-darwin`` for Apple Silicon devices. ++ - ``x86_64-apple-darwin`` for Intel devices. ++ ++* ``/path/to/python.exe`` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for iOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the same version as the Python that is being compiled. To be completely safe, ++ this should be the *exact* same commit hash. However, the longer a Python ++ release has been stable, the more likely it is that this constraint can be ++ relaxed - the same micro version will often be sufficient. ++ ++* The ``install`` target for iOS builds is slightly different to other ++ platforms. On most platforms, ``make install`` will install the build into ++ the final runtime location. This won't be the case for iOS, as the final ++ runtime location will be on a physical device. ++ ++ However, you still need to run the ``install`` target for iOS builds, as it ++ performs some final framework assembly steps. The location specified with ++ ``--enable-framework`` will be the location where ``make install`` will ++ assemble the complete iOS framework. This completed framework can then ++ be copied and relocated as required. ++ ++For a full CPython build, you also need to specify the paths to iOS builds of ++the binary libraries that CPython depends on (XZ, BZip2, LibFFI and OpenSSL). ++This can be done by defining the ``LIBLZMA_CFLAGS``, ``LIBLZMA_LIBS``, ++``BZIP2_CFLAGS``, ``BZIP2_LIBS``, ``LIBFFI_CFLAGS``, and ``LIBFFI_LIBS`` ++environment variables, and the ``--with-openssl`` configure option. Versions of ++these libraries pre-compiled for iOS can be found in `this repository ++`__. LibFFI is ++especially important, as many parts of the standard library (including the ++``platform``, ``sysconfig`` and ``webbrowser`` modules) require the use of the ++``ctypes`` module at runtime. ++ ++By default, Python will be compiled with an iOS deployment target (i.e., the ++minimum supported iOS version) of 13.0. To specify a different deployment ++target, provide the version number as part of the ``--host`` argument - for ++example, ``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 ++simulator build with a deployment target of 15.4. ++ ++Merge thin frameworks into fat frameworks ++----------------------------------------- ++ ++Once you've built a ``Python.framework`` for each ABI and and architecture, you ++must produce a "fat" framework for each ABI that contains all the architectures ++for that ABI. ++ ++The ``iphoneos`` build only needs to support a single architecture, so it can be ++used without modification. ++ ++If you only want to support a single simulator architecture, (e.g., only support ++ARM64 simulators), you can use a single architecture ``Python.framework`` build. ++However, if you want to create ``Python.xcframework`` that supports *all* ++architectures, you'll need to merge the ``iphonesimulator`` builds for ARM64 and ++x86_64 into a single "fat" framework. ++ ++The "fat" framework can be constructed by performing a directory merge of the ++content of the two "thin" ``Python.framework`` directories, plus the ``bin`` and ++``lib`` folders for each thin framework. When performing this merge: ++ ++* The pure Python standard library content is identical for each architecture, ++ except for a handful of platform-specific files (such as the ``sysconfig`` ++ module). Ensure that the "fat" framework has the union of all standard library ++ files. ++ ++* Any binary files in the standard library, plus the main ++ ``libPython3.X.dylib``, can be merged using the ``lipo`` tool, provide by ++ Xcode:: ++ ++ $ lipo -create -output module.dylib path/to/x86_64/module.dylib path/to/arm64/module.dylib ++ ++* The header files will be indentical on both architectures, except for ++ ``pyconfig.h``. Copy all the headers from one platform (say, arm64), rename ++ ``pyconfig.h`` to ``pyconfig-arm64.h``, and copy the ``pyconfig.h`` for the ++ other architecture into the merged header folder as ``pyconfig-x86_64.h``. ++ Then copy the ``iOS/Resources/pyconfig.h`` file from the CPython sources into ++ the merged headers folder. This will allow the two Python architectures to ++ share a common ``pyconfig.h`` header file. ++ ++At this point, you should have 2 Python.framework folders - one for ``iphoneos``, ++and one for ``iphonesimulator`` that is a merge of x86+64 and ARM64 content. ++ ++Merge frameworks into an XCframework ++------------------------------------ ++ ++Now that we have 2 (potentially fat) ABI-specific frameworks, we can merge those ++frameworks into a single ``XCframework``. ++ ++The initial skeleton of an ``XCframework`` is built using:: ++ ++ xcodebuild -create-xcframework -output Python.xcframework -framework path/to/iphoneos/Python.framework -framework path/to/iphonesimulator/Python.framework ++ ++Then, copy the ``bin`` and ``lib`` folders into the architecture-specific slices of ++the XCframework:: ++ ++ cp path/to/iphoneos/bin Python.xcframework/ios-arm64 ++ cp path/to/iphoneos/lib Python.xcframework/ios-arm64 ++ ++ cp path/to/iphonesimulator/bin Python.xcframework/ios-arm64_x86_64-simulator ++ cp path/to/iphonesimulator/lib Python.xcframework/ios-arm64_x86_64-simulator ++ ++Note that the name of the architecture-specific slice for the simulator will ++depend on the CPU architecture(s) that you build. ++ ++You now have a Python.xcframework that can be used in a project. ++ ++Testing Python on iOS ++===================== ++ ++The ``iOS/testbed`` folder that contains an Xcode project that is able to run ++the iOS test suite. This project converts the Python test suite into a single ++test case in Xcode's XCTest framework. The single XCTest passes if the test ++suite passes. ++ ++To run the test suite, configure a Python build for an iOS simulator (i.e., ++``--host=arm64-apple-ios-simulator`` or ``--host=x86_64-apple-ios-simulator`` ++), specifying a framework build (i.e. ``--enable-framework``). Ensure that your ++``PATH`` has been configured to include the ``iOS/Resources/bin`` folder and ++exclude any non-iOS tools, then run:: ++ ++ $ make all ++ $ make install ++ $ make testios ++ ++This will: ++ ++* Build an iOS framework for your chosen architecture; ++* Finalize the single-platform framework; ++* Make a clean copy of the testbed project; ++* Install the Python iOS framework into the copy of the testbed project; and ++* Run the test suite on an "iPhone SE (3rd generation)" simulator. ++ ++While the test suite is running, Xcode does not display any console output. ++After showing some Xcode build commands, the console output will print ``Testing ++started``, and then appear to stop. It will remain in this state until the test ++suite completes. On a 2022 M1 MacBook Pro, the test suite takes approximately 12 ++minutes to run; a couple of extra minutes is required to boot and prepare the ++iOS simulator. ++ ++On success, the test suite will exit and report successful completion of the ++test suite. No output of the Python test suite will be displayed. ++ ++On failure, the output of the Python test suite *will* be displayed. This will ++show the details of the tests that failed. ++ ++Debugging test failures ++----------------------- ++ ++The easiest way to diagnose a single test failure is to open the testbed project ++in Xcode and run the tests from there using the "Product > Test" menu item. ++ ++To test in Xcode, you must ensure the testbed project has a copy of a compiled ++framework. If you've configured your build with the default install location of ++``iOS/Frameworks``, you can copy from that location into the test project. To ++test on an ARM64 simulator, run:: ++ ++ $ rm -rf iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/* ++ $ cp -r iOS/Frameworks/arm64-iphonesimulator/* iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator ++ ++To test on an x86-64 simulator, run:: ++ ++ $ rm -rf iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/* ++ $ cp -r iOS/Frameworks/x86_64-iphonesimulator/* iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator ++ ++To test on a physical device:: ++ ++ $ rm -rf iOS/testbed/Python.xcframework/ios-arm64/* ++ $ cp -r iOS/Frameworks/arm64-iphoneos/* iOS/testbed/Python.xcframework/ios-arm64 ++ ++Alternatively, you can configure your build to install directly into the ++testbed project. For a simulator, use:: ++ ++ --enable-framework=$(pwd)/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator ++ ++For a physical device, use:: ++ ++ --enable-framework=$(pwd)/iOS/testbed/Python.xcframework/ios-arm64 ++ ++ ++Testing on an iOS device ++^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++To test on an iOS device, the app needs to be signed with known developer ++credentials. To obtain these credentials, you must have an iOS Developer ++account, and your Xcode install will need to be logged into your account (see ++the Accounts tab of the Preferences dialog). ++ ++Once the project is open, and you're signed into your Apple Developer account, ++select the root node of the project tree (labeled "iOSTestbed"), then the ++"Signing & Capabilities" tab in the details page. Select a development team ++(this will likely be your own name), and plug in a physical device to your ++macOS machine with a USB cable. You should then be able to select your physical ++device from the list of targets in the pulldown in the Xcode titlebar. ++ ++Running specific tests ++^^^^^^^^^^^^^^^^^^^^^^ ++ ++As the test suite is being executed on an iOS simulator, it is not possible to ++pass in command line arguments to configure test suite operation. To work around ++this limitation, the arguments that would normally be passed as command line ++arguments are configured as a static string at the start of the XCTest method ++``- (void)testPython`` in ``iOSTestbedTests.m``. To pass an argument to the test ++suite, add a a string to the ``argv`` defintion. These arguments will be passed ++to the test suite as if they had been passed to ``python -m test`` at the ++command line. ++ ++Disabling automated breakpoints ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++By default, Xcode will inserts an automatic breakpoint whenever a signal is ++raised. The Python test suite raises many of these signals as part of normal ++operation; unless you are trying to diagnose an issue with signals, the ++automatic breakpoints can be inconvenient. However, they can be disabled by ++creating a symbolic breakpoint that is triggered at the start of the test run. ++ ++Select "Debug > Breakpoints > Create Symbolic Breakpoint" from the Xcode menu, and ++populate the new brewpoint with the following details: ++ ++* **Name**: IgnoreSignals ++* **Symbol**: UIApplicationMain ++* **Action**: Add debugger commands for: ++ - ``process handle SIGINT -n true -p true -s false`` ++ - ``process handle SIGUSR1 -n true -p true -s false`` ++ - ``process handle SIGUSR2 -n true -p true -s false`` ++ - ``process handle SIGXFSZ -n true -p true -s false`` ++* Check the "Automatically continue after evaluating" box. ++ ++All other details can be left blank. When the process executes the ++``UIApplicationMain`` entry point, the breakpoint will trigger, run the debugger ++commands to disable the automatic breakpoints, and automatically resume. +--- /dev/null ++++ b/iOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ %VERSION% ++ CFBundleSupportedPlatforms ++ ++ iPhoneOS ++ ++ MinimumOSVersion ++ @IPHONEOS_DEPLOYMENT_TARGET@ ++ ++ +--- /dev/null ++++ b/iOS/Resources/bin/arm64-apple-ios-ar +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/iOS/Resources/bin/arm64-apple-ios-clang +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios $@ +--- /dev/null ++++ b/iOS/Resources/bin/arm64-apple-ios-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E $@ +--- /dev/null ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator $@ +--- /dev/null ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator -E $@ +--- /dev/null ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator $@ +--- /dev/null ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator -E $@ +--- /dev/null ++++ b/iOS/Resources/dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ iPhoneOS ++ ++ MinimumOSVersion ++ 12.0 ++ CFBundleVersion ++ 1 ++ ++ +--- /dev/null ++++ b/iOS/Resources/pyconfig.h +@@ -0,0 +1,7 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif ++ ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +--- /dev/null ++++ b/iOS/testbed/Python.xcframework/Info.plist +@@ -0,0 +1,44 @@ ++ ++ ++ ++ ++ AvailableLibraries ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ ios-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ ios ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ ios-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ ios ++ SupportedPlatformVariant ++ simulator ++ ++ ++ CFBundlePackageType ++ XFWK ++ XCFrameworkFormatVersion ++ 1.0 ++ ++ +--- /dev/null ++++ b/iOS/testbed/Python.xcframework/ios-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling an iOS on-device ++build for testing purposes. +--- /dev/null ++++ b/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. ++ ++It should be used as a target for `--enable-framework` when compiling an iOS simulator ++build for testing purposes (either x86_64 or ARM64). +--- /dev/null ++++ b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,570 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 56; ++ objects = { ++ ++/* Begin PBXBuildFile section */ ++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; }; ++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; }; ++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; }; ++ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; }; ++ 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */; }; ++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; ++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; ++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; }; ++/* End PBXBuildFile section */ ++ ++/* Begin PBXContainerItemProxy section */ ++ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = 607A66112B0EFA380010BFC8; ++ remoteInfo = iOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ ++ ++/* Begin PBXCopyFilesBuildPhase section */ ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ ++ ++/* Begin PBXFileReference section */ ++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; ++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; ++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; ++ 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; ++ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; ++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSTestbedTests.m; sourceTree = ""; }; ++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = ""; }; ++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = ""; }; ++/* End PBXFileReference section */ ++ ++/* Begin PBXFrameworksBuildPhase section */ ++ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ ++ ++/* Begin PBXGroup section */ ++ 607A66092B0EFA380010BFC8 = { ++ isa = PBXGroup; ++ children = ( ++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */, ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */, ++ 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */, ++ 607A66132B0EFA380010BFC8 /* Products */, ++ 607A664F2B0EFFE00010BFC8 /* Frameworks */, ++ ); ++ sourceTree = ""; ++ }; ++ 607A66132B0EFA380010BFC8 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */, ++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */, ++ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */, ++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */, ++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */, ++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */, ++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */, ++ 607A66272B0EFA390010BFC8 /* main.m */, ++ ); ++ path = iOSTestbed; ++ sourceTree = ""; ++ }; ++ 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */, ++ ); ++ path = iOSTestbedTests; ++ sourceTree = ""; ++ }; ++ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = { ++ isa = PBXGroup; ++ children = ( ++ ); ++ name = Frameworks; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ ++ ++/* Begin PBXNativeTarget section */ ++ 607A66112B0EFA380010BFC8 /* iOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */; ++ buildPhases = ( ++ 607A660E2B0EFA380010BFC8 /* Sources */, ++ 607A660F2B0EFA380010BFC8 /* Frameworks */, ++ 607A66102B0EFA380010BFC8 /* Resources */, ++ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */, ++ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */, ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ name = iOSTestbed; ++ productName = iOSTestbed; ++ productReference = 607A66122B0EFA380010BFC8 /* iOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */; ++ buildPhases = ( ++ 607A66292B0EFA3A0010BFC8 /* Sources */, ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, ++ 607A662B2B0EFA3A0010BFC8 /* Resources */, ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, ++ ); ++ name = iOSTestbedTests; ++ productName = iOSTestbedTests; ++ productReference = 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */; ++ productType = "com.apple.product-type.bundle.unit-test"; ++ }; ++/* End PBXNativeTarget section */ ++ ++/* Begin PBXProject section */ ++ 607A660A2B0EFA380010BFC8 /* Project object */ = { ++ isa = PBXProject; ++ attributes = { ++ BuildIndependentTargetsInParallel = 1; ++ LastUpgradeCheck = 1500; ++ TargetAttributes = { ++ 607A66112B0EFA380010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; ++ }; ++ 607A662C2B0EFA3A0010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; ++ TestTargetID = 607A66112B0EFA380010BFC8; ++ }; ++ }; ++ }; ++ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */; ++ compatibilityVersion = "Xcode 14.0"; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = 607A66092B0EFA380010BFC8; ++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ 607A66112B0EFA380010BFC8 /* iOSTestbed */, ++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ ++ ++/* Begin PBXResourcesBuildPhase section */ ++ 607A66102B0EFA380010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */, ++ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */, ++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A662B2B0EFA3A0010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ ++ ++/* Begin PBXShellScriptBuildPhase section */ ++ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Install Target Specific Python Standard Library"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-iphonesimulator\" ]; then\n echo \"Installing Python modules for iOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for iOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n"; ++ }; ++ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Prepare Python Binary Modules"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n"; ++ }; ++/* End PBXShellScriptBuildPhase section */ ++ ++/* Begin PBXSourcesBuildPhase section */ ++ 607A660E2B0EFA380010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, ++ 607A66282B0EFA390010BFC8 /* main.m in Sources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66292B0EFA3A0010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ ++ ++/* Begin PBXTargetDependency section */ ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; ++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ ++ ++/* Begin PBXVariantGroup section */ ++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = { ++ isa = PBXVariantGroup; ++ children = ( ++ 607A66242B0EFA390010BFC8 /* Base */, ++ ); ++ name = LaunchScreen.storyboard; ++ sourceTree = ""; ++ }; ++/* End PBXVariantGroup section */ ++ ++/* Begin XCBuildConfiguration section */ ++ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = iphoneos; ++ }; ++ name = Debug; ++ }; ++ 607A66402B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = iphoneos; ++ VALIDATE_PRODUCT = YES; ++ }; ++ name = Release; ++ }; ++ 607A66422B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ }; ++ name = Debug; ++ }; ++ 607A66432B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ }; ++ name = Release; ++ }; ++ 607A66452B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; ++ }; ++ name = Debug; ++ }; ++ 607A66462B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ ++ ++/* Begin XCConfigurationList section */ ++ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A663F2B0EFA3A0010BFC8 /* Debug */, ++ 607A66402B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66422B0EFA3A0010BFC8 /* Debug */, ++ 607A66432B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66452B0EFA3A0010BFC8 /* Debug */, ++ 607A66462B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; ++} +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// iOSTestbed ++// ++ ++#import ++ ++@interface AppDelegate : UIResponder ++ ++ ++@end +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/AppDelegate.m +@@ -0,0 +1,19 @@ ++// ++// AppDelegate.m ++// iOSTestbed ++// ++ ++#import "AppDelegate.h" ++ ++@interface AppDelegate () ++ ++@end ++ ++@implementation AppDelegate ++ ++ ++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ++ return YES; ++} ++ ++@end +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +@@ -0,0 +1,11 @@ ++{ ++ "colors" : [ ++ { ++ "idiom" : "universal" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +@@ -0,0 +1,13 @@ ++{ ++ "images" : [ ++ { ++ "idiom" : "universal", ++ "platform" : "ios", ++ "size" : "1024x1024" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/Assets.xcassets/Contents.json +@@ -0,0 +1,6 @@ ++{ ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,9 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ iPhoneOS ++ ++ MinimumOSVersion ++ 12.0 ++ CFBundleVersion ++ 1 ++ ++ +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist +@@ -0,0 +1,54 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.iOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ LSRequiresIPhoneOS ++ ++ UIRequiresFullScreen ++ ++ UILaunchStoryboardName ++ Launch Screen ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ MainModule ++ ios ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ ++ ++ +--- /dev/null ++++ b/iOS/testbed/iOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// iOSTestbed ++// ++ ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +--- /dev/null ++++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m +@@ -0,0 +1,111 @@ ++#import ++#import ++ ++@interface iOSTestbedTests : XCTestCase ++ ++@end ++ ++@implementation iOSTestbedTests ++ ++ ++- (void)testPython { ++ // Arguments to pass into the test suite runner. ++ // argv[0] must identify the process; any subsequent arg ++ // will be handled as if it were an argument to `python -m test` ++ const char *argv[] = { ++ "iOSTestbed", // argv[0] is the process that is running. ++ "-uall", // Enable all resources ++ "-W", // Display test output on failure ++ // To run a subset of tests, add the test names below; e.g., ++ // "test_os", ++ // "test_sys", ++ }; ++ ++ // Start a Python interpreter. ++ int exit_code; ++ PyStatus status; ++ PyPreConfig preconfig; ++ PyConfig config; ++ NSString *python_home; ++ wchar_t *wtmp_str; ++ ++ NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; ++ ++ // Generate an isolated Python configuration. ++ NSLog(@"Configuring isolated Python..."); ++ PyPreConfig_InitIsolatedConfig(&preconfig); ++ PyConfig_InitIsolatedConfig(&config); ++ ++ // Configure the Python interpreter: ++ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. ++ // See https://docs.python.org/3/library/os.html#python-utf-8-mode. ++ preconfig.utf8_mode = 1; ++ // Don't buffer stdio. We want output to appears in the log immediately ++ config.buffered_stdio = 0; ++ // Don't write bytecode; we can't modify the app bundle ++ // after it has been signed. ++ config.write_bytecode = 0; ++ // Ensure that signal handlers are installed ++ config.install_signal_handlers = 1; ++ // Run the test module. ++ config.run_module = Py_DecodeLocale("test", NULL); ++ // For debugging - enable verbose mode. ++ // config.verbose = 1; ++ ++ NSLog(@"Pre-initializing Python runtime..."); ++ status = Py_PreInitialize(&preconfig); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ ++ // Set the home for the Python interpreter ++ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; ++ NSLog(@"PythonHome: %@", python_home); ++ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); ++ status = PyConfig_SetString(&config, &config.home, wtmp_str); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ PyMem_RawFree(wtmp_str); ++ ++ // Read the site config ++ status = PyConfig_Read(&config); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to read site config: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ ++ NSLog(@"Configure argc/argv..."); ++ status = PyConfig_SetBytesArgv(&config, sizeof(argv) / sizeof(char *), (char**) argv); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ ++ NSLog(@"Initializing Python runtime..."); ++ status = Py_InitializeFromConfig(&config); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ ++ // Start the test suite. Print a separator to differentiate Python startup logs from app logs ++ NSLog(@"---------------------------------------------------------------------------"); ++ ++ exit_code = Py_RunMain(); ++ XCTAssertEqual(exit_code, 0, @"Python test suite did not pass"); ++ ++ NSLog(@"---------------------------------------------------------------------------"); ++ ++ Py_Finalize(); ++} ++ ++ ++@end diff --git a/setup.py b/setup.py -index 4f122b62e0..c37ebf0e1e 100644 +index ad8fb81b218..266035c472c 100644 --- a/setup.py +++ b/setup.py @@ -82,6 +82,9 @@ @@ -3112,12 +9170,12 @@ index 4f122b62e0..c37ebf0e1e 100644 + dirs.append(path) + break return dirs - - -@@ -1399,6 +1406,11 @@ + + +@@ -1400,6 +1407,11 @@ extra_compile_args.append('-DMACOSX') include_dirs.append('_ctypes/darwin') - + + elif IOS or TVOS or WATCHOS: + sources.append('_ctypes/malloc_closure.c') + extra_compile_args.append('-DUSING_MALLOC_CLOSURE_DOT_C') @@ -3126,31 +9184,499 @@ index 4f122b62e0..c37ebf0e1e 100644 elif HOST_PLATFORM == 'sunos5': # XXX This shouldn't be necessary; it appears that some # of the assembler code is non-PIC (i.e. it has relocations -@@ -1421,7 +1433,8 @@ +@@ -1422,7 +1434,8 @@ self.addext(Extension('_ctypes_test', ['_ctypes/_ctypes_test.c'])) - + ffi_inc = sysconfig.get_config_var("LIBFFI_INCLUDEDIR") - ffi_lib = None + ffi_lib_dir = sysconfig.get_config_var("LIBFFI_LIBDIR") + ffi_lib = sysconfig.get_config_var("LIBFFI_LIB") - + ffi_inc_dirs = self.inc_dirs.copy() if MACOS: -@@ -1450,6 +1463,7 @@ +@@ -1451,6 +1464,7 @@ for lib_name in ('ffi', 'ffi_pic'): if (self.compiler.find_library_file(self.lib_dirs, lib_name)): ffi_lib = lib_name + self.use_system_libffi = True break - + if ffi_inc and ffi_lib: -@@ -1463,7 +1477,8 @@ - +@@ -1464,7 +1478,8 @@ + ext.include_dirs.append(ffi_inc) ext.libraries.append(ffi_lib) - self.use_system_libffi = True + if ffi_lib_dir: + ext.library_dirs.append(ffi_lib_dir) - + if sysconfig.get_config_var('HAVE_LIBDL'): # for dlopen, see bpo-32647 +--- /dev/null ++++ b/tvOS/README.rst +@@ -0,0 +1,108 @@ ++===================== ++Python on tvOS README ++===================== ++ ++:Authors: ++ Russell Keith-Magee (2023-11) ++ ++This document provides a quick overview of some tvOS specific features in the ++Python distribution. ++ ++Compilers for building on tvOS ++============================== ++ ++Building for tvOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode, on the ++most recently released macOS. ++ ++tvOS specific arguments to configure ++=================================== ++ ++* ``--enable-framework[=DIR]`` ++ ++ This argument specifies the location where the Python.framework will ++ be installed. ++ ++* ``--with-framework-name=NAME`` ++ ++ Specify the name for the python framework, defaults to ``Python``. ++ ++ ++Building and using Python on tvOS ++================================= ++ ++ABIs and Architectures ++---------------------- ++ ++tvOS apps can be deployed on physical devices, and on the tvOS simulator. ++Although the API used on these devices is identical, the ABI is different - you ++need to link against different libraries for an tvOS device build ++(``appletvos``) or an tvOS simulator build (``appletvsimulator``). Apple uses ++the XCframework format to allow specifying a single dependency that supports ++multiple ABIs. An XCframework is a wrapper around multiple ABI-specific ++frameworks. ++ ++tvOS can also support different CPU architectures within each ABI. At present, ++there is only a single support ed architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines.) ++ ++To support multiple CPU architectures on a single platform, Apple uses a "fat ++binary" format - a single physical file that contains support for multiple ++architectures. ++ ++How do I build Python for tvOS? ++------------------------------- ++ ++The Python build system will build a ``Python.framework`` that supports a ++*single* ABI with a *single* architecture. If you want to use Python in an tvOS ++project, you need to: ++ ++1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; ++2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; ++3. Merge the "fat" frameworks for each ABI into a single XCframework. ++ ++tvOS builds of Python *must* be constructed as framework builds. To support this, ++you must provide the ``--enable-framework`` flag when configuring the build. ++ ++The build also requires the use of cross-compilation. The commands for building ++Python for tvOS will look somethign like:: ++ ++ $ ./configure \ ++ --enable-framework=/path/to/install \ ++ --host=aarch64-apple-tvos \ ++ --build=aarch64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++ $ make ++ $ make install ++ ++In this invocation: ++ ++* ``/path/to/install`` is the location where the final Python.framework will be ++ output. ++ ++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: ++ ++ - ``aarch64-apple-tvos`` for ARM64 tvOS devices. ++ - ``aarch64-apple-tvos-simulator`` for the tvOS simulator running on Apple ++ Silicon devices. ++ - ``x86_64-apple-tvos-simulator`` for the tvOS simulator running on Intel ++ devices. ++ ++* ``--build`` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: ++ ++ - ``aarch64-apple-darwin`` for Apple Silicon devices. ++ - ``x86_64-apple-darwin`` for Intel devices. ++ ++* ``/path/to/python.exe`` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for tvOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the version as the Python that is being compiled. ++ ++Using a framework-based Python on tvOS ++====================================== +--- /dev/null ++++ b/tvOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ @TVOS_DEPLOYMENT_TARGET@ ++ ++ +--- /dev/null ++++ b/tvOS/Resources/bin/arm64-apple-tvos-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/tvOS/Resources/bin/arm64-apple-tvos-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos $@ +--- /dev/null ++++ b/tvOS/Resources/bin/arm64-apple-tvos-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos -E $@ +--- /dev/null ++++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator $@ +--- /dev/null ++++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator -E $@ +--- /dev/null ++++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator $@ +--- /dev/null ++++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator -E $@ +--- /dev/null ++++ b/tvOS/Resources/dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ 9.0 ++ CFBundleVersion ++ 1 ++ ++ +--- /dev/null ++++ b/tvOS/Resources/pyconfig.h +@@ -0,0 +1,7 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif ++ ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +--- /dev/null ++++ b/watchOS/README.rst +@@ -0,0 +1,108 @@ ++======================== ++Python on watchOS README ++======================== ++ ++:Authors: ++ Russell Keith-Magee (2023-11) ++ ++This document provides a quick overview of some watchOS specific features in the ++Python distribution. ++ ++Compilers for building on watchOS ++================================= ++ ++Building for watchOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode, on the ++most recently released macOS. ++ ++watchOS specific arguments to configure ++======================================= ++ ++* ``--enable-framework[=DIR]`` ++ ++ This argument specifies the location where the Python.framework will ++ be installed. ++ ++* ``--with-framework-name=NAME`` ++ ++ Specify the name for the python framework, defaults to ``Python``. ++ ++ ++Building and using Python on watchOS ++==================================== ++ ++ABIs and Architectures ++---------------------- ++ ++watchOS apps can be deployed on physical devices, and on the watchOS simulator. ++Although the API used on these devices is identical, the ABI is different - you ++need to link against different libraries for an watchOS device build ++(``watchos``) or an watchOS simulator build (``watchsimulator``). Apple uses the ++XCframework format to allow specifying a single dependency that supports ++multiple ABIs. An XCframework is a wrapper around multiple ABI-specific ++frameworks. ++ ++watchOS can also support different CPU architectures within each ABI. At present, ++there is only a single support ed architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines.) ++ ++To support multiple CPU architectures on a single platform, Apple uses a "fat ++binary" format - a single physical file that contains support for multiple ++architectures. ++ ++How do I build Python for watchOS? ++------------------------------- ++ ++The Python build system will build a ``Python.framework`` that supports a ++*single* ABI with a *single* architecture. If you want to use Python in an watchOS ++project, you need to: ++ ++1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; ++2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; ++3. Merge the "fat" frameworks for each ABI into a single XCframework. ++ ++watchOS builds of Python *must* be constructed as framework builds. To support this, ++you must provide the ``--enable-framework`` flag when configuring the build. ++ ++The build also requires the use of cross-compilation. The commands for building ++Python for watchOS will look somethign like:: ++ ++ $ ./configure \ ++ --enable-framework=/path/to/install \ ++ --host=aarch64-apple-watchos \ ++ --build=aarch64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++ $ make ++ $ make install ++ ++In this invocation: ++ ++* ``/path/to/install`` is the location where the final Python.framework will be ++ output. ++ ++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: ++ ++ - ``arm64_32-apple-watchos`` for ARM64-32 watchOS devices. ++ - ``aarch64-apple-watchos-simulator`` for the watchOS simulator running on Apple ++ Silicon devices. ++ - ``x86_64-apple-watchos-simulator`` for the watchOS simulator running on Intel ++ devices. ++ ++* ``--build`` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: ++ ++ - ``aarch64-apple-darwin`` for Apple Silicon devices. ++ - ``x86_64-apple-darwin`` for Intel devices. ++ ++* ``/path/to/python.exe`` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for watchOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the version as the Python that is being compiled. ++ ++Using a framework-based Python on watchOS ++====================================== +--- /dev/null ++++ b/watchOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2023 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ %VERSION% ++ CFBundleSupportedPlatforms ++ ++ watchOS ++ ++ MinimumOSVersion ++ @WATCHOS_DEPLOYMENT_TARGET@ ++ ++ +--- /dev/null ++++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos-simulator $@ +--- /dev/null ++++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator clang -target arm64-apple-watchos-simulator -E $@ +--- /dev/null ++++ b/watchOS/Resources/bin/arm64_32-apple-watchos-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos $@ +--- /dev/null ++++ b/watchOS/Resources/bin/arm64_32-apple-watchos-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos -E $@ +--- /dev/null ++++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar $@ +--- /dev/null ++++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator $@ +--- /dev/null ++++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator -E $@ +--- /dev/null ++++ b/watchOS/Resources/dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ watchOS ++ ++ MinimumOSVersion ++ 4.0 ++ CFBundleVersion ++ 1 ++ ++ +--- /dev/null ++++ b/watchOS/Resources/pyconfig.h +@@ -0,0 +1,11 @@ ++#ifdef __arm64__ ++# ifdef __LP64__ ++#include "pyconfig-arm64.h" ++# else ++#include "pyconfig-arm64_32.h" ++# endif ++#endif ++ ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif From 7d164ce53b10581df21a01c758b720b9e147f206 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 9 Sep 2024 11:16:02 +0800 Subject: [PATCH 07/13] Update patch for Python 3.11.10. --- Makefile | 2 +- README.rst | 2 +- patch/Python/Python.patch | 9711 +------------------------------------ 3 files changed, 72 insertions(+), 9643 deletions(-) diff --git a/Makefile b/Makefile index 30e3b524..d58ad536 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ BUILD_NUMBER=custom # of a release cycle, as official binaries won't be published. # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.11.9 +PYTHON_VERSION=3.11.10 PYTHON_PKG_VERSION=$(PYTHON_VERSION) PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+") diff --git a/README.rst b/README.rst index eb9ddedd..85aa1520 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ Python Apple Support This is a meta-package for building a version of Python that can be embedded into a macOS, iOS, tvOS or watchOS project. -**This branch builds a packaged version of Python 3.11.9**. +**This branch builds a packaged version of Python 3.11**. Other Python versions are available by cloning other branches of the main repository: diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 6485f7af..ca4e3159 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -23,35 +23,6 @@ index e680e1f9b43..c1b79d2a4a0 100644 - ``wasm32-emscripten`` and ``wasm32-wasi``. See + This module does not work or is not available on WebAssembly. See :ref:`wasm-availability` for more information. -diff --git a/Doc/library/asyncio.rst b/Doc/library/asyncio.rst -index 5f33c6813e7..eec97c2da36 100644 ---- a/Doc/library/asyncio.rst -+++ b/Doc/library/asyncio.rst -@@ -56,8 +56,12 @@ - * :ref:`bridge ` callback-based libraries and code - with async/await syntax. - -+.. include:: ../includes/wasm-notavail.rst -+ - .. _asyncio-cli: - -+.. rubric:: asyncio REPL -+ - You can experiment with an ``asyncio`` concurrent context in the REPL: - - .. code-block:: pycon -@@ -70,7 +74,10 @@ - >>> await asyncio.sleep(10, result='hello') - 'hello' - --.. include:: ../includes/wasm-notavail.rst -+.. audit-event:: cpython.run_stdin "" "" -+ -+.. versionchanged:: 3.11.10 (also 3.10.15, 3.9.20, and 3.8.20) -+ Emits audit events. - - .. We use the "rubric" directive here to avoid creating - the "Reference" subsection in the TOC. diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index 0d743efd28e..dfd71112231 100644 --- a/Doc/library/curses.rst @@ -82,106 +53,6 @@ index 74f96b6c433..30b7738a08e 100644 Close the database. - -diff --git a/Doc/library/email.errors.rst b/Doc/library/email.errors.rst -index 56aea6598b8..27b0481a85a 100644 ---- a/Doc/library/email.errors.rst -+++ b/Doc/library/email.errors.rst -@@ -58,6 +58,13 @@ - :class:`~email.mime.nonmultipart.MIMENonMultipart` (e.g. - :class:`~email.mime.image.MIMEImage`). - -+ -+.. exception:: HeaderWriteError() -+ -+ Raised when an error occurs when the :mod:`~email.generator` outputs -+ headers. -+ -+ - .. exception:: MessageDefect() - - This is the base class for all defects found when parsing email messages. -diff --git a/Doc/library/email.policy.rst b/Doc/library/email.policy.rst -index bb406c5a56c..3edba4028b1 100644 ---- a/Doc/library/email.policy.rst -+++ b/Doc/library/email.policy.rst -@@ -228,6 +228,24 @@ - - .. versionadded:: 3.6 - -+ -+ .. attribute:: verify_generated_headers -+ -+ If ``True`` (the default), the generator will raise -+ :exc:`~email.errors.HeaderWriteError` instead of writing a header -+ that is improperly folded or delimited, such that it would -+ be parsed as multiple headers or joined with adjacent data. -+ Such headers can be generated by custom header classes or bugs -+ in the ``email`` module. -+ -+ As it's a security feature, this defaults to ``True`` even in the -+ :class:`~email.policy.Compat32` policy. -+ For backwards compatible, but unsafe, behavior, it must be set to -+ ``False`` explicitly. -+ -+ .. versionadded:: 3.11.10 -+ -+ - The following :class:`Policy` method is intended to be called by code using - the email library to create policy instances with custom settings: - -diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst -index 0e266b6a457..97ddf492617 100644 ---- a/Doc/library/email.utils.rst -+++ b/Doc/library/email.utils.rst -@@ -60,13 +60,18 @@ - begins with angle brackets, they are stripped off. - - --.. function:: parseaddr(address) -+.. function:: parseaddr(address, *, strict=True) - - Parse address -- which should be the value of some address-containing field such - as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and - *email address* parts. Returns a tuple of that information, unless the parse - fails, in which case a 2-tuple of ``('', '')`` is returned. - -+ If *strict* is true, use a strict parser which rejects malformed inputs. -+ -+ .. versionchanged:: 3.11.10 -+ Add *strict* optional parameter and reject malformed inputs by default. -+ - - .. function:: formataddr(pair, charset='utf-8') - -@@ -84,12 +89,15 @@ - Added the *charset* option. - - --.. function:: getaddresses(fieldvalues) -+.. function:: getaddresses(fieldvalues, *, strict=True) - - This method returns a list of 2-tuples of the form returned by ``parseaddr()``. - *fieldvalues* is a sequence of header field values as might be returned by -- :meth:`Message.get_all `. Here's a simple -- example that gets all the recipients of a message:: -+ :meth:`Message.get_all `. -+ -+ If *strict* is true, use a strict parser which rejects malformed inputs. -+ -+ Here's a simple example that gets all the recipients of a message:: - - from email.utils import getaddresses - -@@ -99,6 +107,9 @@ - resent_ccs = msg.get_all('resent-cc', []) - all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) - -+ .. versionchanged:: 3.11.10 -+ Add *strict* optional parameter and reject malformed inputs by default. -+ - - .. function:: parsedate(date) - diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst index de3b93f5e61..168e45cfd6f 100644 --- a/Doc/library/ensurepip.rst @@ -221,21 +92,6 @@ index ee55b12ea86..80260fec8d3 100644 Group database entries are reported as a tuple-like object, whose attributes correspond to the members of the ``group`` structure (Attribute field below, see -diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst -index 5af667a17d0..aa2c8e717a3 100644 ---- a/Doc/library/gzip.rst -+++ b/Doc/library/gzip.rst -@@ -190,7 +190,9 @@ - .. versionchanged:: 3.11 - Speed is improved by compressing all data at once instead of in a - streamed fashion. Calls with *mtime* set to ``0`` are delegated to -- :func:`zlib.compress` for better speed. -+ :func:`zlib.compress` for better speed. In this situation the -+ output may contain a gzip header "OS" byte value other than 255 -+ "unknown" as supplied by the underlying zlib implementation. - - .. function:: decompress(data) - diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index e9200dd1e2d..5f57362e557 100644 --- a/Doc/library/importlib.rst @@ -368,68 +224,6 @@ index 5a4c9b8b16a..ffc8939d211 100644 + + As a result, Python library that involve console manipulation (such as + :mod:`curses` and :mod:`readline`) are not available on iOS. -diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst -index 03dc956cd13..f57fa15aa5b 100644 ---- a/Doc/library/ipaddress.rst -+++ b/Doc/library/ipaddress.rst -@@ -178,18 +178,53 @@ - - .. attribute:: is_private - -- ``True`` if the address is allocated for private networks. See -+ ``True`` if the address is defined as not globally reachable by - iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -- (for IPv6). -+ (for IPv6) with the following exceptions: -+ -+ * ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``) -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the shared address space -+ (``100.64.0.0/10`` range) where they are both ``False``. -+ -+ .. versionchanged:: 3.11.10 -+ -+ Fixed some false positives and false negatives. -+ -+ * ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and -+ ``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private). -+ * ``64:ff9b:1::/48`` is considered private. -+ * ``2002::/16`` is considered private. -+ * There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``, -+ ``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``. -+ The exceptions are not considered private. - - .. attribute:: is_global - -- ``True`` if the address is allocated for public networks. See -+ ``True`` if the address is defined as globally reachable by - iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -- (for IPv6). -+ (for IPv6) with the following exception: -+ -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global -+ -+ ``is_global`` has value opposite to :attr:`is_private`, except for the shared address space -+ (``100.64.0.0/10`` range) where they are both ``False``. - - .. versionadded:: 3.4 - -+ .. versionchanged:: 3.11.10 -+ -+ Fixed some false positives and false negatives, see :attr:`is_private` for details. -+ - .. attribute:: is_unspecified - - ``True`` if the address is unspecified. See :RFC:`5735` (for IPv4) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index ffd690148f1..4173d76d4fb 100644 --- a/Doc/library/multiprocessing.rst @@ -444,7 +238,7 @@ index ffd690148f1..4173d76d4fb 100644 Introduction ------------ diff --git a/Doc/library/os.rst b/Doc/library/os.rst -index 5fefee382fd..6cd6fb018ff 100644 +index 1af9e79db57..6cd6fb018ff 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -34,12 +34,13 @@ @@ -479,28 +273,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. availability:: Unix. .. versionchanged:: 3.3 -@@ -2178,6 +2184,10 @@ - platform-dependent. On some platforms, they are ignored and you should call - :func:`chmod` explicitly to set them. - -+ On Windows, a *mode* of ``0o700`` is specifically handled to apply access -+ control to the new directory such that only the current user and -+ administrators have access. Other values of *mode* are ignored. -+ - This function can also support :ref:`paths relative to directory descriptors - `. - -@@ -2192,6 +2202,9 @@ - .. versionchanged:: 3.6 - Accepts a :term:`path-like object`. - -+ .. versionchanged:: 3.11.10 -+ Windows now handles a *mode* of ``0o700``. -+ - - .. function:: makedirs(name, mode=0o777, exist_ok=False) - -@@ -3761,7 +3774,7 @@ +@@ -3768,7 +3774,7 @@ .. audit-event:: os.exec path,args,env os.execl @@ -509,7 +282,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionchanged:: 3.3 Added support for specifying *path* as an open file descriptor -@@ -3941,7 +3954,16 @@ +@@ -3948,7 +3954,16 @@ See :mod:`ssl` for applications that use the SSL module with fork(). @@ -527,7 +300,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: forkpty() -@@ -3963,7 +3985,12 @@ +@@ -3970,7 +3985,12 @@ Calling ``forkpty()`` in a subinterpreter is no longer supported (:exc:`RuntimeError` is raised). @@ -541,7 +314,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: kill(pid, sig, /) -@@ -3987,7 +4014,7 @@ +@@ -3994,7 +4014,7 @@ .. audit-event:: os.kill pid,sig os.kill @@ -550,7 +323,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionchanged:: 3.2 Added Windows support. -@@ -4003,7 +4030,7 @@ +@@ -4010,7 +4030,7 @@ .. audit-event:: os.killpg pgid,sig os.killpg @@ -559,7 +332,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: nice(increment, /) -@@ -4031,7 +4058,7 @@ +@@ -4038,7 +4058,7 @@ Lock program segments into memory. The value of *op* (defined in ````) determines which segments are locked. @@ -568,7 +341,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: popen(cmd, mode='r', buffering=-1) -@@ -4063,7 +4090,7 @@ +@@ -4070,7 +4090,7 @@ documentation for more powerful ways to manage and communicate with subprocesses. @@ -577,7 +350,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. note:: The :ref:`Python UTF-8 Mode ` affects encodings used -@@ -4158,7 +4185,7 @@ +@@ -4165,7 +4185,7 @@ .. versionadded:: 3.8 @@ -586,7 +359,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ setpgroup=None, resetids=False, setsid=False, setsigmask=(), \ -@@ -4174,7 +4201,7 @@ +@@ -4181,7 +4201,7 @@ .. versionadded:: 3.8 @@ -595,7 +368,7 @@ index 5fefee382fd..6cd6fb018ff 100644 See :func:`posix_spawn` documentation. -@@ -4207,7 +4234,7 @@ +@@ -4214,7 +4234,7 @@ There is no way to unregister a function. @@ -604,7 +377,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionadded:: 3.7 -@@ -4276,7 +4303,7 @@ +@@ -4283,7 +4303,7 @@ .. audit-event:: os.spawn mode,path,args,env os.spawnl @@ -613,7 +386,7 @@ index 5fefee382fd..6cd6fb018ff 100644 :func:`spawnlp`, :func:`spawnlpe`, :func:`spawnvp` and :func:`spawnvpe` are not available on Windows. :func:`spawnle` and -@@ -4400,7 +4427,7 @@ +@@ -4407,7 +4427,7 @@ .. audit-event:: os.system command os.system @@ -622,7 +395,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: times() -@@ -4444,7 +4471,7 @@ +@@ -4451,7 +4471,7 @@ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an exit code. @@ -631,7 +404,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. seealso:: -@@ -4478,7 +4505,10 @@ +@@ -4485,7 +4505,10 @@ Otherwise, if there are no matching children that could be waited for, :exc:`ChildProcessError` is raised. @@ -643,7 +416,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. note:: This function is not available on macOS. -@@ -4519,7 +4549,7 @@ +@@ -4526,7 +4549,7 @@ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an exit code. @@ -652,7 +425,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionchanged:: 3.5 If the system call is interrupted and the signal handler does not raise an -@@ -4539,7 +4569,7 @@ +@@ -4546,7 +4569,7 @@ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an exitcode. @@ -661,7 +434,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: wait4(pid, options) -@@ -4553,7 +4583,7 @@ +@@ -4560,7 +4583,7 @@ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an exitcode. @@ -670,7 +443,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. data:: P_PID -@@ -4570,7 +4600,7 @@ +@@ -4577,7 +4600,7 @@ * :data:`!P_PIDFD` - wait for the child identified by the file descriptor *id* (a process file descriptor created with :func:`pidfd_open`). @@ -679,7 +452,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. note:: :data:`!P_PIDFD` is only available on Linux >= 5.4. -@@ -4585,7 +4615,7 @@ +@@ -4592,7 +4615,7 @@ :func:`waitid` causes child processes to be reported if they have been continued from a job control stop since they were last reported. @@ -688,7 +461,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. data:: WEXITED -@@ -4596,7 +4626,7 @@ +@@ -4603,7 +4626,7 @@ The other ``wait*`` functions always report children that have terminated, so this option is not available for them. @@ -697,7 +470,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionadded:: 3.3 -@@ -4608,7 +4638,7 @@ +@@ -4615,7 +4638,7 @@ This option is not available for the other ``wait*`` functions. @@ -706,7 +479,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionadded:: 3.3 -@@ -4621,7 +4651,7 @@ +@@ -4628,7 +4651,7 @@ This option is not available for :func:`waitid`. @@ -715,7 +488,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. data:: WNOHANG -@@ -4630,7 +4660,7 @@ +@@ -4637,7 +4660,7 @@ :func:`waitid` to return right away if no child process status is available immediately. @@ -724,7 +497,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. data:: WNOWAIT -@@ -4640,7 +4670,7 @@ +@@ -4647,7 +4670,7 @@ This option is not available for the other ``wait*`` functions. @@ -733,7 +506,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. data:: CLD_EXITED -@@ -4653,7 +4683,7 @@ +@@ -4660,7 +4683,7 @@ These are the possible values for :attr:`!si_code` in the result returned by :func:`waitid`. @@ -742,7 +515,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionadded:: 3.3 -@@ -4688,7 +4718,7 @@ +@@ -4695,7 +4718,7 @@ :func:`WIFEXITED`, :func:`WEXITSTATUS`, :func:`WIFSIGNALED`, :func:`WTERMSIG`, :func:`WIFSTOPPED`, :func:`WSTOPSIG` functions. @@ -751,7 +524,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. versionadded:: 3.9 -@@ -4704,7 +4734,7 @@ +@@ -4711,7 +4734,7 @@ This function should be employed only if :func:`WIFSIGNALED` is true. @@ -760,7 +533,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: WIFCONTINUED(status) -@@ -4715,7 +4745,7 @@ +@@ -4722,7 +4745,7 @@ See :data:`WCONTINUED` option. @@ -769,7 +542,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: WIFSTOPPED(status) -@@ -4727,14 +4757,14 @@ +@@ -4734,14 +4757,14 @@ done using :data:`WUNTRACED` option or when the process is being traced (see :manpage:`ptrace(2)`). @@ -786,7 +559,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: WIFEXITED(status) -@@ -4743,7 +4773,7 @@ +@@ -4750,7 +4773,7 @@ by calling ``exit()`` or ``_exit()``, or by returning from ``main()``; otherwise return ``False``. @@ -795,7 +568,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: WEXITSTATUS(status) -@@ -4752,7 +4782,7 @@ +@@ -4759,7 +4782,7 @@ This function should be employed only if :func:`WIFEXITED` is true. @@ -804,7 +577,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: WSTOPSIG(status) -@@ -4761,7 +4791,7 @@ +@@ -4768,7 +4791,7 @@ This function should be employed only if :func:`WIFSTOPPED` is true. @@ -813,7 +586,7 @@ index 5fefee382fd..6cd6fb018ff 100644 .. function:: WTERMSIG(status) -@@ -4770,7 +4800,7 @@ +@@ -4777,7 +4800,7 @@ This function should be employed only if :func:`WIFSIGNALED` is true. @@ -1002,7 +775,7 @@ index 3a5368717ac..de47e1f6e5f 100644 Unix platforms supporting :meth:`~socket.sendmsg` and :const:`SCM_RIGHTS` mechanism. diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst -index f2d0f83bb68..7009a7e8314 100644 +index c0ae9e5d7aa..7009a7e8314 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -25,7 +25,7 @@ @@ -1014,32 +787,6 @@ index f2d0f83bb68..7009a7e8314 100644 Using the :mod:`subprocess` Module ---------------------------------- -@@ -756,8 +756,8 @@ - Security Considerations - ----------------------- - --Unlike some other popen functions, this implementation will never --implicitly call a system shell. This means that all characters, -+Unlike some other popen functions, this library will not -+implicitly choose to call a system shell. This means that all characters, - including shell metacharacters, can safely be passed to child processes. - If the shell is invoked explicitly, via ``shell=True``, it is the application's - responsibility to ensure that all whitespace and metacharacters are -@@ -766,6 +766,14 @@ - vulnerabilities. On :ref:`some platforms `, it is possible - to use :func:`shlex.quote` for this escaping. - -+On Windows, batch files (:file:`*.bat` or :file:`*.cmd`) may be launched by the -+operating system in a system shell regardless of the arguments passed to this -+library. This could result in arguments being parsed according to shell rules, -+but without any escaping added by Python. If you are intentionally launching a -+batch file with arguments from untrusted sources, consider passing -+``shell=True`` to allow Python to escape special characters. See :gh:`114539` -+for additional discussion. -+ - - Popen Objects - ------------- diff --git a/Doc/library/syslog.rst b/Doc/library/syslog.rst index 889bbb39d58..6c7c0907143 100644 --- a/Doc/library/syslog.rst @@ -1141,19 +888,6 @@ index 734b6321e5a..b709e511afa 100644 Here are some simple examples:: url = 'https://docs.python.org/' -diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst -index 9f3f80294dd..31035bce934 100644 ---- a/Doc/reference/datamodel.rst -+++ b/Doc/reference/datamodel.rst -@@ -1217,7 +1217,7 @@ - - The iterator returns :class:`tuple`\s containing the ``(start_line, end_line, - start_column, end_column)``. The *i-th* tuple corresponds to the -- position of the source code that compiled to the *i-th* instruction. -+ position of the source code that compiled to the *i-th* code unit. - Column information is 0-indexed utf-8 byte offsets on the given source - line. - diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index c849a4d3875..4b8252cb590 100644 --- a/Doc/tools/extensions/pyspecific.py @@ -1176,22 +910,6 @@ index c849a4d3875..4b8252cb590 100644 Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not parsed into separate tokens. -diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst -index fa2e6d08fb1..a1a7073cf5b 100644 ---- a/Doc/using/cmdline.rst -+++ b/Doc/using/cmdline.rst -@@ -697,6 +697,11 @@ - This variable can also be modified by Python code using :data:`os.environ` - to force inspect mode on program termination. - -+ .. audit-event:: cpython.run_stdin "" "" -+ -+ .. versionchanged:: 3.11.10 (also 3.10.15, 3.9.20, and 3.8.20) -+ Emits audit events. -+ - - .. envvar:: PYTHONUNBUFFERED - diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index e45a5a4791e..26ac341bc98 100644 --- a/Doc/using/configure.rst @@ -1626,136 +1344,6 @@ index 986d693d03f..da45aabca28 100644 Other Resources =============== -diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst -index f670fa1f097..583a408cace 100644 ---- a/Doc/whatsnew/3.11.rst -+++ b/Doc/whatsnew/3.11.rst -@@ -769,6 +769,21 @@ - (Contributed by Yurii Karabas in :issue:`46014`.) - - -+.. _whatsnew311-gzip: -+ -+gzip -+---- -+ -+* The :func:`gzip.compress` function is now faster when used with the -+ **mtime=0** argument as it delegates the compression entirely to a single -+ :func:`zlib.compress` operation. There is one side effect of this change: The -+ gzip file header contains an "OS" byte in its header. That was traditionally -+ always set to a value of 255 representing "unknown" by the :mod:`gzip` -+ module. Now, when using :func:`~gzip.compress` with **mtime=0**, it may be -+ set to a different value by the underlying zlib C library Python was linked -+ against. -+ (See :gh:`112346` for details on the side effect.) -+ - .. _whatsnew311-hashlib: - - hashlib -@@ -899,6 +914,13 @@ - instead of ``CryptGenRandom()`` which is deprecated. - (Contributed by Donghee Na in :issue:`44611`.) - -+* As of 3.11.10, :func:`os.mkdir` and :func:`os.makedirs` on Windows -+ now support passing a *mode* value of ``0o700`` to apply access -+ control to the new directory. This implicitly affects -+ :func:`tempfile.mkdtemp` and is a mitigation for CVE-2024-4030. -+ Other values for *mode* continue to be ignored. -+ (Contributed by Steve Dower in :gh:`118486`.) -+ - - .. _whatsnew311-pathlib: - -@@ -1059,6 +1081,11 @@ - such as compression modules. - (Contributed by Carey Metcalfe in :gh:`70363`.) - -+* As of 3.11.10 on Windows, the default mode ``0o700`` used by -+ :func:`tempfile.mkdtemp` now limits access to the new directory due to -+ changes to :func:`os.mkdir`. This is a mitigation for CVE-2024-4030. -+ (Contributed by Steve Dower in :gh:`118486`.) -+ - - .. _whatsnew311-threading: - -@@ -2727,3 +2754,35 @@ - * Windows builds and macOS installers from python.org now use OpenSSL 3.0. - - .. _libb2: https://www.blake2.net/ -+ -+ -+Notable changes in 3.11.10 -+========================== -+ -+ipaddress -+--------- -+ -+* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``, -+ ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``. -+ -+email -+----- -+ -+* Headers with embedded newlines are now quoted on output. -+ -+ The :mod:`~email.generator` will now refuse to serialize (write) headers -+ that are improperly folded or delimited, such that they would be parsed as -+ multiple headers or joined with adjacent data. -+ If you need to turn this safety feature off, -+ set :attr:`~email.policy.Policy.verify_generated_headers`. -+ (Contributed by Bas Bloemsaat and Petr Viktorin in :gh:`121650`.) -+ -+* :func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now return -+ ``('', '')`` 2-tuples in more situations where invalid email addresses are -+ encountered, instead of potentially inaccurate values. -+ An optional *strict* parameter was added to these two functions: -+ use ``strict=False`` to get the old behavior, accepting malformed inputs. -+ ``getattr(email.utils, 'supports_strict_parsing', False)`` can be used to -+ check if the *strict* paramater is available. -+ (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve -+ the CVE-2023-27043 fix.) -diff --git a/Grammar/python.gram b/Grammar/python.gram -index c7e7233c132..0a54279918e 100644 ---- a/Grammar/python.gram -+++ b/Grammar/python.gram -@@ -955,6 +955,7 @@ - - starred_expression[expr_ty]: - | '*' a=expression { _PyAST_Starred(a, Load, EXTRA) } -+ | '*' { RAISE_SYNTAX_ERROR("Invalid star expression") } - - kwarg_or_starred[KeywordOrStarred*]: - | invalid_kwarg -@@ -1075,8 +1076,8 @@ - - # From here on, there are rules for invalid syntax with specialised error messages - invalid_arguments: -- | ((','.(starred_expression | ( assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' b='*' { -- RAISE_SYNTAX_ERROR_KNOWN_LOCATION(b, "iterable argument unpacking follows keyword argument unpacking") } -+ | ((','.(starred_expression | ( assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) a=',' ','.(starred_expression !'=')+ { -+ RAISE_SYNTAX_ERROR_STARTING_FROM(a, "iterable argument unpacking follows keyword argument unpacking") } - | a=expression b=for_if_clauses ',' [args | expression for_if_clauses] { - RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, _PyPegen_get_last_comprehension_item(PyPegen_last_item(b, comprehension_ty)), "Generator expression must be parenthesized") } - | a=NAME b='=' expression for_if_clauses { -diff --git a/Include/patchlevel.h b/Include/patchlevel.h -index 6dea1dd4486..7540bc8a6c8 100644 ---- a/Include/patchlevel.h -+++ b/Include/patchlevel.h -@@ -18,12 +18,12 @@ - /*--start constants--*/ - #define PY_MAJOR_VERSION 3 - #define PY_MINOR_VERSION 11 --#define PY_MICRO_VERSION 9 -+#define PY_MICRO_VERSION 10 - #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL - #define PY_RELEASE_SERIAL 0 - - /* Version as a string */ --#define PY_VERSION "3.11.9" -+#define PY_VERSION "3.11.10" - /*--end constants--*/ - - /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. --- /dev/null +++ b/Lib/_ios_support.py @@ -0,0 +1,71 @@ @@ -1830,19 +1418,6 @@ index 6dea1dd4486..7540bc8a6c8 100644 + model = objc.objc_msgSend(device_model, SEL_UTF8String).decode() + + return system, release, model, is_simulator -diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py -index 18bb87a5bc4..73330f4ac3f 100644 ---- a/Lib/asyncio/__main__.py -+++ b/Lib/asyncio/__main__.py -@@ -90,6 +90,8 @@ - - - if __name__ == '__main__': -+ sys.audit("cpython.run_stdin") -+ - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 26135ad9629..315c725e547 100644 --- a/Lib/ctypes/__init__.py @@ -2001,386 +1576,6 @@ index 2ce5c5b64d6..e927f4af938 100644 try: import pwd os.environ['HOME'] = pwd.getpwuid(os.getuid())[5] -diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py -index 8cb8852cf0b..992394ea9ff 100644 ---- a/Lib/email/_header_value_parser.py -+++ b/Lib/email/_header_value_parser.py -@@ -92,6 +92,8 @@ - ASPECIALS = TSPECIALS | set("*'%") - ATTRIBUTE_ENDS = ASPECIALS | WSP - EXTENDED_ATTRIBUTE_ENDS = ATTRIBUTE_ENDS - set('%') -+NLSET = {'\n', '\r'} -+SPECIALSNL = SPECIALS | NLSET - - def quote_string(value): - return '"'+str(value).replace('\\', '\\\\').replace('"', r'\"')+'"' -@@ -950,6 +952,7 @@ - DOT = ValueTerminal('.', 'dot') - ListSeparator = ValueTerminal(',', 'list-separator') - ListSeparator.as_ew_allowed = False -+ListSeparator.syntactic_break = False - RouteComponentMarker = ValueTerminal('@', 'route-component-marker') - - # -@@ -2780,9 +2783,13 @@ - wrap_as_ew_blocked -= 1 - continue - tstr = str(part) -- if part.token_type == 'ptext' and set(tstr) & SPECIALS: -- # Encode if tstr contains special characters. -- want_encoding = True -+ if not want_encoding: -+ if part.token_type == 'ptext': -+ # Encode if tstr contains special characters. -+ want_encoding = not SPECIALSNL.isdisjoint(tstr) -+ else: -+ # Encode if tstr contains newlines. -+ want_encoding = not NLSET.isdisjoint(tstr) - try: - tstr.encode(encoding) - charset = encoding -@@ -2821,7 +2828,9 @@ - if not hasattr(part, 'encode'): - # It's not a Terminal, do each piece individually. - parts = list(part) + parts -- else: -+ want_encoding = False -+ continue -+ elif part.as_ew_allowed: - # It's a terminal, wrap it as an encoded word, possibly - # combining it with previously encoded words if allowed. - if (last_ew is not None and -@@ -2832,8 +2841,15 @@ - last_ew = _fold_as_ew(tstr, lines, maxlen, last_ew, - part.ew_combine_allowed, charset) - last_charset = charset -- want_encoding = False -- continue -+ want_encoding = False -+ continue -+ else: -+ # It's a terminal which should be kept non-encoded -+ # (e.g. a ListSeparator). -+ last_ew = None -+ want_encoding = False -+ # fall through -+ - if len(tstr) <= maxlen - len(lines[-1]): - lines[-1] += tstr - continue -diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py -index c9cbadd2a80..d1f48211f90 100644 ---- a/Lib/email/_policybase.py -+++ b/Lib/email/_policybase.py -@@ -157,6 +157,13 @@ - message_factory -- the class to use to create new message objects. - If the value is None, the default is Message. - -+ verify_generated_headers -+ -- if true, the generator verifies that each header -+ they are properly folded, so that a parser won't -+ treat it as multiple headers, start-of-body, or -+ part of another header. -+ This is a check against custom Header & fold() -+ implementations. - """ - - raise_on_defect = False -@@ -165,6 +172,7 @@ - max_line_length = 78 - mangle_from_ = False - message_factory = None -+ verify_generated_headers = True - - def handle_defect(self, obj, defect): - """Based on policy, either raise defect or call register_defect. -diff --git a/Lib/email/errors.py b/Lib/email/errors.py -index 3ad00565549..02aa5eced6a 100644 ---- a/Lib/email/errors.py -+++ b/Lib/email/errors.py -@@ -29,6 +29,10 @@ - """An illegal charset was given.""" - - -+class HeaderWriteError(MessageError): -+ """Error while writing headers.""" -+ -+ - # These are parsing defects which the parser was able to work around. - class MessageDefect(ValueError): - """Base class for a message defect.""" -diff --git a/Lib/email/generator.py b/Lib/email/generator.py -index eb597de76d4..563ca170726 100644 ---- a/Lib/email/generator.py -+++ b/Lib/email/generator.py -@@ -14,12 +14,14 @@ - from copy import deepcopy - from io import StringIO, BytesIO - from email.utils import _has_surrogates -+from email.errors import HeaderWriteError - - UNDERSCORE = '_' - NL = '\n' # XXX: no longer used by the code below. - - NLCRE = re.compile(r'\r\n|\r|\n') - fcre = re.compile(r'^From ', re.MULTILINE) -+NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]') - - - class Generator: -@@ -222,7 +224,16 @@ - - def _write_headers(self, msg): - for h, v in msg.raw_items(): -- self.write(self.policy.fold(h, v)) -+ folded = self.policy.fold(h, v) -+ if self.policy.verify_generated_headers: -+ linesep = self.policy.linesep -+ if not folded.endswith(self.policy.linesep): -+ raise HeaderWriteError( -+ f'folded header does not end with {linesep!r}: {folded!r}') -+ if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)): -+ raise HeaderWriteError( -+ f'folded header contains newline: {folded!r}') -+ self.write(folded) - # A blank line always separates headers from body - self.write(self._NL) - -diff --git a/Lib/email/utils.py b/Lib/email/utils.py -index 8993858ab48..94ead0e91fa 100644 ---- a/Lib/email/utils.py -+++ b/Lib/email/utils.py -@@ -48,6 +48,7 @@ - specialsre = re.compile(r'[][\\()<>@,:;".]') - escapesre = re.compile(r'[\\"]') - -+ - def _has_surrogates(s): - """Return True if s may contain surrogate-escaped binary data.""" - # This check is based on the fact that unless there are surrogates, utf8 -@@ -106,12 +107,127 @@ - return address - - -+def _iter_escaped_chars(addr): -+ pos = 0 -+ escape = False -+ for pos, ch in enumerate(addr): -+ if escape: -+ yield (pos, '\\' + ch) -+ escape = False -+ elif ch == '\\': -+ escape = True -+ else: -+ yield (pos, ch) -+ if escape: -+ yield (pos, '\\') -+ -+ -+def _strip_quoted_realnames(addr): -+ """Strip real names between quotes.""" -+ if '"' not in addr: -+ # Fast path -+ return addr -+ -+ start = 0 -+ open_pos = None -+ result = [] -+ for pos, ch in _iter_escaped_chars(addr): -+ if ch == '"': -+ if open_pos is None: -+ open_pos = pos -+ else: -+ if start != open_pos: -+ result.append(addr[start:open_pos]) -+ start = pos + 1 -+ open_pos = None -+ -+ if start < len(addr): -+ result.append(addr[start:]) -+ -+ return ''.join(result) - --def getaddresses(fieldvalues): -- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" -- all = COMMASPACE.join(str(v) for v in fieldvalues) -- a = _AddressList(all) -- return a.addresslist -+ -+supports_strict_parsing = True -+ -+def getaddresses(fieldvalues, *, strict=True): -+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. -+ -+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in -+ its place. -+ -+ If strict is true, use a strict parser which rejects malformed inputs. -+ """ -+ -+ # If strict is true, if the resulting list of parsed addresses is greater -+ # than the number of fieldvalues in the input list, a parsing error has -+ # occurred and consequently a list containing a single empty 2-tuple [('', -+ # '')] is returned in its place. This is done to avoid invalid output. -+ # -+ # Malformed input: getaddresses(['alice@example.com ']) -+ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] -+ # Safe output: [('', '')] -+ -+ if not strict: -+ all = COMMASPACE.join(str(v) for v in fieldvalues) -+ a = _AddressList(all) -+ return a.addresslist -+ -+ fieldvalues = [str(v) for v in fieldvalues] -+ fieldvalues = _pre_parse_validation(fieldvalues) -+ addr = COMMASPACE.join(fieldvalues) -+ a = _AddressList(addr) -+ result = _post_parse_validation(a.addresslist) -+ -+ # Treat output as invalid if the number of addresses is not equal to the -+ # expected number of addresses. -+ n = 0 -+ for v in fieldvalues: -+ # When a comma is used in the Real Name part it is not a deliminator. -+ # So strip those out before counting the commas. -+ v = _strip_quoted_realnames(v) -+ # Expected number of addresses: 1 + number of commas -+ n += 1 + v.count(',') -+ if len(result) != n: -+ return [('', '')] -+ -+ return result -+ -+ -+def _check_parenthesis(addr): -+ # Ignore parenthesis in quoted real names. -+ addr = _strip_quoted_realnames(addr) -+ -+ opens = 0 -+ for pos, ch in _iter_escaped_chars(addr): -+ if ch == '(': -+ opens += 1 -+ elif ch == ')': -+ opens -= 1 -+ if opens < 0: -+ return False -+ return (opens == 0) -+ -+ -+def _pre_parse_validation(email_header_fields): -+ accepted_values = [] -+ for v in email_header_fields: -+ if not _check_parenthesis(v): -+ v = "('', '')" -+ accepted_values.append(v) -+ -+ return accepted_values -+ -+ -+def _post_parse_validation(parsed_email_header_tuples): -+ accepted_values = [] -+ # The parser would have parsed a correctly formatted domain-literal -+ # The existence of an [ after parsing indicates a parsing failure -+ for v in parsed_email_header_tuples: -+ if '[' in v[1]: -+ v = ('', '') -+ accepted_values.append(v) -+ -+ return accepted_values - - - def _format_timetuple_and_zone(timetuple, zone): -@@ -205,16 +321,33 @@ - tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) - - --def parseaddr(addr): -+def parseaddr(addr, *, strict=True): - """ - Parse addr into its constituent realname and email address parts. - - Return a tuple of realname and email address, unless the parse fails, in - which case return a 2-tuple of ('', ''). -+ -+ If strict is True, use a strict parser which rejects malformed inputs. - """ -- addrs = _AddressList(addr).addresslist -- if not addrs: -- return '', '' -+ if not strict: -+ addrs = _AddressList(addr).addresslist -+ if not addrs: -+ return ('', '') -+ return addrs[0] -+ -+ if isinstance(addr, list): -+ addr = addr[0] -+ -+ if not isinstance(addr, str): -+ return ('', '') -+ -+ addr = _pre_parse_validation([addr])[0] -+ addrs = _post_parse_validation(_AddressList(addr).addresslist) -+ -+ if not addrs or len(addrs) > 1: -+ return ('', '') -+ - return addrs[0] - - -diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py -index 35ac2dc6ae2..2c1f021d0ab 100644 ---- a/Lib/http/cookies.py -+++ b/Lib/http/cookies.py -@@ -184,8 +184,13 @@ - return '"' + str.translate(_Translator) + '"' - - --_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") --_QuotePatt = re.compile(r"[\\].") -+_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub -+ -+def _unquote_replace(m): -+ if m[1]: -+ return chr(int(m[1], 8)) -+ else: -+ return m[2] - - def _unquote(str): - # If there aren't any doublequotes, -@@ -205,30 +210,7 @@ - # \012 --> \n - # \" --> " - # -- i = 0 -- n = len(str) -- res = [] -- while 0 <= i < n: -- o_match = _OctalPatt.search(str, i) -- q_match = _QuotePatt.search(str, i) -- if not o_match and not q_match: # Neither matched -- res.append(str[i:]) -- break -- # else: -- j = k = -1 -- if o_match: -- j = o_match.start(0) -- if q_match: -- k = q_match.start(0) -- if q_match and (not o_match or k < j): # QuotePatt matched -- res.append(str[i:k]) -- res.append(str[k+1]) -- i = k + 2 -- else: # OctalPatt matched -- res.append(str[i:j]) -- res.append(chr(int(str[j+1:j+4], 8))) -- i = j + 4 -- return _nulljoin(res) -+ return _unquote_sub(_unquote_replace, str) - - # The _getdate() routine is used to set the expiration time in the cookie's HTTP - # header. By default, _getdate() returns the current time in the appropriate diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index e53f6acf38f..64aa8444410 100644 --- a/Lib/importlib/_bootstrap_external.py @@ -2511,186 +1706,6 @@ index 09e6a23652f..0dbe90f470a 100644 raise OSError('source code not available') module = getmodule(object, file) -diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py -index 16ba16cd7de..567beb37e06 100644 ---- a/Lib/ipaddress.py -+++ b/Lib/ipaddress.py -@@ -1086,7 +1086,11 @@ - """ - return any(self.network_address in priv_network and - self.broadcast_address in priv_network -- for priv_network in self._constants._private_networks) -+ for priv_network in self._constants._private_networks) and all( -+ self.network_address not in network and -+ self.broadcast_address not in network -+ for network in self._constants._private_networks_exceptions -+ ) - - @property - def is_global(self): -@@ -1333,18 +1337,41 @@ - @property - @functools.lru_cache() - def is_private(self): -- """Test if this address is allocated for private networks. -+ """``True`` if the address is defined as not globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exceptions: - -- Returns: -- A boolean, True if the address is reserved per -- iana-ipv4-special-registry. -+ * ``is_private`` is ``False`` for ``100.64.0.0/10`` -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: - -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ -- return any(self in net for net in self._constants._private_networks) -+ return ( -+ any(self in net for net in self._constants._private_networks) -+ and all(self not in net for net in self._constants._private_networks_exceptions) -+ ) - - @property - @functools.lru_cache() - def is_global(self): -+ """``True`` if the address is defined as globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exception: -+ -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global -+ -+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. -+ """ - return self not in self._constants._public_network and not self.is_private - - @property -@@ -1548,13 +1575,15 @@ - - _public_network = IPv4Network('100.64.0.0/10') - -+ # Not globally reachable address blocks listed on -+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml - _private_networks = [ - IPv4Network('0.0.0.0/8'), - IPv4Network('10.0.0.0/8'), - IPv4Network('127.0.0.0/8'), - IPv4Network('169.254.0.0/16'), - IPv4Network('172.16.0.0/12'), -- IPv4Network('192.0.0.0/29'), -+ IPv4Network('192.0.0.0/24'), - IPv4Network('192.0.0.170/31'), - IPv4Network('192.0.2.0/24'), - IPv4Network('192.168.0.0/16'), -@@ -1565,6 +1594,11 @@ - IPv4Network('255.255.255.255/32'), - ] - -+ _private_networks_exceptions = [ -+ IPv4Network('192.0.0.9/32'), -+ IPv4Network('192.0.0.10/32'), -+ ] -+ - _reserved_network = IPv4Network('240.0.0.0/4') - - _unspecified_address = IPv4Address('0.0.0.0') -@@ -2010,27 +2044,42 @@ - @property - @functools.lru_cache() - def is_private(self): -- """Test if this address is allocated for private networks. -+ """``True`` if the address is defined as not globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exceptions: - -- Returns: -- A boolean, True if the address is reserved per -- iana-ipv6-special-registry, or is ipv4_mapped and is -- reserved in the iana-ipv4-special-registry. -+ * ``is_private`` is ``False`` for ``100.64.0.0/10`` -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: - -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ - ipv4_mapped = self.ipv4_mapped - if ipv4_mapped is not None: - return ipv4_mapped.is_private -- return any(self in net for net in self._constants._private_networks) -+ return ( -+ any(self in net for net in self._constants._private_networks) -+ and all(self not in net for net in self._constants._private_networks_exceptions) -+ ) - - @property - def is_global(self): -- """Test if this address is allocated for public networks. -+ """``True`` if the address is defined as globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exception: - -- Returns: -- A boolean, true if the address is not reserved per -- iana-ipv6-special-registry. -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global - -+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ - return not self.is_private - -@@ -2271,19 +2320,31 @@ - - _multicast_network = IPv6Network('ff00::/8') - -+ # Not globally reachable address blocks listed on -+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml - _private_networks = [ - IPv6Network('::1/128'), - IPv6Network('::/128'), - IPv6Network('::ffff:0:0/96'), -+ IPv6Network('64:ff9b:1::/48'), - IPv6Network('100::/64'), - IPv6Network('2001::/23'), -- IPv6Network('2001:2::/48'), - IPv6Network('2001:db8::/32'), -- IPv6Network('2001:10::/28'), -+ # IANA says N/A, let's consider it not globally reachable to be safe -+ IPv6Network('2002::/16'), - IPv6Network('fc00::/7'), - IPv6Network('fe80::/10'), - ] - -+ _private_networks_exceptions = [ -+ IPv6Network('2001:1::1/128'), -+ IPv6Network('2001:1::2/128'), -+ IPv6Network('2001:3::/32'), -+ IPv6Network('2001:4:112::/48'), -+ IPv6Network('2001:20::/28'), -+ IPv6Network('2001:30::/28'), -+ ] -+ - _reserved_networks = [ - IPv6Network('::/8'), IPv6Network('100::/8'), - IPv6Network('200::/7'), IPv6Network('400::/6'), diff --git a/Lib/lib2to3/tests/test_parser.py b/Lib/lib2to3/tests/test_parser.py index 8e7773bcae1..7df3e354f6d 100644 --- a/Lib/lib2to3/tests/test_parser.py @@ -2890,32 +1905,6 @@ index 9b9d88bf584..1bb5fcb96d6 100755 if system == 'Windows': # MS platforms -diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py -index cec9b8b8183..74033ecb600 100644 ---- a/Lib/pydoc_data/topics.py -+++ b/Lib/pydoc_data/topics.py -@@ -1,5 +1,5 @@ - # -*- coding: utf-8 -*- --# Autogenerated by Sphinx on Tue Apr 2 09:24:48 2024 -+# Autogenerated by Sphinx on Sat Sep 7 02:03:11 2024 - # as part of the release process. - topics = {'assert': 'The "assert" statement\n' - '**********************\n' -@@ -13958,10 +13958,10 @@ - ' The iterator returns "tuple"s containing the "(start_line,\n' - ' end_line, start_column, end_column)". The *i-th* tuple ' - 'corresponds\n' -- ' to the position of the source code that compiled to the *i-th*\n' -- ' instruction. Column information is 0-indexed utf-8 byte offsets ' -- 'on\n' -- ' the given source line.\n' -+ ' to the position of the source code that compiled to the *i-th* ' -+ 'code\n' -+ ' unit. Column information is 0-indexed utf-8 byte offsets on the\n' -+ ' given source line.\n' - '\n' - ' This positional information can be missing. A non-exhaustive ' - 'lists\n' diff --git a/Lib/site.py b/Lib/site.py index 2904e44cffd..371a89ff717 100644 --- a/Lib/site.py @@ -2931,130 +1920,6 @@ index 2904e44cffd..371a89ff717 100644 return None def joinuser(*args): -diff --git a/Lib/socket.py b/Lib/socket.py -index a0567b76bcf..f386241abfb 100644 ---- a/Lib/socket.py -+++ b/Lib/socket.py -@@ -590,16 +590,65 @@ - return socket(0, 0, 0, info) - __all__.append("fromshare") - --if hasattr(_socket, "socketpair"): -+# Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. -+# This is used if _socket doesn't natively provide socketpair. It's -+# always defined so that it can be patched in for testing purposes. -+def _fallback_socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): -+ if family == AF_INET: -+ host = _LOCALHOST -+ elif family == AF_INET6: -+ host = _LOCALHOST_V6 -+ else: -+ raise ValueError("Only AF_INET and AF_INET6 socket address families " -+ "are supported") -+ if type != SOCK_STREAM: -+ raise ValueError("Only SOCK_STREAM socket type is supported") -+ if proto != 0: -+ raise ValueError("Only protocol zero is supported") -+ -+ # We create a connected TCP socket. Note the trick with -+ # setblocking(False) that prevents us from having to create a thread. -+ lsock = socket(family, type, proto) -+ try: -+ lsock.bind((host, 0)) -+ lsock.listen() -+ # On IPv6, ignore flow_info and scope_id -+ addr, port = lsock.getsockname()[:2] -+ csock = socket(family, type, proto) -+ try: -+ csock.setblocking(False) -+ try: -+ csock.connect((addr, port)) -+ except (BlockingIOError, InterruptedError): -+ pass -+ csock.setblocking(True) -+ ssock, _ = lsock.accept() -+ except: -+ csock.close() -+ raise -+ finally: -+ lsock.close() - -- def socketpair(family=None, type=SOCK_STREAM, proto=0): -- """socketpair([family[, type[, proto]]]) -> (socket object, socket object) -+ # Authenticating avoids using a connection from something else -+ # able to connect to {host}:{port} instead of us. -+ # We expect only AF_INET and AF_INET6 families. -+ try: -+ if ( -+ ssock.getsockname() != csock.getpeername() -+ or csock.getsockname() != ssock.getpeername() -+ ): -+ raise ConnectionError("Unexpected peer connection") -+ except: -+ # getsockname() and getpeername() can fail -+ # if either socket isn't connected. -+ ssock.close() -+ csock.close() -+ raise - -- Create a pair of socket objects from the sockets returned by the platform -- socketpair() function. -- The arguments are the same as for socket() except the default family is -- AF_UNIX if defined on the platform; otherwise, the default is AF_INET. -- """ -+ return (ssock, csock) -+ -+if hasattr(_socket, "socketpair"): -+ def socketpair(family=None, type=SOCK_STREAM, proto=0): - if family is None: - try: - family = AF_UNIX -@@ -611,44 +660,7 @@ - return a, b - - else: -- -- # Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. -- def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): -- if family == AF_INET: -- host = _LOCALHOST -- elif family == AF_INET6: -- host = _LOCALHOST_V6 -- else: -- raise ValueError("Only AF_INET and AF_INET6 socket address families " -- "are supported") -- if type != SOCK_STREAM: -- raise ValueError("Only SOCK_STREAM socket type is supported") -- if proto != 0: -- raise ValueError("Only protocol zero is supported") -- -- # We create a connected TCP socket. Note the trick with -- # setblocking(False) that prevents us from having to create a thread. -- lsock = socket(family, type, proto) -- try: -- lsock.bind((host, 0)) -- lsock.listen() -- # On IPv6, ignore flow_info and scope_id -- addr, port = lsock.getsockname()[:2] -- csock = socket(family, type, proto) -- try: -- csock.setblocking(False) -- try: -- csock.connect((addr, port)) -- except (BlockingIOError, InterruptedError): -- pass -- csock.setblocking(True) -- ssock, _ = lsock.accept() -- except: -- csock.close() -- raise -- finally: -- lsock.close() -- return (ssock, csock) -+ socketpair = _fallback_socketpair - __all__.append("socketpair") - - socketpair.__doc__ = """socketpair([family[, type[, proto]]]) -> (socket object, socket object) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 1d17ae3608a..34dfa0019a5 100644 --- a/Lib/subprocess.py @@ -3213,168 +2078,6 @@ index ebe37118274..edf12d57068 100644 return f"{osname}-{release}-{machine}" -diff --git a/Lib/tarfile.py b/Lib/tarfile.py -index 612217b1ad0..0d6b925533b 100755 ---- a/Lib/tarfile.py -+++ b/Lib/tarfile.py -@@ -842,6 +842,9 @@ - # Sentinel for replace() defaults, meaning "don't change the attribute" - _KEEP = object() - -+# Header length is digits followed by a space. -+_header_length_prefix_re = re.compile(br"([0-9]{1,20}) ") -+ - class TarInfo(object): - """Informational class which holds the details about an - archive member given by a tar header block. -@@ -1411,41 +1414,59 @@ - else: - pax_headers = tarfile.pax_headers.copy() - -- # Check if the pax header contains a hdrcharset field. This tells us -- # the encoding of the path, linkpath, uname and gname fields. Normally, -- # these fields are UTF-8 encoded but since POSIX.1-2008 tar -- # implementations are allowed to store them as raw binary strings if -- # the translation to UTF-8 fails. -- match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf) -- if match is not None: -- pax_headers["hdrcharset"] = match.group(1).decode("utf-8") -- -- # For the time being, we don't care about anything other than "BINARY". -- # The only other value that is currently allowed by the standard is -- # "ISO-IR 10646 2000 UTF-8" in other words UTF-8. -- hdrcharset = pax_headers.get("hdrcharset") -- if hdrcharset == "BINARY": -- encoding = tarfile.encoding -- else: -- encoding = "utf-8" -- - # Parse pax header information. A record looks like that: - # "%d %s=%s\n" % (length, keyword, value). length is the size - # of the complete record including the length field itself and -- # the newline. keyword and value are both UTF-8 encoded strings. -- regex = re.compile(br"(\d+) ([^=]+)=") -+ # the newline. - pos = 0 -- while True: -- match = regex.match(buf, pos) -- if not match: -- break -+ encoding = None -+ raw_headers = [] -+ while len(buf) > pos and buf[pos] != 0x00: -+ if not (match := _header_length_prefix_re.match(buf, pos)): -+ raise InvalidHeaderError("invalid header") -+ try: -+ length = int(match.group(1)) -+ except ValueError: -+ raise InvalidHeaderError("invalid header") -+ # Headers must be at least 5 bytes, shortest being '5 x=\n'. -+ # Value is allowed to be empty. -+ if length < 5: -+ raise InvalidHeaderError("invalid header") -+ if pos + length > len(buf): -+ raise InvalidHeaderError("invalid header") - -- length, keyword = match.groups() -- length = int(length) -- if length == 0: -+ header_value_end_offset = match.start(1) + length - 1 # Last byte of the header -+ keyword_and_value = buf[match.end(1) + 1:header_value_end_offset] -+ raw_keyword, equals, raw_value = keyword_and_value.partition(b"=") -+ -+ # Check the framing of the header. The last character must be '\n' (0x0A) -+ if not raw_keyword or equals != b"=" or buf[header_value_end_offset] != 0x0A: - raise InvalidHeaderError("invalid header") -- value = buf[match.end(2) + 1:match.start(1) + length - 1] -+ raw_headers.append((length, raw_keyword, raw_value)) -+ -+ # Check if the pax header contains a hdrcharset field. This tells us -+ # the encoding of the path, linkpath, uname and gname fields. Normally, -+ # these fields are UTF-8 encoded but since POSIX.1-2008 tar -+ # implementations are allowed to store them as raw binary strings if -+ # the translation to UTF-8 fails. For the time being, we don't care about -+ # anything other than "BINARY". The only other value that is currently -+ # allowed by the standard is "ISO-IR 10646 2000 UTF-8" in other words UTF-8. -+ # Note that we only follow the initial 'hdrcharset' setting to preserve -+ # the initial behavior of the 'tarfile' module. -+ if raw_keyword == b"hdrcharset" and encoding is None: -+ if raw_value == b"BINARY": -+ encoding = tarfile.encoding -+ else: # This branch ensures only the first 'hdrcharset' header is used. -+ encoding = "utf-8" -+ -+ pos += length - -+ # If no explicit hdrcharset is set, we use UTF-8 as a default. -+ if encoding is None: -+ encoding = "utf-8" -+ -+ # After parsing the raw headers we can decode them to text. -+ for length, raw_keyword, raw_value in raw_headers: - # Normally, we could just use "utf-8" as the encoding and "strict" - # as the error handler, but we better not take the risk. For - # example, GNU tar <= 1.23 is known to store filenames it cannot -@@ -1453,17 +1474,16 @@ - # hdrcharset=BINARY header). - # We first try the strict standard encoding, and if that fails we - # fall back on the user's encoding and error handler. -- keyword = self._decode_pax_field(keyword, "utf-8", "utf-8", -+ keyword = self._decode_pax_field(raw_keyword, "utf-8", "utf-8", - tarfile.errors) - if keyword in PAX_NAME_FIELDS: -- value = self._decode_pax_field(value, encoding, tarfile.encoding, -+ value = self._decode_pax_field(raw_value, encoding, tarfile.encoding, - tarfile.errors) - else: -- value = self._decode_pax_field(value, "utf-8", "utf-8", -+ value = self._decode_pax_field(raw_value, "utf-8", "utf-8", - tarfile.errors) - - pax_headers[keyword] = value -- pos += length - - # Fetch the next header. - try: -@@ -1478,7 +1498,7 @@ - - elif "GNU.sparse.size" in pax_headers: - # GNU extended sparse format version 0.0. -- self._proc_gnusparse_00(next, pax_headers, buf) -+ self._proc_gnusparse_00(next, raw_headers) - - elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0": - # GNU extended sparse format version 1.0. -@@ -1500,15 +1520,24 @@ - - return next - -- def _proc_gnusparse_00(self, next, pax_headers, buf): -+ def _proc_gnusparse_00(self, next, raw_headers): - """Process a GNU tar extended sparse header, version 0.0. - """ - offsets = [] -- for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf): -- offsets.append(int(match.group(1))) - numbytes = [] -- for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf): -- numbytes.append(int(match.group(1))) -+ for _, keyword, value in raw_headers: -+ if keyword == b"GNU.sparse.offset": -+ try: -+ offsets.append(int(value.decode())) -+ except ValueError: -+ raise InvalidHeaderError("invalid header") -+ -+ elif keyword == b"GNU.sparse.numbytes": -+ try: -+ numbytes.append(int(value.decode())) -+ except ValueError: -+ raise InvalidHeaderError("invalid header") -+ - next.sparse = list(zip(offsets, numbytes)) - - def _proc_gnusparse_01(self, next, pax_headers): diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 74ebb5e5b8a..d3e5fddf69b 100644 --- a/Lib/test/pythoninfo.py @@ -3846,391 +2549,6 @@ index 812f989d8f3..dfcf9e16e40 100644 @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork') @support.requires_resource('cpu') def test_hang_global_shutdown_lock(self): -diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py -index f7e80749c45..0d9343478ce 100644 ---- a/Lib/test/test_email/test__header_value_parser.py -+++ b/Lib/test/test_email/test__header_value_parser.py -@@ -2986,9 +2986,17 @@ - ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= \n') - - def test_address_list_with_list_separator_after_fold(self): -- to = '0123456789' * 8 + '@foo, ä ' -+ a = 'x' * 66 + '@example.com' -+ to = f'{a}, "Hübsch Kaktus" ' - self._test(parser.get_address_list(to)[0], -- '0123456789' * 8 + '@foo,\n =?utf-8?q?=C3=A4?= \n') -+ f'{a},\n =?utf-8?q?H=C3=BCbsch?= Kaktus \n') -+ -+ a = '.' * 79 -+ to = f'"{a}" , "Hübsch Kaktus" ' -+ self._test(parser.get_address_list(to)[0], -+ f'{a}\n' -+ ' , =?utf-8?q?H=C3=BCbsch?= Kaktus ' -+ '\n') - - # XXX Need tests with comments on various sides of a unicode token, - # and with unicode tokens in the comments. Spaces inside the quotes -diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py -index 785696e5c54..ad60ed3a759 100644 ---- a/Lib/test/test_email/test_email.py -+++ b/Lib/test/test_email/test_email.py -@@ -17,6 +17,7 @@ - - import email - import email.policy -+import email.utils - - from email.charset import Charset - from email.generator import Generator, DecodedGenerator, BytesGenerator -@@ -3336,15 +3337,154 @@ - [('Al Person', 'aperson@dom.ain'), - ('Bud Person', 'bperson@dom.ain')]) - -+ def test_getaddresses_comma_in_name(self): -+ """GH-106669 regression test.""" -+ self.assertEqual( -+ utils.getaddresses( -+ [ -+ '"Bud, Person" ', -+ 'aperson@dom.ain (Al Person)', -+ '"Mariusz Felisiak" ', -+ ] -+ ), -+ [ -+ ('Bud, Person', 'bperson@dom.ain'), -+ ('Al Person', 'aperson@dom.ain'), -+ ('Mariusz Felisiak', 'to@example.com'), -+ ], -+ ) -+ -+ def test_parsing_errors(self): -+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" -+ alice = 'alice@example.org' -+ bob = 'bob@example.com' -+ empty = ('', '') -+ -+ # Test utils.getaddresses() and utils.parseaddr() on malformed email -+ # addresses: default behavior (strict=True) rejects malformed address, -+ # and strict=False which tolerates malformed address. -+ for invalid_separator, expected_non_strict in ( -+ ('(', [(f'<{bob}>', alice)]), -+ (')', [('', alice), empty, ('', bob)]), -+ ('<', [('', alice), empty, ('', bob), empty]), -+ ('>', [('', alice), empty, ('', bob)]), -+ ('[', [('', f'{alice}[<{bob}>]')]), -+ (']', [('', alice), empty, ('', bob)]), -+ ('@', [empty, empty, ('', bob)]), -+ (';', [('', alice), empty, ('', bob)]), -+ (':', [('', alice), ('', bob)]), -+ ('.', [('', alice + '.'), ('', bob)]), -+ ('"', [('', alice), ('', f'<{bob}>')]), -+ ): -+ address = f'{alice}{invalid_separator}<{bob}>' -+ with self.subTest(address=address): -+ self.assertEqual(utils.getaddresses([address]), -+ [empty]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ expected_non_strict) -+ -+ self.assertEqual(utils.parseaddr([address]), -+ empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Comma (',') is treated differently depending on strict parameter. -+ # Comma without quotes. -+ address = f'{alice},<{bob}>' -+ self.assertEqual(utils.getaddresses([address]), -+ [('', alice), ('', bob)]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ [('', alice), ('', bob)]) -+ self.assertEqual(utils.parseaddr([address]), -+ empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Real name between quotes containing comma. -+ address = '"Alice, alice@example.org" ' -+ expected_strict = ('Alice, alice@example.org', 'bob@example.com') -+ self.assertEqual(utils.getaddresses([address]), [expected_strict]) -+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) -+ self.assertEqual(utils.parseaddr([address]), expected_strict) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Valid parenthesis in comments. -+ address = 'alice@example.org (Alice)' -+ expected_strict = ('Alice', 'alice@example.org') -+ self.assertEqual(utils.getaddresses([address]), [expected_strict]) -+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) -+ self.assertEqual(utils.parseaddr([address]), expected_strict) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Invalid parenthesis in comments. -+ address = 'alice@example.org )Alice(' -+ self.assertEqual(utils.getaddresses([address]), [empty]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) -+ self.assertEqual(utils.parseaddr([address]), empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Two addresses with quotes separated by comma. -+ address = '"Jane Doe" , "John Doe" ' -+ self.assertEqual(utils.getaddresses([address]), -+ [('Jane Doe', 'jane@example.net'), -+ ('John Doe', 'john@example.net')]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ [('Jane Doe', 'jane@example.net'), -+ ('John Doe', 'john@example.net')]) -+ self.assertEqual(utils.parseaddr([address]), empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Test email.utils.supports_strict_parsing attribute -+ self.assertEqual(email.utils.supports_strict_parsing, True) -+ - def test_getaddresses_nasty(self): -- eq = self.assertEqual -- eq(utils.getaddresses(['foo: ;']), [('', '')]) -- eq(utils.getaddresses( -- ['[]*-- =~$']), -- [('', ''), ('', ''), ('', '*--')]) -- eq(utils.getaddresses( -- ['foo: ;', '"Jason R. Mastaler" ']), -- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) -+ for addresses, expected in ( -+ (['"Sürname, Firstname" '], -+ [('Sürname, Firstname', 'to@example.com')]), -+ -+ (['foo: ;'], -+ [('', '')]), -+ -+ (['foo: ;', '"Jason R. Mastaler" '], -+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), -+ -+ ([r'Pete(A nice \) chap) '], -+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), -+ -+ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], -+ [('', '')]), -+ -+ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], -+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), -+ -+ (['John Doe '], -+ [('John Doe (comment)', 'jdoe@machine.example')]), -+ -+ (['"Mary Smith: Personal Account" '], -+ [('Mary Smith: Personal Account', 'smith@home.example')]), -+ -+ (['Undisclosed recipients:;'], -+ [('', '')]), -+ -+ ([r', "Giant; \"Big\" Box" '], -+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), -+ ): -+ with self.subTest(addresses=addresses): -+ self.assertEqual(utils.getaddresses(addresses), -+ expected) -+ self.assertEqual(utils.getaddresses(addresses, strict=False), -+ expected) -+ -+ addresses = ['[]*-- =~$'] -+ self.assertEqual(utils.getaddresses(addresses), -+ [('', '')]) -+ self.assertEqual(utils.getaddresses(addresses, strict=False), -+ [('', ''), ('', ''), ('', '*--')]) - - def test_getaddresses_embedded_comment(self): - """Test proper handling of a nested comment""" -@@ -3535,6 +3675,54 @@ - m = cls(*constructor, policy=email.policy.default) - self.assertIs(m.policy, email.policy.default) - -+ def test_iter_escaped_chars(self): -+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), -+ [(0, 'a'), -+ (2, '\\\\'), -+ (3, 'b'), -+ (5, '\\"'), -+ (6, 'c'), -+ (8, '\\\\'), -+ (9, '"'), -+ (10, 'd')]) -+ self.assertEqual(list(utils._iter_escaped_chars('a\\')), -+ [(0, 'a'), (1, '\\')]) -+ -+ def test_strip_quoted_realnames(self): -+ def check(addr, expected): -+ self.assertEqual(utils._strip_quoted_realnames(addr), expected) -+ -+ check('"Jane Doe" , "John Doe" ', -+ ' , ') -+ check(r'"Jane \"Doe\"." ', -+ ' ') -+ -+ # special cases -+ check(r'before"name"after', 'beforeafter') -+ check(r'before"name"', 'before') -+ check(r'b"name"', 'b') # single char -+ check(r'"name"after', 'after') -+ check(r'"name"a', 'a') # single char -+ check(r'"name"', '') -+ -+ # no change -+ for addr in ( -+ 'Jane Doe , John Doe ', -+ 'lone " quote', -+ ): -+ self.assertEqual(utils._strip_quoted_realnames(addr), addr) -+ -+ -+ def test_check_parenthesis(self): -+ addr = 'alice@example.net' -+ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) -+ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) -+ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) -+ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) -+ -+ # Ignore real name between quotes -+ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) -+ - - # Test the iterator/generators - class TestIterators(TestEmailBase): -diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py -index 89e7edeb63a..d29400f0ed1 100644 ---- a/Lib/test/test_email/test_generator.py -+++ b/Lib/test/test_email/test_generator.py -@@ -6,6 +6,7 @@ - from email.generator import Generator, BytesGenerator - from email.headerregistry import Address - from email import policy -+import email.errors - from test.test_email import TestEmailBase, parameterize - - -@@ -216,6 +217,44 @@ - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(expected)) - -+ def test_keep_encoded_newlines(self): -+ msg = self.msgmaker(self.typ(textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """))) -+ expected = textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """) -+ s = self.ioclass() -+ g = self.genclass(s, policy=self.policy.clone(max_line_length=80)) -+ g.flatten(msg) -+ self.assertEqual(s.getvalue(), self.typ(expected)) -+ -+ def test_keep_long_encoded_newlines(self): -+ msg = self.msgmaker(self.typ(textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """))) -+ expected = textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject -+ =?utf-8?q?=0A?=Bcc: -+ injection@example.com -+ -+ None -+ """) -+ s = self.ioclass() -+ g = self.genclass(s, policy=self.policy.clone(max_line_length=30)) -+ g.flatten(msg) -+ self.assertEqual(s.getvalue(), self.typ(expected)) -+ - - class TestGenerator(TestGeneratorBase, TestEmailBase): - -@@ -224,6 +263,29 @@ - ioclass = io.StringIO - typ = str - -+ def test_verify_generated_headers(self): -+ """gh-121650: by default the generator prevents header injection""" -+ class LiteralHeader(str): -+ name = 'Header' -+ def fold(self, **kwargs): -+ return self -+ -+ for text in ( -+ 'Value\r\nBad Injection\r\n', -+ 'NoNewLine' -+ ): -+ with self.subTest(text=text): -+ message = message_from_string( -+ "Header: Value\r\n\r\nBody", -+ policy=self.policy, -+ ) -+ -+ del message['Header'] -+ message['Header'] = LiteralHeader(text) -+ -+ with self.assertRaises(email.errors.HeaderWriteError): -+ message.as_string() -+ - - class TestBytesGenerator(TestGeneratorBase, TestEmailBase): - -diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py -index c6b9c80efe1..baa35fd68e4 100644 ---- a/Lib/test/test_email/test_policy.py -+++ b/Lib/test/test_email/test_policy.py -@@ -26,6 +26,7 @@ - 'raise_on_defect': False, - 'mangle_from_': True, - 'message_factory': None, -+ 'verify_generated_headers': True, - } - # These default values are the ones set on email.policy.default. - # If any of these defaults change, the docs must be updated. -@@ -294,6 +295,31 @@ - with self.assertRaises(email.errors.HeaderParseError): - policy.fold("Subject", subject) - -+ def test_verify_generated_headers(self): -+ """Turning protection off allows header injection""" -+ policy = email.policy.default.clone(verify_generated_headers=False) -+ for text in ( -+ 'Header: Value\r\nBad: Injection\r\n', -+ 'Header: NoNewLine' -+ ): -+ with self.subTest(text=text): -+ message = email.message_from_string( -+ "Header: Value\r\n\r\nBody", -+ policy=policy, -+ ) -+ class LiteralHeader(str): -+ name = 'Header' -+ def fold(self, **kwargs): -+ return self -+ -+ del message['Header'] -+ message['Header'] = LiteralHeader(text) -+ -+ self.assertEqual( -+ message.as_string(), -+ f"{text}\nBody", -+ ) -+ - # XXX: Need subclassing tests. - # For adding subclassed objects, make sure the usual rules apply (subclass - # wins), but that the order still works (right overrides left). diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 8e98256a62c..7681e829d8b 100644 --- a/Lib/test/test_fcntl.py @@ -4349,62 +2667,6 @@ index 4f311c2d498..b77cd4c67d6 100644 name = os_helper.TESTFN_UNDECODABLE elif os_helper.TESTFN_NONASCII: name = os_helper.TESTFN_NONASCII -diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py -index 925c8697f60..8879902a6e2 100644 ---- a/Lib/test/test_http_cookies.py -+++ b/Lib/test/test_http_cookies.py -@@ -5,6 +5,7 @@ - import doctest - from http import cookies - import pickle -+from test import support - - - class CookieTests(unittest.TestCase): -@@ -58,6 +59,43 @@ - for k, v in sorted(case['dict'].items()): - self.assertEqual(C[k].value, v) - -+ def test_unquote(self): -+ cases = [ -+ (r'a="b=\""', 'b="'), -+ (r'a="b=\\"', 'b=\\'), -+ (r'a="b=\="', 'b=='), -+ (r'a="b=\n"', 'b=n'), -+ (r'a="b=\042"', 'b="'), -+ (r'a="b=\134"', 'b=\\'), -+ (r'a="b=\377"', 'b=\xff'), -+ (r'a="b=\400"', 'b=400'), -+ (r'a="b=\42"', 'b=42'), -+ (r'a="b=\\042"', 'b=\\042'), -+ (r'a="b=\\134"', 'b=\\134'), -+ (r'a="b=\\\""', 'b=\\"'), -+ (r'a="b=\\\042"', 'b=\\"'), -+ (r'a="b=\134\""', 'b=\\"'), -+ (r'a="b=\134\042"', 'b=\\"'), -+ ] -+ for encoded, decoded in cases: -+ with self.subTest(encoded): -+ C = cookies.SimpleCookie() -+ C.load(encoded) -+ self.assertEqual(C['a'].value, decoded) -+ -+ @support.requires_resource('cpu') -+ def test_unquote_large(self): -+ n = 10**6 -+ for encoded in r'\\', r'\134': -+ with self.subTest(encoded): -+ data = 'a="b=' + encoded*n + ';"' -+ C = cookies.SimpleCookie() -+ C.load(data) -+ value = C['a'].value -+ self.assertEqual(value[:3], 'b=\\') -+ self.assertEqual(value[-2:], '\\;') -+ self.assertEqual(len(value), n + 3) -+ - def test_load(self): - C = cookies.SimpleCookie() - C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme') diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index cfd8a101dcc..b8ac2544be5 100644 --- a/Lib/test/test_httpservers.py @@ -4714,7 +2976,7 @@ index 5abb66c7ae4..f69f9bd38b8 100644 self.assertRaises(OSError, inspect.getsource, unicodedata) self.assertRaises(OSError, inspect.findsource, unicodedata) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py -index 537c9fa7b98..22d17a2a829 100644 +index 1e444d2566e..22d17a2a829 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -39,11 +39,9 @@ @@ -4747,89 +3009,6 @@ index 537c9fa7b98..22d17a2a829 100644 support.requires( 'largefile', 'test requires %s bytes and a long time to run' % self.LARGE) -@@ -3960,6 +3958,28 @@ - t.write("x"*chunk_size) - self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], buf._write_stack) - -+ def test_issue119506(self): -+ chunk_size = 8192 -+ -+ class MockIO(self.MockRawIO): -+ written = False -+ def write(self, data): -+ if not self.written: -+ self.written = True -+ t.write("middle") -+ return super().write(data) -+ -+ buf = MockIO() -+ t = self.TextIOWrapper(buf) -+ t.write("abc") -+ t.write("def") -+ # writing data which size >= chunk_size cause flushing buffer before write. -+ t.write("g" * chunk_size) -+ t.flush() -+ -+ self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size], -+ buf._write_stack) -+ - - class PyTextIOWrapperTest(TextIOWrapperTest): - io = pyio -diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py -index fc27628af17..16c34163a00 100644 ---- a/Lib/test/test_ipaddress.py -+++ b/Lib/test/test_ipaddress.py -@@ -2269,6 +2269,10 @@ - self.assertEqual(True, ipaddress.ip_address( - '172.31.255.255').is_private) - self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private) -+ self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global) -+ self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global) -+ self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global) -+ self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global) - - self.assertEqual(True, - ipaddress.ip_address('169.254.100.200').is_link_local) -@@ -2294,6 +2298,7 @@ - self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private) - self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private) - self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private) -+ self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private) - self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private) - self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private) - self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private) -@@ -2310,8 +2315,8 @@ - self.assertEqual(True, ipaddress.ip_network("::/128").is_private) - self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private) - self.assertEqual(True, ipaddress.ip_network("100::/64").is_private) -- self.assertEqual(True, ipaddress.ip_network("2001::/23").is_private) - self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private) -+ self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private) - self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private) - self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private) - self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private) -@@ -2390,6 +2395,20 @@ - self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified) - self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified) - -+ self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:1::1').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:1::2').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:2::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:3::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:4::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:10::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:20::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:30::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:40::').is_global) -+ self.assertFalse(ipaddress.ip_address('2002::').is_global) -+ - # some generic IETF reserved addresses - self.assertEqual(True, ipaddress.ip_address('100::').is_reserved) - self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index fbaafde881c..7117b160949 100644 --- a/Lib/test/test_logging.py @@ -4891,29 +3070,10 @@ index 1867e8c957f..f75e40940e4 100644 'test requires %s bytes and a long time to run' % str(0x180000000)) f = open(TESTFN, 'w+b') diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py -index 3dab98d4577..7b6497a2619 100644 +index 2d5b76ec826..7b6497a2619 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py -@@ -1743,6 +1743,18 @@ - self.assertRaises(OSError, os.makedirs, path, exist_ok=True) - os.remove(path) - -+ @unittest.skipUnless(os.name == 'nt', "requires Windows") -+ def test_win32_mkdir_700(self): -+ base = os_helper.TESTFN -+ path = os.path.abspath(os.path.join(os_helper.TESTFN, 'dir')) -+ os.mkdir(path, mode=0o700) -+ out = subprocess.check_output(["cacls.exe", path, "/s"], encoding="oem") -+ os.rmdir(path) -+ self.assertEqual( -+ out.strip(), -+ f'{path} "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)"', -+ ) -+ - def tearDown(self): - path = os.path.join(os_helper.TESTFN, 'dir1', 'dir2', 'dir3', - 'dir4', 'dir5', 'dir6') -@@ -2286,6 +2298,7 @@ +@@ -2298,6 +2298,7 @@ support.is_emscripten or support.is_wasi, "musl libc issue on Emscripten/WASI, bpo-46390" ) @@ -4921,7 +3081,7 @@ index 3dab98d4577..7b6497a2619 100644 def test_fpathconf(self): self.check(os.pathconf, "PC_NAME_MAX") self.check(os.fpathconf, "PC_NAME_MAX") -@@ -3750,6 +3763,7 @@ +@@ -3762,6 +3763,7 @@ self.assertGreaterEqual(size.columns, 0) self.assertGreaterEqual(size.lines, 0) @@ -5220,7 +3380,7 @@ index 3711e7d1c6c..8003f3a226c 100644 "test needs SIGUSR1") @threading_helper.requires_working_threading() diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py -index 42adc573ecc..92ffda88617 100644 +index cc803d8753b..92ffda88617 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3,6 +3,7 @@ @@ -5231,38 +3391,7 @@ index 42adc573ecc..92ffda88617 100644 import errno import io -@@ -542,19 +543,27 @@ - def __init__(self, methodName='runTest'): - unittest.TestCase.__init__(self, methodName=methodName) - ThreadableTest.__init__(self) -+ self.cli = None -+ self.serv = None -+ -+ def socketpair(self): -+ # To be overridden by some child classes. -+ return socket.socketpair() - - def setUp(self): -- self.serv, self.cli = socket.socketpair() -+ self.serv, self.cli = self.socketpair() - - def tearDown(self): -- self.serv.close() -+ if self.serv: -+ self.serv.close() - self.serv = None - - def clientSetUp(self): - pass - - def clientTearDown(self): -- self.cli.close() -+ if self.cli: -+ self.cli.close() - self.cli = None - ThreadableTest.clientTearDown(self) - -@@ -676,7 +685,7 @@ +@@ -684,7 +685,7 @@ super().setUp() def bindSock(self, sock): @@ -5271,7 +3400,7 @@ index 42adc573ecc..92ffda88617 100644 socket_helper.bind_unix_socket(sock, path) self.addCleanup(os_helper.unlink, path) -@@ -1153,8 +1162,11 @@ +@@ -1161,8 +1162,11 @@ # Find one service that exists, then check all the related interfaces. # I've ordered this by protocols that have both a tcp and udp # protocol, at least for modern Linuxes. @@ -5285,7 +3414,7 @@ index 42adc573ecc..92ffda88617 100644 # avoid the 'echo' service on this platform, as there is an # assumption breaking non-standard port/protocol entry services = ('daytime', 'qotd', 'domain') -@@ -1916,12 +1928,13 @@ +@@ -1924,12 +1928,13 @@ self._test_socket_fileno(s, socket.AF_INET6, socket.SOCK_STREAM) if hasattr(socket, "AF_UNIX"): @@ -5302,7 +3431,7 @@ index 42adc573ecc..92ffda88617 100644 except PermissionError: pass else: -@@ -3564,7 +3577,7 @@ +@@ -3572,7 +3577,7 @@ def _testFDPassCMSG_LEN(self): self.createAndSendFDs(1) @@ -5311,7 +3440,7 @@ index 42adc573ecc..92ffda88617 100644 @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparate(self): -@@ -3575,7 +3588,7 @@ +@@ -3583,7 +3588,7 @@ maxcmsgs=2) @testFDPassSeparate.client_skip @@ -5320,7 +3449,7 @@ index 42adc573ecc..92ffda88617 100644 @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparate(self): fd0, fd1 = self.newFDs(2) -@@ -3588,7 +3601,7 @@ +@@ -3596,7 +3601,7 @@ array.array("i", [fd1]))]), len(MSG)) @@ -5329,7 +3458,7 @@ index 42adc573ecc..92ffda88617 100644 @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparateMinSpace(self): -@@ -3602,7 +3615,7 @@ +@@ -3610,7 +3615,7 @@ maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC) @testFDPassSeparateMinSpace.client_skip @@ -5338,7 +3467,7 @@ index 42adc573ecc..92ffda88617 100644 @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparateMinSpace(self): fd0, fd1 = self.newFDs(2) -@@ -3626,7 +3639,7 @@ +@@ -3634,7 +3639,7 @@ nbytes = self.sendmsgToServer([msg]) self.assertEqual(nbytes, len(msg)) @@ -5347,119 +3476,6 @@ index 42adc573ecc..92ffda88617 100644 def testFDPassEmpty(self): # Try to pass an empty FD array. Can receive either no array # or an empty array. -@@ -4667,6 +4680,112 @@ - self.assertEqual(msg, MSG) - - -+class PurePythonSocketPairTest(SocketPairTest): -+ # Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the -+ # code path we're using regardless platform is the pure python one where -+ # `_socket.socketpair` does not exist. (AF_INET does not work with -+ # _socket.socketpair on many platforms). -+ def socketpair(self): -+ # called by super().setUp(). -+ try: -+ return socket.socketpair(socket.AF_INET6) -+ except OSError: -+ return socket.socketpair(socket.AF_INET) -+ -+ # Local imports in this class make for easy security fix backporting. -+ -+ def setUp(self): -+ if hasattr(_socket, "socketpair"): -+ self._orig_sp = socket.socketpair -+ # This forces the version using the non-OS provided socketpair -+ # emulation via an AF_INET socket in Lib/socket.py. -+ socket.socketpair = socket._fallback_socketpair -+ else: -+ # This platform already uses the non-OS provided version. -+ self._orig_sp = None -+ super().setUp() -+ -+ def tearDown(self): -+ super().tearDown() -+ if self._orig_sp is not None: -+ # Restore the default socket.socketpair definition. -+ socket.socketpair = self._orig_sp -+ -+ def test_recv(self): -+ msg = self.serv.recv(1024) -+ self.assertEqual(msg, MSG) -+ -+ def _test_recv(self): -+ self.cli.send(MSG) -+ -+ def test_send(self): -+ self.serv.send(MSG) -+ -+ def _test_send(self): -+ msg = self.cli.recv(1024) -+ self.assertEqual(msg, MSG) -+ -+ def test_ipv4(self): -+ cli, srv = socket.socketpair(socket.AF_INET) -+ cli.close() -+ srv.close() -+ -+ def _test_ipv4(self): -+ pass -+ -+ @unittest.skipIf(not hasattr(_socket, 'IPPROTO_IPV6') or -+ not hasattr(_socket, 'IPV6_V6ONLY'), -+ "IPV6_V6ONLY option not supported") -+ @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test') -+ def test_ipv6(self): -+ cli, srv = socket.socketpair(socket.AF_INET6) -+ cli.close() -+ srv.close() -+ -+ def _test_ipv6(self): -+ pass -+ -+ def test_injected_authentication_failure(self): -+ orig_getsockname = socket.socket.getsockname -+ inject_sock = None -+ -+ def inject_getsocketname(self): -+ nonlocal inject_sock -+ sockname = orig_getsockname(self) -+ # Connect to the listening socket ahead of the -+ # client socket. -+ if inject_sock is None: -+ inject_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -+ inject_sock.setblocking(False) -+ try: -+ inject_sock.connect(sockname[:2]) -+ except (BlockingIOError, InterruptedError): -+ pass -+ inject_sock.setblocking(True) -+ return sockname -+ -+ sock1 = sock2 = None -+ try: -+ socket.socket.getsockname = inject_getsocketname -+ with self.assertRaises(OSError): -+ sock1, sock2 = socket.socketpair() -+ finally: -+ socket.socket.getsockname = orig_getsockname -+ if inject_sock: -+ inject_sock.close() -+ if sock1: # This cleanup isn't needed on a successful test. -+ sock1.close() -+ if sock2: -+ sock2.close() -+ -+ def _test_injected_authentication_failure(self): -+ # No-op. Exists for base class threading infrastructure to call. -+ # We could refactor this test into its own lesser class along with the -+ # setUp and tearDown code to construct an ideal; it is simpler to keep -+ # it here and live with extra overhead one this _one_ failure test. -+ pass -+ -+ - class NonBlockingTCPTests(ThreadedTCPSocketTest): - - def __init__(self, methodName='runTest'): diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index 80e1968c0bf..529b6405f28 100644 --- a/Lib/test/test_socketserver.py @@ -5549,72 +3565,6 @@ index de2e7305ccc..3b247e06442 100644 import distutils.filelist import distutils.text_file import distutils.unixccompiler -diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py -index 9dd0f745cf2..4ecce225bbd 100644 ---- a/Lib/test/test_syntax.py -+++ b/Lib/test/test_syntax.py -@@ -1725,22 +1725,22 @@ - >>> A[*(1:2)] - Traceback (most recent call last): - ... -- SyntaxError: invalid syntax -+ SyntaxError: Invalid star expression - >>> A[*(1:2)] = 1 - Traceback (most recent call last): - ... -- SyntaxError: invalid syntax -+ SyntaxError: Invalid star expression - >>> del A[*(1:2)] - Traceback (most recent call last): - ... -- SyntaxError: invalid syntax -+ SyntaxError: Invalid star expression - - A[*:] and A[:*] - - >>> A[*:] - Traceback (most recent call last): - ... -- SyntaxError: invalid syntax -+ SyntaxError: Invalid star expression - >>> A[:*] - Traceback (most recent call last): - ... -@@ -1751,7 +1751,7 @@ - >>> A[*] - Traceback (most recent call last): - ... -- SyntaxError: invalid syntax -+ SyntaxError: Invalid star expression - - A[**] - -@@ -1833,11 +1833,23 @@ - - >>> f(**x, *) - Traceback (most recent call last): -- SyntaxError: iterable argument unpacking follows keyword argument unpacking -+ SyntaxError: Invalid star expression - - >>> f(x, *:) - Traceback (most recent call last): -- SyntaxError: invalid syntax -+ SyntaxError: Invalid star expression -+ -+ >>> f(x, *) -+ Traceback (most recent call last): -+ SyntaxError: Invalid star expression -+ -+ >>> f(x = 5, *) -+ Traceback (most recent call last): -+ SyntaxError: Invalid star expression -+ -+ >>> f(x = 5, *:) -+ Traceback (most recent call last): -+ SyntaxError: Invalid star expression - """ - - import re diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index d3bb0d25cac..f487fb5313b 100644 --- a/Lib/test/test_sysconfig.py @@ -5659,105 +3609,6 @@ index d3bb0d25cac..f487fb5313b 100644 def test_get_makefile_filename(self): makefile = sysconfig.get_makefile_filename() self.assertTrue(os.path.isfile(makefile), makefile) -diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py -index 389da7be3a3..c99c88ce93a 100644 ---- a/Lib/test/test_tarfile.py -+++ b/Lib/test/test_tarfile.py -@@ -1208,6 +1208,48 @@ - finally: - tar.close() - -+ def test_pax_header_bad_formats(self): -+ # The fields from the pax header have priority over the -+ # TarInfo. -+ pax_header_replacements = ( -+ b" foo=bar\n", -+ b"0 \n", -+ b"1 \n", -+ b"2 \n", -+ b"3 =\n", -+ b"4 =a\n", -+ b"1000000 foo=bar\n", -+ b"0 foo=bar\n", -+ b"-12 foo=bar\n", -+ b"000000000000000000000000036 foo=bar\n", -+ ) -+ pax_headers = {"foo": "bar"} -+ -+ for replacement in pax_header_replacements: -+ with self.subTest(header=replacement): -+ tar = tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT, -+ encoding="iso8859-1") -+ try: -+ t = tarfile.TarInfo() -+ t.name = "pax" # non-ASCII -+ t.uid = 1 -+ t.pax_headers = pax_headers -+ tar.addfile(t) -+ finally: -+ tar.close() -+ -+ with open(tmpname, "rb") as f: -+ data = f.read() -+ self.assertIn(b"11 foo=bar\n", data) -+ data = data.replace(b"11 foo=bar\n", replacement) -+ -+ with open(tmpname, "wb") as f: -+ f.truncate() -+ f.write(data) -+ -+ with self.assertRaisesRegex(tarfile.ReadError, r"method tar: ReadError\('invalid header'\)"): -+ tarfile.open(tmpname, encoding="iso8859-1") -+ - - class WriteTestBase(TarTest): - # Put all write tests in here that are supposed to be tested -diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py -index e735a2520e7..0d6b95441cd 100644 ---- a/Lib/test/test_tempfile.py -+++ b/Lib/test/test_tempfile.py -@@ -11,6 +11,7 @@ - import stat - import types - import weakref -+import subprocess - from unittest import mock - - import unittest -@@ -801,6 +802,33 @@ - finally: - os.rmdir(dir) - -+ @unittest.skipUnless(os.name == "nt", "Only on Windows.") -+ def test_mode_win32(self): -+ # Use icacls.exe to extract the users with some level of access -+ # Main thing we are testing is that the BUILTIN\Users group has -+ # no access. The exact ACL is going to vary based on which user -+ # is running the test. -+ dir = self.do_create() -+ try: -+ out = subprocess.check_output(["icacls.exe", dir], encoding="oem").casefold() -+ finally: -+ os.rmdir(dir) -+ -+ dir = dir.casefold() -+ users = set() -+ found_user = False -+ for line in out.strip().splitlines(): -+ acl = None -+ # First line of result includes our directory -+ if line.startswith(dir): -+ acl = line.removeprefix(dir).strip() -+ elif line and line[:1].isspace(): -+ acl = line.strip() -+ if acl: -+ users.add(acl.partition(":")[0]) -+ -+ self.assertNotIn(r"BUILTIN\Users".casefold(), users) -+ - def test_collision_with_existing_file(self): - # mkdtemp tries another name when a file with - # the chosen name already exists diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py index 47619c8807b..25c16e3a0b7 100644 --- a/Lib/test/test_unicode_file_functions.py @@ -5835,120 +3686,8 @@ index 94cbc8a62f9..06a553f093f 100644 def test_http_body_pipe(self): # A file reading from a pipe. # A pipe cannot be seek'ed. There is no way to determine the -diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py -index 4fef4303c48..2376dad81b2 100644 ---- a/Lib/test/test_urlparse.py -+++ b/Lib/test/test_urlparse.py -@@ -103,7 +103,9 @@ - - class UrlParseTestCase(unittest.TestCase): - -- def checkRoundtrips(self, url, parsed, split): -+ def checkRoundtrips(self, url, parsed, split, url2=None): -+ if url2 is None: -+ url2 = url - result = urllib.parse.urlparse(url) - self.assertEqual(result, parsed) - t = (result.scheme, result.netloc, result.path, -@@ -111,7 +113,7 @@ - self.assertEqual(t, parsed) - # put it back together and it should be the same - result2 = urllib.parse.urlunparse(result) -- self.assertEqual(result2, url) -+ self.assertEqual(result2, url2) - self.assertEqual(result2, result.geturl()) - - # the result of geturl() is a fixpoint; we can always parse it -@@ -137,7 +139,7 @@ - result.query, result.fragment) - self.assertEqual(t, split) - result2 = urllib.parse.urlunsplit(result) -- self.assertEqual(result2, url) -+ self.assertEqual(result2, url2) - self.assertEqual(result2, result.geturl()) - - # check the fixpoint property of re-parsing the result of geturl() -@@ -175,9 +177,39 @@ - - def test_roundtrips(self): - str_cases = [ -+ ('path/to/file', -+ ('', '', 'path/to/file', '', '', ''), -+ ('', '', 'path/to/file', '', '')), -+ ('/path/to/file', -+ ('', '', '/path/to/file', '', '', ''), -+ ('', '', '/path/to/file', '', '')), -+ ('//path/to/file', -+ ('', 'path', '/to/file', '', '', ''), -+ ('', 'path', '/to/file', '', '')), -+ ('////path/to/file', -+ ('', '', '//path/to/file', '', '', ''), -+ ('', '', '//path/to/file', '', '')), -+ ('scheme:path/to/file', -+ ('scheme', '', 'path/to/file', '', '', ''), -+ ('scheme', '', 'path/to/file', '', '')), -+ ('scheme:/path/to/file', -+ ('scheme', '', '/path/to/file', '', '', ''), -+ ('scheme', '', '/path/to/file', '', '')), -+ ('scheme://path/to/file', -+ ('scheme', 'path', '/to/file', '', '', ''), -+ ('scheme', 'path', '/to/file', '', '')), -+ ('scheme:////path/to/file', -+ ('scheme', '', '//path/to/file', '', '', ''), -+ ('scheme', '', '//path/to/file', '', '')), - ('file:///tmp/junk.txt', - ('file', '', '/tmp/junk.txt', '', '', ''), - ('file', '', '/tmp/junk.txt', '', '')), -+ ('file:////tmp/junk.txt', -+ ('file', '', '//tmp/junk.txt', '', '', ''), -+ ('file', '', '//tmp/junk.txt', '', '')), -+ ('file://///tmp/junk.txt', -+ ('file', '', '///tmp/junk.txt', '', '', ''), -+ ('file', '', '///tmp/junk.txt', '', '')), - ('imap://mail.python.org/mbox1', - ('imap', 'mail.python.org', '/mbox1', '', '', ''), - ('imap', 'mail.python.org', '/mbox1', '', '')), -@@ -208,6 +240,38 @@ - for url, parsed, split in str_cases + bytes_cases: - self.checkRoundtrips(url, parsed, split) - -+ def test_roundtrips_normalization(self): -+ str_cases = [ -+ ('///path/to/file', -+ '/path/to/file', -+ ('', '', '/path/to/file', '', '', ''), -+ ('', '', '/path/to/file', '', '')), -+ ('scheme:///path/to/file', -+ 'scheme:/path/to/file', -+ ('scheme', '', '/path/to/file', '', '', ''), -+ ('scheme', '', '/path/to/file', '', '')), -+ ('file:/tmp/junk.txt', -+ 'file:///tmp/junk.txt', -+ ('file', '', '/tmp/junk.txt', '', '', ''), -+ ('file', '', '/tmp/junk.txt', '', '')), -+ ('http:/tmp/junk.txt', -+ 'http:///tmp/junk.txt', -+ ('http', '', '/tmp/junk.txt', '', '', ''), -+ ('http', '', '/tmp/junk.txt', '', '')), -+ ('https:/tmp/junk.txt', -+ 'https:///tmp/junk.txt', -+ ('https', '', '/tmp/junk.txt', '', '', ''), -+ ('https', '', '/tmp/junk.txt', '', '')), -+ ] -+ def _encode(t): -+ return (t[0].encode('ascii'), -+ t[1].encode('ascii'), -+ tuple(x.encode('ascii') for x in t[2]), -+ tuple(x.encode('ascii') for x in t[3])) -+ bytes_cases = [_encode(x) for x in str_cases] -+ for url, url2, parsed, split in str_cases + bytes_cases: -+ self.checkRoundtrips(url, parsed, split, url2) -+ - def test_http_roundtrips(self): - # urllib.parse.urlsplit treats 'http:' as an optimized special case, - # so we test both 'http:' and 'https:' in all the following. diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py -index a60f4cd3111..b49c020bc92 100644 +index 9563282e6d9..b49c020bc92 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -19,8 +19,8 @@ @@ -5975,16 +3714,6 @@ index a60f4cd3111..b49c020bc92 100644 @requires_subprocess() def check_output(cmd, encoding=None): -@@ -253,7 +255,8 @@ - ('base_exec_prefix', sys.base_exec_prefix)): - cmd[2] = 'import sys; print(sys.%s)' % prefix - out, err = check_output(cmd) -- self.assertEqual(out.strip(), expected.encode(), prefix) -+ self.assertEqual(pathlib.Path(out.strip().decode()), -+ pathlib.Path(expected), prefix) - - @requireVenvCreate - def test_sysconfig(self): diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 9d608d63a01..24b1103f500 100644 --- a/Lib/test/test_webbrowser.py @@ -6110,107 +3839,6 @@ index 9d608d63a01..24b1103f500 100644 def test_environment_preferred(self): webbrowser = import_helper.import_fresh_module('webbrowser') try: -diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py -index 4de6f379a47..52831a7bd7c 100644 ---- a/Lib/test/test_zipfile.py -+++ b/Lib/test/test_zipfile.py -@@ -3651,6 +3651,83 @@ - zipfile.Path(zf) - zf.extractall(source_path.parent) - -+ def test_malformed_paths(self): -+ """ -+ Path should handle malformed paths gracefully. -+ -+ Paths with leading slashes are not visible. -+ -+ Paths with dots are treated like regular files. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr("/one-slash.txt", b"content") -+ zf.writestr("//two-slash.txt", b"content") -+ zf.writestr("../parent.txt", b"content") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ assert list(map(str, root.iterdir())) == ['../'] -+ assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content' -+ -+ def test_unsupported_names(self): -+ """ -+ Path segments with special characters are readable. -+ -+ On some platforms or file systems, characters like -+ ``:`` and ``?`` are not allowed, but they are valid -+ in the zip file. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr("path?", b"content") -+ zf.writestr("V: NMS.flac", b"fLaC...") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ contents = root.iterdir() -+ assert next(contents).name == 'path?' -+ assert next(contents).name == 'V: NMS.flac' -+ assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..." -+ -+ def test_backslash_not_separator(self): -+ """ -+ In a zip file, backslashes are not separators. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ (first,) = root.iterdir() -+ assert not first.is_dir() -+ assert first.name == 'foo\\bar' -+ -+ -+class DirtyZipInfo(zipfile.ZipInfo): -+ """ -+ Bypass name sanitization. -+ """ -+ -+ def __init__(self, filename, *args, **kwargs): -+ super().__init__(filename, *args, **kwargs) -+ self.filename = filename -+ -+ @classmethod -+ def for_name(cls, name, archive): -+ """ -+ Construct the same way that ZipFile.writestr does. -+ -+ TODO: extract this functionality and re-use -+ """ -+ self = cls(filename=name, date_time=time.localtime(time.time())[:6]) -+ self.compress_type = archive.compression -+ self.compress_level = archive.compresslevel -+ if self.filename.endswith('/'): # pragma: no cover -+ self.external_attr = 0o40775 << 16 # drwxrwxr-x -+ self.external_attr |= 0x10 # MS-DOS directory flag -+ else: -+ self.external_attr = 0o600 << 16 # ?rw------- -+ return self -+ - - class EncodedMetadataTests(unittest.TestCase): - file_names = ['\u4e00', '\u4e8c', '\u4e09'] # Han 'one', 'two', 'three' -diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py -index 10c302d34c1..abf1d1b5466 100644 ---- a/Lib/urllib/parse.py -+++ b/Lib/urllib/parse.py -@@ -525,7 +525,7 @@ - empty query; the RFC states that these are equivalent).""" - scheme, netloc, url, query, fragment, _coerce_result = ( - _coerce_args(*components)) -- if netloc or (scheme and scheme in uses_netloc and url[:2] != '//'): -+ if netloc or (scheme and scheme in uses_netloc) or url[:2] == '//': - if url and url[:1] != '/': url = '/' + url - url = '//' + (netloc or '') + url - if scheme: diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 5d72524c087..48976e4e11f 100755 --- a/Lib/webbrowser.py @@ -6296,43 +3924,6 @@ index 5d72524c087..48976e4e11f 100755 def main(): import getopt -diff --git a/Lib/zipfile.py b/Lib/zipfile.py -index 86829abce4e..6a4cf7a972b 100644 ---- a/Lib/zipfile.py -+++ b/Lib/zipfile.py -@@ -9,6 +9,7 @@ - import itertools - import os - import posixpath -+import re - import shutil - import stat - import struct -@@ -2212,7 +2213,7 @@ - def _ancestry(path): - """ - Given a path with elements separated by -- posixpath.sep, generate all elements of that path -+ posixpath.sep, generate all elements of that path. - - >>> list(_ancestry('b/d')) - ['b/d', 'b'] -@@ -2224,9 +2225,14 @@ - ['b'] - >>> list(_ancestry('')) - [] -+ -+ Multiple separators are treated like a single. -+ -+ >>> list(_ancestry('//b//d///f//')) -+ ['//b//d///f', '//b//d', '//b'] - """ - path = path.rstrip(posixpath.sep) -- while path and path != posixpath.sep: -+ while path.rstrip(posixpath.sep): - yield path - path, tail = posixpath.split(path) - --- /dev/null +++ b/Mac/Resources/app-store-compliance.patch @@ -0,0 +1 @@ @@ -6628,7182 +4219,32 @@ index 81d4d50f82f..9e7e0cd0ac0 100644 .PHONY: frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools .PHONY: frameworkaltinstallunixtools recheck clean clobber distclean .PHONY: smelly funny patchcheck touch altmaninstall commoninstall -diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c -index 3de4c06704b..ba69e2afd27 100644 ---- a/Modules/_io/textio.c -+++ b/Modules/_io/textio.c -@@ -1701,34 +1701,56 @@ - bytes_len = PyBytes_GET_SIZE(b); - } +diff --git a/Modules/getpath.c b/Modules/getpath.c +index 61d654065fd..8bd844d6459 100644 +--- a/Modules/getpath.c ++++ b/Modules/getpath.c +@@ -15,6 +15,7 @@ + #endif -- if (self->pending_bytes == NULL) { -- self->pending_bytes_count = 0; -- self->pending_bytes = b; -- } -- else if (self->pending_bytes_count + bytes_len > self->chunk_size) { -- // Prevent to concatenate more than chunk_size data. -- if (_textiowrapper_writeflush(self) < 0) { -- Py_DECREF(b); -- return NULL; -+ // We should avoid concatinating huge data. -+ // Flush the buffer before adding b to the buffer if b is not small. -+ // https://github.com/python/cpython/issues/87426 -+ if (bytes_len >= self->chunk_size) { -+ // _textiowrapper_writeflush() calls buffer.write(). -+ // self->pending_bytes can be appended during buffer->write() -+ // or other thread. -+ // We need to loop until buffer becomes empty. -+ // https://github.com/python/cpython/issues/118138 -+ // https://github.com/python/cpython/issues/119506 -+ while (self->pending_bytes != NULL) { -+ if (_textiowrapper_writeflush(self) < 0) { -+ Py_DECREF(b); -+ return NULL; -+ } - } -- self->pending_bytes = b; - } -- else if (!PyList_CheckExact(self->pending_bytes)) { -- PyObject *list = PyList_New(2); -- if (list == NULL) { -- Py_DECREF(b); -- return NULL; -- } -- PyList_SET_ITEM(list, 0, self->pending_bytes); -- PyList_SET_ITEM(list, 1, b); -- self->pending_bytes = list; -+ -+ if (self->pending_bytes == NULL) { -+ assert(self->pending_bytes_count == 0); -+ self->pending_bytes = b; - } - else { -- if (PyList_Append(self->pending_bytes, b) < 0) { -- Py_DECREF(b); -- return NULL; -+ if (!PyList_CheckExact(self->pending_bytes)) { -+ PyObject *list = PyList_New(0); -+ if (list == NULL) { -+ Py_DECREF(b); -+ return NULL; -+ } -+ // PyList_New() may trigger GC and other thread may call write(). -+ // So, we need to check the self->pending_bytes is a list again. -+ if (PyList_CheckExact(self->pending_bytes)) { -+ // Releasing empty list won't trigger GC and/or __del__. -+ Py_DECREF(list); -+ } -+ else { -+ if (PyList_Append(list, self->pending_bytes) < 0) { -+ Py_DECREF(list); -+ Py_DECREF(b); -+ return NULL; -+ } -+ Py_SETREF(self->pending_bytes, list); -+ } - } -+ -+ int ret = PyList_Append(self->pending_bytes, b); - Py_DECREF(b); -+ if (ret < 0) { -+ return NULL; -+ } - } - - self->pending_bytes_count += bytes_len; -diff --git a/Modules/_winapi.c b/Modules/_winapi.c -index 54eee9e8c3f..0376cc0a26f 100644 ---- a/Modules/_winapi.c -+++ b/Modules/_winapi.c -@@ -708,7 +708,7 @@ - { - HANDLE handle; - -- if (PySys_Audit("_winapi.CreateNamedPipe", "uII", -+ if (PySys_Audit("_winapi.CreateNamedPipe", "sII", - name, open_mode, pipe_mode) < 0) { - return INVALID_HANDLE_VALUE; - } -diff --git a/Modules/expat/expat.h b/Modules/expat/expat.h -index 95464b0dd17..d0d6015a662 100644 ---- a/Modules/expat/expat.h -+++ b/Modules/expat/expat.h -@@ -18,6 +18,7 @@ - Copyright (c) 2022 Thijs Schreijer - Copyright (c) 2023 Hanno Böck - Copyright (c) 2023 Sony Corporation / Snild Dolkow -+ Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp> - Licensed under the MIT license: - - Permission is hereby granted, free of charge, to any person obtaining -@@ -1042,7 +1043,7 @@ - XMLPARSEAPI(const XML_Feature *) - XML_GetFeatureList(void); - --#if XML_GE == 1 -+#if defined(XML_DTD) || (defined(XML_GE) && XML_GE == 1) - /* Added in Expat 2.4.0 for XML_DTD defined and - * added in Expat 2.6.0 for XML_GE == 1. */ - XMLPARSEAPI(XML_Bool) -@@ -1065,7 +1066,7 @@ - */ - #define XML_MAJOR_VERSION 2 - #define XML_MINOR_VERSION 6 --#define XML_MICRO_VERSION 0 -+#define XML_MICRO_VERSION 3 - - #ifdef __cplusplus - } -diff --git a/Modules/expat/internal.h b/Modules/expat/internal.h -index cce71e4c516..167ec36804a 100644 ---- a/Modules/expat/internal.h -+++ b/Modules/expat/internal.h -@@ -28,10 +28,11 @@ - Copyright (c) 2002-2003 Fred L. Drake, Jr. - Copyright (c) 2002-2006 Karl Waclawek - Copyright (c) 2003 Greg Stein -- Copyright (c) 2016-2023 Sebastian Pipping -+ Copyright (c) 2016-2024 Sebastian Pipping - Copyright (c) 2018 Yury Gribov - Copyright (c) 2019 David Loffredo -- Copyright (c) 2023 Sony Corporation / Snild Dolkow -+ Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow -+ Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp> - Licensed under the MIT license: - - Permission is hereby granted, free of charge, to any person obtaining -@@ -155,14 +156,20 @@ - void _INTERNAL_trim_to_complete_utf8_characters(const char *from, - const char **fromLimRef); - --#if XML_GE == 1 -+#if defined(XML_GE) && XML_GE == 1 - unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser); - unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser); - const char *unsignedCharToPrintable(unsigned char c); - #endif - --extern XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c --extern unsigned int g_parseAttempts; // used for testing only -+extern -+#if ! defined(XML_TESTING) -+ const -+#endif -+ XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c -+#if defined(XML_TESTING) -+extern unsigned int g_bytesScanned; // used for testing only -+#endif - - #ifdef __cplusplus - } -diff --git a/Modules/expat/siphash.h b/Modules/expat/siphash.h -index a1ed99e687b..04f6f74585b 100644 ---- a/Modules/expat/siphash.h -+++ b/Modules/expat/siphash.h -@@ -126,8 +126,7 @@ - | ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) \ - | ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) - --#define SIPHASH_INITIALIZER \ -- { 0, 0, 0, 0, {0}, 0, 0 } -+#define SIPHASH_INITIALIZER {0, 0, 0, 0, {0}, 0, 0} - - struct siphash { - uint64_t v0, v1, v2, v3; -diff --git a/Modules/expat/xmlparse.c b/Modules/expat/xmlparse.c -index aaf0fa9c8f9..d9285b213b3 100644 ---- a/Modules/expat/xmlparse.c -+++ b/Modules/expat/xmlparse.c -@@ -1,4 +1,4 @@ --/* 628e24d4966bedbd4800f6ed128d06d29703765b4bce12d3b7f099f90f842fc9 (2.6.0+) -+/* ba4cdf9bdb534f355a9def4c9e25d20ee8e72f95b0a4d930be52e563f5080196 (2.6.3+) - __ __ _ - ___\ \/ /_ __ __ _| |_ - / _ \\ /| '_ \ / _` | __| -@@ -38,7 +38,8 @@ - Copyright (c) 2022 Jann Horn - Copyright (c) 2022 Sean McBride - Copyright (c) 2023 Owain Davies -- Copyright (c) 2023 Sony Corporation / Snild Dolkow -+ Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow -+ Copyright (c) 2024 Berkay Eren Ürün - Licensed under the MIT license: - - Permission is hereby granted, free of charge, to any person obtaining -@@ -210,7 +211,7 @@ - #endif - - /* Round up n to be a multiple of sz, where sz is a power of 2. */ --#define ROUND_UP(n, sz) (((n) + ((sz)-1)) & ~((sz)-1)) -+#define ROUND_UP(n, sz) (((n) + ((sz) - 1)) & ~((sz) - 1)) - - /* Do safe (NULL-aware) pointer arithmetic */ - #define EXPAT_SAFE_PTR_DIFF(p, q) (((p) && (q)) ? ((p) - (q)) : 0) -@@ -248,7 +249,7 @@ - it odd, since odd numbers are always relative prime to a power of 2. - */ - #define SECOND_HASH(hash, mask, power) \ -- ((((hash) & ~(mask)) >> ((power)-1)) & ((mask) >> 2)) -+ ((((hash) & ~(mask)) >> ((power) - 1)) & ((mask) >> 2)) - #define PROBE_STEP(hash, mask, power) \ - ((unsigned char)((SECOND_HASH(hash, mask, power)) | 1)) - -@@ -294,7 +295,7 @@ - The name of the element is stored in both the document and API - encodings. The memory buffer 'buf' is a separately-allocated - memory area which stores the name. During the XML_Parse()/ -- XMLParseBuffer() when the element is open, the memory for the 'raw' -+ XML_ParseBuffer() when the element is open, the memory for the 'raw' - version of the name (in the document encoding) is shared with the - document buffer. If the element is open across calls to - XML_Parse()/XML_ParseBuffer(), the buffer is re-allocated to -@@ -629,8 +630,14 @@ - ? 0 \ - : ((*((pool)->ptr)++ = c), 1)) - --XML_Bool g_reparseDeferralEnabledDefault = XML_TRUE; // write ONLY in runtests.c --unsigned int g_parseAttempts = 0; // used for testing only -+#if ! defined(XML_TESTING) -+const -+#endif -+ XML_Bool g_reparseDeferralEnabledDefault -+ = XML_TRUE; // write ONLY in runtests.c -+#if defined(XML_TESTING) -+unsigned int g_bytesScanned = 0; // used for testing only -+#endif - - struct XML_ParserStruct { - /* The first member must be m_userData so that the XML_GetUserData -@@ -1017,7 +1024,9 @@ - return XML_ERROR_NONE; - } - } -- g_parseAttempts += 1; -+#if defined(XML_TESTING) -+ g_bytesScanned += (unsigned)have_now; -+#endif - const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr); - if (ret == XML_ERROR_NONE) { - // if we consumed nothing, remember what we had on this parse attempt. -@@ -2030,6 +2039,12 @@ - - if (parser == NULL) - return XML_STATUS_ERROR; -+ -+ if (len < 0) { -+ parser->m_errorCode = XML_ERROR_INVALID_ARGUMENT; -+ return XML_STATUS_ERROR; -+ } -+ - switch (parser->m_parsingStatus.parsing) { - case XML_SUSPENDED: - parser->m_errorCode = XML_ERROR_SUSPENDED; -@@ -5838,18 +5853,17 @@ - /* Set a safe default value in case 'next' does not get set */ - next = textStart; - --#ifdef XML_DTD - if (entity->is_param) { - int tok - = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); - result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, - tok, next, &next, XML_FALSE, XML_FALSE, - XML_ACCOUNT_ENTITY_EXPANSION); -- } else --#endif /* XML_DTD */ -+ } else { - result = doContent(parser, parser->m_tagLevel, parser->m_internalEncoding, - textStart, textEnd, &next, XML_FALSE, - XML_ACCOUNT_ENTITY_EXPANSION); -+ } - - if (result == XML_ERROR_NONE) { - if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) { -@@ -5886,18 +5900,17 @@ - /* Set a safe default value in case 'next' does not get set */ - next = textStart; - --#ifdef XML_DTD - if (entity->is_param) { - int tok - = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); - result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, - tok, next, &next, XML_FALSE, XML_TRUE, - XML_ACCOUNT_ENTITY_EXPANSION); -- } else --#endif /* XML_DTD */ -+ } else { - result = doContent(parser, openEntity->startTagLevel, - parser->m_internalEncoding, textStart, textEnd, &next, - XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION); -+ } - - if (result != XML_ERROR_NONE) - return result; -@@ -5924,7 +5937,6 @@ - return XML_ERROR_NONE; - } - --#ifdef XML_DTD - if (entity->is_param) { - int tok; - parser->m_processor = prologProcessor; -@@ -5932,9 +5944,7 @@ - return doProlog(parser, parser->m_encoding, s, end, tok, next, nextPtr, - (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE, - XML_ACCOUNT_DIRECT); -- } else --#endif /* XML_DTD */ -- { -+ } else { - parser->m_processor = contentProcessor; - /* see externalEntityContentProcessor vs contentProcessor */ - result = doContent(parser, parser->m_parentParser ? 1 : 0, -@@ -6232,7 +6242,7 @@ - dtd->keepProcessing = dtd->standalone; - goto endEntityValue; - } -- if (entity->open) { -+ if (entity->open || (entity == parser->m_declEntity)) { - if (enc == parser->m_encoding) - parser->m_eventPtr = entityTextPtr; - result = XML_ERROR_RECURSIVE_ENTITY_REF; -@@ -7008,6 +7018,16 @@ - if (! newE) - return 0; - if (oldE->nDefaultAtts) { -+ /* Detect and prevent integer overflow. -+ * The preprocessor guard addresses the "always false" warning -+ * from -Wtype-limits on platforms where -+ * sizeof(int) < sizeof(size_t), e.g. on x86_64. */ -+#if UINT_MAX >= SIZE_MAX -+ if ((size_t)oldE->nDefaultAtts -+ > ((size_t)(-1) / sizeof(DEFAULT_ATTRIBUTE))) { -+ return 0; -+ } -+#endif - newE->defaultAtts - = ms->malloc_fcn(oldE->nDefaultAtts * sizeof(DEFAULT_ATTRIBUTE)); - if (! newE->defaultAtts) { -@@ -7550,6 +7570,15 @@ - int next; - - if (! dtd->scaffIndex) { -+ /* Detect and prevent integer overflow. -+ * The preprocessor guard addresses the "always false" warning -+ * from -Wtype-limits on platforms where -+ * sizeof(unsigned int) < sizeof(size_t), e.g. on x86_64. */ -+#if UINT_MAX >= SIZE_MAX -+ if (parser->m_groupSize > ((size_t)(-1) / sizeof(int))) { -+ return -1; -+ } -+#endif - dtd->scaffIndex = (int *)MALLOC(parser, parser->m_groupSize * sizeof(int)); - if (! dtd->scaffIndex) - return -1; -@@ -7779,6 +7808,8 @@ - - static float - accountingGetCurrentAmplification(XML_Parser rootParser) { -+ // 1.........1.........12 => 22 -+ const size_t lenOfShortestInclude = sizeof("") - 1; - const XmlBigCount countBytesOutput - = rootParser->m_accounting.countBytesDirect - + rootParser->m_accounting.countBytesIndirect; -@@ -7786,7 +7817,9 @@ - = rootParser->m_accounting.countBytesDirect - ? (countBytesOutput - / (float)(rootParser->m_accounting.countBytesDirect)) -- : 1.0f; -+ : ((lenOfShortestInclude -+ + rootParser->m_accounting.countBytesIndirect) -+ / (float)lenOfShortestInclude); - assert(! rootParser->m_parentParser); - return amplificationFactor; - } -diff --git a/Modules/getpath.c b/Modules/getpath.c -index 61d654065fd..8bd844d6459 100644 ---- a/Modules/getpath.c -+++ b/Modules/getpath.c -@@ -15,6 +15,7 @@ - #endif - - #ifdef __APPLE__ -+# include "TargetConditionals.h" - # include - #endif - -@@ -767,7 +768,7 @@ - if (PyWin_DLLhModule) { - return winmodule_to_dict(dict, key, PyWin_DLLhModule); - } --#elif defined(WITH_NEXT_FRAMEWORK) -+#elif defined(WITH_NEXT_FRAMEWORK) && !defined(TARGET_OS_IPHONE) - static char modPath[MAXPATHLEN + 1]; - static int modPathInitialized = -1; - if (modPathInitialized < 0) { -@@ -961,4 +962,3 @@ - - return _PyStatus_OK(); - } -- -diff --git a/Modules/main.c b/Modules/main.c -index 6904e3f76e8..f6dade1e409 100644 ---- a/Modules/main.c -+++ b/Modules/main.c -@@ -531,6 +531,10 @@ - return; - } - -+ if (PySys_Audit("cpython.run_stdin", NULL) < 0) { -+ return; -+ } -+ - PyCompilerFlags cf = _PyCompilerFlags_INIT; - int res = PyRun_AnyFileFlags(stdin, "", &cf); - *exitcode = (res != 0); -diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c -index 6114f611682..6fb2fc8ce25 100644 ---- a/Modules/posixmodule.c -+++ b/Modules/posixmodule.c -@@ -34,6 +34,11 @@ - #include "pycore_pystate.h" // _PyInterpreterState_GET() - #include "pycore_signal.h" // Py_NSIG - -+#ifdef MS_WINDOWS -+# include // SetEntriesInAcl -+# include // SDDL_REVISION_1 -+#endif -+ - #include "structmember.h" // PyMemberDef - #ifndef MS_WINDOWS - # include "posixmodule.h" -@@ -598,6 +603,11 @@ - goto fatal_error; - } - -+ status = _PyRuntimeState_ReInitThreads(runtime); -+ if (_PyStatus_EXCEPTION(status)) { -+ goto fatal_error; -+ } -+ - PyThreadState *tstate = _PyThreadState_GET(); - _Py_EnsureTstateNotNULL(tstate); - -@@ -617,11 +627,6 @@ - - _PySignal_AfterFork(); - -- status = _PyRuntimeState_ReInitThreads(runtime); -- if (_PyStatus_EXCEPTION(status)) { -- goto fatal_error; -- } -- - status = _PyInterpreterState_DeleteExceptMain(runtime); - if (_PyStatus_EXCEPTION(status)) { - goto fatal_error; -@@ -4611,6 +4616,12 @@ - /*[clinic end generated code: output=a70446903abe821f input=a61722e1576fab03]*/ - { - int result; -+#ifdef MS_WINDOWS -+ int error = 0; -+ int pathError = 0; -+ SECURITY_ATTRIBUTES secAttr = { sizeof(secAttr) }; -+ SECURITY_ATTRIBUTES *pSecAttr = NULL; -+#endif - #ifdef HAVE_MKDIRAT - int mkdirat_unavailable = 0; - #endif -@@ -4622,11 +4633,38 @@ - - #ifdef MS_WINDOWS - Py_BEGIN_ALLOW_THREADS -- result = CreateDirectoryW(path->wide, NULL); -+ if (mode == 0700 /* 0o700 */) { -+ ULONG sdSize; -+ pSecAttr = &secAttr; -+ // Set a discretionary ACL (D) that is protected (P) and includes -+ // inheritable (OICI) entries that allow (A) full control (FA) to -+ // SYSTEM (SY), Administrators (BA), and the owner (OW). -+ if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( -+ L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)", -+ SDDL_REVISION_1, -+ &secAttr.lpSecurityDescriptor, -+ &sdSize -+ )) { -+ error = GetLastError(); -+ } -+ } -+ if (!error) { -+ result = CreateDirectoryW(path->wide, pSecAttr); -+ if (secAttr.lpSecurityDescriptor && -+ // uncommonly, LocalFree returns non-zero on error, but still uses -+ // GetLastError() to see what the error code is -+ LocalFree(secAttr.lpSecurityDescriptor)) { -+ error = GetLastError(); -+ } -+ } - Py_END_ALLOW_THREADS - -- if (!result) -+ if (error) { -+ return PyErr_SetFromWindowsErr(error); -+ } -+ if (!result) { - return path_error(path); -+ } - #else - Py_BEGIN_ALLOW_THREADS - #if HAVE_MKDIRAT -diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat -index b4362a5a259..b675a555c6c 100644 ---- a/PCbuild/get_externals.bat -+++ b/PCbuild/get_externals.bat -@@ -53,7 +53,7 @@ - set libraries= - set libraries=%libraries% bzip2-1.0.8 - if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4 --if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.13 -+if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.15 - set libraries=%libraries% sqlite-3.45.1.0 - if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.12.1 - if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.12.1 -@@ -77,7 +77,7 @@ - - set binaries= - if NOT "%IncludeLibffi%"=="false" set binaries=%binaries% libffi-3.4.4 --if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.0.13 -+if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.0.15 - if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.12.1 - if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06 - -diff --git a/PCbuild/python.props b/PCbuild/python.props -index d85fe4ab2a3..49347c2b78d 100644 ---- a/PCbuild/python.props -+++ b/PCbuild/python.props -@@ -74,8 +74,8 @@ - $(ExternalsDir)libffi-3.4.4\ - $(libffiDir)$(ArchName)\ - $(libffiOutDir)include -- $(ExternalsDir)openssl-3.0.13\ -- $(ExternalsDir)openssl-bin-3.0.13\$(ArchName)\ -+ $(ExternalsDir)openssl-3.0.15\ -+ $(ExternalsDir)openssl-bin-3.0.15\$(ArchName)\ - $(opensslOutDir)include - $(ExternalsDir)\nasm-2.11.06\ - $(ExternalsDir)\zlib-1.3.1\ -diff --git a/Parser/parser.c b/Parser/parser.c -index 2f1427af501..1eae6af1956 100644 ---- a/Parser/parser.c -+++ b/Parser/parser.c -@@ -450,72 +450,72 @@ - #define _gather_141_type 1370 - #define _tmp_143_type 1371 - #define _tmp_144_type 1372 --#define _tmp_145_type 1373 --#define _tmp_146_type 1374 -+#define _loop0_146_type 1373 -+#define _gather_145_type 1374 - #define _tmp_147_type 1375 - #define _tmp_148_type 1376 - #define _tmp_149_type 1377 - #define _tmp_150_type 1378 - #define _tmp_151_type 1379 - #define _tmp_152_type 1380 --#define _loop0_153_type 1381 --#define _loop0_154_type 1382 -+#define _tmp_153_type 1381 -+#define _tmp_154_type 1382 - #define _loop0_155_type 1383 --#define _tmp_156_type 1384 --#define _tmp_157_type 1385 -+#define _loop0_156_type 1384 -+#define _loop0_157_type 1385 - #define _tmp_158_type 1386 - #define _tmp_159_type 1387 --#define _loop0_160_type 1388 --#define _loop0_161_type 1389 --#define _loop1_162_type 1390 --#define _tmp_163_type 1391 --#define _loop0_164_type 1392 -+#define _tmp_160_type 1388 -+#define _tmp_161_type 1389 -+#define _loop0_162_type 1390 -+#define _loop0_163_type 1391 -+#define _loop1_164_type 1392 - #define _tmp_165_type 1393 - #define _loop0_166_type 1394 - #define _tmp_167_type 1395 - #define _loop0_168_type 1396 --#define _loop1_169_type 1397 --#define _tmp_170_type 1398 --#define _tmp_171_type 1399 -+#define _tmp_169_type 1397 -+#define _loop0_170_type 1398 -+#define _loop1_171_type 1399 - #define _tmp_172_type 1400 --#define _loop0_173_type 1401 -+#define _tmp_173_type 1401 - #define _tmp_174_type 1402 --#define _tmp_175_type 1403 --#define _loop1_176_type 1404 --#define _loop0_177_type 1405 --#define _loop0_178_type 1406 --#define _loop0_180_type 1407 --#define _gather_179_type 1408 --#define _tmp_181_type 1409 --#define _loop0_182_type 1410 -+#define _loop0_175_type 1403 -+#define _tmp_176_type 1404 -+#define _tmp_177_type 1405 -+#define _loop1_178_type 1406 -+#define _loop0_179_type 1407 -+#define _loop0_180_type 1408 -+#define _loop0_182_type 1409 -+#define _gather_181_type 1410 - #define _tmp_183_type 1411 - #define _loop0_184_type 1412 - #define _tmp_185_type 1413 - #define _loop0_186_type 1414 --#define _loop1_187_type 1415 --#define _loop1_188_type 1416 --#define _tmp_189_type 1417 --#define _tmp_190_type 1418 --#define _loop0_191_type 1419 -+#define _tmp_187_type 1415 -+#define _loop0_188_type 1416 -+#define _loop1_189_type 1417 -+#define _loop1_190_type 1418 -+#define _tmp_191_type 1419 - #define _tmp_192_type 1420 --#define _tmp_193_type 1421 -+#define _loop0_193_type 1421 - #define _tmp_194_type 1422 --#define _loop0_196_type 1423 --#define _gather_195_type 1424 -+#define _tmp_195_type 1423 -+#define _tmp_196_type 1424 - #define _loop0_198_type 1425 - #define _gather_197_type 1426 - #define _loop0_200_type 1427 - #define _gather_199_type 1428 - #define _loop0_202_type 1429 - #define _gather_201_type 1430 --#define _tmp_203_type 1431 --#define _loop0_204_type 1432 --#define _loop1_205_type 1433 --#define _tmp_206_type 1434 --#define _loop0_207_type 1435 --#define _loop1_208_type 1436 --#define _tmp_209_type 1437 --#define _tmp_210_type 1438 -+#define _loop0_204_type 1431 -+#define _gather_203_type 1432 -+#define _tmp_205_type 1433 -+#define _loop0_206_type 1434 -+#define _loop1_207_type 1435 -+#define _tmp_208_type 1436 -+#define _loop0_209_type 1437 -+#define _loop1_210_type 1438 - #define _tmp_211_type 1439 - #define _tmp_212_type 1440 - #define _tmp_213_type 1441 -@@ -524,10 +524,10 @@ - #define _tmp_216_type 1444 - #define _tmp_217_type 1445 - #define _tmp_218_type 1446 --#define _loop0_220_type 1447 --#define _gather_219_type 1448 --#define _tmp_221_type 1449 --#define _tmp_222_type 1450 -+#define _tmp_219_type 1447 -+#define _tmp_220_type 1448 -+#define _loop0_222_type 1449 -+#define _gather_221_type 1450 - #define _tmp_223_type 1451 - #define _tmp_224_type 1452 - #define _tmp_225_type 1453 -@@ -553,14 +553,17 @@ - #define _tmp_245_type 1473 - #define _tmp_246_type 1474 - #define _tmp_247_type 1475 --#define _loop0_249_type 1476 --#define _gather_248_type 1477 -+#define _tmp_248_type 1476 -+#define _tmp_249_type 1477 - #define _tmp_250_type 1478 --#define _tmp_251_type 1479 --#define _tmp_252_type 1480 -+#define _loop0_252_type 1479 -+#define _gather_251_type 1480 - #define _tmp_253_type 1481 - #define _tmp_254_type 1482 - #define _tmp_255_type 1483 -+#define _tmp_256_type 1484 -+#define _tmp_257_type 1485 -+#define _tmp_258_type 1486 - - static mod_ty file_rule(Parser *p); - static mod_ty interactive_rule(Parser *p); -@@ -935,72 +938,72 @@ - static asdl_seq *_gather_141_rule(Parser *p); - static void *_tmp_143_rule(Parser *p); - static void *_tmp_144_rule(Parser *p); --static void *_tmp_145_rule(Parser *p); --static void *_tmp_146_rule(Parser *p); -+static asdl_seq *_loop0_146_rule(Parser *p); -+static asdl_seq *_gather_145_rule(Parser *p); - static void *_tmp_147_rule(Parser *p); - static void *_tmp_148_rule(Parser *p); - static void *_tmp_149_rule(Parser *p); - static void *_tmp_150_rule(Parser *p); - static void *_tmp_151_rule(Parser *p); - static void *_tmp_152_rule(Parser *p); --static asdl_seq *_loop0_153_rule(Parser *p); --static asdl_seq *_loop0_154_rule(Parser *p); -+static void *_tmp_153_rule(Parser *p); -+static void *_tmp_154_rule(Parser *p); - static asdl_seq *_loop0_155_rule(Parser *p); --static void *_tmp_156_rule(Parser *p); --static void *_tmp_157_rule(Parser *p); -+static asdl_seq *_loop0_156_rule(Parser *p); -+static asdl_seq *_loop0_157_rule(Parser *p); - static void *_tmp_158_rule(Parser *p); - static void *_tmp_159_rule(Parser *p); --static asdl_seq *_loop0_160_rule(Parser *p); --static asdl_seq *_loop0_161_rule(Parser *p); --static asdl_seq *_loop1_162_rule(Parser *p); --static void *_tmp_163_rule(Parser *p); --static asdl_seq *_loop0_164_rule(Parser *p); -+static void *_tmp_160_rule(Parser *p); -+static void *_tmp_161_rule(Parser *p); -+static asdl_seq *_loop0_162_rule(Parser *p); -+static asdl_seq *_loop0_163_rule(Parser *p); -+static asdl_seq *_loop1_164_rule(Parser *p); - static void *_tmp_165_rule(Parser *p); - static asdl_seq *_loop0_166_rule(Parser *p); - static void *_tmp_167_rule(Parser *p); - static asdl_seq *_loop0_168_rule(Parser *p); --static asdl_seq *_loop1_169_rule(Parser *p); --static void *_tmp_170_rule(Parser *p); --static void *_tmp_171_rule(Parser *p); -+static void *_tmp_169_rule(Parser *p); -+static asdl_seq *_loop0_170_rule(Parser *p); -+static asdl_seq *_loop1_171_rule(Parser *p); - static void *_tmp_172_rule(Parser *p); --static asdl_seq *_loop0_173_rule(Parser *p); -+static void *_tmp_173_rule(Parser *p); - static void *_tmp_174_rule(Parser *p); --static void *_tmp_175_rule(Parser *p); --static asdl_seq *_loop1_176_rule(Parser *p); --static asdl_seq *_loop0_177_rule(Parser *p); --static asdl_seq *_loop0_178_rule(Parser *p); -+static asdl_seq *_loop0_175_rule(Parser *p); -+static void *_tmp_176_rule(Parser *p); -+static void *_tmp_177_rule(Parser *p); -+static asdl_seq *_loop1_178_rule(Parser *p); -+static asdl_seq *_loop0_179_rule(Parser *p); - static asdl_seq *_loop0_180_rule(Parser *p); --static asdl_seq *_gather_179_rule(Parser *p); --static void *_tmp_181_rule(Parser *p); - static asdl_seq *_loop0_182_rule(Parser *p); -+static asdl_seq *_gather_181_rule(Parser *p); - static void *_tmp_183_rule(Parser *p); - static asdl_seq *_loop0_184_rule(Parser *p); - static void *_tmp_185_rule(Parser *p); - static asdl_seq *_loop0_186_rule(Parser *p); --static asdl_seq *_loop1_187_rule(Parser *p); --static asdl_seq *_loop1_188_rule(Parser *p); --static void *_tmp_189_rule(Parser *p); --static void *_tmp_190_rule(Parser *p); --static asdl_seq *_loop0_191_rule(Parser *p); -+static void *_tmp_187_rule(Parser *p); -+static asdl_seq *_loop0_188_rule(Parser *p); -+static asdl_seq *_loop1_189_rule(Parser *p); -+static asdl_seq *_loop1_190_rule(Parser *p); -+static void *_tmp_191_rule(Parser *p); - static void *_tmp_192_rule(Parser *p); --static void *_tmp_193_rule(Parser *p); -+static asdl_seq *_loop0_193_rule(Parser *p); - static void *_tmp_194_rule(Parser *p); --static asdl_seq *_loop0_196_rule(Parser *p); --static asdl_seq *_gather_195_rule(Parser *p); -+static void *_tmp_195_rule(Parser *p); -+static void *_tmp_196_rule(Parser *p); - static asdl_seq *_loop0_198_rule(Parser *p); - static asdl_seq *_gather_197_rule(Parser *p); - static asdl_seq *_loop0_200_rule(Parser *p); - static asdl_seq *_gather_199_rule(Parser *p); - static asdl_seq *_loop0_202_rule(Parser *p); - static asdl_seq *_gather_201_rule(Parser *p); --static void *_tmp_203_rule(Parser *p); - static asdl_seq *_loop0_204_rule(Parser *p); --static asdl_seq *_loop1_205_rule(Parser *p); --static void *_tmp_206_rule(Parser *p); --static asdl_seq *_loop0_207_rule(Parser *p); --static asdl_seq *_loop1_208_rule(Parser *p); --static void *_tmp_209_rule(Parser *p); --static void *_tmp_210_rule(Parser *p); -+static asdl_seq *_gather_203_rule(Parser *p); -+static void *_tmp_205_rule(Parser *p); -+static asdl_seq *_loop0_206_rule(Parser *p); -+static asdl_seq *_loop1_207_rule(Parser *p); -+static void *_tmp_208_rule(Parser *p); -+static asdl_seq *_loop0_209_rule(Parser *p); -+static asdl_seq *_loop1_210_rule(Parser *p); - static void *_tmp_211_rule(Parser *p); - static void *_tmp_212_rule(Parser *p); - static void *_tmp_213_rule(Parser *p); -@@ -1009,10 +1012,10 @@ - static void *_tmp_216_rule(Parser *p); - static void *_tmp_217_rule(Parser *p); - static void *_tmp_218_rule(Parser *p); --static asdl_seq *_loop0_220_rule(Parser *p); --static asdl_seq *_gather_219_rule(Parser *p); --static void *_tmp_221_rule(Parser *p); --static void *_tmp_222_rule(Parser *p); -+static void *_tmp_219_rule(Parser *p); -+static void *_tmp_220_rule(Parser *p); -+static asdl_seq *_loop0_222_rule(Parser *p); -+static asdl_seq *_gather_221_rule(Parser *p); - static void *_tmp_223_rule(Parser *p); - static void *_tmp_224_rule(Parser *p); - static void *_tmp_225_rule(Parser *p); -@@ -1038,14 +1041,17 @@ - static void *_tmp_245_rule(Parser *p); - static void *_tmp_246_rule(Parser *p); - static void *_tmp_247_rule(Parser *p); --static asdl_seq *_loop0_249_rule(Parser *p); --static asdl_seq *_gather_248_rule(Parser *p); -+static void *_tmp_248_rule(Parser *p); -+static void *_tmp_249_rule(Parser *p); - static void *_tmp_250_rule(Parser *p); --static void *_tmp_251_rule(Parser *p); --static void *_tmp_252_rule(Parser *p); -+static asdl_seq *_loop0_252_rule(Parser *p); -+static asdl_seq *_gather_251_rule(Parser *p); - static void *_tmp_253_rule(Parser *p); - static void *_tmp_254_rule(Parser *p); - static void *_tmp_255_rule(Parser *p); -+static void *_tmp_256_rule(Parser *p); -+static void *_tmp_257_rule(Parser *p); -+static void *_tmp_258_rule(Parser *p); - - - // file: statements? $ -@@ -16772,7 +16778,7 @@ - return _res; - } - --// starred_expression: '*' expression -+// starred_expression: '*' expression | '*' - static expr_ty - starred_expression_rule(Parser *p) - { -@@ -16831,6 +16837,30 @@ - D(fprintf(stderr, "%*c%s starred_expression[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'*' expression")); - } -+ { // '*' -+ if (p->error_indicator) { -+ p->level--; -+ return NULL; -+ } -+ D(fprintf(stderr, "%*c> starred_expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'")); -+ Token * _literal; -+ if ( -+ (_literal = _PyPegen_expect_token(p, 16)) // token='*' -+ ) -+ { -+ D(fprintf(stderr, "%*c+ starred_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'")); -+ _res = RAISE_SYNTAX_ERROR ( "Invalid star expression" ); -+ if (_res == NULL && PyErr_Occurred()) { -+ p->error_indicator = 1; -+ p->level--; -+ return NULL; -+ } -+ goto done; -+ } -+ p->mark = _mark; -+ D(fprintf(stderr, "%*c%s starred_expression[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'*'")); -+ } - _res = NULL; - done: - p->level--; -@@ -18927,7 +18957,7 @@ - } - - // invalid_arguments: --// | ((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' '*' -+// | ((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' ','.(starred_expression !'=')+ - // | expression for_if_clauses ',' [args | expression for_if_clauses] - // | NAME '=' expression for_if_clauses - // | args for_if_clauses -@@ -18946,25 +18976,25 @@ - } - void * _res = NULL; - int _mark = p->mark; -- { // ((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' '*' -+ { // ((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' ','.(starred_expression !'=')+ - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> invalid_arguments[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' '*'")); -- Token * _literal; -+ D(fprintf(stderr, "%*c> invalid_arguments[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' ','.(starred_expression !'=')+")); -+ asdl_seq * _gather_145_var; - void *_tmp_144_var; -- Token * b; -+ Token * a; - if ( - (_tmp_144_var = _tmp_144_rule(p)) // (','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs - && -- (_literal = _PyPegen_expect_token(p, 12)) // token=',' -+ (a = _PyPegen_expect_token(p, 12)) // token=',' - && -- (b = _PyPegen_expect_token(p, 16)) // token='*' -+ (_gather_145_var = _gather_145_rule(p)) // ','.(starred_expression !'=')+ - ) - { -- D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' '*'")); -- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( b , "iterable argument unpacking follows keyword argument unpacking" ); -+ D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' ','.(starred_expression !'=')+")); -+ _res = RAISE_SYNTAX_ERROR_STARTING_FROM ( a , "iterable argument unpacking follows keyword argument unpacking" ); - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; - p->level--; -@@ -18974,7 +19004,7 @@ - } - p->mark = _mark; - D(fprintf(stderr, "%*c%s invalid_arguments[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' '*'")); -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' ','.(starred_expression !'=')+")); - } - { // expression for_if_clauses ',' [args | expression for_if_clauses] - if (p->error_indicator) { -@@ -18994,7 +19024,7 @@ - && - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (_opt_var = _tmp_145_rule(p), !p->error_indicator) // [args | expression for_if_clauses] -+ (_opt_var = _tmp_147_rule(p), !p->error_indicator) // [args | expression for_if_clauses] - ) - { - D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses ',' [args | expression for_if_clauses]")); -@@ -19165,7 +19195,7 @@ - Token* a; - Token * b; - if ( -- (a = (Token*)_tmp_146_rule(p)) // 'True' | 'False' | 'None' -+ (a = (Token*)_tmp_148_rule(p)) // 'True' | 'False' | 'None' - && - (b = _PyPegen_expect_token(p, 22)) // token='=' - ) -@@ -19225,7 +19255,7 @@ - expr_ty a; - Token * b; - if ( -- _PyPegen_lookahead(0, _tmp_147_rule, p) -+ _PyPegen_lookahead(0, _tmp_149_rule, p) - && - (a = expression_rule(p)) // expression - && -@@ -19450,7 +19480,7 @@ - expr_ty a; - expr_ty b; - if ( -- _PyPegen_lookahead(0, _tmp_148_rule, p) -+ _PyPegen_lookahead(0, _tmp_150_rule, p) - && - (a = disjunction_rule(p)) // disjunction - && -@@ -19486,7 +19516,7 @@ - && - (b = disjunction_rule(p)) // disjunction - && -- _PyPegen_lookahead(0, _tmp_149_rule, p) -+ _PyPegen_lookahead(0, _tmp_151_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "disjunction 'if' disjunction !('else' | ':')")); -@@ -19575,7 +19605,7 @@ - && - (b = bitwise_or_rule(p)) // bitwise_or - && -- _PyPegen_lookahead(0, _tmp_150_rule, p) -+ _PyPegen_lookahead(0, _tmp_152_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_named_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME '=' bitwise_or !('=' | ':=')")); -@@ -19601,7 +19631,7 @@ - Token * b; - expr_ty bitwise_or_var; - if ( -- _PyPegen_lookahead(0, _tmp_151_rule, p) -+ _PyPegen_lookahead(0, _tmp_153_rule, p) - && - (a = bitwise_or_rule(p)) // bitwise_or - && -@@ -19609,7 +19639,7 @@ - && - (bitwise_or_var = bitwise_or_rule(p)) // bitwise_or - && -- _PyPegen_lookahead(0, _tmp_152_rule, p) -+ _PyPegen_lookahead(0, _tmp_154_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_named_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "!(list | tuple | genexp | 'True' | 'None' | 'False') bitwise_or '=' bitwise_or !('=' | ':=')")); -@@ -19690,7 +19720,7 @@ - D(fprintf(stderr, "%*c> invalid_assignment[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expression ',' star_named_expressions* ':' expression")); - Token * _literal; - Token * _literal_1; -- asdl_seq * _loop0_153_var; -+ asdl_seq * _loop0_155_var; - expr_ty a; - expr_ty expression_var; - if ( -@@ -19698,7 +19728,7 @@ - && - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (_loop0_153_var = _loop0_153_rule(p)) // star_named_expressions* -+ (_loop0_155_var = _loop0_155_rule(p)) // star_named_expressions* - && - (_literal_1 = _PyPegen_expect_token(p, 11)) // token=':' - && -@@ -19755,10 +19785,10 @@ - } - D(fprintf(stderr, "%*c> invalid_assignment[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((star_targets '='))* star_expressions '='")); - Token * _literal; -- asdl_seq * _loop0_154_var; -+ asdl_seq * _loop0_156_var; - expr_ty a; - if ( -- (_loop0_154_var = _loop0_154_rule(p)) // ((star_targets '='))* -+ (_loop0_156_var = _loop0_156_rule(p)) // ((star_targets '='))* - && - (a = star_expressions_rule(p)) // star_expressions - && -@@ -19785,10 +19815,10 @@ - } - D(fprintf(stderr, "%*c> invalid_assignment[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((star_targets '='))* yield_expr '='")); - Token * _literal; -- asdl_seq * _loop0_155_var; -+ asdl_seq * _loop0_157_var; - expr_ty a; - if ( -- (_loop0_155_var = _loop0_155_rule(p)) // ((star_targets '='))* -+ (_loop0_157_var = _loop0_157_rule(p)) // ((star_targets '='))* - && - (a = yield_expr_rule(p)) // yield_expr - && -@@ -19814,7 +19844,7 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_assignment[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_expressions augassign (yield_expr | star_expressions)")); -- void *_tmp_156_var; -+ void *_tmp_158_var; - expr_ty a; - AugOperator* augassign_var; - if ( -@@ -19822,7 +19852,7 @@ - && - (augassign_var = augassign_rule(p)) // augassign - && -- (_tmp_156_var = _tmp_156_rule(p)) // yield_expr | star_expressions -+ (_tmp_158_var = _tmp_158_rule(p)) // yield_expr | star_expressions - ) - { - D(fprintf(stderr, "%*c+ invalid_assignment[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_expressions augassign (yield_expr | star_expressions)")); -@@ -20048,11 +20078,11 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_comprehension[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('[' | '(' | '{') starred_expression for_if_clauses")); -- void *_tmp_157_var; -+ void *_tmp_159_var; - expr_ty a; - asdl_comprehension_seq* for_if_clauses_var; - if ( -- (_tmp_157_var = _tmp_157_rule(p)) // '[' | '(' | '{' -+ (_tmp_159_var = _tmp_159_rule(p)) // '[' | '(' | '{' - && - (a = starred_expression_rule(p)) // starred_expression - && -@@ -20079,12 +20109,12 @@ - } - D(fprintf(stderr, "%*c> invalid_comprehension[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('[' | '{') star_named_expression ',' star_named_expressions for_if_clauses")); - Token * _literal; -- void *_tmp_158_var; -+ void *_tmp_160_var; - expr_ty a; - asdl_expr_seq* b; - asdl_comprehension_seq* for_if_clauses_var; - if ( -- (_tmp_158_var = _tmp_158_rule(p)) // '[' | '{' -+ (_tmp_160_var = _tmp_160_rule(p)) // '[' | '{' - && - (a = star_named_expression_rule(p)) // star_named_expression - && -@@ -20114,12 +20144,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_comprehension[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('[' | '{') star_named_expression ',' for_if_clauses")); -- void *_tmp_159_var; -+ void *_tmp_161_var; - expr_ty a; - Token * b; - asdl_comprehension_seq* for_if_clauses_var; - if ( -- (_tmp_159_var = _tmp_159_rule(p)) // '[' | '{' -+ (_tmp_161_var = _tmp_161_rule(p)) // '[' | '{' - && - (a = star_named_expression_rule(p)) // star_named_expression - && -@@ -20229,11 +20259,11 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default* invalid_parameters_helper param_no_default")); -- asdl_seq * _loop0_160_var; -+ asdl_seq * _loop0_162_var; - arg_ty a; - void *invalid_parameters_helper_var; - if ( -- (_loop0_160_var = _loop0_160_rule(p)) // param_no_default* -+ (_loop0_162_var = _loop0_162_rule(p)) // param_no_default* - && - (invalid_parameters_helper_var = invalid_parameters_helper_rule(p)) // invalid_parameters_helper - && -@@ -20259,18 +20289,18 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default* '(' param_no_default+ ','? ')'")); -- asdl_seq * _loop0_161_var; -- asdl_seq * _loop1_162_var; -+ asdl_seq * _loop0_163_var; -+ asdl_seq * _loop1_164_var; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - Token * a; - Token * b; - if ( -- (_loop0_161_var = _loop0_161_rule(p)) // param_no_default* -+ (_loop0_163_var = _loop0_163_rule(p)) // param_no_default* - && - (a = _PyPegen_expect_token(p, 7)) // token='(' - && -- (_loop1_162_var = _loop1_162_rule(p)) // param_no_default+ -+ (_loop1_164_var = _loop1_164_rule(p)) // param_no_default+ - && - (_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','? - && -@@ -20323,13 +20353,13 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(slash_no_default | slash_with_default) param_maybe_default* '/'")); -- asdl_seq * _loop0_164_var; -- void *_tmp_163_var; -+ asdl_seq * _loop0_166_var; -+ void *_tmp_165_var; - Token * a; - if ( -- (_tmp_163_var = _tmp_163_rule(p)) // slash_no_default | slash_with_default -+ (_tmp_165_var = _tmp_165_rule(p)) // slash_no_default | slash_with_default - && -- (_loop0_164_var = _loop0_164_rule(p)) // param_maybe_default* -+ (_loop0_166_var = _loop0_166_rule(p)) // param_maybe_default* - && - (a = _PyPegen_expect_token(p, 17)) // token='/' - ) -@@ -20354,22 +20384,22 @@ - } - D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "[(slash_no_default | slash_with_default)] param_maybe_default* '*' (',' | param_no_default) param_maybe_default* '/'")); - Token * _literal; -- asdl_seq * _loop0_166_var; - asdl_seq * _loop0_168_var; -+ asdl_seq * _loop0_170_var; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings -- void *_tmp_167_var; -+ void *_tmp_169_var; - Token * a; - if ( -- (_opt_var = _tmp_165_rule(p), !p->error_indicator) // [(slash_no_default | slash_with_default)] -+ (_opt_var = _tmp_167_rule(p), !p->error_indicator) // [(slash_no_default | slash_with_default)] - && -- (_loop0_166_var = _loop0_166_rule(p)) // param_maybe_default* -+ (_loop0_168_var = _loop0_168_rule(p)) // param_maybe_default* - && - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_167_var = _tmp_167_rule(p)) // ',' | param_no_default -+ (_tmp_169_var = _tmp_169_rule(p)) // ',' | param_no_default - && -- (_loop0_168_var = _loop0_168_rule(p)) // param_maybe_default* -+ (_loop0_170_var = _loop0_170_rule(p)) // param_maybe_default* - && - (a = _PyPegen_expect_token(p, 17)) // token='/' - ) -@@ -20394,10 +20424,10 @@ - } - D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default+ '/' '*'")); - Token * _literal; -- asdl_seq * _loop1_169_var; -+ asdl_seq * _loop1_171_var; - Token * a; - if ( -- (_loop1_169_var = _loop1_169_rule(p)) // param_maybe_default+ -+ (_loop1_171_var = _loop1_171_rule(p)) // param_maybe_default+ - && - (_literal = _PyPegen_expect_token(p, 17)) // token='/' - && -@@ -20447,7 +20477,7 @@ - if ( - (a = _PyPegen_expect_token(p, 22)) // token='=' - && -- _PyPegen_lookahead(1, _tmp_170_rule, p) -+ _PyPegen_lookahead(1, _tmp_172_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_default[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'=' &(')' | ',')")); -@@ -20493,12 +20523,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (')' | ',' (')' | '**'))")); -- void *_tmp_171_var; -+ void *_tmp_173_var; - Token * a; - if ( - (a = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_171_var = _tmp_171_rule(p)) // ')' | ',' (')' | '**') -+ (_tmp_173_var = _tmp_173_rule(p)) // ')' | ',' (')' | '**') - ) - { - D(fprintf(stderr, "%*c+ invalid_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (')' | ',' (')' | '**'))")); -@@ -20581,20 +20611,20 @@ - } - D(fprintf(stderr, "%*c> invalid_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (param_no_default | ',') param_maybe_default* '*' (param_no_default | ',')")); - Token * _literal; -- asdl_seq * _loop0_173_var; -- void *_tmp_172_var; -+ asdl_seq * _loop0_175_var; - void *_tmp_174_var; -+ void *_tmp_176_var; - Token * a; - if ( - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_172_var = _tmp_172_rule(p)) // param_no_default | ',' -+ (_tmp_174_var = _tmp_174_rule(p)) // param_no_default | ',' - && -- (_loop0_173_var = _loop0_173_rule(p)) // param_maybe_default* -+ (_loop0_175_var = _loop0_175_rule(p)) // param_maybe_default* - && - (a = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_174_var = _tmp_174_rule(p)) // param_no_default | ',' -+ (_tmp_176_var = _tmp_176_rule(p)) // param_no_default | ',' - ) - { - D(fprintf(stderr, "%*c+ invalid_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (param_no_default | ',') param_maybe_default* '*' (param_no_default | ',')")); -@@ -20710,7 +20740,7 @@ - && - (_literal_1 = _PyPegen_expect_token(p, 12)) // token=',' - && -- (a = (Token*)_tmp_175_rule(p)) // '*' | '**' | '/' -+ (a = (Token*)_tmp_177_rule(p)) // '*' | '**' | '/' - ) - { - D(fprintf(stderr, "%*c+ invalid_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' param ',' ('*' | '**' | '/')")); -@@ -20776,13 +20806,13 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_parameters_helper[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default+")); -- asdl_seq * _loop1_176_var; -+ asdl_seq * _loop1_178_var; - if ( -- (_loop1_176_var = _loop1_176_rule(p)) // param_with_default+ -+ (_loop1_178_var = _loop1_178_rule(p)) // param_with_default+ - ) - { - D(fprintf(stderr, "%*c+ invalid_parameters_helper[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_with_default+")); -- _res = _loop1_176_var; -+ _res = _loop1_178_var; - goto done; - } - p->mark = _mark; -@@ -20821,11 +20851,11 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default* invalid_lambda_parameters_helper lambda_param_no_default")); -- asdl_seq * _loop0_177_var; -+ asdl_seq * _loop0_179_var; - arg_ty a; - void *invalid_lambda_parameters_helper_var; - if ( -- (_loop0_177_var = _loop0_177_rule(p)) // lambda_param_no_default* -+ (_loop0_179_var = _loop0_179_rule(p)) // lambda_param_no_default* - && - (invalid_lambda_parameters_helper_var = invalid_lambda_parameters_helper_rule(p)) // invalid_lambda_parameters_helper - && -@@ -20851,18 +20881,18 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default* '(' ','.lambda_param+ ','? ')'")); -- asdl_seq * _gather_179_var; -- asdl_seq * _loop0_178_var; -+ asdl_seq * _gather_181_var; -+ asdl_seq * _loop0_180_var; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - Token * a; - Token * b; - if ( -- (_loop0_178_var = _loop0_178_rule(p)) // lambda_param_no_default* -+ (_loop0_180_var = _loop0_180_rule(p)) // lambda_param_no_default* - && - (a = _PyPegen_expect_token(p, 7)) // token='(' - && -- (_gather_179_var = _gather_179_rule(p)) // ','.lambda_param+ -+ (_gather_181_var = _gather_181_rule(p)) // ','.lambda_param+ - && - (_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','? - && -@@ -20915,13 +20945,13 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(lambda_slash_no_default | lambda_slash_with_default) lambda_param_maybe_default* '/'")); -- asdl_seq * _loop0_182_var; -- void *_tmp_181_var; -+ asdl_seq * _loop0_184_var; -+ void *_tmp_183_var; - Token * a; - if ( -- (_tmp_181_var = _tmp_181_rule(p)) // lambda_slash_no_default | lambda_slash_with_default -+ (_tmp_183_var = _tmp_183_rule(p)) // lambda_slash_no_default | lambda_slash_with_default - && -- (_loop0_182_var = _loop0_182_rule(p)) // lambda_param_maybe_default* -+ (_loop0_184_var = _loop0_184_rule(p)) // lambda_param_maybe_default* - && - (a = _PyPegen_expect_token(p, 17)) // token='/' - ) -@@ -20946,22 +20976,22 @@ - } - D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "[(lambda_slash_no_default | lambda_slash_with_default)] lambda_param_maybe_default* '*' (',' | lambda_param_no_default) lambda_param_maybe_default* '/'")); - Token * _literal; -- asdl_seq * _loop0_184_var; - asdl_seq * _loop0_186_var; -+ asdl_seq * _loop0_188_var; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings -- void *_tmp_185_var; -+ void *_tmp_187_var; - Token * a; - if ( -- (_opt_var = _tmp_183_rule(p), !p->error_indicator) // [(lambda_slash_no_default | lambda_slash_with_default)] -+ (_opt_var = _tmp_185_rule(p), !p->error_indicator) // [(lambda_slash_no_default | lambda_slash_with_default)] - && -- (_loop0_184_var = _loop0_184_rule(p)) // lambda_param_maybe_default* -+ (_loop0_186_var = _loop0_186_rule(p)) // lambda_param_maybe_default* - && - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_185_var = _tmp_185_rule(p)) // ',' | lambda_param_no_default -+ (_tmp_187_var = _tmp_187_rule(p)) // ',' | lambda_param_no_default - && -- (_loop0_186_var = _loop0_186_rule(p)) // lambda_param_maybe_default* -+ (_loop0_188_var = _loop0_188_rule(p)) // lambda_param_maybe_default* - && - (a = _PyPegen_expect_token(p, 17)) // token='/' - ) -@@ -20986,10 +21016,10 @@ - } - D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default+ '/' '*'")); - Token * _literal; -- asdl_seq * _loop1_187_var; -+ asdl_seq * _loop1_189_var; - Token * a; - if ( -- (_loop1_187_var = _loop1_187_rule(p)) // lambda_param_maybe_default+ -+ (_loop1_189_var = _loop1_189_rule(p)) // lambda_param_maybe_default+ - && - (_literal = _PyPegen_expect_token(p, 17)) // token='/' - && -@@ -21061,13 +21091,13 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_lambda_parameters_helper[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default+")); -- asdl_seq * _loop1_188_var; -+ asdl_seq * _loop1_190_var; - if ( -- (_loop1_188_var = _loop1_188_rule(p)) // lambda_param_with_default+ -+ (_loop1_190_var = _loop1_190_rule(p)) // lambda_param_with_default+ - ) - { - D(fprintf(stderr, "%*c+ invalid_lambda_parameters_helper[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default+")); -- _res = _loop1_188_var; -+ _res = _loop1_190_var; - goto done; - } - p->mark = _mark; -@@ -21104,11 +21134,11 @@ - } - D(fprintf(stderr, "%*c> invalid_lambda_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (':' | ',' (':' | '**'))")); - Token * _literal; -- void *_tmp_189_var; -+ void *_tmp_191_var; - if ( - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_189_var = _tmp_189_rule(p)) // ':' | ',' (':' | '**') -+ (_tmp_191_var = _tmp_191_rule(p)) // ':' | ',' (':' | '**') - ) - { - D(fprintf(stderr, "%*c+ invalid_lambda_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (':' | ',' (':' | '**'))")); -@@ -21161,20 +21191,20 @@ - } - D(fprintf(stderr, "%*c> invalid_lambda_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (lambda_param_no_default | ',') lambda_param_maybe_default* '*' (lambda_param_no_default | ',')")); - Token * _literal; -- asdl_seq * _loop0_191_var; -- void *_tmp_190_var; -+ asdl_seq * _loop0_193_var; - void *_tmp_192_var; -+ void *_tmp_194_var; - Token * a; - if ( - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_190_var = _tmp_190_rule(p)) // lambda_param_no_default | ',' -+ (_tmp_192_var = _tmp_192_rule(p)) // lambda_param_no_default | ',' - && -- (_loop0_191_var = _loop0_191_rule(p)) // lambda_param_maybe_default* -+ (_loop0_193_var = _loop0_193_rule(p)) // lambda_param_maybe_default* - && - (a = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_192_var = _tmp_192_rule(p)) // lambda_param_no_default | ',' -+ (_tmp_194_var = _tmp_194_rule(p)) // lambda_param_no_default | ',' - ) - { - D(fprintf(stderr, "%*c+ invalid_lambda_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (lambda_param_no_default | ',') lambda_param_maybe_default* '*' (lambda_param_no_default | ',')")); -@@ -21293,7 +21323,7 @@ - && - (_literal_1 = _PyPegen_expect_token(p, 12)) // token=',' - && -- (a = (Token*)_tmp_193_rule(p)) // '*' | '**' | '/' -+ (a = (Token*)_tmp_195_rule(p)) // '*' | '**' | '/' - ) - { - D(fprintf(stderr, "%*c+ invalid_lambda_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' lambda_param ',' ('*' | '**' | '/')")); -@@ -21401,7 +21431,7 @@ - && - (a = expression_rule(p)) // expression - && -- _PyPegen_lookahead(1, _tmp_194_rule, p) -+ _PyPegen_lookahead(1, _tmp_196_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_with_item[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression 'as' expression &(',' | ')' | ':')")); -@@ -21629,7 +21659,7 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_with_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "ASYNC? 'with' ','.(expression ['as' star_target])+ NEWLINE")); -- asdl_seq * _gather_195_var; -+ asdl_seq * _gather_197_var; - Token * _keyword; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings -@@ -21639,7 +21669,7 @@ - && - (_keyword = _PyPegen_expect_token(p, 612)) // token='with' - && -- (_gather_195_var = _gather_195_rule(p)) // ','.(expression ['as' star_target])+ -+ (_gather_197_var = _gather_197_rule(p)) // ','.(expression ['as' star_target])+ - && - (newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE' - ) -@@ -21663,7 +21693,7 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_with_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "ASYNC? 'with' '(' ','.(expressions ['as' star_target])+ ','? ')' NEWLINE")); -- asdl_seq * _gather_197_var; -+ asdl_seq * _gather_199_var; - Token * _keyword; - Token * _literal; - Token * _literal_1; -@@ -21679,7 +21709,7 @@ - && - (_literal = _PyPegen_expect_token(p, 7)) // token='(' - && -- (_gather_197_var = _gather_197_rule(p)) // ','.(expressions ['as' star_target])+ -+ (_gather_199_var = _gather_199_rule(p)) // ','.(expressions ['as' star_target])+ - && - (_opt_var_1 = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','? - && -@@ -21729,7 +21759,7 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_with_stmt_indent[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "ASYNC? 'with' ','.(expression ['as' star_target])+ ':' NEWLINE !INDENT")); -- asdl_seq * _gather_199_var; -+ asdl_seq * _gather_201_var; - Token * _literal; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings -@@ -21740,7 +21770,7 @@ - && - (a = _PyPegen_expect_token(p, 612)) // token='with' - && -- (_gather_199_var = _gather_199_rule(p)) // ','.(expression ['as' star_target])+ -+ (_gather_201_var = _gather_201_rule(p)) // ','.(expression ['as' star_target])+ - && - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - && -@@ -21768,7 +21798,7 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_with_stmt_indent[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "ASYNC? 'with' '(' ','.(expressions ['as' star_target])+ ','? ')' ':' NEWLINE !INDENT")); -- asdl_seq * _gather_201_var; -+ asdl_seq * _gather_203_var; - Token * _literal; - Token * _literal_1; - Token * _literal_2; -@@ -21785,7 +21815,7 @@ - && - (_literal = _PyPegen_expect_token(p, 7)) // token='(' - && -- (_gather_201_var = _gather_201_rule(p)) // ','.(expressions ['as' star_target])+ -+ (_gather_203_var = _gather_203_rule(p)) // ','.(expressions ['as' star_target])+ - && - (_opt_var_1 = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','? - && -@@ -21883,7 +21913,7 @@ - && - (block_var = block_rule(p)) // block - && -- _PyPegen_lookahead(0, _tmp_203_rule, p) -+ _PyPegen_lookahead(0, _tmp_205_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_try_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'try' ':' block !('except' | 'finally')")); -@@ -21908,8 +21938,8 @@ - Token * _keyword; - Token * _literal; - Token * _literal_1; -- asdl_seq * _loop0_204_var; -- asdl_seq * _loop1_205_var; -+ asdl_seq * _loop0_206_var; -+ asdl_seq * _loop1_207_var; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - Token * a; -@@ -21920,9 +21950,9 @@ - && - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - && -- (_loop0_204_var = _loop0_204_rule(p)) // block* -+ (_loop0_206_var = _loop0_206_rule(p)) // block* - && -- (_loop1_205_var = _loop1_205_rule(p)) // except_block+ -+ (_loop1_207_var = _loop1_207_rule(p)) // except_block+ - && - (a = _PyPegen_expect_token(p, 634)) // token='except' - && -@@ -21930,7 +21960,7 @@ - && - (expression_var = expression_rule(p)) // expression - && -- (_opt_var = _tmp_206_rule(p), !p->error_indicator) // ['as' NAME] -+ (_opt_var = _tmp_208_rule(p), !p->error_indicator) // ['as' NAME] - && - (_literal_1 = _PyPegen_expect_token(p, 11)) // token=':' - ) -@@ -21957,8 +21987,8 @@ - Token * _keyword; - Token * _literal; - Token * _literal_1; -- asdl_seq * _loop0_207_var; -- asdl_seq * _loop1_208_var; -+ asdl_seq * _loop0_209_var; -+ asdl_seq * _loop1_210_var; - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - Token * a; -@@ -21967,13 +21997,13 @@ - && - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - && -- (_loop0_207_var = _loop0_207_rule(p)) // block* -+ (_loop0_209_var = _loop0_209_rule(p)) // block* - && -- (_loop1_208_var = _loop1_208_rule(p)) // except_star_block+ -+ (_loop1_210_var = _loop1_210_rule(p)) // except_star_block+ - && - (a = _PyPegen_expect_token(p, 634)) // token='except' - && -- (_opt_var = _tmp_209_rule(p), !p->error_indicator) // [expression ['as' NAME]] -+ (_opt_var = _tmp_211_rule(p), !p->error_indicator) // [expression ['as' NAME]] - && - (_literal_1 = _PyPegen_expect_token(p, 11)) // token=':' - ) -@@ -22041,7 +22071,7 @@ - && - (expressions_var = expressions_rule(p)) // expressions - && -- (_opt_var_1 = _tmp_210_rule(p), !p->error_indicator) // ['as' NAME] -+ (_opt_var_1 = _tmp_212_rule(p), !p->error_indicator) // ['as' NAME] - && - (_literal_1 = _PyPegen_expect_token(p, 11)) // token=':' - ) -@@ -22079,7 +22109,7 @@ - && - (expression_var = expression_rule(p)) // expression - && -- (_opt_var_1 = _tmp_211_rule(p), !p->error_indicator) // ['as' NAME] -+ (_opt_var_1 = _tmp_213_rule(p), !p->error_indicator) // ['as' NAME] - && - (newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE' - ) -@@ -22131,14 +22161,14 @@ - } - D(fprintf(stderr, "%*c> invalid_except_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except' '*' (NEWLINE | ':')")); - Token * _literal; -- void *_tmp_212_var; -+ void *_tmp_214_var; - Token * a; - if ( - (a = _PyPegen_expect_token(p, 634)) // token='except' - && - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - && -- (_tmp_212_var = _tmp_212_rule(p)) // NEWLINE | ':' -+ (_tmp_214_var = _tmp_214_rule(p)) // NEWLINE | ':' - ) - { - D(fprintf(stderr, "%*c+ invalid_except_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' '*' (NEWLINE | ':')")); -@@ -22245,7 +22275,7 @@ - && - (expression_var = expression_rule(p)) // expression - && -- (_opt_var = _tmp_213_rule(p), !p->error_indicator) // ['as' NAME] -+ (_opt_var = _tmp_215_rule(p), !p->error_indicator) // ['as' NAME] - && - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - && -@@ -22340,7 +22370,7 @@ - && - (expression_var = expression_rule(p)) // expression - && -- (_opt_var = _tmp_214_rule(p), !p->error_indicator) // ['as' NAME] -+ (_opt_var = _tmp_216_rule(p), !p->error_indicator) // ['as' NAME] - && - (_literal_1 = _PyPegen_expect_token(p, 11)) // token=':' - && -@@ -22709,7 +22739,7 @@ - asdl_pattern_seq* a; - asdl_seq* keyword_patterns_var; - if ( -- (_opt_var = _tmp_215_rule(p), !p->error_indicator) // [positional_patterns ','] -+ (_opt_var = _tmp_217_rule(p), !p->error_indicator) // [positional_patterns ','] - && - (keyword_patterns_var = keyword_patterns_rule(p)) // keyword_patterns - && -@@ -23203,7 +23233,7 @@ - && - (_literal_1 = _PyPegen_expect_token(p, 8)) // token=')' - && -- (_opt_var_2 = _tmp_216_rule(p), !p->error_indicator) // ['->' expression] -+ (_opt_var_2 = _tmp_218_rule(p), !p->error_indicator) // ['->' expression] - && - (_literal_2 = _PyPegen_expect_token(p, 11)) // token=':' - && -@@ -23263,7 +23293,7 @@ - && - (name_var = _PyPegen_name_token(p)) // NAME - && -- (_opt_var = _tmp_217_rule(p), !p->error_indicator) // ['(' arguments? ')'] -+ (_opt_var = _tmp_219_rule(p), !p->error_indicator) // ['(' arguments? ')'] - && - (newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE' - ) -@@ -23298,7 +23328,7 @@ - && - (name_var = _PyPegen_name_token(p)) // NAME - && -- (_opt_var = _tmp_218_rule(p), !p->error_indicator) // ['(' arguments? ')'] -+ (_opt_var = _tmp_220_rule(p), !p->error_indicator) // ['(' arguments? ')'] - && - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - && -@@ -23349,11 +23379,11 @@ - return NULL; - } - D(fprintf(stderr, "%*c> invalid_double_starred_kvpairs[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.double_starred_kvpair+ ',' invalid_kvpair")); -- asdl_seq * _gather_219_var; -+ asdl_seq * _gather_221_var; - Token * _literal; - void *invalid_kvpair_var; - if ( -- (_gather_219_var = _gather_219_rule(p)) // ','.double_starred_kvpair+ -+ (_gather_221_var = _gather_221_rule(p)) // ','.double_starred_kvpair+ - && - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -@@ -23361,7 +23391,7 @@ - ) - { - D(fprintf(stderr, "%*c+ invalid_double_starred_kvpairs[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.double_starred_kvpair+ ',' invalid_kvpair")); -- _res = _PyPegen_dummy_name(p, _gather_219_var, _literal, invalid_kvpair_var); -+ _res = _PyPegen_dummy_name(p, _gather_221_var, _literal, invalid_kvpair_var); - goto done; - } - p->mark = _mark; -@@ -23414,7 +23444,7 @@ - && - (a = _PyPegen_expect_token(p, 11)) // token=':' - && -- _PyPegen_lookahead(1, _tmp_221_rule, p) -+ _PyPegen_lookahead(1, _tmp_223_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_double_starred_kvpairs[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ':' &('}' | ',')")); -@@ -23525,7 +23555,7 @@ - && - (a = _PyPegen_expect_token(p, 11)) // token=':' - && -- _PyPegen_lookahead(1, _tmp_222_rule, p) -+ _PyPegen_lookahead(1, _tmp_224_rule, p) - ) - { - D(fprintf(stderr, "%*c+ invalid_kvpair[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ':' &('}' | ',')")); -@@ -24376,12 +24406,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_14[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')")); -- void *_tmp_223_var; -+ void *_tmp_225_var; - while ( -- (_tmp_223_var = _tmp_223_rule(p)) // star_targets '=' -+ (_tmp_225_var = _tmp_225_rule(p)) // star_targets '=' - ) - { -- _res = _tmp_223_var; -+ _res = _tmp_225_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -24955,12 +24985,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop0_24[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('.' | '...')")); -- void *_tmp_224_var; -+ void *_tmp_226_var; - while ( -- (_tmp_224_var = _tmp_224_rule(p)) // '.' | '...' -+ (_tmp_226_var = _tmp_226_rule(p)) // '.' | '...' - ) - { -- _res = _tmp_224_var; -+ _res = _tmp_226_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -25023,12 +25053,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_25[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('.' | '...')")); -- void *_tmp_225_var; -+ void *_tmp_227_var; - while ( -- (_tmp_225_var = _tmp_225_rule(p)) // '.' | '...' -+ (_tmp_227_var = _tmp_227_rule(p)) // '.' | '...' - ) - { -- _res = _tmp_225_var; -+ _res = _tmp_227_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -25428,12 +25458,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_32[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('@' named_expression NEWLINE)")); -- void *_tmp_226_var; -+ void *_tmp_228_var; - while ( -- (_tmp_226_var = _tmp_226_rule(p)) // '@' named_expression NEWLINE -+ (_tmp_228_var = _tmp_228_rule(p)) // '@' named_expression NEWLINE - ) - { -- _res = _tmp_226_var; -+ _res = _tmp_228_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -28489,12 +28519,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_80[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' expression)")); -- void *_tmp_227_var; -+ void *_tmp_229_var; - while ( -- (_tmp_227_var = _tmp_227_rule(p)) // ',' expression -+ (_tmp_229_var = _tmp_229_rule(p)) // ',' expression - ) - { -- _res = _tmp_227_var; -+ _res = _tmp_229_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -28562,12 +28592,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_81[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_expression)")); -- void *_tmp_228_var; -+ void *_tmp_230_var; - while ( -- (_tmp_228_var = _tmp_228_rule(p)) // ',' star_expression -+ (_tmp_230_var = _tmp_230_rule(p)) // ',' star_expression - ) - { -- _res = _tmp_228_var; -+ _res = _tmp_230_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -28754,12 +28784,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_84[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('or' conjunction)")); -- void *_tmp_229_var; -+ void *_tmp_231_var; - while ( -- (_tmp_229_var = _tmp_229_rule(p)) // 'or' conjunction -+ (_tmp_231_var = _tmp_231_rule(p)) // 'or' conjunction - ) - { -- _res = _tmp_229_var; -+ _res = _tmp_231_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -28827,12 +28857,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_85[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('and' inversion)")); -- void *_tmp_230_var; -+ void *_tmp_232_var; - while ( -- (_tmp_230_var = _tmp_230_rule(p)) // 'and' inversion -+ (_tmp_232_var = _tmp_232_rule(p)) // 'and' inversion - ) - { -- _res = _tmp_230_var; -+ _res = _tmp_232_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -29022,7 +29052,7 @@ - while ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (elem = _tmp_231_rule(p)) // slice | starred_expression -+ (elem = _tmp_233_rule(p)) // slice | starred_expression - ) - { - _res = elem; -@@ -29088,7 +29118,7 @@ - void *elem; - asdl_seq * seq; - if ( -- (elem = _tmp_231_rule(p)) // slice | starred_expression -+ (elem = _tmp_233_rule(p)) // slice | starred_expression - && - (seq = _loop0_89_rule(p)) // _loop0_89 - ) -@@ -30774,12 +30804,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop0_114[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('if' disjunction)")); -- void *_tmp_232_var; -+ void *_tmp_234_var; - while ( -- (_tmp_232_var = _tmp_232_rule(p)) // 'if' disjunction -+ (_tmp_234_var = _tmp_234_rule(p)) // 'if' disjunction - ) - { -- _res = _tmp_232_var; -+ _res = _tmp_234_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -30842,12 +30872,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop0_115[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('if' disjunction)")); -- void *_tmp_233_var; -+ void *_tmp_235_var; - while ( -- (_tmp_233_var = _tmp_233_rule(p)) // 'if' disjunction -+ (_tmp_235_var = _tmp_235_rule(p)) // 'if' disjunction - ) - { -- _res = _tmp_233_var; -+ _res = _tmp_235_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -30975,7 +31005,7 @@ - while ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (elem = _tmp_234_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' -+ (elem = _tmp_236_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' - ) - { - _res = elem; -@@ -31042,7 +31072,7 @@ - void *elem; - asdl_seq * seq; - if ( -- (elem = _tmp_234_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' -+ (elem = _tmp_236_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' - && - (seq = _loop0_118_rule(p)) // _loop0_118 - ) -@@ -31613,12 +31643,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop0_128[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_target)")); -- void *_tmp_235_var; -+ void *_tmp_237_var; - while ( -- (_tmp_235_var = _tmp_235_rule(p)) // ',' star_target -+ (_tmp_237_var = _tmp_237_rule(p)) // ',' star_target - ) - { -- _res = _tmp_235_var; -+ _res = _tmp_237_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -31800,12 +31830,12 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _loop1_131[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_target)")); -- void *_tmp_236_var; -+ void *_tmp_238_var; - while ( -- (_tmp_236_var = _tmp_236_rule(p)) // ',' star_target -+ (_tmp_238_var = _tmp_238_rule(p)) // ',' star_target - ) - { -- _res = _tmp_236_var; -+ _res = _tmp_238_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -32544,13 +32574,13 @@ - return NULL; - } - D(fprintf(stderr, "%*c> _tmp_144[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)")); -- void *_tmp_237_var; -+ void *_tmp_239_var; - if ( -- (_tmp_237_var = _tmp_237_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs -+ (_tmp_239_var = _tmp_239_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs - ) - { - D(fprintf(stderr, "%*c+ _tmp_144[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)")); -- _res = _tmp_237_var; -+ _res = _tmp_239_var; - goto done; - } - p->mark = _mark; -@@ -32582,9 +32612,128 @@ - return _res; - } - --// _tmp_145: args | expression for_if_clauses -+// _loop0_146: ',' (starred_expression !'=') -+static asdl_seq * -+_loop0_146_rule(Parser *p) -+{ -+ if (p->level++ == MAXSTACK) { -+ p->error_indicator = 1; -+ PyErr_NoMemory(); -+ } -+ if (p->error_indicator) { -+ p->level--; -+ return NULL; -+ } -+ void *_res = NULL; -+ int _mark = p->mark; -+ void **_children = PyMem_Malloc(sizeof(void *)); -+ if (!_children) { -+ p->error_indicator = 1; -+ PyErr_NoMemory(); -+ p->level--; -+ return NULL; -+ } -+ Py_ssize_t _children_capacity = 1; -+ Py_ssize_t _n = 0; -+ { // ',' (starred_expression !'=') -+ if (p->error_indicator) { -+ p->level--; -+ return NULL; -+ } -+ D(fprintf(stderr, "%*c> _loop0_146[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (starred_expression !'=')")); -+ Token * _literal; -+ void *elem; -+ while ( -+ (_literal = _PyPegen_expect_token(p, 12)) // token=',' -+ && -+ (elem = _tmp_240_rule(p)) // starred_expression !'=' -+ ) -+ { -+ _res = elem; -+ if (_res == NULL && PyErr_Occurred()) { -+ p->error_indicator = 1; -+ PyMem_Free(_children); -+ p->level--; -+ return NULL; -+ } -+ if (_n == _children_capacity) { -+ _children_capacity *= 2; -+ void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -+ if (!_new_children) { -+ PyMem_Free(_children); -+ p->error_indicator = 1; -+ PyErr_NoMemory(); -+ p->level--; -+ return NULL; -+ } -+ _children = _new_children; -+ } -+ _children[_n++] = _res; -+ _mark = p->mark; -+ } -+ p->mark = _mark; -+ D(fprintf(stderr, "%*c%s _loop0_146[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (starred_expression !'=')")); -+ } -+ asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -+ if (!_seq) { -+ PyMem_Free(_children); -+ p->error_indicator = 1; -+ PyErr_NoMemory(); -+ p->level--; -+ return NULL; -+ } -+ for (int i = 0; i < _n; i++) asdl_seq_SET_UNTYPED(_seq, i, _children[i]); -+ PyMem_Free(_children); -+ p->level--; -+ return _seq; -+} -+ -+// _gather_145: (starred_expression !'=') _loop0_146 -+static asdl_seq * -+_gather_145_rule(Parser *p) -+{ -+ if (p->level++ == MAXSTACK) { -+ p->error_indicator = 1; -+ PyErr_NoMemory(); -+ } -+ if (p->error_indicator) { -+ p->level--; -+ return NULL; -+ } -+ asdl_seq * _res = NULL; -+ int _mark = p->mark; -+ { // (starred_expression !'=') _loop0_146 -+ if (p->error_indicator) { -+ p->level--; -+ return NULL; -+ } -+ D(fprintf(stderr, "%*c> _gather_145[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(starred_expression !'=') _loop0_146")); -+ void *elem; -+ asdl_seq * seq; -+ if ( -+ (elem = _tmp_240_rule(p)) // starred_expression !'=' -+ && -+ (seq = _loop0_146_rule(p)) // _loop0_146 -+ ) -+ { -+ D(fprintf(stderr, "%*c+ _gather_145[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(starred_expression !'=') _loop0_146")); -+ _res = _PyPegen_seq_insert_in_front(p, elem, seq); -+ goto done; -+ } -+ p->mark = _mark; -+ D(fprintf(stderr, "%*c%s _gather_145[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(starred_expression !'=') _loop0_146")); -+ } -+ _res = NULL; -+ done: -+ p->level--; -+ return _res; -+} -+ -+// _tmp_147: args | expression for_if_clauses - static void * --_tmp_145_rule(Parser *p) -+_tmp_147_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -32601,18 +32750,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_145[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "args")); -+ D(fprintf(stderr, "%*c> _tmp_147[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "args")); - expr_ty args_var; - if ( - (args_var = args_rule(p)) // args - ) - { -- D(fprintf(stderr, "%*c+ _tmp_145[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args")); -+ D(fprintf(stderr, "%*c+ _tmp_147[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args")); - _res = args_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_145[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_147[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "args")); - } - { // expression for_if_clauses -@@ -32620,7 +32769,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_145[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses")); -+ D(fprintf(stderr, "%*c> _tmp_147[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses")); - expr_ty expression_var; - asdl_comprehension_seq* for_if_clauses_var; - if ( -@@ -32629,12 +32778,12 @@ - (for_if_clauses_var = for_if_clauses_rule(p)) // for_if_clauses - ) - { -- D(fprintf(stderr, "%*c+ _tmp_145[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses")); -+ D(fprintf(stderr, "%*c+ _tmp_147[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses")); - _res = _PyPegen_dummy_name(p, expression_var, for_if_clauses_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_145[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_147[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression for_if_clauses")); - } - _res = NULL; -@@ -32643,9 +32792,9 @@ - return _res; - } - --// _tmp_146: 'True' | 'False' | 'None' -+// _tmp_148: 'True' | 'False' | 'None' - static void * --_tmp_146_rule(Parser *p) -+_tmp_148_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -32662,18 +32811,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_146[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'")); -+ D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 600)) // token='True' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_146[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'")); -+ D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_146[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'True'")); - } - { // 'False' -@@ -32681,18 +32830,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_146[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'")); -+ D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 602)) // token='False' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_146[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'")); -+ D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_146[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'False'")); - } - { // 'None' -@@ -32700,18 +32849,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_146[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'")); -+ D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 601)) // token='None' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_146[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'")); -+ D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_146[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'None'")); - } - _res = NULL; -@@ -32720,9 +32869,9 @@ - return _res; - } - --// _tmp_147: NAME '=' -+// _tmp_149: NAME '=' - static void * --_tmp_147_rule(Parser *p) -+_tmp_149_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -32739,7 +32888,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_147[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME '='")); -+ D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME '='")); - Token * _literal; - expr_ty name_var; - if ( -@@ -32748,12 +32897,12 @@ - (_literal = _PyPegen_expect_token(p, 22)) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_147[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME '='")); -+ D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME '='")); - _res = _PyPegen_dummy_name(p, name_var, _literal); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_147[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME '='")); - } - _res = NULL; -@@ -32762,9 +32911,9 @@ - return _res; - } - --// _tmp_148: NAME STRING | SOFT_KEYWORD -+// _tmp_150: NAME STRING | SOFT_KEYWORD - static void * --_tmp_148_rule(Parser *p) -+_tmp_150_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -32781,7 +32930,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME STRING")); -+ D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME STRING")); - expr_ty name_var; - expr_ty string_var; - if ( -@@ -32790,12 +32939,12 @@ - (string_var = _PyPegen_string_token(p)) // STRING - ) - { -- D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME STRING")); -+ D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME STRING")); - _res = _PyPegen_dummy_name(p, name_var, string_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME STRING")); - } - { // SOFT_KEYWORD -@@ -32803,18 +32952,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD")); -+ D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD")); - expr_ty soft_keyword_var; - if ( - (soft_keyword_var = _PyPegen_soft_keyword_token(p)) // SOFT_KEYWORD - ) - { -- D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD")); -+ D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD")); - _res = soft_keyword_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "SOFT_KEYWORD")); - } - _res = NULL; -@@ -32823,9 +32972,9 @@ - return _res; - } - --// _tmp_149: 'else' | ':' -+// _tmp_151: 'else' | ':' - static void * --_tmp_149_rule(Parser *p) -+_tmp_151_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -32842,18 +32991,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'else'")); -+ D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'else'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 642)) // token='else' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'else'")); -+ D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'else'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'else'")); - } - { // ':' -@@ -32861,18 +33010,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'")); - } - _res = NULL; -@@ -32881,9 +33030,9 @@ - return _res; - } - --// _tmp_150: '=' | ':=' -+// _tmp_152: '=' | ':=' - static void * --_tmp_150_rule(Parser *p) -+_tmp_152_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -32900,18 +33049,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='")); -+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 22)) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='")); -+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'='")); - } - { // ':=' -@@ -32919,18 +33068,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':='")); -+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':='")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 53)) // token=':=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':='")); -+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':='")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':='")); - } - _res = NULL; -@@ -32939,9 +33088,9 @@ - return _res; - } - --// _tmp_151: list | tuple | genexp | 'True' | 'None' | 'False' -+// _tmp_153: list | tuple | genexp | 'True' | 'None' | 'False' - static void * --_tmp_151_rule(Parser *p) -+_tmp_153_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -32958,18 +33107,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "list")); -+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "list")); - expr_ty list_var; - if ( - (list_var = list_rule(p)) // list - ) - { -- D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "list")); -+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "list")); - _res = list_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "list")); - } - { // tuple -@@ -32977,18 +33126,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tuple")); -+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tuple")); - expr_ty tuple_var; - if ( - (tuple_var = tuple_rule(p)) // tuple - ) - { -- D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tuple")); -+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tuple")); - _res = tuple_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "tuple")); - } - { // genexp -@@ -32996,18 +33145,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "genexp")); -+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "genexp")); - expr_ty genexp_var; - if ( - (genexp_var = genexp_rule(p)) // genexp - ) - { -- D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "genexp")); -+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "genexp")); - _res = genexp_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "genexp")); - } - { // 'True' -@@ -33015,18 +33164,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'")); -+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 600)) // token='True' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'")); -+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'True'")); - } - { // 'None' -@@ -33034,18 +33183,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'")); -+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 601)) // token='None' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'")); -+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'None'")); - } - { // 'False' -@@ -33053,18 +33202,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'")); -+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 602)) // token='False' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'")); -+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'False'")); - } - _res = NULL; -@@ -33073,9 +33222,9 @@ - return _res; - } - --// _tmp_152: '=' | ':=' -+// _tmp_154: '=' | ':=' - static void * --_tmp_152_rule(Parser *p) -+_tmp_154_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33092,18 +33241,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='")); -+ D(fprintf(stderr, "%*c> _tmp_154[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 22)) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='")); -+ D(fprintf(stderr, "%*c+ _tmp_154[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_154[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'='")); - } - { // ':=' -@@ -33111,18 +33260,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':='")); -+ D(fprintf(stderr, "%*c> _tmp_154[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':='")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 53)) // token=':=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':='")); -+ D(fprintf(stderr, "%*c+ _tmp_154[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':='")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_154[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':='")); - } - _res = NULL; -@@ -33131,9 +33280,9 @@ - return _res; - } - --// _loop0_153: star_named_expressions -+// _loop0_155: star_named_expressions - static asdl_seq * --_loop0_153_rule(Parser *p) -+_loop0_155_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33159,7 +33308,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expressions")); -+ D(fprintf(stderr, "%*c> _loop0_155[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expressions")); - asdl_expr_seq* star_named_expressions_var; - while ( - (star_named_expressions_var = star_named_expressions_rule(p)) // star_named_expressions -@@ -33182,7 +33331,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_153[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_155[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_named_expressions")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -33199,9 +33348,9 @@ - return _seq; - } - --// _loop0_154: (star_targets '=') -+// _loop0_156: (star_targets '=') - static asdl_seq * --_loop0_154_rule(Parser *p) -+_loop0_156_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33227,13 +33376,13 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_154[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')")); -- void *_tmp_238_var; -+ D(fprintf(stderr, "%*c> _loop0_156[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')")); -+ void *_tmp_241_var; - while ( -- (_tmp_238_var = _tmp_238_rule(p)) // star_targets '=' -+ (_tmp_241_var = _tmp_241_rule(p)) // star_targets '=' - ) - { -- _res = _tmp_238_var; -+ _res = _tmp_241_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -33250,7 +33399,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_154[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_156[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(star_targets '=')")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -33267,9 +33416,9 @@ - return _seq; - } - --// _loop0_155: (star_targets '=') -+// _loop0_157: (star_targets '=') - static asdl_seq * --_loop0_155_rule(Parser *p) -+_loop0_157_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33295,13 +33444,13 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_155[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')")); -- void *_tmp_239_var; -+ D(fprintf(stderr, "%*c> _loop0_157[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')")); -+ void *_tmp_242_var; - while ( -- (_tmp_239_var = _tmp_239_rule(p)) // star_targets '=' -+ (_tmp_242_var = _tmp_242_rule(p)) // star_targets '=' - ) - { -- _res = _tmp_239_var; -+ _res = _tmp_242_var; - if (_n == _children_capacity) { - _children_capacity *= 2; - void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *)); -@@ -33318,7 +33467,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_155[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_157[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(star_targets '=')")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -33335,9 +33484,9 @@ - return _seq; - } - --// _tmp_156: yield_expr | star_expressions -+// _tmp_158: yield_expr | star_expressions - static void * --_tmp_156_rule(Parser *p) -+_tmp_158_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33354,18 +33503,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_156[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "yield_expr")); -+ D(fprintf(stderr, "%*c> _tmp_158[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "yield_expr")); - expr_ty yield_expr_var; - if ( - (yield_expr_var = yield_expr_rule(p)) // yield_expr - ) - { -- D(fprintf(stderr, "%*c+ _tmp_156[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "yield_expr")); -+ D(fprintf(stderr, "%*c+ _tmp_158[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "yield_expr")); - _res = yield_expr_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_156[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_158[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "yield_expr")); - } - { // star_expressions -@@ -33373,18 +33522,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_156[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_expressions")); -+ D(fprintf(stderr, "%*c> _tmp_158[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_expressions")); - expr_ty star_expressions_var; - if ( - (star_expressions_var = star_expressions_rule(p)) // star_expressions - ) - { -- D(fprintf(stderr, "%*c+ _tmp_156[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_expressions")); -+ D(fprintf(stderr, "%*c+ _tmp_158[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_expressions")); - _res = star_expressions_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_156[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_158[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_expressions")); - } - _res = NULL; -@@ -33393,9 +33542,9 @@ - return _res; - } - --// _tmp_157: '[' | '(' | '{' -+// _tmp_159: '[' | '(' | '{' - static void * --_tmp_157_rule(Parser *p) -+_tmp_159_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33412,18 +33561,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_157[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['")); -+ D(fprintf(stderr, "%*c> _tmp_159[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 9)) // token='[' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_157[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['")); -+ D(fprintf(stderr, "%*c+ _tmp_159[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_157[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_159[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'['")); - } - { // '(' -@@ -33431,18 +33580,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_157[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'('")); -+ D(fprintf(stderr, "%*c> _tmp_159[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'('")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 7)) // token='(' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_157[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'('")); -+ D(fprintf(stderr, "%*c+ _tmp_159[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'('")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_157[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_159[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'('")); - } - { // '{' -@@ -33450,18 +33599,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_157[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'")); -+ D(fprintf(stderr, "%*c> _tmp_159[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 25)) // token='{' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_157[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'")); -+ D(fprintf(stderr, "%*c+ _tmp_159[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_157[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_159[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'{'")); - } - _res = NULL; -@@ -33470,9 +33619,9 @@ - return _res; - } - --// _tmp_158: '[' | '{' -+// _tmp_160: '[' | '{' - static void * --_tmp_158_rule(Parser *p) -+_tmp_160_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33489,18 +33638,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_158[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['")); -+ D(fprintf(stderr, "%*c> _tmp_160[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 9)) // token='[' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_158[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['")); -+ D(fprintf(stderr, "%*c+ _tmp_160[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_158[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_160[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'['")); - } - { // '{' -@@ -33508,18 +33657,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_158[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'")); -+ D(fprintf(stderr, "%*c> _tmp_160[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 25)) // token='{' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_158[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'")); -+ D(fprintf(stderr, "%*c+ _tmp_160[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_158[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_160[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'{'")); - } - _res = NULL; -@@ -33528,9 +33677,9 @@ - return _res; - } - --// _tmp_159: '[' | '{' -+// _tmp_161: '[' | '{' - static void * --_tmp_159_rule(Parser *p) -+_tmp_161_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33547,18 +33696,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_159[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['")); -+ D(fprintf(stderr, "%*c> _tmp_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 9)) // token='[' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_159[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['")); -+ D(fprintf(stderr, "%*c+ _tmp_161[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_159[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_161[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'['")); - } - { // '{' -@@ -33566,18 +33715,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_159[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'")); -+ D(fprintf(stderr, "%*c> _tmp_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 25)) // token='{' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_159[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'")); -+ D(fprintf(stderr, "%*c+ _tmp_161[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_159[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_161[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'{'")); - } - _res = NULL; -@@ -33586,9 +33735,9 @@ - return _res; - } - --// _loop0_160: param_no_default -+// _loop0_162: param_no_default - static asdl_seq * --_loop0_160_rule(Parser *p) -+_loop0_162_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33614,7 +33763,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_160[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c> _loop0_162[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); - arg_ty param_no_default_var; - while ( - (param_no_default_var = param_no_default_rule(p)) // param_no_default -@@ -33637,7 +33786,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_160[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_162[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -33654,9 +33803,9 @@ - return _seq; - } - --// _loop0_161: param_no_default -+// _loop0_163: param_no_default - static asdl_seq * --_loop0_161_rule(Parser *p) -+_loop0_163_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33682,7 +33831,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c> _loop0_163[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); - arg_ty param_no_default_var; - while ( - (param_no_default_var = param_no_default_rule(p)) // param_no_default -@@ -33705,7 +33854,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_161[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_163[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -33722,9 +33871,9 @@ - return _seq; - } - --// _loop1_162: param_no_default -+// _loop1_164: param_no_default - static asdl_seq * --_loop1_162_rule(Parser *p) -+_loop1_164_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33750,7 +33899,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop1_162[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c> _loop1_164[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); - arg_ty param_no_default_var; - while ( - (param_no_default_var = param_no_default_rule(p)) // param_no_default -@@ -33773,7 +33922,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop1_162[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop1_164[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default")); - } - if (_n == 0 || p->error_indicator) { -@@ -33795,9 +33944,9 @@ - return _seq; - } - --// _tmp_163: slash_no_default | slash_with_default -+// _tmp_165: slash_no_default | slash_with_default - static void * --_tmp_163_rule(Parser *p) -+_tmp_165_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33814,18 +33963,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_163[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_165[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_no_default")); - asdl_arg_seq* slash_no_default_var; - if ( - (slash_no_default_var = slash_no_default_rule(p)) // slash_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_163[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_165[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_no_default")); - _res = slash_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_163[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_165[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slash_no_default")); - } - { // slash_with_default -@@ -33833,18 +33982,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_163[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_with_default")); -+ D(fprintf(stderr, "%*c> _tmp_165[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_with_default")); - SlashWithDefault* slash_with_default_var; - if ( - (slash_with_default_var = slash_with_default_rule(p)) // slash_with_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_163[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_with_default")); -+ D(fprintf(stderr, "%*c+ _tmp_165[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_with_default")); - _res = slash_with_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_163[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_165[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slash_with_default")); - } - _res = NULL; -@@ -33853,9 +34002,9 @@ - return _res; - } - --// _loop0_164: param_maybe_default -+// _loop0_166: param_maybe_default - static asdl_seq * --_loop0_164_rule(Parser *p) -+_loop0_166_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33881,7 +34030,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_164[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_166[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); - NameDefaultPair* param_maybe_default_var; - while ( - (param_maybe_default_var = param_maybe_default_rule(p)) // param_maybe_default -@@ -33904,7 +34053,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_164[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_166[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -33921,9 +34070,9 @@ - return _seq; - } - --// _tmp_165: slash_no_default | slash_with_default -+// _tmp_167: slash_no_default | slash_with_default - static void * --_tmp_165_rule(Parser *p) -+_tmp_167_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -33940,18 +34089,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_165[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_167[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_no_default")); - asdl_arg_seq* slash_no_default_var; - if ( - (slash_no_default_var = slash_no_default_rule(p)) // slash_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_165[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_167[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_no_default")); - _res = slash_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_165[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_167[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slash_no_default")); - } - { // slash_with_default -@@ -33959,18 +34108,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_165[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_with_default")); -+ D(fprintf(stderr, "%*c> _tmp_167[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_with_default")); - SlashWithDefault* slash_with_default_var; - if ( - (slash_with_default_var = slash_with_default_rule(p)) // slash_with_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_165[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_with_default")); -+ D(fprintf(stderr, "%*c+ _tmp_167[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_with_default")); - _res = slash_with_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_165[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_167[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slash_with_default")); - } - _res = NULL; -@@ -33979,9 +34128,9 @@ - return _res; - } - --// _loop0_166: param_maybe_default -+// _loop0_168: param_maybe_default - static asdl_seq * --_loop0_166_rule(Parser *p) -+_loop0_168_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34007,7 +34156,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_166[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_168[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); - NameDefaultPair* param_maybe_default_var; - while ( - (param_maybe_default_var = param_maybe_default_rule(p)) // param_maybe_default -@@ -34030,7 +34179,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_166[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_168[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -34047,9 +34196,9 @@ - return _seq; - } - --// _tmp_167: ',' | param_no_default -+// _tmp_169: ',' | param_no_default - static void * --_tmp_167_rule(Parser *p) -+_tmp_169_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34066,18 +34215,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_167[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_169[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_167[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_169[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_167[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_169[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - { // param_no_default -@@ -34085,18 +34234,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_167[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_169[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); - arg_ty param_no_default_var; - if ( - (param_no_default_var = param_no_default_rule(p)) // param_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_167[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_169[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default")); - _res = param_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_167[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_169[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default")); - } - _res = NULL; -@@ -34105,9 +34254,9 @@ - return _res; - } - --// _loop0_168: param_maybe_default -+// _loop0_170: param_maybe_default - static asdl_seq * --_loop0_168_rule(Parser *p) -+_loop0_170_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34133,7 +34282,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_168[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_170[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); - NameDefaultPair* param_maybe_default_var; - while ( - (param_maybe_default_var = param_maybe_default_rule(p)) // param_maybe_default -@@ -34156,7 +34305,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_168[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_170[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -34173,9 +34322,9 @@ - return _seq; - } - --// _loop1_169: param_maybe_default -+// _loop1_171: param_maybe_default - static asdl_seq * --_loop1_169_rule(Parser *p) -+_loop1_171_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34201,7 +34350,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop1_169[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop1_171[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); - NameDefaultPair* param_maybe_default_var; - while ( - (param_maybe_default_var = param_maybe_default_rule(p)) // param_maybe_default -@@ -34224,7 +34373,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop1_169[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop1_171[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_maybe_default")); - } - if (_n == 0 || p->error_indicator) { -@@ -34246,9 +34395,9 @@ - return _seq; - } - --// _tmp_170: ')' | ',' -+// _tmp_172: ')' | ',' - static void * --_tmp_170_rule(Parser *p) -+_tmp_172_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34265,18 +34414,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_170[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 8)) // token=')' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_170[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_170[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'")); - } - { // ',' -@@ -34284,18 +34433,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_170[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_170[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_170[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - _res = NULL; -@@ -34304,9 +34453,9 @@ - return _res; - } - --// _tmp_171: ')' | ',' (')' | '**') -+// _tmp_173: ')' | ',' (')' | '**') - static void * --_tmp_171_rule(Parser *p) -+_tmp_173_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34323,18 +34472,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_171[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c> _tmp_173[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 8)) // token=')' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_171[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c+ _tmp_173[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_171[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_173[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'")); - } - { // ',' (')' | '**') -@@ -34342,21 +34491,21 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_171[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')")); -+ D(fprintf(stderr, "%*c> _tmp_173[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')")); - Token * _literal; -- void *_tmp_240_var; -+ void *_tmp_243_var; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (_tmp_240_var = _tmp_240_rule(p)) // ')' | '**' -+ (_tmp_243_var = _tmp_243_rule(p)) // ')' | '**' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_171[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')")); -- _res = _PyPegen_dummy_name(p, _literal, _tmp_240_var); -+ D(fprintf(stderr, "%*c+ _tmp_173[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')")); -+ _res = _PyPegen_dummy_name(p, _literal, _tmp_243_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_171[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_173[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (')' | '**')")); - } - _res = NULL; -@@ -34365,9 +34514,9 @@ - return _res; - } - --// _tmp_172: param_no_default | ',' -+// _tmp_174: param_no_default | ',' - static void * --_tmp_172_rule(Parser *p) -+_tmp_174_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34384,18 +34533,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_174[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); - arg_ty param_no_default_var; - if ( - (param_no_default_var = param_no_default_rule(p)) // param_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_174[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default")); - _res = param_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_174[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default")); - } - { // ',' -@@ -34403,18 +34552,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_174[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_174[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_174[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - _res = NULL; -@@ -34423,9 +34572,9 @@ - return _res; - } - --// _loop0_173: param_maybe_default -+// _loop0_175: param_maybe_default - static asdl_seq * --_loop0_173_rule(Parser *p) -+_loop0_175_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34451,7 +34600,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_173[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_175[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default")); - NameDefaultPair* param_maybe_default_var; - while ( - (param_maybe_default_var = param_maybe_default_rule(p)) // param_maybe_default -@@ -34474,7 +34623,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_173[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_175[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -34491,9 +34640,9 @@ - return _seq; - } - --// _tmp_174: param_no_default | ',' -+// _tmp_176: param_no_default | ',' - static void * --_tmp_174_rule(Parser *p) -+_tmp_176_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34510,18 +34659,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_174[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_176[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default")); - arg_ty param_no_default_var; - if ( - (param_no_default_var = param_no_default_rule(p)) // param_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_174[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_176[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default")); - _res = param_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_174[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_176[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default")); - } - { // ',' -@@ -34529,18 +34678,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_174[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_176[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_174[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_176[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_174[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_176[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - _res = NULL; -@@ -34549,9 +34698,9 @@ - return _res; - } - --// _tmp_175: '*' | '**' | '/' -+// _tmp_177: '*' | '**' | '/' - static void * --_tmp_175_rule(Parser *p) -+_tmp_177_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34568,18 +34717,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_175[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'")); -+ D(fprintf(stderr, "%*c> _tmp_177[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_175[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'")); -+ D(fprintf(stderr, "%*c+ _tmp_177[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_175[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_177[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'*'")); - } - { // '**' -@@ -34587,18 +34736,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_175[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c> _tmp_177[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 35)) // token='**' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_175[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c+ _tmp_177[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_175[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_177[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'**'")); - } - { // '/' -@@ -34606,18 +34755,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_175[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'")); -+ D(fprintf(stderr, "%*c> _tmp_177[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 17)) // token='/' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_175[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'")); -+ D(fprintf(stderr, "%*c+ _tmp_177[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_175[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_177[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'/'")); - } - _res = NULL; -@@ -34626,9 +34775,9 @@ - return _res; - } - --// _loop1_176: param_with_default -+// _loop1_178: param_with_default - static asdl_seq * --_loop1_176_rule(Parser *p) -+_loop1_178_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34654,7 +34803,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop1_176[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default")); -+ D(fprintf(stderr, "%*c> _loop1_178[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default")); - NameDefaultPair* param_with_default_var; - while ( - (param_with_default_var = param_with_default_rule(p)) // param_with_default -@@ -34677,7 +34826,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop1_176[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop1_178[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_with_default")); - } - if (_n == 0 || p->error_indicator) { -@@ -34699,9 +34848,9 @@ - return _seq; - } - --// _loop0_177: lambda_param_no_default -+// _loop0_179: lambda_param_no_default - static asdl_seq * --_loop0_177_rule(Parser *p) -+_loop0_179_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34727,7 +34876,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_177[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c> _loop0_179[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - arg_ty lambda_param_no_default_var; - while ( - (lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default -@@ -34750,7 +34899,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_177[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_179[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -34767,9 +34916,9 @@ - return _seq; - } - --// _loop0_178: lambda_param_no_default -+// _loop0_180: lambda_param_no_default - static asdl_seq * --_loop0_178_rule(Parser *p) -+_loop0_180_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34795,7 +34944,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_178[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c> _loop0_180[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - arg_ty lambda_param_no_default_var; - while ( - (lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default -@@ -34818,7 +34967,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_178[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_180[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -34835,9 +34984,9 @@ - return _seq; - } - --// _loop0_180: ',' lambda_param -+// _loop0_182: ',' lambda_param - static asdl_seq * --_loop0_180_rule(Parser *p) -+_loop0_182_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34863,7 +35012,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_180[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' lambda_param")); -+ D(fprintf(stderr, "%*c> _loop0_182[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' lambda_param")); - Token * _literal; - arg_ty elem; - while ( -@@ -34895,7 +35044,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_180[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_182[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' lambda_param")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -34912,9 +35061,9 @@ - return _seq; - } - --// _gather_179: lambda_param _loop0_180 -+// _gather_181: lambda_param _loop0_182 - static asdl_seq * --_gather_179_rule(Parser *p) -+_gather_181_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34926,27 +35075,27 @@ - } - asdl_seq * _res = NULL; - int _mark = p->mark; -- { // lambda_param _loop0_180 -+ { // lambda_param _loop0_182 - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _gather_179[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_180")); -+ D(fprintf(stderr, "%*c> _gather_181[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_182")); - arg_ty elem; - asdl_seq * seq; - if ( - (elem = lambda_param_rule(p)) // lambda_param - && -- (seq = _loop0_180_rule(p)) // _loop0_180 -+ (seq = _loop0_182_rule(p)) // _loop0_182 - ) - { -- D(fprintf(stderr, "%*c+ _gather_179[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_180")); -+ D(fprintf(stderr, "%*c+ _gather_181[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_182")); - _res = _PyPegen_seq_insert_in_front(p, elem, seq); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _gather_179[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param _loop0_180")); -+ D(fprintf(stderr, "%*c%s _gather_181[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param _loop0_182")); - } - _res = NULL; - done: -@@ -34954,9 +35103,9 @@ - return _res; - } - --// _tmp_181: lambda_slash_no_default | lambda_slash_with_default -+// _tmp_183: lambda_slash_no_default | lambda_slash_with_default - static void * --_tmp_181_rule(Parser *p) -+_tmp_183_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -34973,18 +35122,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_181[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_183[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); - asdl_arg_seq* lambda_slash_no_default_var; - if ( - (lambda_slash_no_default_var = lambda_slash_no_default_rule(p)) // lambda_slash_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_181[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_183[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); - _res = lambda_slash_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_181[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_183[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_slash_no_default")); - } - { // lambda_slash_with_default -@@ -34992,18 +35141,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_181[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); -+ D(fprintf(stderr, "%*c> _tmp_183[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); - SlashWithDefault* lambda_slash_with_default_var; - if ( - (lambda_slash_with_default_var = lambda_slash_with_default_rule(p)) // lambda_slash_with_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_181[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); -+ D(fprintf(stderr, "%*c+ _tmp_183[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); - _res = lambda_slash_with_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_181[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_183[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_slash_with_default")); - } - _res = NULL; -@@ -35012,9 +35161,9 @@ - return _res; - } - --// _loop0_182: lambda_param_maybe_default -+// _loop0_184: lambda_param_maybe_default - static asdl_seq * --_loop0_182_rule(Parser *p) -+_loop0_184_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35040,7 +35189,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_182[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_184[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); - NameDefaultPair* lambda_param_maybe_default_var; - while ( - (lambda_param_maybe_default_var = lambda_param_maybe_default_rule(p)) // lambda_param_maybe_default -@@ -35063,7 +35212,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_182[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_184[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -35080,9 +35229,9 @@ - return _seq; - } - --// _tmp_183: lambda_slash_no_default | lambda_slash_with_default -+// _tmp_185: lambda_slash_no_default | lambda_slash_with_default - static void * --_tmp_183_rule(Parser *p) -+_tmp_185_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35099,18 +35248,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_183[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_185[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); - asdl_arg_seq* lambda_slash_no_default_var; - if ( - (lambda_slash_no_default_var = lambda_slash_no_default_rule(p)) // lambda_slash_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_183[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_185[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default")); - _res = lambda_slash_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_183[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_185[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_slash_no_default")); - } - { // lambda_slash_with_default -@@ -35118,18 +35267,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_183[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); -+ D(fprintf(stderr, "%*c> _tmp_185[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); - SlashWithDefault* lambda_slash_with_default_var; - if ( - (lambda_slash_with_default_var = lambda_slash_with_default_rule(p)) // lambda_slash_with_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_183[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); -+ D(fprintf(stderr, "%*c+ _tmp_185[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default")); - _res = lambda_slash_with_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_183[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_185[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_slash_with_default")); - } - _res = NULL; -@@ -35138,9 +35287,9 @@ - return _res; - } - --// _loop0_184: lambda_param_maybe_default -+// _loop0_186: lambda_param_maybe_default - static asdl_seq * --_loop0_184_rule(Parser *p) -+_loop0_186_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35166,7 +35315,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_184[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_186[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); - NameDefaultPair* lambda_param_maybe_default_var; - while ( - (lambda_param_maybe_default_var = lambda_param_maybe_default_rule(p)) // lambda_param_maybe_default -@@ -35189,7 +35338,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_184[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_186[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -35206,9 +35355,9 @@ - return _seq; - } - --// _tmp_185: ',' | lambda_param_no_default -+// _tmp_187: ',' | lambda_param_no_default - static void * --_tmp_185_rule(Parser *p) -+_tmp_187_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35225,18 +35374,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_185[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_187[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_185[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_187[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_185[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_187[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - { // lambda_param_no_default -@@ -35244,18 +35393,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_185[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_187[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - arg_ty lambda_param_no_default_var; - if ( - (lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_185[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_187[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - _res = lambda_param_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_185[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_187[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default")); - } - _res = NULL; -@@ -35264,9 +35413,9 @@ - return _res; - } - --// _loop0_186: lambda_param_maybe_default -+// _loop0_188: lambda_param_maybe_default - static asdl_seq * --_loop0_186_rule(Parser *p) -+_loop0_188_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35292,7 +35441,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_186[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_188[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); - NameDefaultPair* lambda_param_maybe_default_var; - while ( - (lambda_param_maybe_default_var = lambda_param_maybe_default_rule(p)) // lambda_param_maybe_default -@@ -35315,7 +35464,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_186[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_188[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -35332,9 +35481,9 @@ - return _seq; - } - --// _loop1_187: lambda_param_maybe_default -+// _loop1_189: lambda_param_maybe_default - static asdl_seq * --_loop1_187_rule(Parser *p) -+_loop1_189_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35360,7 +35509,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop1_187[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop1_189[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); - NameDefaultPair* lambda_param_maybe_default_var; - while ( - (lambda_param_maybe_default_var = lambda_param_maybe_default_rule(p)) // lambda_param_maybe_default -@@ -35383,7 +35532,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop1_187[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop1_189[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_maybe_default")); - } - if (_n == 0 || p->error_indicator) { -@@ -35405,9 +35554,9 @@ - return _seq; - } - --// _loop1_188: lambda_param_with_default -+// _loop1_190: lambda_param_with_default - static asdl_seq * --_loop1_188_rule(Parser *p) -+_loop1_190_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35433,7 +35582,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop1_188[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default")); -+ D(fprintf(stderr, "%*c> _loop1_190[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default")); - NameDefaultPair* lambda_param_with_default_var; - while ( - (lambda_param_with_default_var = lambda_param_with_default_rule(p)) // lambda_param_with_default -@@ -35456,7 +35605,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop1_188[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop1_190[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_with_default")); - } - if (_n == 0 || p->error_indicator) { -@@ -35478,9 +35627,9 @@ - return _seq; - } - --// _tmp_189: ':' | ',' (':' | '**') -+// _tmp_191: ':' | ',' (':' | '**') - static void * --_tmp_189_rule(Parser *p) -+_tmp_191_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35497,18 +35646,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_189[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c> _tmp_191[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_189[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c+ _tmp_191[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_189[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_191[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'")); - } - { // ',' (':' | '**') -@@ -35516,21 +35665,21 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_189[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')")); -+ D(fprintf(stderr, "%*c> _tmp_191[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')")); - Token * _literal; -- void *_tmp_241_var; -+ void *_tmp_244_var; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (_tmp_241_var = _tmp_241_rule(p)) // ':' | '**' -+ (_tmp_244_var = _tmp_244_rule(p)) // ':' | '**' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_189[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')")); -- _res = _PyPegen_dummy_name(p, _literal, _tmp_241_var); -+ D(fprintf(stderr, "%*c+ _tmp_191[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')")); -+ _res = _PyPegen_dummy_name(p, _literal, _tmp_244_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_189[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_191[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (':' | '**')")); - } - _res = NULL; -@@ -35539,9 +35688,9 @@ - return _res; - } - --// _tmp_190: lambda_param_no_default | ',' -+// _tmp_192: lambda_param_no_default | ',' - static void * --_tmp_190_rule(Parser *p) -+_tmp_192_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35558,18 +35707,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_190[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_192[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - arg_ty lambda_param_no_default_var; - if ( - (lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_190[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_192[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - _res = lambda_param_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_190[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_192[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default")); - } - { // ',' -@@ -35577,18 +35726,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_190[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_192[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_190[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_192[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_190[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_192[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - _res = NULL; -@@ -35597,9 +35746,9 @@ - return _res; - } - --// _loop0_191: lambda_param_maybe_default -+// _loop0_193: lambda_param_maybe_default - static asdl_seq * --_loop0_191_rule(Parser *p) -+_loop0_193_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35625,7 +35774,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_191[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); -+ D(fprintf(stderr, "%*c> _loop0_193[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default")); - NameDefaultPair* lambda_param_maybe_default_var; - while ( - (lambda_param_maybe_default_var = lambda_param_maybe_default_rule(p)) // lambda_param_maybe_default -@@ -35648,7 +35797,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_191[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_193[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_maybe_default")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -35665,9 +35814,9 @@ - return _seq; - } - --// _tmp_192: lambda_param_no_default | ',' -+// _tmp_194: lambda_param_no_default | ',' - static void * --_tmp_192_rule(Parser *p) -+_tmp_194_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35684,18 +35833,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_192[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c> _tmp_194[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - arg_ty lambda_param_no_default_var; - if ( - (lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default - ) - { -- D(fprintf(stderr, "%*c+ _tmp_192[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); -+ D(fprintf(stderr, "%*c+ _tmp_194[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default")); - _res = lambda_param_no_default_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_192[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_194[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default")); - } - { // ',' -@@ -35703,18 +35852,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_192[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_194[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_192[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_194[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_192[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_194[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - _res = NULL; -@@ -35723,9 +35872,9 @@ - return _res; - } - --// _tmp_193: '*' | '**' | '/' -+// _tmp_195: '*' | '**' | '/' - static void * --_tmp_193_rule(Parser *p) -+_tmp_195_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35742,18 +35891,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_193[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'")); -+ D(fprintf(stderr, "%*c> _tmp_195[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 16)) // token='*' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_193[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'")); -+ D(fprintf(stderr, "%*c+ _tmp_195[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_193[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_195[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'*'")); - } - { // '**' -@@ -35761,18 +35910,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_193[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c> _tmp_195[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 35)) // token='**' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_193[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c+ _tmp_195[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_193[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_195[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'**'")); - } - { // '/' -@@ -35780,18 +35929,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_193[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'")); -+ D(fprintf(stderr, "%*c> _tmp_195[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 17)) // token='/' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_193[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'")); -+ D(fprintf(stderr, "%*c+ _tmp_195[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_193[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_195[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'/'")); - } - _res = NULL; -@@ -35800,9 +35949,9 @@ - return _res; - } - --// _tmp_194: ',' | ')' | ':' -+// _tmp_196: ',' | ')' | ':' - static void * --_tmp_194_rule(Parser *p) -+_tmp_196_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35819,18 +35968,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_194[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_196[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_194[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_196[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_194[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_196[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - { // ')' -@@ -35838,18 +35987,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_194[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c> _tmp_196[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 8)) // token=')' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_194[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c+ _tmp_196[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_194[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_196[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'")); - } - { // ':' -@@ -35857,18 +36006,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_194[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c> _tmp_196[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_194[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c+ _tmp_196[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_194[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_196[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'")); - } - _res = NULL; -@@ -35877,9 +36026,9 @@ - return _res; - } - --// _loop0_196: ',' (expression ['as' star_target]) -+// _loop0_198: ',' (expression ['as' star_target]) - static asdl_seq * --_loop0_196_rule(Parser *p) -+_loop0_198_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35905,13 +36054,13 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_196[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expression ['as' star_target])")); -+ D(fprintf(stderr, "%*c> _loop0_198[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expression ['as' star_target])")); - Token * _literal; - void *elem; - while ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (elem = _tmp_242_rule(p)) // expression ['as' star_target] -+ (elem = _tmp_245_rule(p)) // expression ['as' star_target] - ) - { - _res = elem; -@@ -35937,7 +36086,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_196[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_198[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (expression ['as' star_target])")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -35954,9 +36103,9 @@ - return _seq; - } - --// _gather_195: (expression ['as' star_target]) _loop0_196 -+// _gather_197: (expression ['as' star_target]) _loop0_198 - static asdl_seq * --_gather_195_rule(Parser *p) -+_gather_197_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -35968,27 +36117,27 @@ - } - asdl_seq * _res = NULL; - int _mark = p->mark; -- { // (expression ['as' star_target]) _loop0_196 -+ { // (expression ['as' star_target]) _loop0_198 - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _gather_195[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_196")); -+ D(fprintf(stderr, "%*c> _gather_197[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_198")); - void *elem; - asdl_seq * seq; - if ( -- (elem = _tmp_242_rule(p)) // expression ['as' star_target] -+ (elem = _tmp_245_rule(p)) // expression ['as' star_target] - && -- (seq = _loop0_196_rule(p)) // _loop0_196 -+ (seq = _loop0_198_rule(p)) // _loop0_198 - ) - { -- D(fprintf(stderr, "%*c+ _gather_195[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_196")); -+ D(fprintf(stderr, "%*c+ _gather_197[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_198")); - _res = _PyPegen_seq_insert_in_front(p, elem, seq); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _gather_195[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expression ['as' star_target]) _loop0_196")); -+ D(fprintf(stderr, "%*c%s _gather_197[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expression ['as' star_target]) _loop0_198")); - } - _res = NULL; - done: -@@ -35996,9 +36145,9 @@ - return _res; - } - --// _loop0_198: ',' (expressions ['as' star_target]) -+// _loop0_200: ',' (expressions ['as' star_target]) - static asdl_seq * --_loop0_198_rule(Parser *p) -+_loop0_200_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36024,13 +36173,13 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_198[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expressions ['as' star_target])")); -+ D(fprintf(stderr, "%*c> _loop0_200[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expressions ['as' star_target])")); - Token * _literal; - void *elem; - while ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (elem = _tmp_243_rule(p)) // expressions ['as' star_target] -+ (elem = _tmp_246_rule(p)) // expressions ['as' star_target] - ) - { - _res = elem; -@@ -36056,7 +36205,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_198[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_200[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (expressions ['as' star_target])")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -36073,9 +36222,9 @@ - return _seq; - } - --// _gather_197: (expressions ['as' star_target]) _loop0_198 -+// _gather_199: (expressions ['as' star_target]) _loop0_200 - static asdl_seq * --_gather_197_rule(Parser *p) -+_gather_199_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36087,27 +36236,27 @@ - } - asdl_seq * _res = NULL; - int _mark = p->mark; -- { // (expressions ['as' star_target]) _loop0_198 -+ { // (expressions ['as' star_target]) _loop0_200 - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _gather_197[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_198")); -+ D(fprintf(stderr, "%*c> _gather_199[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_200")); - void *elem; - asdl_seq * seq; - if ( -- (elem = _tmp_243_rule(p)) // expressions ['as' star_target] -+ (elem = _tmp_246_rule(p)) // expressions ['as' star_target] - && -- (seq = _loop0_198_rule(p)) // _loop0_198 -+ (seq = _loop0_200_rule(p)) // _loop0_200 - ) - { -- D(fprintf(stderr, "%*c+ _gather_197[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_198")); -+ D(fprintf(stderr, "%*c+ _gather_199[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_200")); - _res = _PyPegen_seq_insert_in_front(p, elem, seq); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _gather_197[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expressions ['as' star_target]) _loop0_198")); -+ D(fprintf(stderr, "%*c%s _gather_199[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expressions ['as' star_target]) _loop0_200")); - } - _res = NULL; - done: -@@ -36115,9 +36264,9 @@ - return _res; - } - --// _loop0_200: ',' (expression ['as' star_target]) -+// _loop0_202: ',' (expression ['as' star_target]) - static asdl_seq * --_loop0_200_rule(Parser *p) -+_loop0_202_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36143,13 +36292,13 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_200[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expression ['as' star_target])")); -+ D(fprintf(stderr, "%*c> _loop0_202[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expression ['as' star_target])")); - Token * _literal; - void *elem; - while ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (elem = _tmp_244_rule(p)) // expression ['as' star_target] -+ (elem = _tmp_247_rule(p)) // expression ['as' star_target] - ) - { - _res = elem; -@@ -36175,7 +36324,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_200[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_202[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (expression ['as' star_target])")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -36192,9 +36341,9 @@ - return _seq; - } - --// _gather_199: (expression ['as' star_target]) _loop0_200 -+// _gather_201: (expression ['as' star_target]) _loop0_202 - static asdl_seq * --_gather_199_rule(Parser *p) -+_gather_201_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36206,27 +36355,27 @@ - } - asdl_seq * _res = NULL; - int _mark = p->mark; -- { // (expression ['as' star_target]) _loop0_200 -+ { // (expression ['as' star_target]) _loop0_202 - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _gather_199[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_200")); -+ D(fprintf(stderr, "%*c> _gather_201[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_202")); - void *elem; - asdl_seq * seq; - if ( -- (elem = _tmp_244_rule(p)) // expression ['as' star_target] -+ (elem = _tmp_247_rule(p)) // expression ['as' star_target] - && -- (seq = _loop0_200_rule(p)) // _loop0_200 -+ (seq = _loop0_202_rule(p)) // _loop0_202 - ) - { -- D(fprintf(stderr, "%*c+ _gather_199[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_200")); -+ D(fprintf(stderr, "%*c+ _gather_201[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_202")); - _res = _PyPegen_seq_insert_in_front(p, elem, seq); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _gather_199[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expression ['as' star_target]) _loop0_200")); -+ D(fprintf(stderr, "%*c%s _gather_201[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expression ['as' star_target]) _loop0_202")); - } - _res = NULL; - done: -@@ -36234,9 +36383,9 @@ - return _res; - } - --// _loop0_202: ',' (expressions ['as' star_target]) -+// _loop0_204: ',' (expressions ['as' star_target]) - static asdl_seq * --_loop0_202_rule(Parser *p) -+_loop0_204_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36262,13 +36411,13 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_202[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expressions ['as' star_target])")); -+ D(fprintf(stderr, "%*c> _loop0_204[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expressions ['as' star_target])")); - Token * _literal; - void *elem; - while ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (elem = _tmp_245_rule(p)) // expressions ['as' star_target] -+ (elem = _tmp_248_rule(p)) // expressions ['as' star_target] - ) - { - _res = elem; -@@ -36294,7 +36443,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_202[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_204[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (expressions ['as' star_target])")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -36311,9 +36460,9 @@ - return _seq; - } - --// _gather_201: (expressions ['as' star_target]) _loop0_202 -+// _gather_203: (expressions ['as' star_target]) _loop0_204 - static asdl_seq * --_gather_201_rule(Parser *p) -+_gather_203_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36325,27 +36474,27 @@ - } - asdl_seq * _res = NULL; - int _mark = p->mark; -- { // (expressions ['as' star_target]) _loop0_202 -+ { // (expressions ['as' star_target]) _loop0_204 - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _gather_201[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_202")); -+ D(fprintf(stderr, "%*c> _gather_203[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_204")); - void *elem; - asdl_seq * seq; - if ( -- (elem = _tmp_245_rule(p)) // expressions ['as' star_target] -+ (elem = _tmp_248_rule(p)) // expressions ['as' star_target] - && -- (seq = _loop0_202_rule(p)) // _loop0_202 -+ (seq = _loop0_204_rule(p)) // _loop0_204 - ) - { -- D(fprintf(stderr, "%*c+ _gather_201[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_202")); -+ D(fprintf(stderr, "%*c+ _gather_203[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_204")); - _res = _PyPegen_seq_insert_in_front(p, elem, seq); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _gather_201[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expressions ['as' star_target]) _loop0_202")); -+ D(fprintf(stderr, "%*c%s _gather_203[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expressions ['as' star_target]) _loop0_204")); - } - _res = NULL; - done: -@@ -36353,9 +36502,9 @@ - return _res; - } - --// _tmp_203: 'except' | 'finally' -+// _tmp_205: 'except' | 'finally' - static void * --_tmp_203_rule(Parser *p) -+_tmp_205_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36372,18 +36521,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_203[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except'")); -+ D(fprintf(stderr, "%*c> _tmp_205[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 634)) // token='except' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_203[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except'")); -+ D(fprintf(stderr, "%*c+ _tmp_205[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_203[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_205[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except'")); - } - { // 'finally' -@@ -36391,18 +36540,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_203[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'finally'")); -+ D(fprintf(stderr, "%*c> _tmp_205[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'finally'")); - Token * _keyword; - if ( - (_keyword = _PyPegen_expect_token(p, 630)) // token='finally' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_203[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'finally'")); -+ D(fprintf(stderr, "%*c+ _tmp_205[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'finally'")); - _res = _keyword; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_203[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_205[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'finally'")); - } - _res = NULL; -@@ -36411,9 +36560,9 @@ - return _res; - } - --// _loop0_204: block -+// _loop0_206: block - static asdl_seq * --_loop0_204_rule(Parser *p) -+_loop0_206_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36439,7 +36588,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_204[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "block")); -+ D(fprintf(stderr, "%*c> _loop0_206[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "block")); - asdl_stmt_seq* block_var; - while ( - (block_var = block_rule(p)) // block -@@ -36462,7 +36611,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_204[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_206[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "block")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -36479,9 +36628,9 @@ - return _seq; - } - --// _loop1_205: except_block -+// _loop1_207: except_block - static asdl_seq * --_loop1_205_rule(Parser *p) -+_loop1_207_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36507,7 +36656,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop1_205[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_block")); -+ D(fprintf(stderr, "%*c> _loop1_207[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_block")); - excepthandler_ty except_block_var; - while ( - (except_block_var = except_block_rule(p)) // except_block -@@ -36530,7 +36679,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop1_205[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop1_207[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "except_block")); - } - if (_n == 0 || p->error_indicator) { -@@ -36552,9 +36701,9 @@ - return _seq; - } - --// _tmp_206: 'as' NAME -+// _tmp_208: 'as' NAME - static void * --_tmp_206_rule(Parser *p) -+_tmp_208_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36571,7 +36720,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_206[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c> _tmp_208[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - Token * _keyword; - expr_ty name_var; - if ( -@@ -36580,12 +36729,12 @@ - (name_var = _PyPegen_name_token(p)) // NAME - ) - { -- D(fprintf(stderr, "%*c+ _tmp_206[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c+ _tmp_208[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - _res = _PyPegen_dummy_name(p, _keyword, name_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_206[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_208[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' NAME")); - } - _res = NULL; -@@ -36594,9 +36743,9 @@ - return _res; - } - --// _loop0_207: block -+// _loop0_209: block - static asdl_seq * --_loop0_207_rule(Parser *p) -+_loop0_209_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36622,7 +36771,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_207[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "block")); -+ D(fprintf(stderr, "%*c> _loop0_209[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "block")); - asdl_stmt_seq* block_var; - while ( - (block_var = block_rule(p)) // block -@@ -36645,7 +36794,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_207[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_209[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "block")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -36662,9 +36811,9 @@ - return _seq; - } - --// _loop1_208: except_star_block -+// _loop1_210: except_star_block - static asdl_seq * --_loop1_208_rule(Parser *p) -+_loop1_210_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36690,7 +36839,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop1_208[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_star_block")); -+ D(fprintf(stderr, "%*c> _loop1_210[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_star_block")); - excepthandler_ty except_star_block_var; - while ( - (except_star_block_var = except_star_block_rule(p)) // except_star_block -@@ -36713,7 +36862,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop1_208[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop1_210[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "except_star_block")); - } - if (_n == 0 || p->error_indicator) { -@@ -36735,9 +36884,9 @@ - return _seq; - } - --// _tmp_209: expression ['as' NAME] -+// _tmp_211: expression ['as' NAME] - static void * --_tmp_209_rule(Parser *p) -+_tmp_211_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36754,22 +36903,22 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_209[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]")); -+ D(fprintf(stderr, "%*c> _tmp_211[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]")); - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - expr_ty expression_var; - if ( - (expression_var = expression_rule(p)) // expression - && -- (_opt_var = _tmp_246_rule(p), !p->error_indicator) // ['as' NAME] -+ (_opt_var = _tmp_249_rule(p), !p->error_indicator) // ['as' NAME] - ) - { -- D(fprintf(stderr, "%*c+ _tmp_209[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]")); -+ D(fprintf(stderr, "%*c+ _tmp_211[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]")); - _res = _PyPegen_dummy_name(p, expression_var, _opt_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_209[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_211[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression ['as' NAME]")); - } - _res = NULL; -@@ -36778,9 +36927,9 @@ - return _res; - } - --// _tmp_210: 'as' NAME -+// _tmp_212: 'as' NAME - static void * --_tmp_210_rule(Parser *p) -+_tmp_212_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36797,7 +36946,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_210[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c> _tmp_212[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - Token * _keyword; - expr_ty name_var; - if ( -@@ -36806,12 +36955,12 @@ - (name_var = _PyPegen_name_token(p)) // NAME - ) - { -- D(fprintf(stderr, "%*c+ _tmp_210[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c+ _tmp_212[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - _res = _PyPegen_dummy_name(p, _keyword, name_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_210[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_212[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' NAME")); - } - _res = NULL; -@@ -36820,9 +36969,9 @@ - return _res; - } - --// _tmp_211: 'as' NAME -+// _tmp_213: 'as' NAME - static void * --_tmp_211_rule(Parser *p) -+_tmp_213_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36839,7 +36988,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_211[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c> _tmp_213[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - Token * _keyword; - expr_ty name_var; - if ( -@@ -36848,12 +36997,12 @@ - (name_var = _PyPegen_name_token(p)) // NAME - ) - { -- D(fprintf(stderr, "%*c+ _tmp_211[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c+ _tmp_213[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - _res = _PyPegen_dummy_name(p, _keyword, name_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_211[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_213[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' NAME")); - } - _res = NULL; -@@ -36862,9 +37011,9 @@ - return _res; - } - --// _tmp_212: NEWLINE | ':' -+// _tmp_214: NEWLINE | ':' - static void * --_tmp_212_rule(Parser *p) -+_tmp_214_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36881,18 +37030,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_212[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE")); -+ D(fprintf(stderr, "%*c> _tmp_214[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE")); - Token * newline_var; - if ( - (newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_212[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE")); -+ D(fprintf(stderr, "%*c+ _tmp_214[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE")); - _res = newline_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_212[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_214[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NEWLINE")); - } - { // ':' -@@ -36900,18 +37049,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_212[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c> _tmp_214[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_212[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c+ _tmp_214[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_212[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_214[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'")); - } - _res = NULL; -@@ -36920,9 +37069,9 @@ - return _res; - } - --// _tmp_213: 'as' NAME -+// _tmp_215: 'as' NAME - static void * --_tmp_213_rule(Parser *p) -+_tmp_215_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36939,7 +37088,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_213[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c> _tmp_215[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - Token * _keyword; - expr_ty name_var; - if ( -@@ -36948,12 +37097,12 @@ - (name_var = _PyPegen_name_token(p)) // NAME - ) - { -- D(fprintf(stderr, "%*c+ _tmp_213[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c+ _tmp_215[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - _res = _PyPegen_dummy_name(p, _keyword, name_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_213[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_215[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' NAME")); - } - _res = NULL; -@@ -36962,9 +37111,9 @@ - return _res; - } - --// _tmp_214: 'as' NAME -+// _tmp_216: 'as' NAME - static void * --_tmp_214_rule(Parser *p) -+_tmp_216_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -36981,7 +37130,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_214[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c> _tmp_216[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - Token * _keyword; - expr_ty name_var; - if ( -@@ -36990,12 +37139,12 @@ - (name_var = _PyPegen_name_token(p)) // NAME - ) - { -- D(fprintf(stderr, "%*c+ _tmp_214[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c+ _tmp_216[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - _res = _PyPegen_dummy_name(p, _keyword, name_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_214[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_216[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' NAME")); - } - _res = NULL; -@@ -37004,9 +37153,9 @@ - return _res; - } - --// _tmp_215: positional_patterns ',' -+// _tmp_217: positional_patterns ',' - static void * --_tmp_215_rule(Parser *p) -+_tmp_217_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37023,7 +37172,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_215[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "positional_patterns ','")); -+ D(fprintf(stderr, "%*c> _tmp_217[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "positional_patterns ','")); - Token * _literal; - asdl_pattern_seq* positional_patterns_var; - if ( -@@ -37032,12 +37181,12 @@ - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_215[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "positional_patterns ','")); -+ D(fprintf(stderr, "%*c+ _tmp_217[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "positional_patterns ','")); - _res = _PyPegen_dummy_name(p, positional_patterns_var, _literal); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_215[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_217[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "positional_patterns ','")); - } - _res = NULL; -@@ -37046,9 +37195,9 @@ - return _res; - } - --// _tmp_216: '->' expression -+// _tmp_218: '->' expression - static void * --_tmp_216_rule(Parser *p) -+_tmp_218_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37065,7 +37214,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_216[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'->' expression")); -+ D(fprintf(stderr, "%*c> _tmp_218[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'->' expression")); - Token * _literal; - expr_ty expression_var; - if ( -@@ -37074,12 +37223,12 @@ - (expression_var = expression_rule(p)) // expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_216[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'->' expression")); -+ D(fprintf(stderr, "%*c+ _tmp_218[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'->' expression")); - _res = _PyPegen_dummy_name(p, _literal, expression_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_216[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_218[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'->' expression")); - } - _res = NULL; -@@ -37088,9 +37237,9 @@ - return _res; - } - --// _tmp_217: '(' arguments? ')' -+// _tmp_219: '(' arguments? ')' - static void * --_tmp_217_rule(Parser *p) -+_tmp_219_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37107,7 +37256,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_217[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); -+ D(fprintf(stderr, "%*c> _tmp_219[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); - Token * _literal; - Token * _literal_1; - void *_opt_var; -@@ -37120,12 +37269,12 @@ - (_literal_1 = _PyPegen_expect_token(p, 8)) // token=')' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_217[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); -+ D(fprintf(stderr, "%*c+ _tmp_219[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); - _res = _PyPegen_dummy_name(p, _literal, _opt_var, _literal_1); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_217[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_219[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'(' arguments? ')'")); - } - _res = NULL; -@@ -37134,9 +37283,9 @@ - return _res; - } - --// _tmp_218: '(' arguments? ')' -+// _tmp_220: '(' arguments? ')' - static void * --_tmp_218_rule(Parser *p) -+_tmp_220_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37153,7 +37302,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_218[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); -+ D(fprintf(stderr, "%*c> _tmp_220[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); - Token * _literal; - Token * _literal_1; - void *_opt_var; -@@ -37166,12 +37315,12 @@ - (_literal_1 = _PyPegen_expect_token(p, 8)) // token=')' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_218[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); -+ D(fprintf(stderr, "%*c+ _tmp_220[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'")); - _res = _PyPegen_dummy_name(p, _literal, _opt_var, _literal_1); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_218[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_220[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'(' arguments? ')'")); - } - _res = NULL; -@@ -37180,9 +37329,9 @@ - return _res; - } - --// _loop0_220: ',' double_starred_kvpair -+// _loop0_222: ',' double_starred_kvpair - static asdl_seq * --_loop0_220_rule(Parser *p) -+_loop0_222_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37208,7 +37357,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_220[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' double_starred_kvpair")); -+ D(fprintf(stderr, "%*c> _loop0_222[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' double_starred_kvpair")); - Token * _literal; - KeyValuePair* elem; - while ( -@@ -37240,7 +37389,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_220[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_222[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' double_starred_kvpair")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -37257,9 +37406,9 @@ - return _seq; - } - --// _gather_219: double_starred_kvpair _loop0_220 -+// _gather_221: double_starred_kvpair _loop0_222 - static asdl_seq * --_gather_219_rule(Parser *p) -+_gather_221_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37271,27 +37420,27 @@ - } - asdl_seq * _res = NULL; - int _mark = p->mark; -- { // double_starred_kvpair _loop0_220 -+ { // double_starred_kvpair _loop0_222 - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _gather_219[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_220")); -+ D(fprintf(stderr, "%*c> _gather_221[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_222")); - KeyValuePair* elem; - asdl_seq * seq; - if ( - (elem = double_starred_kvpair_rule(p)) // double_starred_kvpair - && -- (seq = _loop0_220_rule(p)) // _loop0_220 -+ (seq = _loop0_222_rule(p)) // _loop0_222 - ) - { -- D(fprintf(stderr, "%*c+ _gather_219[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_220")); -+ D(fprintf(stderr, "%*c+ _gather_221[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_222")); - _res = _PyPegen_seq_insert_in_front(p, elem, seq); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _gather_219[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "double_starred_kvpair _loop0_220")); -+ D(fprintf(stderr, "%*c%s _gather_221[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "double_starred_kvpair _loop0_222")); - } - _res = NULL; - done: -@@ -37299,9 +37448,9 @@ - return _res; - } - --// _tmp_221: '}' | ',' -+// _tmp_223: '}' | ',' - static void * --_tmp_221_rule(Parser *p) -+_tmp_223_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37318,18 +37467,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_221[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'")); -+ D(fprintf(stderr, "%*c> _tmp_223[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 26)) // token='}' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_221[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'")); -+ D(fprintf(stderr, "%*c+ _tmp_223[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_221[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_223[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'}'")); - } - { // ',' -@@ -37337,18 +37486,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_221[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_223[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_221[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_223[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_221[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_223[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - _res = NULL; -@@ -37357,9 +37506,9 @@ - return _res; - } - --// _tmp_222: '}' | ',' -+// _tmp_224: '}' | ',' - static void * --_tmp_222_rule(Parser *p) -+_tmp_224_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37376,18 +37525,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_222[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'")); -+ D(fprintf(stderr, "%*c> _tmp_224[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 26)) // token='}' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_222[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'")); -+ D(fprintf(stderr, "%*c+ _tmp_224[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_222[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_224[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'}'")); - } - { // ',' -@@ -37395,18 +37544,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_222[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c> _tmp_224[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_222[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); -+ D(fprintf(stderr, "%*c+ _tmp_224[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_222[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_224[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','")); - } - _res = NULL; -@@ -37415,9 +37564,9 @@ - return _res; - } - --// _tmp_223: star_targets '=' -+// _tmp_225: star_targets '=' - static void * --_tmp_223_rule(Parser *p) -+_tmp_225_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37434,7 +37583,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_223[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='")); -+ D(fprintf(stderr, "%*c> _tmp_225[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='")); - Token * _literal; - expr_ty z; - if ( -@@ -37443,7 +37592,7 @@ - (_literal = _PyPegen_expect_token(p, 22)) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_223[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='")); -+ D(fprintf(stderr, "%*c+ _tmp_225[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='")); - _res = z; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37453,7 +37602,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_223[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_225[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_targets '='")); - } - _res = NULL; -@@ -37462,9 +37611,9 @@ - return _res; - } - --// _tmp_224: '.' | '...' -+// _tmp_226: '.' | '...' - static void * --_tmp_224_rule(Parser *p) -+_tmp_226_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37481,18 +37630,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_224[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'")); -+ D(fprintf(stderr, "%*c> _tmp_226[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 23)) // token='.' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_224[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'")); -+ D(fprintf(stderr, "%*c+ _tmp_226[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_224[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_226[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'.'")); - } - { // '...' -@@ -37500,18 +37649,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_224[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'...'")); -+ D(fprintf(stderr, "%*c> _tmp_226[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'...'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 52)) // token='...' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_224[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'...'")); -+ D(fprintf(stderr, "%*c+ _tmp_226[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'...'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_224[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_226[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'...'")); - } - _res = NULL; -@@ -37520,9 +37669,9 @@ - return _res; - } - --// _tmp_225: '.' | '...' -+// _tmp_227: '.' | '...' - static void * --_tmp_225_rule(Parser *p) -+_tmp_227_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37539,18 +37688,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_225[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'")); -+ D(fprintf(stderr, "%*c> _tmp_227[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 23)) // token='.' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_225[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'")); -+ D(fprintf(stderr, "%*c+ _tmp_227[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_225[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_227[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'.'")); - } - { // '...' -@@ -37558,18 +37707,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_225[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'...'")); -+ D(fprintf(stderr, "%*c> _tmp_227[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'...'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 52)) // token='...' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_225[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'...'")); -+ D(fprintf(stderr, "%*c+ _tmp_227[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'...'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_225[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_227[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'...'")); - } - _res = NULL; -@@ -37578,9 +37727,9 @@ - return _res; - } - --// _tmp_226: '@' named_expression NEWLINE -+// _tmp_228: '@' named_expression NEWLINE - static void * --_tmp_226_rule(Parser *p) -+_tmp_228_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37597,7 +37746,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_226[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE")); -+ D(fprintf(stderr, "%*c> _tmp_228[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE")); - Token * _literal; - expr_ty f; - Token * newline_var; -@@ -37609,7 +37758,7 @@ - (newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_226[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE")); -+ D(fprintf(stderr, "%*c+ _tmp_228[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE")); - _res = f; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37619,7 +37768,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_226[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_228[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'@' named_expression NEWLINE")); - } - _res = NULL; -@@ -37628,9 +37777,9 @@ - return _res; - } - --// _tmp_227: ',' expression -+// _tmp_229: ',' expression - static void * --_tmp_227_rule(Parser *p) -+_tmp_229_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37647,7 +37796,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_227[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' expression")); -+ D(fprintf(stderr, "%*c> _tmp_229[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' expression")); - Token * _literal; - expr_ty c; - if ( -@@ -37656,7 +37805,7 @@ - (c = expression_rule(p)) // expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_227[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' expression")); -+ D(fprintf(stderr, "%*c+ _tmp_229[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' expression")); - _res = c; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37666,7 +37815,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_227[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_229[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' expression")); - } - _res = NULL; -@@ -37675,9 +37824,9 @@ - return _res; - } - --// _tmp_228: ',' star_expression -+// _tmp_230: ',' star_expression - static void * --_tmp_228_rule(Parser *p) -+_tmp_230_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37694,7 +37843,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_228[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_expression")); -+ D(fprintf(stderr, "%*c> _tmp_230[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_expression")); - Token * _literal; - expr_ty c; - if ( -@@ -37703,7 +37852,7 @@ - (c = star_expression_rule(p)) // star_expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_228[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_expression")); -+ D(fprintf(stderr, "%*c+ _tmp_230[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_expression")); - _res = c; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37713,7 +37862,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_228[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_230[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' star_expression")); - } - _res = NULL; -@@ -37722,9 +37871,9 @@ - return _res; - } - --// _tmp_229: 'or' conjunction -+// _tmp_231: 'or' conjunction - static void * --_tmp_229_rule(Parser *p) -+_tmp_231_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37741,7 +37890,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_229[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'or' conjunction")); -+ D(fprintf(stderr, "%*c> _tmp_231[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'or' conjunction")); - Token * _keyword; - expr_ty c; - if ( -@@ -37750,7 +37899,7 @@ - (c = conjunction_rule(p)) // conjunction - ) - { -- D(fprintf(stderr, "%*c+ _tmp_229[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'or' conjunction")); -+ D(fprintf(stderr, "%*c+ _tmp_231[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'or' conjunction")); - _res = c; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37760,7 +37909,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_229[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_231[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'or' conjunction")); - } - _res = NULL; -@@ -37769,9 +37918,9 @@ - return _res; - } - --// _tmp_230: 'and' inversion -+// _tmp_232: 'and' inversion - static void * --_tmp_230_rule(Parser *p) -+_tmp_232_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37788,7 +37937,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_230[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'and' inversion")); -+ D(fprintf(stderr, "%*c> _tmp_232[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'and' inversion")); - Token * _keyword; - expr_ty c; - if ( -@@ -37797,7 +37946,7 @@ - (c = inversion_rule(p)) // inversion - ) - { -- D(fprintf(stderr, "%*c+ _tmp_230[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'and' inversion")); -+ D(fprintf(stderr, "%*c+ _tmp_232[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'and' inversion")); - _res = c; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37807,7 +37956,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_230[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_232[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'and' inversion")); - } - _res = NULL; -@@ -37816,9 +37965,9 @@ - return _res; - } - --// _tmp_231: slice | starred_expression -+// _tmp_233: slice | starred_expression - static void * --_tmp_231_rule(Parser *p) -+_tmp_233_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37835,18 +37984,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_231[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slice")); -+ D(fprintf(stderr, "%*c> _tmp_233[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slice")); - expr_ty slice_var; - if ( - (slice_var = slice_rule(p)) // slice - ) - { -- D(fprintf(stderr, "%*c+ _tmp_231[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slice")); -+ D(fprintf(stderr, "%*c+ _tmp_233[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slice")); - _res = slice_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_231[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_233[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slice")); - } - { // starred_expression -@@ -37854,18 +38003,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_231[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression")); -+ D(fprintf(stderr, "%*c> _tmp_233[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression")); - expr_ty starred_expression_var; - if ( - (starred_expression_var = starred_expression_rule(p)) // starred_expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_231[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression")); -+ D(fprintf(stderr, "%*c+ _tmp_233[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression")); - _res = starred_expression_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_231[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_233[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "starred_expression")); - } - _res = NULL; -@@ -37874,9 +38023,9 @@ - return _res; - } - --// _tmp_232: 'if' disjunction -+// _tmp_234: 'if' disjunction - static void * --_tmp_232_rule(Parser *p) -+_tmp_234_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37893,7 +38042,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_232[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); -+ D(fprintf(stderr, "%*c> _tmp_234[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); - Token * _keyword; - expr_ty z; - if ( -@@ -37902,7 +38051,7 @@ - (z = disjunction_rule(p)) // disjunction - ) - { -- D(fprintf(stderr, "%*c+ _tmp_232[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); -+ D(fprintf(stderr, "%*c+ _tmp_234[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); - _res = z; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37912,7 +38061,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_232[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_234[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'if' disjunction")); - } - _res = NULL; -@@ -37921,9 +38070,9 @@ - return _res; - } - --// _tmp_233: 'if' disjunction -+// _tmp_235: 'if' disjunction - static void * --_tmp_233_rule(Parser *p) -+_tmp_235_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37940,7 +38089,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_233[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); -+ D(fprintf(stderr, "%*c> _tmp_235[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); - Token * _keyword; - expr_ty z; - if ( -@@ -37949,7 +38098,7 @@ - (z = disjunction_rule(p)) // disjunction - ) - { -- D(fprintf(stderr, "%*c+ _tmp_233[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); -+ D(fprintf(stderr, "%*c+ _tmp_235[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'if' disjunction")); - _res = z; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -37959,7 +38108,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_233[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_235[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'if' disjunction")); - } - _res = NULL; -@@ -37968,9 +38117,9 @@ - return _res; - } - --// _tmp_234: starred_expression | (assignment_expression | expression !':=') !'=' -+// _tmp_236: starred_expression | (assignment_expression | expression !':=') !'=' - static void * --_tmp_234_rule(Parser *p) -+_tmp_236_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -37987,18 +38136,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_234[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression")); -+ D(fprintf(stderr, "%*c> _tmp_236[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression")); - expr_ty starred_expression_var; - if ( - (starred_expression_var = starred_expression_rule(p)) // starred_expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_234[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression")); -+ D(fprintf(stderr, "%*c+ _tmp_236[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression")); - _res = starred_expression_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_234[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_236[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "starred_expression")); - } - { // (assignment_expression | expression !':=') !'=' -@@ -38006,20 +38155,20 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_234[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -- void *_tmp_247_var; -+ D(fprintf(stderr, "%*c> _tmp_236[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -+ void *_tmp_250_var; - if ( -- (_tmp_247_var = _tmp_247_rule(p)) // assignment_expression | expression !':=' -+ (_tmp_250_var = _tmp_250_rule(p)) // assignment_expression | expression !':=' - && - _PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 22) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_234[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -- _res = _tmp_247_var; -+ D(fprintf(stderr, "%*c+ _tmp_236[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -+ _res = _tmp_250_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_234[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_236[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(assignment_expression | expression !':=') !'='")); - } - _res = NULL; -@@ -38028,9 +38177,9 @@ - return _res; - } - --// _tmp_235: ',' star_target -+// _tmp_237: ',' star_target - static void * --_tmp_235_rule(Parser *p) -+_tmp_237_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38047,7 +38196,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_235[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target")); -+ D(fprintf(stderr, "%*c> _tmp_237[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target")); - Token * _literal; - expr_ty c; - if ( -@@ -38056,7 +38205,7 @@ - (c = star_target_rule(p)) // star_target - ) - { -- D(fprintf(stderr, "%*c+ _tmp_235[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_target")); -+ D(fprintf(stderr, "%*c+ _tmp_237[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_target")); - _res = c; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -38066,7 +38215,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_235[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_237[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' star_target")); - } - _res = NULL; -@@ -38075,9 +38224,9 @@ - return _res; - } - --// _tmp_236: ',' star_target -+// _tmp_238: ',' star_target - static void * --_tmp_236_rule(Parser *p) -+_tmp_238_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38094,7 +38243,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_236[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target")); -+ D(fprintf(stderr, "%*c> _tmp_238[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target")); - Token * _literal; - expr_ty c; - if ( -@@ -38103,7 +38252,7 @@ - (c = star_target_rule(p)) // star_target - ) - { -- D(fprintf(stderr, "%*c+ _tmp_236[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_target")); -+ D(fprintf(stderr, "%*c+ _tmp_238[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_target")); - _res = c; - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; -@@ -38113,7 +38262,7 @@ - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_236[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_238[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' star_target")); - } - _res = NULL; -@@ -38122,10 +38271,10 @@ - return _res; - } - --// _tmp_237: -+// _tmp_239: - // | ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs - static void * --_tmp_237_rule(Parser *p) -+_tmp_239_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38142,24 +38291,24 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_237[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs")); -- asdl_seq * _gather_248_var; -+ D(fprintf(stderr, "%*c> _tmp_239[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs")); -+ asdl_seq * _gather_251_var; - Token * _literal; - asdl_seq* kwargs_var; - if ( -- (_gather_248_var = _gather_248_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ -+ (_gather_251_var = _gather_251_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ - && - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && - (kwargs_var = kwargs_rule(p)) // kwargs - ) - { -- D(fprintf(stderr, "%*c+ _tmp_237[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs")); -- _res = _PyPegen_dummy_name(p, _gather_248_var, _literal, kwargs_var); -+ D(fprintf(stderr, "%*c+ _tmp_239[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs")); -+ _res = _PyPegen_dummy_name(p, _gather_251_var, _literal, kwargs_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_237[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_239[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs")); - } - _res = NULL; -@@ -38168,9 +38317,50 @@ - return _res; - } - --// _tmp_238: star_targets '=' -+// _tmp_240: starred_expression !'=' - static void * --_tmp_238_rule(Parser *p) -+_tmp_240_rule(Parser *p) -+{ -+ if (p->level++ == MAXSTACK) { -+ p->error_indicator = 1; -+ PyErr_NoMemory(); -+ } -+ if (p->error_indicator) { -+ p->level--; -+ return NULL; -+ } -+ void * _res = NULL; -+ int _mark = p->mark; -+ { // starred_expression !'=' -+ if (p->error_indicator) { -+ p->level--; -+ return NULL; -+ } -+ D(fprintf(stderr, "%*c> _tmp_240[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression !'='")); -+ expr_ty starred_expression_var; -+ if ( -+ (starred_expression_var = starred_expression_rule(p)) // starred_expression -+ && -+ _PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 22) // token='=' -+ ) -+ { -+ D(fprintf(stderr, "%*c+ _tmp_240[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression !'='")); -+ _res = starred_expression_var; -+ goto done; -+ } -+ p->mark = _mark; -+ D(fprintf(stderr, "%*c%s _tmp_240[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "starred_expression !'='")); -+ } -+ _res = NULL; -+ done: -+ p->level--; -+ return _res; -+} -+ -+// _tmp_241: star_targets '=' -+static void * -+_tmp_241_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38187,7 +38377,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_238[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='")); -+ D(fprintf(stderr, "%*c> _tmp_241[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='")); - Token * _literal; - expr_ty star_targets_var; - if ( -@@ -38196,12 +38386,12 @@ - (_literal = _PyPegen_expect_token(p, 22)) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_238[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='")); -+ D(fprintf(stderr, "%*c+ _tmp_241[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='")); - _res = _PyPegen_dummy_name(p, star_targets_var, _literal); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_238[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_241[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_targets '='")); - } - _res = NULL; -@@ -38210,9 +38400,9 @@ - return _res; - } - --// _tmp_239: star_targets '=' -+// _tmp_242: star_targets '=' - static void * --_tmp_239_rule(Parser *p) -+_tmp_242_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38229,7 +38419,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_239[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='")); -+ D(fprintf(stderr, "%*c> _tmp_242[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='")); - Token * _literal; - expr_ty star_targets_var; - if ( -@@ -38238,12 +38428,12 @@ - (_literal = _PyPegen_expect_token(p, 22)) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_239[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='")); -+ D(fprintf(stderr, "%*c+ _tmp_242[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='")); - _res = _PyPegen_dummy_name(p, star_targets_var, _literal); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_239[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_242[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_targets '='")); - } - _res = NULL; -@@ -38252,9 +38442,9 @@ - return _res; - } - --// _tmp_240: ')' | '**' -+// _tmp_243: ')' | '**' - static void * --_tmp_240_rule(Parser *p) -+_tmp_243_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38271,18 +38461,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_240[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c> _tmp_243[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 8)) // token=')' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_240[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); -+ D(fprintf(stderr, "%*c+ _tmp_243[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_240[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_243[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'")); - } - { // '**' -@@ -38290,18 +38480,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_240[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c> _tmp_243[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 35)) // token='**' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_240[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c+ _tmp_243[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_240[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_243[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'**'")); - } - _res = NULL; -@@ -38310,9 +38500,9 @@ - return _res; - } - --// _tmp_241: ':' | '**' -+// _tmp_244: ':' | '**' - static void * --_tmp_241_rule(Parser *p) -+_tmp_244_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38329,18 +38519,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_241[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c> _tmp_244[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 11)) // token=':' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_241[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); -+ D(fprintf(stderr, "%*c+ _tmp_244[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_241[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_244[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'")); - } - { // '**' -@@ -38348,18 +38538,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_241[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c> _tmp_244[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'")); - Token * _literal; - if ( - (_literal = _PyPegen_expect_token(p, 35)) // token='**' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_241[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); -+ D(fprintf(stderr, "%*c+ _tmp_244[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'")); - _res = _literal; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_241[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_244[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'**'")); - } - _res = NULL; -@@ -38368,9 +38558,9 @@ - return _res; - } - --// _tmp_242: expression ['as' star_target] -+// _tmp_245: expression ['as' star_target] - static void * --_tmp_242_rule(Parser *p) -+_tmp_245_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38387,22 +38577,22 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_242[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); -+ D(fprintf(stderr, "%*c> _tmp_245[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - expr_ty expression_var; - if ( - (expression_var = expression_rule(p)) // expression - && -- (_opt_var = _tmp_250_rule(p), !p->error_indicator) // ['as' star_target] -+ (_opt_var = _tmp_253_rule(p), !p->error_indicator) // ['as' star_target] - ) - { -- D(fprintf(stderr, "%*c+ _tmp_242[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); -+ D(fprintf(stderr, "%*c+ _tmp_245[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); - _res = _PyPegen_dummy_name(p, expression_var, _opt_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_242[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_245[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression ['as' star_target]")); - } - _res = NULL; -@@ -38411,9 +38601,9 @@ - return _res; - } - --// _tmp_243: expressions ['as' star_target] -+// _tmp_246: expressions ['as' star_target] - static void * --_tmp_243_rule(Parser *p) -+_tmp_246_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38430,22 +38620,22 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_243[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); -+ D(fprintf(stderr, "%*c> _tmp_246[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - expr_ty expressions_var; - if ( - (expressions_var = expressions_rule(p)) // expressions - && -- (_opt_var = _tmp_251_rule(p), !p->error_indicator) // ['as' star_target] -+ (_opt_var = _tmp_254_rule(p), !p->error_indicator) // ['as' star_target] - ) - { -- D(fprintf(stderr, "%*c+ _tmp_243[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); -+ D(fprintf(stderr, "%*c+ _tmp_246[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); - _res = _PyPegen_dummy_name(p, expressions_var, _opt_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_243[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_246[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expressions ['as' star_target]")); - } - _res = NULL; -@@ -38454,9 +38644,9 @@ - return _res; - } - --// _tmp_244: expression ['as' star_target] -+// _tmp_247: expression ['as' star_target] - static void * --_tmp_244_rule(Parser *p) -+_tmp_247_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38473,22 +38663,22 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_244[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); -+ D(fprintf(stderr, "%*c> _tmp_247[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - expr_ty expression_var; - if ( - (expression_var = expression_rule(p)) // expression - && -- (_opt_var = _tmp_252_rule(p), !p->error_indicator) // ['as' star_target] -+ (_opt_var = _tmp_255_rule(p), !p->error_indicator) // ['as' star_target] - ) - { -- D(fprintf(stderr, "%*c+ _tmp_244[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); -+ D(fprintf(stderr, "%*c+ _tmp_247[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]")); - _res = _PyPegen_dummy_name(p, expression_var, _opt_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_244[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_247[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression ['as' star_target]")); - } - _res = NULL; -@@ -38497,9 +38687,9 @@ - return _res; - } - --// _tmp_245: expressions ['as' star_target] -+// _tmp_248: expressions ['as' star_target] - static void * --_tmp_245_rule(Parser *p) -+_tmp_248_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38516,22 +38706,22 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_245[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); -+ D(fprintf(stderr, "%*c> _tmp_248[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); - void *_opt_var; - UNUSED(_opt_var); // Silence compiler warnings - expr_ty expressions_var; - if ( - (expressions_var = expressions_rule(p)) // expressions - && -- (_opt_var = _tmp_253_rule(p), !p->error_indicator) // ['as' star_target] -+ (_opt_var = _tmp_256_rule(p), !p->error_indicator) // ['as' star_target] - ) - { -- D(fprintf(stderr, "%*c+ _tmp_245[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); -+ D(fprintf(stderr, "%*c+ _tmp_248[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]")); - _res = _PyPegen_dummy_name(p, expressions_var, _opt_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_245[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_248[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expressions ['as' star_target]")); - } - _res = NULL; -@@ -38540,9 +38730,9 @@ - return _res; - } - --// _tmp_246: 'as' NAME -+// _tmp_249: 'as' NAME - static void * --_tmp_246_rule(Parser *p) -+_tmp_249_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38559,7 +38749,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_246[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c> _tmp_249[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - Token * _keyword; - expr_ty name_var; - if ( -@@ -38568,12 +38758,12 @@ - (name_var = _PyPegen_name_token(p)) // NAME - ) - { -- D(fprintf(stderr, "%*c+ _tmp_246[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); -+ D(fprintf(stderr, "%*c+ _tmp_249[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME")); - _res = _PyPegen_dummy_name(p, _keyword, name_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_246[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_249[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' NAME")); - } - _res = NULL; -@@ -38582,9 +38772,9 @@ - return _res; - } - --// _tmp_247: assignment_expression | expression !':=' -+// _tmp_250: assignment_expression | expression !':=' - static void * --_tmp_247_rule(Parser *p) -+_tmp_250_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38601,18 +38791,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_247[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "assignment_expression")); -+ D(fprintf(stderr, "%*c> _tmp_250[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "assignment_expression")); - expr_ty assignment_expression_var; - if ( - (assignment_expression_var = assignment_expression_rule(p)) // assignment_expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_247[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "assignment_expression")); -+ D(fprintf(stderr, "%*c+ _tmp_250[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "assignment_expression")); - _res = assignment_expression_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_247[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_250[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "assignment_expression")); - } - { // expression !':=' -@@ -38620,7 +38810,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_247[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression !':='")); -+ D(fprintf(stderr, "%*c> _tmp_250[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression !':='")); - expr_ty expression_var; - if ( - (expression_var = expression_rule(p)) // expression -@@ -38628,12 +38818,12 @@ - _PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 53) // token=':=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_247[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression !':='")); -+ D(fprintf(stderr, "%*c+ _tmp_250[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression !':='")); - _res = expression_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_247[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_250[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression !':='")); - } - _res = NULL; -@@ -38642,9 +38832,9 @@ - return _res; - } - --// _loop0_249: ',' (starred_expression | (assignment_expression | expression !':=') !'=') -+// _loop0_252: ',' (starred_expression | (assignment_expression | expression !':=') !'=') - static asdl_seq * --_loop0_249_rule(Parser *p) -+_loop0_252_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38670,13 +38860,13 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _loop0_249[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (starred_expression | (assignment_expression | expression !':=') !'=')")); -+ D(fprintf(stderr, "%*c> _loop0_252[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (starred_expression | (assignment_expression | expression !':=') !'=')")); - Token * _literal; - void *elem; - while ( - (_literal = _PyPegen_expect_token(p, 12)) // token=',' - && -- (elem = _tmp_254_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' -+ (elem = _tmp_257_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' - ) - { - _res = elem; -@@ -38702,7 +38892,7 @@ - _mark = p->mark; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _loop0_249[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _loop0_252[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (starred_expression | (assignment_expression | expression !':=') !'=')")); - } - asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena); -@@ -38719,10 +38909,10 @@ - return _seq; - } - --// _gather_248: --// | (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_249 -+// _gather_251: -+// | (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_252 - static asdl_seq * --_gather_248_rule(Parser *p) -+_gather_251_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38734,27 +38924,27 @@ - } - asdl_seq * _res = NULL; - int _mark = p->mark; -- { // (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_249 -+ { // (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_252 - if (p->error_indicator) { - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _gather_248[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_249")); -+ D(fprintf(stderr, "%*c> _gather_251[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_252")); - void *elem; - asdl_seq * seq; - if ( -- (elem = _tmp_254_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' -+ (elem = _tmp_257_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'=' - && -- (seq = _loop0_249_rule(p)) // _loop0_249 -+ (seq = _loop0_252_rule(p)) // _loop0_252 - ) - { -- D(fprintf(stderr, "%*c+ _gather_248[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_249")); -+ D(fprintf(stderr, "%*c+ _gather_251[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_252")); - _res = _PyPegen_seq_insert_in_front(p, elem, seq); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _gather_248[%d-%d]: %s failed!\n", p->level, ' ', -- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_249")); -+ D(fprintf(stderr, "%*c%s _gather_251[%d-%d]: %s failed!\n", p->level, ' ', -+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_252")); - } - _res = NULL; - done: -@@ -38762,9 +38952,9 @@ - return _res; - } - --// _tmp_250: 'as' star_target -+// _tmp_253: 'as' star_target - static void * --_tmp_250_rule(Parser *p) -+_tmp_253_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38781,7 +38971,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_250[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c> _tmp_253[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - Token * _keyword; - expr_ty star_target_var; - if ( -@@ -38790,12 +38980,12 @@ - (star_target_var = star_target_rule(p)) // star_target - ) - { -- D(fprintf(stderr, "%*c+ _tmp_250[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c+ _tmp_253[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - _res = _PyPegen_dummy_name(p, _keyword, star_target_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_250[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_253[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' star_target")); - } - _res = NULL; -@@ -38804,9 +38994,9 @@ - return _res; - } - --// _tmp_251: 'as' star_target -+// _tmp_254: 'as' star_target - static void * --_tmp_251_rule(Parser *p) -+_tmp_254_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38823,7 +39013,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_251[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c> _tmp_254[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - Token * _keyword; - expr_ty star_target_var; - if ( -@@ -38832,12 +39022,12 @@ - (star_target_var = star_target_rule(p)) // star_target - ) - { -- D(fprintf(stderr, "%*c+ _tmp_251[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c+ _tmp_254[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - _res = _PyPegen_dummy_name(p, _keyword, star_target_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_251[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_254[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' star_target")); - } - _res = NULL; -@@ -38846,9 +39036,9 @@ - return _res; - } - --// _tmp_252: 'as' star_target -+// _tmp_255: 'as' star_target - static void * --_tmp_252_rule(Parser *p) -+_tmp_255_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38865,7 +39055,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_252[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c> _tmp_255[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - Token * _keyword; - expr_ty star_target_var; - if ( -@@ -38874,12 +39064,12 @@ - (star_target_var = star_target_rule(p)) // star_target - ) - { -- D(fprintf(stderr, "%*c+ _tmp_252[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c+ _tmp_255[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - _res = _PyPegen_dummy_name(p, _keyword, star_target_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_252[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_255[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' star_target")); - } - _res = NULL; -@@ -38888,9 +39078,9 @@ - return _res; - } + #ifdef __APPLE__ ++# include "TargetConditionals.h" + # include + #endif --// _tmp_253: 'as' star_target -+// _tmp_256: 'as' star_target - static void * --_tmp_253_rule(Parser *p) -+_tmp_256_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38907,7 +39097,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_253[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c> _tmp_256[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - Token * _keyword; - expr_ty star_target_var; - if ( -@@ -38916,12 +39106,12 @@ - (star_target_var = star_target_rule(p)) // star_target - ) - { -- D(fprintf(stderr, "%*c+ _tmp_253[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); -+ D(fprintf(stderr, "%*c+ _tmp_256[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target")); - _res = _PyPegen_dummy_name(p, _keyword, star_target_var); - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_253[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_256[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' star_target")); +@@ -767,7 +768,7 @@ + if (PyWin_DLLhModule) { + return winmodule_to_dict(dict, key, PyWin_DLLhModule); } - _res = NULL; -@@ -38930,9 +39120,9 @@ - return _res; - } +-#elif defined(WITH_NEXT_FRAMEWORK) ++#elif defined(WITH_NEXT_FRAMEWORK) && !defined(TARGET_OS_IPHONE) + static char modPath[MAXPATHLEN + 1]; + static int modPathInitialized = -1; + if (modPathInitialized < 0) { +@@ -961,4 +962,3 @@ --// _tmp_254: starred_expression | (assignment_expression | expression !':=') !'=' -+// _tmp_257: starred_expression | (assignment_expression | expression !':=') !'=' - static void * --_tmp_254_rule(Parser *p) -+_tmp_257_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -38949,18 +39139,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_254[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression")); -+ D(fprintf(stderr, "%*c> _tmp_257[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression")); - expr_ty starred_expression_var; - if ( - (starred_expression_var = starred_expression_rule(p)) // starred_expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_254[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression")); -+ D(fprintf(stderr, "%*c+ _tmp_257[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression")); - _res = starred_expression_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_254[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_257[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "starred_expression")); - } - { // (assignment_expression | expression !':=') !'=' -@@ -38968,20 +39158,20 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_254[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -- void *_tmp_255_var; -+ D(fprintf(stderr, "%*c> _tmp_257[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -+ void *_tmp_258_var; - if ( -- (_tmp_255_var = _tmp_255_rule(p)) // assignment_expression | expression !':=' -+ (_tmp_258_var = _tmp_258_rule(p)) // assignment_expression | expression !':=' - && - _PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 22) // token='=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_254[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -- _res = _tmp_255_var; -+ D(fprintf(stderr, "%*c+ _tmp_257[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='")); -+ _res = _tmp_258_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_254[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_257[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(assignment_expression | expression !':=') !'='")); - } - _res = NULL; -@@ -38990,9 +39180,9 @@ - return _res; + return _PyStatus_OK(); } - --// _tmp_255: assignment_expression | expression !':=' -+// _tmp_258: assignment_expression | expression !':=' - static void * --_tmp_255_rule(Parser *p) -+_tmp_258_rule(Parser *p) - { - if (p->level++ == MAXSTACK) { - p->error_indicator = 1; -@@ -39009,18 +39199,18 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_255[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "assignment_expression")); -+ D(fprintf(stderr, "%*c> _tmp_258[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "assignment_expression")); - expr_ty assignment_expression_var; - if ( - (assignment_expression_var = assignment_expression_rule(p)) // assignment_expression - ) - { -- D(fprintf(stderr, "%*c+ _tmp_255[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "assignment_expression")); -+ D(fprintf(stderr, "%*c+ _tmp_258[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "assignment_expression")); - _res = assignment_expression_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_255[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_258[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "assignment_expression")); - } - { // expression !':=' -@@ -39028,7 +39218,7 @@ - p->level--; - return NULL; - } -- D(fprintf(stderr, "%*c> _tmp_255[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression !':='")); -+ D(fprintf(stderr, "%*c> _tmp_258[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression !':='")); - expr_ty expression_var; - if ( - (expression_var = expression_rule(p)) // expression -@@ -39036,12 +39226,12 @@ - _PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 53) // token=':=' - ) - { -- D(fprintf(stderr, "%*c+ _tmp_255[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression !':='")); -+ D(fprintf(stderr, "%*c+ _tmp_258[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression !':='")); - _res = expression_var; - goto done; - } - p->mark = _mark; -- D(fprintf(stderr, "%*c%s _tmp_255[%d-%d]: %s failed!\n", p->level, ' ', -+ D(fprintf(stderr, "%*c%s _tmp_258[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression !':='")); - } - _res = NULL; +- diff --git a/Python/marshal.c b/Python/marshal.c index 29f3bab60a5..7d32bb3661d 100644 --- a/Python/marshal.c @@ -13849,18 +4290,6 @@ index 553585a76a3..2223b9a9a55 100644 "_json", "_locale", "_lsprof", -diff --git a/README.rst b/README.rst -index ffa409a54c5..15ed568e96d 100644 ---- a/README.rst -+++ b/README.rst -@@ -1,5 +1,5 @@ --This is Python version 3.11.9 --============================= -+This is Python version 3.11.10 -+============================== - - .. image:: https://github.com/python/cpython/workflows/Tests/badge.svg - :alt: CPython build status on GitHub Actions diff --git a/config.sub b/config.sub index d74fb6deac9..1bb6a05dc11 100755 --- a/config.sub From f038626092fd5fd003e803f246f994a2e1d486b0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 9 Sep 2024 11:59:49 +0800 Subject: [PATCH 08/13] 3.11.9 was the last macOS binary release. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d58ad536..aa6df570 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ BUILD_NUMBER=custom # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) PYTHON_VERSION=3.11.10 -PYTHON_PKG_VERSION=$(PYTHON_VERSION) +PYTHON_PKG_VERSION=3.11.9 PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_VER=$(basename $(PYTHON_VERSION)) From 960c30b0ee130801eeb6d0c528dd1cc59de89887 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 19 Mar 2025 13:28:07 +0800 Subject: [PATCH 09/13] Update 3.11 patch with recent changes (#261) * Adds recent iOS patches from 3.14 development * Adds CI workflow changes from main branch * Adds modulemap generation * Adds new-style platform-site handling --- .github/workflows/ci.yaml | 11 +- .github/workflows/publish.yaml | 2 +- .github/workflows/release.yaml | 4 +- Makefile | 92 ++++++--- README.rst | 35 +++- USAGE.md | 125 +++++++----- patch/Python/Python.patch | 262 +++++++++++--------------- patch/Python/_cross_target.py.tmpl | 78 ++++++++ patch/Python/_cross_venv.py | 103 ++++++++++ patch/Python/make_cross_venv.py | 123 ++++++++++++ patch/Python/module.modulemap.prefix | 20 ++ patch/Python/sitecustomize.iOS.py | 114 ----------- patch/Python/sitecustomize.macOS.py | 14 -- patch/Python/sitecustomize.py.tmpl | 22 +++ patch/Python/sitecustomize.tvOS.py | 99 ---------- patch/Python/sitecustomize.watchOS.py | 99 ---------- 16 files changed, 640 insertions(+), 563 deletions(-) create mode 100644 patch/Python/_cross_target.py.tmpl create mode 100644 patch/Python/_cross_venv.py create mode 100644 patch/Python/make_cross_venv.py create mode 100644 patch/Python/module.modulemap.prefix delete mode 100644 patch/Python/sitecustomize.iOS.py delete mode 100644 patch/Python/sitecustomize.macOS.py create mode 100644 patch/Python/sitecustomize.py.tmpl delete mode 100644 patch/Python/sitecustomize.tvOS.py delete mode 100644 patch/Python/sitecustomize.watchOS.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2a78d775..6befbc46 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,10 +1,6 @@ name: CI on: pull_request: - push: - branches: - - main - - 3.* workflow_call: inputs: build-number: @@ -109,11 +105,14 @@ jobs: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.3.0 + uses: actions/setup-python@v5.4.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true - name: Build ${{ matrix.target }} run: | @@ -121,7 +120,7 @@ jobs: make ${{ matrix.target }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} - name: Upload build artefacts - uses: actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@v4.6.1 with: name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index a9f69ed4..5d2ce753 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python environment - uses: actions/setup-python@v5.3.0 + uses: actions/setup-python@v5.4.0 with: python-version: "3.X" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5e5ef751..61da403c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,14 +40,14 @@ jobs: needs: [ config, ci ] steps: - name: Get build artifacts - uses: actions/download-artifact@v4.1.8 + uses: actions/download-artifact@v4.1.9 with: pattern: Python-* path: dist merge-multiple: true - name: Create Release - uses: ncipollo/release-action@v1.14.0 + uses: ncipollo/release-action@v1.16.0 with: name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} diff --git a/Makefile b/Makefile index a1f7dd4f..49b72834 100644 --- a/Makefile +++ b/Makefile @@ -27,10 +27,10 @@ PYTHON_VER=$(basename $(PYTHON_VERSION)) # The binary releases of dependencies, published at: # https://github.com/beeware/cpython-apple-source-deps/releases BZIP2_VERSION=1.0.8-1 -LIBFFI_VERSION=3.4.6-1 +LIBFFI_VERSION=3.4.7-1 MPDECIMAL_VERSION=4.0.0-1 -OPENSSL_VERSION=3.0.15-1 -XZ_VERSION=5.6.2-1 +OPENSSL_VERSION=3.0.16-1 +XZ_VERSION=5.6.4-1 # Supported OS OS_LIST=macOS iOS tvOS watchOS @@ -40,24 +40,18 @@ CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar # macOS targets TARGETS-macOS=macosx.x86_64 macosx.arm64 VERSION_MIN-macOS=11.0 -CFLAGS-macOS=-mmacosx-version-min=$(VERSION_MIN-macOS) # iOS targets TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64 VERSION_MIN-iOS=13.0 -CFLAGS-iOS=-mios-version-min=$(VERSION_MIN-iOS) # tvOS targets TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64 VERSION_MIN-tvOS=12.0 -CFLAGS-tvOS=-mtvos-version-min=$(VERSION_MIN-tvOS) -PYTHON_CONFIGURE-tvOS=ac_cv_func_sigaltstack=no # watchOS targets TARGETS-watchOS=watchsimulator.x86_64 watchsimulator.arm64 watchos.arm64_32 VERSION_MIN-watchOS=4.0 -CFLAGS-watchOS=-mwatchos-version-min=$(VERSION_MIN-watchOS) -PYTHON_CONFIGURE-watchOS=ac_cv_func_sigaltstack=no # The architecture of the machine doing the build HOST_ARCH=$(shell uname -m) @@ -72,7 +66,7 @@ PATH=/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin all: $(OS_LIST) .PHONY: \ - all clean distclean update-patch vars \ + all clean distclean update-patch vars config \ $(foreach os,$(OS_LIST),$(os) clean-$(os) dev-clean-$(os) vars-$(os)) \ $(foreach os,$(OS_LIST),$(foreach sdk,$$(sort $$(basename $$(TARGETS-$(os)))),$(sdk) vars-$(sdk))) $(foreach os,$(OS_LIST),$(foreach target,$$(TARGETS-$(os)),$(target) vars-$(target))) @@ -135,10 +129,10 @@ ARCH-$(target)=$$(subst .,,$$(suffix $(target))) ifneq ($(os),macOS) ifeq ($$(findstring simulator,$$(SDK-$(target))),) TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os)) -IS_SIMULATOR-$(target)="False" +IS_SIMULATOR-$(target)=False else TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os))-simulator -IS_SIMULATOR-$(target)="True" +IS_SIMULATOR-$(target)=True endif endif @@ -267,6 +261,9 @@ PYTHON_LIB-$(target)=$$(PYTHON_FRAMEWORK-$(target))/Python PYTHON_BIN-$(target)=$$(PYTHON_INSTALL-$(target))/bin PYTHON_INCLUDE-$(target)=$$(PYTHON_FRAMEWORK-$(target))/Headers PYTHON_STDLIB-$(target)=$$(PYTHON_INSTALL-$(target))/lib/python$(PYTHON_VER) +PYTHON_PLATFORM_CONFIG-$(target)=$$(PYTHON_INSTALL-$(target))/platform-config/$$(ARCH-$(target))-$$(SDK-$(target)) +PYTHON_PLATFORM_SITECUSTOMIZE-$(target)=$$(PYTHON_PLATFORM_CONFIG-$(target))/sitecustomize.py + $$(PYTHON_SRCDIR-$(target))/configure: \ downloads/Python-$(PYTHON_VERSION).tar.gz \ @@ -325,23 +322,35 @@ $$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe # Remove any .orig files produced by the compliance patching process find $$(PYTHON_INSTALL-$(target)) -name "*.orig" -exec rm {} \; -endif - -PYTHON_SITECUSTOMIZE-$(target)=$(PROJECT_DIR)/support/$(PYTHON_VER)/$(os)/platform-site/$(target)/sitecustomize.py -$$(PYTHON_SITECUSTOMIZE-$(target)): - @echo ">>> Create cross-platform sitecustomize.py for $(target)" - mkdir -p $$(dir $$(PYTHON_SITECUSTOMIZE-$(target))) - cat $(PROJECT_DIR)/patch/Python/sitecustomize.$(os).py \ +$$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)): + @echo ">>> Create cross-plaform config for $(target)" + mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(target)) + # Create the cross-platform site definition + echo "import _cross_$$(ARCH-$(target))_$$(SDK-$(target)); import _cross_venv;" \ + > $$(PYTHON_PLATFORM_CONFIG-$(target))/_cross_venv.pth + cp $(PROJECT_DIR)/patch/Python/make_cross_venv.py \ + $$(PYTHON_PLATFORM_CONFIG-$(target))/make_cross_venv.py + cp $(PROJECT_DIR)/patch/Python/_cross_venv.py \ + $$(PYTHON_PLATFORM_CONFIG-$(target))/_cross_venv.py + cp $$(PYTHON_STDLIB-$(target))/_sysconfig* \ + $$(PYTHON_PLATFORM_CONFIG-$(target)) + cat $(PROJECT_DIR)/patch/Python/_cross_target.py.tmpl \ | sed -e "s/{{os}}/$(os)/g" \ + | sed -e "s/{{platform}}/$$(OS_LOWER-$(target))/g" \ | sed -e "s/{{arch}}/$$(ARCH-$(target))/g" \ + | sed -e "s/{{sdk}}/$$(SDK-$(target))/g" \ | sed -e "s/{{version_min}}/$$(VERSION_MIN-$(os))/g" \ | sed -e "s/{{is_simulator}}/$$(IS_SIMULATOR-$(target))/g" \ - | sed -e "s/{{multiarch}}/$$(ARCH-$(target))-$$(SDK-$(target))/g" \ - | sed -e "s/{{tag}}/$$(OS_LOWER-$(target))-$$(VERSION_MIN-$(os))-$$(ARCH-$(target))-$$(SDK-$(target))/g" \ - > $$(PYTHON_SITECUSTOMIZE-$(target)) + > $$(PYTHON_PLATFORM_CONFIG-$(target))/_cross_$$(ARCH-$(target))_$$(SDK-$(target)).py + cat $(PROJECT_DIR)/patch/Python/sitecustomize.py.tmpl \ + | sed -e "s/{{arch}}/$$(ARCH-$(target))/g" \ + | sed -e "s/{{sdk}}/$$(SDK-$(target))/g" \ + > $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)) + +endif -$(target): $$(PYTHON_SITECUSTOMIZE-$(target)) $$(PYTHON_LIB-$(target)) +$(target): $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)) $$(PYTHON_LIB-$(target)) ########################################################################### # Target: Debug @@ -370,6 +379,8 @@ vars-$(target): @echo "PYTHON_BIN-$(target): $$(PYTHON_BIN-$(target))" @echo "PYTHON_INCLUDE-$(target): $$(PYTHON_INCLUDE-$(target))" @echo "PYTHON_STDLIB-$(target): $$(PYTHON_STDLIB-$(target))" + @echo "PYTHON_PLATFORM_CONFIG-$(target): $$(PYTHON_PLATFORM_CONFIG-$(target))" + @echo "PYTHON_PLATFORM_SITECUSTOMIZE-$(target): $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target))" @echo endef # build-target @@ -415,6 +426,7 @@ PYTHON_FRAMEWORK-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/Python.framework PYTHON_INSTALL_VERSION-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Versions/$(PYTHON_VER) PYTHON_LIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/Python PYTHON_INCLUDE-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/include/python$(PYTHON_VER) +PYTHON_MODULEMAP-$(sdk)=$$(PYTHON_INCLUDE-$(sdk))/module.modulemap PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/lib/python$(PYTHON_VER) else @@ -425,11 +437,13 @@ else # The non-macOS frameworks don't use the versioning structure. PYTHON_INSTALL-$(sdk)=$(PROJECT_DIR)/install/$(os)/$(sdk)/python-$(PYTHON_VERSION) +PYTHON_MODULEMAP-$(sdk)=$$(PYTHON_INCLUDE-$(sdk))/module.modulemap PYTHON_FRAMEWORK-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/Python.framework PYTHON_LIB-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Python PYTHON_BIN-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/bin PYTHON_INCLUDE-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Headers PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/lib/python$(PYTHON_VER) +PYTHON_PLATFORM_CONFIG-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/platform-config $$(PYTHON_LIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_LIB-$$(target))) @echo ">>> Build Python fat library for the $(sdk) SDK" @@ -454,6 +468,15 @@ $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h: $$(PYTHON_LIB-$(sdk)) # Copy headers as-is from the first target in the $(sdk) SDK cp -r $$(PYTHON_INCLUDE-$$(firstword $$(SDK_TARGETS-$(sdk)))) $$(PYTHON_INCLUDE-$(sdk)) + # Create the modulemap file + cp -r patch/Python/module.modulemap.prefix $$(PYTHON_MODULEMAP-$(sdk)) + echo "" >> $$(PYTHON_MODULEMAP-$(sdk)) + cd $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/Include && \ + find cpython -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-$(sdk)) && \ + echo "" >> $$(PYTHON_MODULEMAP-$(sdk)) && \ + find internal -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-$(sdk)) + echo "\n}" >> $$(PYTHON_MODULEMAP-$(sdk)) + # Link the PYTHONHOME version of the headers mkdir -p $$(PYTHON_INSTALL-$(sdk))/include ln -si ../Python.framework/Headers $$(PYTHON_INSTALL-$(sdk))/include/python$(PYTHON_VER) @@ -465,7 +488,7 @@ $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h: $$(PYTHON_LIB-$(sdk)) cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h -$$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h +$$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target))) @echo ">>> Build Python stdlib for the $(sdk) SDK" mkdir -p $$(PYTHON_STDLIB-$(sdk))/lib-dynload # Copy stdlib from the first target associated with the $(sdk) SDK @@ -480,6 +503,10 @@ $$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK- # Copy the individual _sysconfigdata modules into names that include the architecture $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfigdata_* $$(PYTHON_STDLIB-$(sdk))/; ) + # Copy the platform site folders for each architecture + mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(sdk)) + $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp -r $$(PYTHON_PLATFORM_CONFIG-$$(target)) $$(PYTHON_PLATFORM_CONFIG-$(sdk)); ) + # Merge the binary modules from each target in the $(sdk) SDK into a single binary $$(foreach module,$$(wildcard $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/lib-dynload/*),lipo -create -output $$(PYTHON_STDLIB-$(sdk))/lib-dynload/$$(notdir $$(module)) $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_STDLIB-$$(target))/lib-dynload/$$(notdir $$(module))); ) @@ -564,6 +591,15 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ # Rewrite the framework to make it standalone patch/make-relocatable.sh $$(PYTHON_INSTALL_VERSION-macosx) 2>&1 > /dev/null + # Create the modulemap file + cp -r patch/Python/module.modulemap.prefix $$(PYTHON_MODULEMAP-macosx) + echo "" >> $$(PYTHON_MODULEMAP-macosx) + cd $$(PYTHON_INCLUDE-macosx) && \ + find cpython -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-macosx) && \ + echo "" >> $$(PYTHON_MODULEMAP-macosx) && \ + find internal -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-macosx) + echo "\n}" >> $$(PYTHON_MODULEMAP-macosx) + # Re-apply the signature on the binaries. codesign -s - --preserve-metadata=identifier,entitlements,flags,runtime -f $$(PYTHON_LIB-macosx) \ 2>&1 | tee $$(PYTHON_INSTALL-macosx)/python-$(os).codesign.log @@ -587,7 +623,7 @@ support/$(PYTHON_VER)/macOS/VERSIONS: dist/Python-$(PYTHON_VER)-macOS-support.$(BUILD_NUMBER).tar.gz: \ $$(PYTHON_XCFRAMEWORK-macOS)/Info.plist \ support/$(PYTHON_VER)/macOS/VERSIONS \ - $$(foreach target,$$(TARGETS-macOS), $$(PYTHON_SITECUSTOMIZE-$$(target))) + $$(foreach target,$$(TARGETS-macOS), $$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target))) @echo ">>> Create final distribution artefact for macOS" mkdir -p dist @@ -610,9 +646,7 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/include $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/bin $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/lib $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) - - @echo ">>> Create helper links in XCframework for $(os)" - $$(foreach sdk,$$(SDKS-$(os)),ln -si $$(SDK_SLICE-$$(sdk)) $$(PYTHON_XCFRAMEWORK-$(os))/$$(sdk); ) + $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/platform-config $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) ifeq ($(os),iOS) @echo ">>> Clone testbed project for $(os)" @@ -632,7 +666,7 @@ endif dist/Python-$(PYTHON_VER)-$(os)-support.$(BUILD_NUMBER).tar.gz: \ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist \ - $$(foreach target,$$(TARGETS-$(os)), $$(PYTHON_SITECUSTOMIZE-$$(target))) + $$(foreach target,$$(TARGETS-$(os)), $$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target))) @echo ">>> Create final distribution artefact for $(os)" mkdir -p dist diff --git a/README.rst b/README.rst index e90a650f..61448a33 100644 --- a/README.rst +++ b/README.rst @@ -83,15 +83,6 @@ Each support package contains: * ``VERSIONS``, a text file describing the specific versions of code used to build the support package; -* ``platform-site``, a folder that contains site customization scripts that can be used - to make your local Python install look like it is an on-device install for each of the - underlying target architectures supported by the platform. This is needed because when - you run ``pip`` you'll be on a macOS machine with a specific architecture; if ``pip`` - tries to install a binary package, it will install a macOS binary wheel (which won't - work on iOS/tvOS/watchOS). However, if you add the ``platform-site`` folder to your - ``PYTHONPATH`` when invoking pip, the site customization will make your Python install - return ``platform`` and ``sysconfig`` responses consistent with on-device behavior, - which will cause ``pip`` to install platform-appropriate packages. * ``Python.xcframework``, a multi-architecture build of the Python runtime library On iOS/tvOS/watchOS, the ``Python.xcframework`` contains a @@ -105,6 +96,32 @@ needed to build packages. This is required because Xcode uses the ``xcrun`` alias to dynamically generate the name of binaries, but a lot of C tooling expects that ``CC`` will not contain spaces. +Each slice of an iOS/tvOS/watchOS XCframework also contains a +``platform-config`` folder with a subfolder for each supported architecture in +that slice. These subfolders can be used to make a macOS Python environment +behave as if it were on an iOS/tvOS/watchOS device. This works in one of two +ways: + +1. **A sitecustomize.py script**. If the ``platform-config`` subfolder is on + your ``PYTHONPATH`` when a Python interpreter is started, a site + customization will be applied that patches methods in ``sys``, ``sysconfig`` + and ``platform`` that are used to identify the system. + +2. **A make_cross_venv.py script**. If you call ``make_cross_venv.py``, + providing the location of a virtual environment, the script will add some + files to the ``site-packages`` folder of that environment that will + automatically apply the same set of patches as the ``sitecustomize.py`` + script whenever the environment is activated, without any need to modify + ``PYTHONPATH``. If you use ``build`` to create an isolated PEP 517 + environment to build a wheel, these patches will also be applied to the + isolated build environment that is created. + +iOS distributions also contain a copy of the iOS ``testbed`` project - an Xcode +project that can be used to run test suites of Python code. See the `CPython +documentation on testing packages +`__ for +details on how to use this testbed. + For a detailed instructions on using the support package in your own project, see the `usage guide <./USAGE.md>`__ diff --git a/USAGE.md b/USAGE.md index 12bc1dc7..096f71b0 100644 --- a/USAGE.md +++ b/USAGE.md @@ -20,71 +20,110 @@ what Briefcase is doing). The steps required are documented in the CPython usage guides: * [macOS](https://docs.python.org/3/using/mac.html) -* [iOS](https://docs.python.org/3.14/using/ios.html) +* [iOS](https://docs.python.org/3/using/ios.html#adding-python-to-an-ios-project) For tvOS and watchOS, you should be able to broadly follow the instructions in the iOS guide. +### Using Objective C + +Once you've added the Python XCframework to your project, you'll need to +initialize the Python runtime in your Objective C code (This is step 10 of the +iOS guide linked above). This initialization should generally be done as early +as possible in the application's lifecycle, but definitely needs to be done +before you invoke Python code. + +As a *bare minimum*, you can do the following: + +1. Import the Python C API headers: + ```objc + #include + ``` + +2. Initialize the Python interpreter: + ```objc + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + NSString *pythonHome = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; + NSString *pythonPath = [NSString stringWithFormat:@"%@/lib/python3.13", python_home, nil]; + NSString *libDynloadPath = [NSString stringWithFormat:@"%@/lib/python3.13/lib-dynload", python_home, nil]; + NSString *appPath = [NSString stringWithFormat:@"%@/app", resourcePath, nil]; + + setenv("PYTHONHOME", pythonHome, 1); + setenv("PYTHONPATH", [NSString stringWithFormat:@"%@:%@:%@", pythonpath, libDynloadPath, appPath, nil]); + + Py_Initialize(); + + // we now have a Python interpreter ready to be used + ``` + References to a specific Python version should reflect the version of + Python you are using. + +Again - this is the *bare minimum* initialization. In practice, you will likely +need to configure other aspects of the Python interpreter using the +`PyPreConfig` and `PyConfig` mechanisms. Consult the [Python documentation on +interpreter configuration](https://docs.python.org/3/c-api/init_config.html) for +more details on the configuration options that are available. You may find the +[bootstrap mainline code used by +Briefcase](https://github.com/beeware/briefcase-iOS-Xcode-template/blob/main/%7B%7B%20cookiecutter.format%20%7D%7D/%7B%7B%20cookiecutter.class_name%20%7D%7D/main.m) +a helpful point of comparison. + +### Using Swift + +If you want to use Swift instead of Objective C, the bare minimum initialization +code will look something like this: + +1. Import the Python framework: + ```swift + import Python + ``` + +2. Initialize the Python interpreter: + ```swift + guard let pythonHome = Bundle.main.path(forResource: "python", ofType: nil) else { return } + guard let pythonPath = Bundle.main.path(forResource: "python/lib/python3.13", ofType: nil) else { return } + guard let libDynloadPath = Bundle.main.path(forResource: "python/lib/python3.13/lib-dynload", ofType: nil) else { return } + let appPath = Bundle.main.path(forResource: "app", ofType: nil) + + setenv("PYTHONHOME", pythonHome, 1) + setenv("PYTHONPATH", [pythonPath, libDynloadPath, appPath].compactMap { $0 }.joined(separator: ":"), 1) + Py_Initialize() + // we now have a Python interpreter ready to be used + ``` + + Again, references to a specific Python version should reflect the version of + Python you are using; and you will likely need to use `PyPreConfig` and + `PreConfig` APIs. + ## Accessing the Python runtime There are 2 ways to access the Python runtime in your project code. -### Embedded C API. +### Embedded C API You can use the [Python Embedded C -API](https://docs.python.org/3/extending/embedding.html) to instantiate a Python -interpreter. This is the approach taken by Briefcase; you may find the bootstrap -mainline code generated by Briefcase a helpful guide to what is needed to start -an interpreter and run Python code. +API](https://docs.python.org/3/extending/embedding.html) to invoke Python code +and interact with Python objects. This is a raw C API that is accesible to both +Objective C and Swift. ### PythonKit -An alternate approach is to use +If you're using Swift, an alternate approach is to use [PythonKit](https://github.com/pvieito/PythonKit). PythonKit is a package that provides a Swift API to running Python code. -To use PythonKit in your project: - -1. Add PythonKit to your project using the Swift Package manager. See the - PythonKit documentation for details. - -2. Create a file called `module.modulemap` inside - `Python.xcframework/macos-arm64_x86_64/Headers/`, containing the following - code: -``` -module Python { - umbrella header "Python.h" - export * - link "Python" -} -``` +To use PythonKit in your project, add the Python Apple Support package to your +project and instantiate a Python interpreter as described above; then add +PythonKit to your project using the Swift Package manager (see the [PythonKit +documentation](https://github.com/pvieito/PythonKit) for details). -3. In your Swift code, initialize the Python runtime. This should generally be - done as early as possible in the application's lifecycle, but definitely - needs to be done before you invoke Python code: +Once you've done this, you can import PythonKit: ```swift -import Python - -guard let stdLibPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil) else { return } -guard let libDynloadPath = Bundle.main.path(forResource: "python-stdlib/lib-dynload", ofType: nil) else { return } -setenv("PYTHONHOME", stdLibPath, 1) -setenv("PYTHONPATH", "\(stdLibPath):\(libDynloadPath)", 1) -Py_Initialize() -// we now have a Python interpreter ready to be used +import PythonKit ``` - -5. Invoke Python code in your app. For example: +and use the PythonKit Swift API to interact with Python code: ```swift -import PythonKit - let sys = Python.import("sys") print("Python Version: \(sys.version_info.major).\(sys.version_info.minor)") print("Python Encoding: \(sys.getdefaultencoding().upper())") print("Python Path: \(sys.path)") - -_ = Python.import("math") // verifies `lib-dynload` is found and signed successfully ``` - -To integrate 3rd party python code and dependencies, you will need to make sure -`PYTHONPATH` contains their paths; once this has been done, you can run -`Python.import("")`. to import that module from inside swift. diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 7dbd3358..38610b3a 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,25 +1,3 @@ -diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst -index ad1cd3f4f06..6743a4d2ad5 100644 ---- a/Doc/c-api/init_config.rst -+++ b/Doc/c-api/init_config.rst -@@ -1174,6 +1174,17 @@ - - Default: ``1`` in Python config and ``0`` in isolated config. - -+ .. c:member:: int use_system_logger -+ -+ If non-zero, ``stdout`` and ``stderr`` will be redirected to the system -+ log. -+ -+ Only available on macOS 10.12 and later, and on iOS. -+ -+ Default: ``0`` (don't use system log). -+ -+ .. versionadded:: 3.13.2 -+ - .. c:member:: int user_site_directory - - If non-zero, add the user site directory to :data:`sys.path`. --- /dev/null +++ b/Doc/includes/wasm-ios-notavail.rst @@ -0,0 +1,8 @@ @@ -989,7 +967,7 @@ index e1a3111f36a..f55a12f1ab8 100644 editors.rst --- /dev/null +++ b/Doc/using/ios.rst -@@ -0,0 +1,388 @@ +@@ -0,0 +1,386 @@ +.. _using-ios: + +=================== @@ -1288,8 +1266,6 @@ index e1a3111f36a..f55a12f1ab8 100644 + * Buffered stdio (:c:member:`PyConfig.buffered_stdio`) is *disabled*; + * Writing bytecode (:c:member:`PyConfig.write_bytecode`) is *disabled*; + * Signal handlers (:c:member:`PyConfig.install_signal_handlers`) are *enabled*; -+ * System logging (:c:member:`PyConfig.use_system_logger`) is *enabled* -+ (optional, but strongly recommended); + * ``PYTHONHOME`` for the interpreter is configured to point at the + ``python`` subfolder of your app's bundle; and + * The ``PYTHONPATH`` for the interpreter includes: @@ -1411,20 +1387,6 @@ index 986d693d03f..da45aabca28 100644 Other Resources =============== -diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h -index 3b6d59389f2..161af88d472 100644 ---- a/Include/cpython/initconfig.h -+++ b/Include/cpython/initconfig.h -@@ -177,6 +177,9 @@ - wchar_t *check_hash_pycs_mode; - int use_frozen_modules; - int safe_path; -+#ifdef __APPLE__ -+ int use_system_logger; -+#endif - - /* --- Path configuration inputs ------------ */ - int pathconfig_warnings; --- /dev/null +++ b/Lib/_apple_support.py @@ -0,0 +1,66 @@ @@ -2382,11 +2344,11 @@ index ec6d1dee4e9..48900f50508 100644 @@ -0,0 +1,155 @@ +import unittest +from _apple_support import SystemLog -+from test.support import is_apple ++from test.support import is_apple_mobile +from unittest.mock import Mock, call + -+if not is_apple: -+ raise unittest.SkipTest("Apple-specific") ++if not is_apple_mobile: ++ raise unittest.SkipTest("iOS-specific") + + +# Test redirection of stdout and stderr to the Apple system log. @@ -2857,19 +2819,6 @@ index 812f989d8f3..dfcf9e16e40 100644 @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork') @support.requires_resource('cpu') def test_hang_global_shutdown_lock(self): -diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py -index 1026dcd0aca..7b2c05f2fb6 100644 ---- a/Lib/test/test_embed.py -+++ b/Lib/test/test_embed.py -@@ -508,6 +508,8 @@ - CONFIG_COMPAT.update({ - 'legacy_windows_stdio': 0, - }) -+ if support.is_apple: -+ CONFIG_COMPAT['use_system_logger'] = False - - CONFIG_PYTHON = dict(CONFIG_COMPAT, - _config_init=API_PYTHON, diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 8e98256a62c..7681e829d8b 100644 --- a/Lib/test/test_fcntl.py @@ -4556,50 +4505,6 @@ index 61d654065fd..8bd844d6459 100644 return _PyStatus_OK(); } - -diff --git a/Python/initconfig.c b/Python/initconfig.c -index 322a57dfb1a..90d84a35a6d 100644 ---- a/Python/initconfig.c -+++ b/Python/initconfig.c -@@ -665,6 +665,9 @@ - assert(config->safe_path >= 0); - // config->use_frozen_modules is initialized later - // by _PyConfig_InitImportConfig(). -+#ifdef __APPLE__ -+ assert(config->use_system_logger >= 0); -+#endif - return 1; - } - #endif -@@ -758,6 +761,9 @@ - config->safe_path = 0; - config->_is_python_build = 0; - config->code_debug_ranges = 1; -+#ifdef __APPLE__ -+ config->use_system_logger = 0; -+#endif - } - - /* Excluded from public struct PyConfig for backporting reasons. */ -@@ -787,6 +793,9 @@ - #ifdef MS_WINDOWS - config->legacy_windows_stdio = 0; - #endif -+#ifdef __APPLE__ -+ config->use_system_logger = 0; -+#endif - } - - -@@ -820,6 +829,9 @@ - #ifdef MS_WINDOWS - config->legacy_windows_stdio = 0; - #endif -+#ifdef __APPLE__ -+ config->use_system_logger = 0; -+#endif - } - - diff --git a/Python/marshal.c b/Python/marshal.c index 29f3bab60a5..7d32bb3661d 100644 --- a/Python/marshal.c @@ -4634,40 +4539,50 @@ index 29f3bab60a5..7d32bb3661d 100644 #define TYPE_NULL '0' diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c -index 9248e971d9c..e75067aa03f 100644 +index 9248e971d9c..9bac4fed20b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c -@@ -35,7 +35,9 @@ +@@ -35,7 +35,21 @@ #include // getenv() #if defined(__APPLE__) -#include +# include ++# include +# include -+# include ++// The os_log unified logging APIs were introduced in macOS 10.12, iOS 10.0, ++// tvOS 10.0, and watchOS 3.0; we enable the use of the system logger ++// automatically on non-macOS platforms. ++# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE ++# define USE_APPLE_SYSTEM_LOG 1 ++# else ++# define USE_APPLE_SYSTEM_LOG 0 ++# endif ++ ++# if USE_APPLE_SYSTEM_LOG ++# include ++# endif #endif #ifdef HAVE_SIGNAL_H -@@ -72,6 +74,9 @@ +@@ -72,6 +86,9 @@ static PyStatus init_import_site(void); static PyStatus init_set_builtins_open(void); static PyStatus init_sys_streams(PyThreadState *tstate); -+#if defined(__APPLE__) ++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG +static PyStatus init_apple_streams(PyThreadState *tstate); +#endif static void wait_for_thread_shutdown(PyThreadState *tstate); static void call_ll_exitfuncs(_PyRuntimeState *runtime); -@@ -1161,6 +1166,19 @@ +@@ -1161,6 +1178,17 @@ return status; } -+#if defined(__APPLE__) -+ if (config->use_system_logger) { -+ status = init_apple_streams(tstate); -+ if (_PyStatus_EXCEPTION(status)) { -+ return status; -+ } ++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG ++ status = init_apple_streams(tstate); ++ if (_PyStatus_EXCEPTION(status)) { ++ return status; + } +#endif + @@ -4678,11 +4593,11 @@ index 9248e971d9c..e75067aa03f 100644 status = add_main_module(interp); if (_PyStatus_EXCEPTION(status)) { return status; -@@ -2481,6 +2499,75 @@ +@@ -2481,6 +2509,69 @@ return res; } -+#if defined(__APPLE__) ++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG + +static PyObject * +apple_log_write_impl(PyObject *self, PyObject *args) @@ -4693,14 +4608,9 @@ index 9248e971d9c..e75067aa03f 100644 + return NULL; + } + -+ // Call the underlying Apple logging API. The os_log unified logging APIs -+ // were introduced in macOS 10.12, iOS 10.0, tvOS 10.0, and watchOS 3.0; -+ // this call is a no-op on older versions. -+ #if TARGET_OS_IPHONE || (TARGET_OS_OSX && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) + // Pass the user-provided text through explicit %s formatting + // to avoid % literals being interpreted as a formatting directive. + os_log_with_type(OS_LOG_DEFAULT, logtype, "%s", text); -+ #endif + Py_RETURN_NONE; +} + @@ -4735,7 +4645,6 @@ index 9248e971d9c..e75067aa03f 100644 + if (result == NULL) { + goto error; + } -+ + goto done; + +error: @@ -4749,7 +4658,7 @@ index 9248e971d9c..e75067aa03f 100644 + return status; +} + -+#endif // __APPLE__ ++#endif // __APPLE__ && USE_APPLE_SYSTEM_LOG + static void @@ -8739,11 +8648,12 @@ index 7b4000fa9c3..08c9405a465 100644 +build for testing purposes (either x86_64 or ARM64). --- /dev/null +++ b/iOS/testbed/__main__.py -@@ -0,0 +1,407 @@ +@@ -0,0 +1,468 @@ +import argparse +import asyncio +import json +import plistlib ++import re +import shutil +import subprocess +import sys @@ -8755,6 +8665,18 @@ index 7b4000fa9c3..08c9405a465 100644 + +DECODE_ARGS = ("UTF-8", "backslashreplace") + ++# The system log prefixes each line: ++# 2025-01-17 16:14:29.090 Df iOSTestbed[23987:1fd393b4] (Python) ... ++# 2025-01-17 16:14:29.090 E iOSTestbed[23987:1fd393b4] (Python) ... ++ ++LOG_PREFIX_REGEX = re.compile( ++ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD ++ r"\s+\d+:\d{2}:\d{2}\.\d+" # HH:MM:SS.sss ++ r"\s+\w+" # Df/E ++ r"\s+iOSTestbed\[\d+:\w+\]" # Process/thread ID ++ r"\s+\(Python\)\s" # Logger name ++) ++ + +# Work around a bug involving sys.exit and TaskGroups +# (https://github.com/python/cpython/issues/101515). @@ -8812,19 +8734,29 @@ index 7b4000fa9c3..08c9405a465 100644 + +# Return a list of UDIDs associated with booted simulators +async def list_devices(): -+ # List the testing simulators, in JSON format -+ raw_json = await async_check_output( -+ "xcrun", "simctl", "--set", "testing", "list", "-j" -+ ) -+ json_data = json.loads(raw_json) -+ -+ # Filter out the booted iOS simulators -+ return [ -+ simulator["udid"] -+ for runtime, simulators in json_data["devices"].items() -+ for simulator in simulators -+ if runtime.split(".")[-1].startswith("iOS") and simulator["state"] == "Booted" -+ ] ++ try: ++ # List the testing simulators, in JSON format ++ raw_json = await async_check_output( ++ "xcrun", "simctl", "--set", "testing", "list", "-j" ++ ) ++ json_data = json.loads(raw_json) ++ ++ # Filter out the booted iOS simulators ++ return [ ++ simulator["udid"] ++ for runtime, simulators in json_data["devices"].items() ++ for simulator in simulators ++ if runtime.split(".")[-1].startswith("iOS") and simulator["state"] == "Booted" ++ ] ++ except subprocess.CalledProcessError as e: ++ # If there's no ~/Library/Developer/XCTestDevices folder (which is the ++ # case on fresh installs, and in some CI environments), `simctl list` ++ # returns error code 1, rather than an empty list. Handle that case, ++ # but raise all other errors. ++ if e.returncode == 1: ++ return [] ++ else: ++ raise + + +async def find_device(initial_devices): @@ -8874,6 +8806,8 @@ index 7b4000fa9c3..08c9405a465 100644 + ) as process: + suppress_dupes = False + while line := (await process.stdout.readline()).decode(*DECODE_ARGS): ++ # Strip the prefix from each log line ++ line = LOG_PREFIX_REGEX.sub("", line) + # The iOS log streamer can sometimes lag; when it does, it outputs + # a warning about messages being dropped... often multiple times. + # Only print the first of these duplicated warnings. @@ -8969,33 +8903,69 @@ index 7b4000fa9c3..08c9405a465 100644 + shutil.copytree(source, target, symlinks=True) + print(" done") + ++ xc_framework_path = target / "Python.xcframework" ++ sim_framework_path = xc_framework_path / "ios-arm64_x86_64-simulator" + if framework is not None: + if framework.suffix == ".xcframework": + print(" Installing XCFramework...", end="", flush=True) -+ xc_framework_path = (target / "Python.xcframework").resolve() + if xc_framework_path.is_dir(): + shutil.rmtree(xc_framework_path) + else: -+ xc_framework_path.unlink() ++ xc_framework_path.unlink(missing_ok=True) + xc_framework_path.symlink_to( + relative_to(framework, xc_framework_path.parent) + ) + print(" done") + else: + print(" Installing simulator framework...", end="", flush=True) -+ sim_framework_path = ( -+ target / "Python.xcframework" / "ios-arm64_x86_64-simulator" -+ ).resolve() + if sim_framework_path.is_dir(): + shutil.rmtree(sim_framework_path) + else: -+ sim_framework_path.unlink() ++ sim_framework_path.unlink(missing_ok=True) + sim_framework_path.symlink_to( + relative_to(framework, sim_framework_path.parent) + ) + print(" done") + else: -+ print(" Using pre-existing iOS framework.") ++ if ( ++ xc_framework_path.is_symlink() ++ and not xc_framework_path.readlink().is_absolute() ++ ): ++ # XCFramework is a relative symlink. Rewrite the symlink relative ++ # to the new location. ++ print(" Rewriting symlink to XCframework...", end="", flush=True) ++ orig_xc_framework_path = ( ++ source ++ / xc_framework_path.readlink() ++ ).resolve() ++ xc_framework_path.unlink() ++ xc_framework_path.symlink_to( ++ orig_xc_framework_path.relative_to( ++ xc_framework_path.parent, walk_up=True ++ ) ++ ) ++ print(" done") ++ elif ( ++ sim_framework_path.is_symlink() ++ and not sim_framework_path.readlink().is_absolute() ++ ): ++ print(" Rewriting symlink to simulator framework...", end="", flush=True) ++ # Simulator framework is a relative symlink. Rewrite the symlink ++ # relative to the new location. ++ orig_sim_framework_path = ( ++ source ++ / "Python.XCframework" ++ / sim_framework_path.readlink() ++ ).resolve() ++ sim_framework_path.unlink() ++ sim_framework_path.symlink_to( ++ orig_sim_framework_path.relative_to( ++ sim_framework_path.parent, walk_up=True ++ ) ++ ) ++ print(" done") ++ else: ++ print(" Using pre-existing iOS framework.") + + for app_src in apps: + print(f" Installing app {app_src.name!r}...", end="", flush=True) @@ -9111,8 +9081,8 @@ index 7b4000fa9c3..08c9405a465 100644 + + if context.subcommand == "clone": + clone_testbed( -+ source=Path(__file__).parent, -+ target=Path(context.location), ++ source=Path(__file__).parent.resolve(), ++ target=Path(context.location).resolve(), + framework=Path(context.framework).resolve() if context.framework else None, + apps=[Path(app) for app in context.apps], + ) @@ -9952,7 +9922,7 @@ index 7b4000fa9c3..08c9405a465 100644 +} --- /dev/null +++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m -@@ -0,0 +1,162 @@ +@@ -0,0 +1,160 @@ +#import +#import + @@ -9983,7 +9953,7 @@ index 7b4000fa9c3..08c9405a465 100644 + // Xcode log can't display color. Stdout will report that it is *not* a + // TTY. + setenv("NO_COLOR", "1", true); -+ setenv("PY_COLORS", "0", true); ++ setenv("PYTHON_COLORS", "0", true); + + // Arguments to pass into the test suite runner. + // argv[0] must identify the process; any subsequent arg @@ -10008,8 +9978,6 @@ index 7b4000fa9c3..08c9405a465 100644 + // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. + // See https://docs.python.org/3/library/os.html#python-utf-8-mode. + preconfig.utf8_mode = 1; -+ // Use the system logger for stdout/err -+ config.use_system_logger = 1; + // Don't buffer stdio. We want output to appears in the log immediately + config.buffered_stdio = 0; + // Don't write bytecode; we can't modify the app bundle diff --git a/patch/Python/_cross_target.py.tmpl b/patch/Python/_cross_target.py.tmpl new file mode 100644 index 00000000..5f822e24 --- /dev/null +++ b/patch/Python/_cross_target.py.tmpl @@ -0,0 +1,78 @@ +# A site package that turns a macOS virtual environment +# into an {{arch}} {{sdk}} cross-platform virtual environment +import collections +import platform +import subprocess +import sys +import sysconfig + +########################################################################### +# sys module patches +########################################################################### +sys.cross_compiling = True +sys.platform = "{{platform}}" +sys.implementation._multiarch = "{{arch}}-{{sdk}}" + +########################################################################### +# subprocess module patches +########################################################################### +subprocess._can_fork_exec = True + + +########################################################################### +# platform module patches +########################################################################### + +def cross_system(): + return "{{os}}" + + +def cross_uname(): + return platform.uname_result( + system="{{os}}", + node="build", + release="{{version_min}}", + version="", + machine="{{arch}}", + ) + + +platform.IOSVersionInfo = collections.namedtuple( + "IOSVersionInfo", + ["system", "release", "model", "is_simulator"] +) + + +def cross_ios_ver(system="", release="", model="", is_simulator=False): + if system == "": + system = "{{os}}" + if release == "": + release = "{{version_min}}" + if model == "": + model = "{{sdk}}" + + return platform.IOSVersionInfo(system, release, model, {{is_simulator}}) + + +platform.system = cross_system +platform.uname = cross_uname +platform.ios_ver = cross_ios_ver + + +########################################################################### +# sysconfig module patches +########################################################################### + +def cross_get_platform(): + return "{{platform}}-{{version_min}}-{{arch}}-{{sdk}}" + + +def cross_get_sysconfigdata_name(): + return "_sysconfigdata__{{platform}}_{{arch}}-{{sdk}}" + + +sysconfig.get_platform = cross_get_platform +sysconfig._get_sysconfigdata_name = cross_get_sysconfigdata_name + +# Force sysconfig data to be loaded (and cached). +sysconfig._init_config_vars() diff --git a/patch/Python/_cross_venv.py b/patch/Python/_cross_venv.py new file mode 100644 index 00000000..a51e5cbe --- /dev/null +++ b/patch/Python/_cross_venv.py @@ -0,0 +1,103 @@ +import shutil +import sys +import sysconfig +from pathlib import Path + +SITE_PACKAGE_PATH = Path(__file__).parent + +########################################################################### +# importlib module patches +########################################################################### + + +def patch_env_create(env): + """ + Patch the process of creating virtual environments to ensure that the cross + environment modification files are also copied as part of environment + creation. + """ + old_pip_env_create = env._PipBackend.create + + def pip_env_create(self, path, *args, **kwargs): + result = old_pip_env_create(self, path, *args, **kwargs) + # Copy any _cross_*.pth or _cross_*.py file, plus the cross-platform + # sysconfigdata module to the new environment. + data_name = sysconfig._get_sysconfigdata_name() + for filename in [ + "_cross_venv.pth", + "_cross_venv.py", + f"_cross_{sys.implementation._multiarch.replace('-', '_')}.py", + f"{data_name}.py", + ]: + src = SITE_PACKAGE_PATH / filename + target = Path(path) / src.relative_to( + SITE_PACKAGE_PATH.parent.parent.parent + ) + if not target.exists(): + shutil.copy(src, target) + return result + + env._PipBackend.create = pip_env_create + + +# Import hook that patches the creation of virtual environments by `build` +# +# The approach used here is the same as the one used by virtualenv to patch +# distutils (but without support for the older load_module API). +# https://docs.python.org/3/library/importlib.html#setting-up-an-importer +_BUILD_PATCH = ("build.env",) + + +class _Finder: + """A meta path finder that allows patching the imported build modules.""" + + fullname = None + + # lock[0] is threading.Lock(), but initialized lazily to avoid importing + # threading very early at startup, because there are gevent-based + # applications that need to be first to import threading by themselves. + # See https://github.com/pypa/virtualenv/issues/1895 for details. + lock = [] # noqa: RUF012 + + def find_spec(self, fullname, path, target=None): + if fullname in _BUILD_PATCH and self.fullname is None: + # initialize lock[0] lazily + if len(self.lock) == 0: + import threading + + lock = threading.Lock() + # there is possibility that two threads T1 and T2 are + # simultaneously running into find_spec, observing .lock as + # empty, and further going into hereby initialization. However + # due to the GIL, list.append() operation is atomic and this + # way only one of the threads will "win" to put the lock + # - that every thread will use - into .lock[0]. + # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe + self.lock.append(lock) + + from functools import partial + from importlib.util import find_spec + + with self.lock[0]: + self.fullname = fullname + try: + spec = find_spec(fullname, path) + if spec is not None: + # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work + old = spec.loader.exec_module + func = self.exec_module + if old is not func: + spec.loader.exec_module = partial(func, old) + return spec + finally: + self.fullname = None + return None + + @staticmethod + def exec_module(old, module): + old(module) + if module.__name__ in _BUILD_PATCH: + patch_env_create(module) + + +sys.meta_path.insert(0, _Finder()) diff --git a/patch/Python/make_cross_venv.py b/patch/Python/make_cross_venv.py new file mode 100644 index 00000000..7eb9da21 --- /dev/null +++ b/patch/Python/make_cross_venv.py @@ -0,0 +1,123 @@ +import pprint +import shutil +import sys +from pathlib import Path +from importlib import util as importlib_util + + +def localized_vars(orig_vars, slice_path): + """Update (where possible) any references to build-time variables with the + best guess of the installed location. + """ + # The host's sysconfigdata will include references to build-time variables. + # Update these to refer to the current known install location. + orig_prefix = orig_vars["prefix"] + localized_vars = {} + for key, value in orig_vars.items(): + final = value + if isinstance(value, str): + # Replace any reference to the build installation prefix + final = final.replace(orig_prefix, str(slice_path)) + # Replace any reference to the build-time Framework location + final = final.replace("-F .", f"-F {slice_path}") + localized_vars[key] = final + + return localized_vars + + +def localize_sysconfigdata(platform_config_path, venv_site_packages): + """Localize a sysconfigdata python module. + + :param platform_config_path: The platform config that contains the + sysconfigdata module to localize. + :param venv_site_packages: The site packages folder where the localized + sysconfigdata module should be output. + """ + # Find the "_sysconfigdata_*.py" file in the platform config + sysconfigdata_path = next(platform_config_path.glob("_sysconfigdata_*.py")) + + # Import the sysconfigdata module + spec = importlib_util.spec_from_file_location( + sysconfigdata_path.stem, + sysconfigdata_path + ) + if spec is None: + msg = f"Unable to load spec for {sysconfigdata_path}" + raise ValueError(msg) + if spec.loader is None: + msg = f"Spec for {sysconfigdata_path} does not define a loader" + raise ValueError(msg) + sysconfigdata = importlib_util.module_from_spec(spec) + spec.loader.exec_module(sysconfigdata) + + # Write the updated sysconfigdata module into the cross-platform site. + slice_path = sysconfigdata_path.parent.parent.parent + with (venv_site_packages / sysconfigdata_path.name).open("w") as f: + f.write(f"# Generated from {sysconfigdata_path}\n") + f.write("build_time_vars = ") + pprint.pprint( + localized_vars(sysconfigdata.build_time_vars, slice_path), + stream=f, + compact=True + ) + + +def make_cross_venv(venv_path: Path, platform_config_path: Path): + """Convert a virtual environment into a cross-platform environment. + + :param venv_path: The path to the root of the venv. + :param platform_config_path: The path containing the platform config. + """ + if not venv_path.exists(): + raise ValueError(f"Virtual environment {venv_path} does not exist.") + if not (venv_path / "bin/python3").exists(): + raise ValueError(f"{venv_path} does not appear to be a virtual environment.") + + print( + f"Converting {venv_path} into a {platform_config_path.name} environment... ", + end="", + ) + + LIB_PATH = f"lib/python{sys.version_info[0]}.{sys.version_info[1]}" + + # Update path references in the sysconfigdata to reflect local conditions. + venv_site_packages = venv_path / LIB_PATH / "site-packages" + localize_sysconfigdata(platform_config_path, venv_site_packages) + + # Copy in the site-package environment modifications. + cross_multiarch = f"_cross_{platform_config_path.name.replace('-', '_')}" + shutil.copy( + platform_config_path / f"{cross_multiarch}.py", + venv_site_packages / f"{cross_multiarch}.py", + ) + shutil.copy( + platform_config_path / "_cross_venv.py", + venv_site_packages / "_cross_venv.py", + ) + # Write the .pth file that will enable the cross-env modifications + (venv_site_packages / "_cross_venv.pth").write_text( + f"import {cross_multiarch}; import _cross_venv\n" + ) + + print("done.") + + +if __name__ == "__main__": + try: + platform_config_path = Path(sys.argv[2]).resolve() + except IndexError: + platform_config_path = Path(__file__).parent + + try: + venv_path = Path(sys.argv[1]).resolve() + make_cross_venv(venv_path, platform_config_path) + except IndexError: + print(""" +Convert a virtual environment in to a cross-platform environment. + +Usage: + make_cross_venv () + +If an explicit platform config isn't provided, it is assumed the directory +containing the make_cross_venv script *is* a platform config. +""") diff --git a/patch/Python/module.modulemap.prefix b/patch/Python/module.modulemap.prefix new file mode 100644 index 00000000..e3b3aafb --- /dev/null +++ b/patch/Python/module.modulemap.prefix @@ -0,0 +1,20 @@ +module Python { + umbrella header "Python.h" + export * + link "Python" + + exclude header "datetime.h" + exclude header "dynamic_annotations.h" + exclude header "errcode.h" + exclude header "frameobject.h" + exclude header "marshal.h" + exclude header "opcode_ids.h" + exclude header "opcode.h" + exclude header "osdefs.h" + exclude header "py_curses.h" + exclude header "pyconfig-arm32_64.h" + exclude header "pyconfig-arm64.h" + exclude header "pyconfig-x86_64.h" + exclude header "pydtrace.h" + exclude header "pyexpat.h" + exclude header "structmember.h" diff --git a/patch/Python/sitecustomize.iOS.py b/patch/Python/sitecustomize.iOS.py deleted file mode 100644 index ccc291f3..00000000 --- a/patch/Python/sitecustomize.iOS.py +++ /dev/null @@ -1,114 +0,0 @@ -# A site customization that can be used to trick pip into installing -# packages cross-platform. If the folder containing this file is on -# your PYTHONPATH when you invoke pip, pip will behave as if it were -# running on {{os}}. -import collections -import distutils.ccompiler -import distutils.unixccompiler -import os -import platform -import sys -import sysconfig -import types - -# Make platform.system() return "{{os}}" -def custom_system(): - return "{{os}}" - -platform.system = custom_system - -# Make platform.ios_ver() return an appropriate namedtuple -IOSVersionInfo = collections.namedtuple( - "IOSVersionInfo", - ["system", "release", "model", "is_simulator"] -) - -def custom_ios_ver(system="", release="", model="", is_simulator=False): - return IOSVersionInfo("{{os}}", "{{version_min}}", "iPhone", {{is_simulator}}) - -platform.ios_ver = custom_ios_ver - -# Make sys.implementation._multiarch return the multiarch description -sys.implementation._multiarch = "{{multiarch}}" - -# Make sysconfig.get_platform() return the platform tag -def custom_get_platform(): - return "{{tag}}" - -sysconfig.get_platform = custom_get_platform - -# Make distutils raise errors if you try to use it to build modules. -DISABLED_COMPILER_ERROR = "Cannot compile native modules" - -distutils.ccompiler.get_default_compiler = lambda *args, **kwargs: "disabled" -distutils.ccompiler.compiler_class["disabled"] = ( - "disabledcompiler", - "DisabledCompiler", - "Compiler disabled ({})".format(DISABLED_COMPILER_ERROR), -) - - -def disabled_compiler(prefix): - # No need to give any more advice here: that will come from the higher-level code in pip. - from distutils.errors import DistutilsPlatformError - - raise DistutilsPlatformError("{}: {}".format(prefix, DISABLED_COMPILER_ERROR)) - - -class DisabledCompiler(distutils.ccompiler.CCompiler): - compiler_type = "disabled" - - def preprocess(*args, **kwargs): - disabled_compiler("CCompiler.preprocess") - - def compile(*args, **kwargs): - disabled_compiler("CCompiler.compile") - - def create_static_lib(*args, **kwargs): - disabled_compiler("CCompiler.create_static_lib") - - def link(*args, **kwargs): - disabled_compiler("CCompiler.link") - - -# To maximize the chance of the build getting as far as actually calling compile(), make -# sure the class has all of the expected attributes. -for name in [ - "src_extensions", - "obj_extension", - "static_lib_extension", - "shared_lib_extension", - "static_lib_format", - "shared_lib_format", - "exe_extension", -]: - setattr( - DisabledCompiler, name, getattr(distutils.unixccompiler.UnixCCompiler, name) - ) - -DisabledCompiler.executables = { - name: [DISABLED_COMPILER_ERROR.replace(" ", "_")] - for name in distutils.unixccompiler.UnixCCompiler.executables -} - -disabled_mod = types.ModuleType("distutils.disabledcompiler") -disabled_mod.DisabledCompiler = DisabledCompiler -sys.modules["distutils.disabledcompiler"] = disabled_mod - - -# Try to disable native builds for packages which don't use the distutils native build -# system at all (e.g. uwsgi), or only use it to wrap an external build script (e.g. pynacl). -for tool in ["ar", "as", "cc", "cxx", "ld"]: - os.environ[tool.upper()] = DISABLED_COMPILER_ERROR.replace(" ", "_") - - -# Call the next sitecustomize script if there is one -# (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html). -del sys.modules["sitecustomize"] -this_dir = os.path.dirname(__file__) -path_index = sys.path.index(this_dir) -del sys.path[path_index] -try: - import sitecustomize # noqa: F401 -finally: - sys.path.insert(path_index, this_dir) diff --git a/patch/Python/sitecustomize.macOS.py b/patch/Python/sitecustomize.macOS.py deleted file mode 100644 index 500714da..00000000 --- a/patch/Python/sitecustomize.macOS.py +++ /dev/null @@ -1,14 +0,0 @@ -# A site customization that can be used to trick pip into installing -# packages cross-platform. If the folder containing this file is on -# your PYTHONPATH when you invoke pip, pip will behave as if it were -# running on {{arch}}. -import platform - -# Make platform.mac_ver() return {{arch}} -orig_mac_ver = platform.mac_ver - -def custom_mac_ver(): - orig = orig_mac_ver() - return orig[0], orig[1], "{{arch}}" - -platform.mac_ver = custom_mac_ver diff --git a/patch/Python/sitecustomize.py.tmpl b/patch/Python/sitecustomize.py.tmpl new file mode 100644 index 00000000..0330575a --- /dev/null +++ b/patch/Python/sitecustomize.py.tmpl @@ -0,0 +1,22 @@ +# A site customization that can be used to trick pip into installing packages +# cross-platform. If the folder containing this file is on your PYTHONPATH when +# you invoke python, the interpreter will behave as if it were running on +# {{arch}} {{sdk}}. +import sys +import os + +# Apply the cross-platform patch +import _cross_{{arch}}_{{sdk}} +import _cross_venv + + +# Call the next sitecustomize script if there is one +# (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html). +del sys.modules["sitecustomize"] +this_dir = os.path.dirname(__file__) +path_index = sys.path.index(this_dir) +del sys.path[path_index] +try: + import sitecustomize # noqa: F401 +finally: + sys.path.insert(path_index, this_dir) diff --git a/patch/Python/sitecustomize.tvOS.py b/patch/Python/sitecustomize.tvOS.py deleted file mode 100644 index d7d86e36..00000000 --- a/patch/Python/sitecustomize.tvOS.py +++ /dev/null @@ -1,99 +0,0 @@ -# A site customization that can be used to trick pip into installing -# packages cross-platform. If the folder containing this file is on -# your PYTHONPATH when you invoke pip, pip will behave as if it were -# running on {{os}}. -import distutils.ccompiler -import distutils.unixccompiler -import os -import platform -import sys -import sysconfig -import types - -# Make platform.system() return "{{os}}" -def custom_system(): - return "{{os}}" - -platform.system = custom_system - -# Make sysconfig.get_platform() return "{{tag}}" -def custom_get_platform(): - return "{{tag}}" - -sysconfig.get_platform = custom_get_platform - -# Make distutils raise errors if you try to use it to build modules. -DISABLED_COMPILER_ERROR = "Cannot compile native modules" - -distutils.ccompiler.get_default_compiler = lambda *args, **kwargs: "disabled" -distutils.ccompiler.compiler_class["disabled"] = ( - "disabledcompiler", - "DisabledCompiler", - "Compiler disabled ({})".format(DISABLED_COMPILER_ERROR), -) - - -def disabled_compiler(prefix): - # No need to give any more advice here: that will come from the higher-level code in pip. - from distutils.errors import DistutilsPlatformError - - raise DistutilsPlatformError("{}: {}".format(prefix, DISABLED_COMPILER_ERROR)) - - -class DisabledCompiler(distutils.ccompiler.CCompiler): - compiler_type = "disabled" - - def preprocess(*args, **kwargs): - disabled_compiler("CCompiler.preprocess") - - def compile(*args, **kwargs): - disabled_compiler("CCompiler.compile") - - def create_static_lib(*args, **kwargs): - disabled_compiler("CCompiler.create_static_lib") - - def link(*args, **kwargs): - disabled_compiler("CCompiler.link") - - -# To maximize the chance of the build getting as far as actually calling compile(), make -# sure the class has all of the expected attributes. -for name in [ - "src_extensions", - "obj_extension", - "static_lib_extension", - "shared_lib_extension", - "static_lib_format", - "shared_lib_format", - "exe_extension", -]: - setattr( - DisabledCompiler, name, getattr(distutils.unixccompiler.UnixCCompiler, name) - ) - -DisabledCompiler.executables = { - name: [DISABLED_COMPILER_ERROR.replace(" ", "_")] - for name in distutils.unixccompiler.UnixCCompiler.executables -} - -disabled_mod = types.ModuleType("distutils.disabledcompiler") -disabled_mod.DisabledCompiler = DisabledCompiler -sys.modules["distutils.disabledcompiler"] = disabled_mod - - -# Try to disable native builds for packages which don't use the distutils native build -# system at all (e.g. uwsgi), or only use it to wrap an external build script (e.g. pynacl). -for tool in ["ar", "as", "cc", "cxx", "ld"]: - os.environ[tool.upper()] = DISABLED_COMPILER_ERROR.replace(" ", "_") - - -# Call the next sitecustomize script if there is one -# (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html). -del sys.modules["sitecustomize"] -this_dir = os.path.dirname(__file__) -path_index = sys.path.index(this_dir) -del sys.path[path_index] -try: - import sitecustomize # noqa: F401 -finally: - sys.path.insert(path_index, this_dir) diff --git a/patch/Python/sitecustomize.watchOS.py b/patch/Python/sitecustomize.watchOS.py deleted file mode 100644 index d7d86e36..00000000 --- a/patch/Python/sitecustomize.watchOS.py +++ /dev/null @@ -1,99 +0,0 @@ -# A site customization that can be used to trick pip into installing -# packages cross-platform. If the folder containing this file is on -# your PYTHONPATH when you invoke pip, pip will behave as if it were -# running on {{os}}. -import distutils.ccompiler -import distutils.unixccompiler -import os -import platform -import sys -import sysconfig -import types - -# Make platform.system() return "{{os}}" -def custom_system(): - return "{{os}}" - -platform.system = custom_system - -# Make sysconfig.get_platform() return "{{tag}}" -def custom_get_platform(): - return "{{tag}}" - -sysconfig.get_platform = custom_get_platform - -# Make distutils raise errors if you try to use it to build modules. -DISABLED_COMPILER_ERROR = "Cannot compile native modules" - -distutils.ccompiler.get_default_compiler = lambda *args, **kwargs: "disabled" -distutils.ccompiler.compiler_class["disabled"] = ( - "disabledcompiler", - "DisabledCompiler", - "Compiler disabled ({})".format(DISABLED_COMPILER_ERROR), -) - - -def disabled_compiler(prefix): - # No need to give any more advice here: that will come from the higher-level code in pip. - from distutils.errors import DistutilsPlatformError - - raise DistutilsPlatformError("{}: {}".format(prefix, DISABLED_COMPILER_ERROR)) - - -class DisabledCompiler(distutils.ccompiler.CCompiler): - compiler_type = "disabled" - - def preprocess(*args, **kwargs): - disabled_compiler("CCompiler.preprocess") - - def compile(*args, **kwargs): - disabled_compiler("CCompiler.compile") - - def create_static_lib(*args, **kwargs): - disabled_compiler("CCompiler.create_static_lib") - - def link(*args, **kwargs): - disabled_compiler("CCompiler.link") - - -# To maximize the chance of the build getting as far as actually calling compile(), make -# sure the class has all of the expected attributes. -for name in [ - "src_extensions", - "obj_extension", - "static_lib_extension", - "shared_lib_extension", - "static_lib_format", - "shared_lib_format", - "exe_extension", -]: - setattr( - DisabledCompiler, name, getattr(distutils.unixccompiler.UnixCCompiler, name) - ) - -DisabledCompiler.executables = { - name: [DISABLED_COMPILER_ERROR.replace(" ", "_")] - for name in distutils.unixccompiler.UnixCCompiler.executables -} - -disabled_mod = types.ModuleType("distutils.disabledcompiler") -disabled_mod.DisabledCompiler = DisabledCompiler -sys.modules["distutils.disabledcompiler"] = disabled_mod - - -# Try to disable native builds for packages which don't use the distutils native build -# system at all (e.g. uwsgi), or only use it to wrap an external build script (e.g. pynacl). -for tool in ["ar", "as", "cc", "cxx", "ld"]: - os.environ[tool.upper()] = DISABLED_COMPILER_ERROR.replace(" ", "_") - - -# Call the next sitecustomize script if there is one -# (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html). -del sys.modules["sitecustomize"] -this_dir = os.path.dirname(__file__) -path_index = sys.path.index(this_dir) -del sys.path[path_index] -try: - import sitecustomize # noqa: F401 -finally: - sys.path.insert(path_index, this_dir) From 2edd665f8eb5bad77ae3ef0ae43a30948d4c12a0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 19 Mar 2025 14:22:34 +0800 Subject: [PATCH 10/13] Add missing modulemap base files. --- patch/Python/module.modulemap.prefix | 1 + 1 file changed, 1 insertion(+) diff --git a/patch/Python/module.modulemap.prefix b/patch/Python/module.modulemap.prefix index e3b3aafb..2eaff5ed 100644 --- a/patch/Python/module.modulemap.prefix +++ b/patch/Python/module.modulemap.prefix @@ -18,3 +18,4 @@ module Python { exclude header "pydtrace.h" exclude header "pyexpat.h" exclude header "structmember.h" + exclude header "token.h" From fa319a764b3ca48b8d9305fb2206255cadaf5660 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 31 Jan 2026 14:15:34 +0800 Subject: [PATCH 11/13] Update patch to Python 3.11.14 (#343) --- .github/dependabot.yml | 7 +- .github/workflows/ci.yaml | 215 +- .github/workflows/publish.yaml | 4 +- .github/workflows/release.yaml | 4 +- Makefile | 104 +- patch/Python/OpenSSL.xcprivacy | 23 + patch/Python/Python.patch | 22500 +++++++++++++++------------ patch/Python/_cross_target.py.tmpl | 9 +- tests/test_cross_env.py | 98 + 9 files changed, 12886 insertions(+), 10078 deletions(-) create mode 100644 patch/Python/OpenSSL.xcprivacy create mode 100644 tests/test_cross_env.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ecbb02f7..c35bb42d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - # Check for updates on Sunday, 8PM UTC - interval: "weekly" - day: "sunday" - time: "20:00" + # Check for updates on the first Sunday of every month, 8PM UTC + interval: "cron" + cronjob: "0 20 * * sun#1" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6befbc46..d726b62d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -56,7 +56,7 @@ jobs: XZ_VERSION: ${{ steps.extract.outputs.XZ_VERSION }} steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v6 - name: Extract config variables id: extract @@ -84,28 +84,27 @@ jobs: echo "XZ_VERSION=${XZ_VERSION}" | tee -a ${GITHUB_OUTPUT} build: - runs-on: macOS-latest + runs-on: macOS-15 needs: [ config ] strategy: fail-fast: false matrix: - target: ['macOS', 'iOS', 'tvOS', 'watchOS'] - include: - - briefcase-run-args: - - run-tests: false - - - target: macOS - run-tests: true - - - target: iOS - briefcase-run-args: ' -d "iPhone SE (3rd generation)"' - run-tests: true + platform: ['macOS', 'iOS', 'tvOS', 'watchOS'] steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v6 + + - name: Set up Xcode + # GitHub recommends explicitly selecting the desired Xcode version: + # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 + # This became a necessity as a result of + # https://github.com/actions/runner-images/issues/12541 and + # https://github.com/actions/runner-images/issues/12751. + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app - name: Set up Python - uses: actions/setup-python@v5.4.0 + uses: actions/setup-python@v6.1.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. @@ -114,31 +113,193 @@ jobs: # It's an edge case, but when a new alpha is released, we need to use it ASAP. check-latest: true - - name: Build ${{ matrix.target }} + - name: Build ${{ matrix.platform }} run: | - # Do the build for the requested target. - make ${{ matrix.target }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} + # Do the build for the requested platform. + make ${{ matrix.platform }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} - name: Upload build artefacts - uses: actions/upload-artifact@v4.6.1 + uses: actions/upload-artifact@v6.0.0 + with: + name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + + briefcase-testbed: + name: Briefcase testbed (${{ matrix.platform }}) + runs-on: macOS-15 + needs: [ config, build ] + strategy: + fail-fast: false + matrix: + platform: ["macOS", "iOS"] + include: + - briefcase-run-args: + + - platform: iOS + briefcase-run-args: ' -d "iPhone 16e::iOS 18.5"' + + steps: + - uses: actions/checkout@v6 + + - name: Set up Xcode + # GitHub recommends explicitly selecting the desired Xcode version: + # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 + # This became a necessity as a result of + # https://github.com/actions/runner-images/issues/12541 and + # https://github.com/actions/runner-images/issues/12751. + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app + + - name: Get build artifact + uses: actions/download-artifact@v7.0.0 with: - name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist + merge-multiple: true - - uses: actions/checkout@v4.1.7 - if: matrix.run-tests + - name: Set up Python + uses: actions/setup-python@v6.1.0 + with: + # Appending -dev ensures that we can always build the dev release. + # It's a no-op for versions that have been published. + python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true + + - uses: actions/checkout@v6 with: repository: beeware/Python-support-testbed path: Python-support-testbed - name: Install dependencies - if: matrix.run-tests run: | # Use the development version of Briefcase python -m pip install git+https://github.com/beeware/briefcase.git - name: Run support testbed check - if: matrix.run-tests - timeout-minutes: 10 + timeout-minutes: 15 working-directory: Python-support-testbed - run: briefcase run ${{ matrix.target }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\' + run: briefcase run ${{ matrix.platform }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\' + + cpython-testbed: + name: CPython testbed (${{ matrix.platform }}) + runs-on: macOS-15 + needs: [ config, build ] + strategy: + fail-fast: false + matrix: + platform: ["iOS", "tvOS"] + include: + - platform: "iOS" + testbed-args: '--simulator "iPhone 16e,arch=arm64,OS=18.5"' + + steps: + - uses: actions/checkout@v6 + + - name: Get build artifact + uses: actions/download-artifact@v7.0.0 + with: + pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist + merge-multiple: true + + - name: Set up Xcode + # GitHub recommends explicitly selecting the desired Xcode version: + # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 + # This became a necessity as a result of + # https://github.com/actions/runner-images/issues/12541 and + # https://github.com/actions/runner-images/issues/12751. + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app + + - name: Set up Python + uses: actions/setup-python@v6.1.0 + with: + # Appending -dev ensures that we can always build the dev release. + # It's a no-op for versions that have been published. + python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true + + - name: Unpack support package + run: | + mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + + - name: Run CPython testbed + timeout-minutes: 15 + working-directory: support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + run: | + # Run a representative subset of CPython core tests: + # - test_builtin as a test of core language tools + # - test_grammar as a test of core language features + # - test_os as a test of system library calls + # - test_bz2 as a simple test of third party libraries + # - test_ctypes as a test of FFI + python -m testbed run --verbose ${{ matrix.testbed-args }} -- test --rerun -W --timeout=-1 test_builtin test_grammar test_os test_bz2 test_ctypes + + crossenv-test: + name: Cross-platform env test (${{ matrix.multiarch }}) + runs-on: macOS-latest + needs: [ config, build ] + strategy: + fail-fast: false + matrix: + include: + - platform: iOS + slice: ios-arm64_x86_64-simulator + multiarch: arm64-iphonesimulator + - platform: iOS + slice: ios-arm64_x86_64-simulator + multiarch: x86_64-iphonesimulator + - platform: iOS + slice: ios-arm64 + multiarch: arm64-iphoneos + + steps: + - uses: actions/checkout@v6 + + - name: Get build artifact + uses: actions/download-artifact@v7.0.0 + with: + pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist + merge-multiple: true + + - name: Set up Python + uses: actions/setup-python@v6.1.0 + with: + # Appending -dev ensures that we can always build the dev release. + # It's a no-op for versions that have been published. + python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true + + - name: Unpack support package + run: | + mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + + - name: Run cross-platform environment test + env: + PYTHON_CROSS_PLATFORM: ${{ matrix.platform }} + PYTHON_CROSS_SLICE: ${{ matrix.slice }} + PYTHON_CROSS_MULTIARCH: ${{ matrix.multiarch }} + run: | + # Create and activate a native virtual environment + python${{ needs.config.outputs.PYTHON_VER }} -m venv cross-venv + source cross-venv/bin/activate + + # Install pytest + python -m pip install pytest + + # Convert venv into cross-venv + python support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}/Python.xcframework/${{ matrix.slice }}/platform-config/${{ matrix.multiarch }}/make_cross_venv.py cross-venv + + # Run the test suite + python -m pytest tests diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 5d2ce753..8ba1300f 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -8,10 +8,10 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python environment - uses: actions/setup-python@v5.4.0 + uses: actions/setup-python@v6.1.0 with: python-version: "3.X" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 61da403c..1b65e9fd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,14 +40,14 @@ jobs: needs: [ config, ci ] steps: - name: Get build artifacts - uses: actions/download-artifact@v4.1.9 + uses: actions/download-artifact@v7.0.0 with: pattern: Python-* path: dist merge-multiple: true - name: Create Release - uses: ncipollo/release-action@v1.16.0 + uses: ncipollo/release-action@v1.20.0 with: name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} diff --git a/Makefile b/Makefile index 49b72834..4c6e2dfc 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ BUILD_NUMBER=custom # of a release cycle, as official binaries won't be published. # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.11.11 +PYTHON_VERSION=3.11.14 PYTHON_PKG_VERSION=3.11.9 PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+") @@ -26,11 +26,11 @@ PYTHON_VER=$(basename $(PYTHON_VERSION)) # The binary releases of dependencies, published at: # https://github.com/beeware/cpython-apple-source-deps/releases -BZIP2_VERSION=1.0.8-1 -LIBFFI_VERSION=3.4.7-1 -MPDECIMAL_VERSION=4.0.0-1 -OPENSSL_VERSION=3.0.16-1 -XZ_VERSION=5.6.4-1 +BZIP2_VERSION=1.0.8-2 +LIBFFI_VERSION=3.4.7-2 +MPDECIMAL_VERSION=4.0.0-2 +OPENSSL_VERSION=3.0.18-1 +XZ_VERSION=5.6.4-2 # Supported OS OS_LIST=macOS iOS tvOS watchOS @@ -39,18 +39,26 @@ CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar # macOS targets TARGETS-macOS=macosx.x86_64 macosx.arm64 +TRIPLE_OS-macOS=macos +PLATFORM_NAME-macOS=macOS VERSION_MIN-macOS=11.0 # iOS targets TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64 +TRIPLE_OS-iOS=ios +PLATFORM_NAME-iOS=iOS VERSION_MIN-iOS=13.0 # tvOS targets TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64 +TRIPLE_OS-tvOS=tvos +PLATFORM_NAME-tvOS=tvOS VERSION_MIN-tvOS=12.0 # watchOS targets TARGETS-watchOS=watchsimulator.x86_64 watchsimulator.arm64 watchos.arm64_32 +TRIPLE_OS-watchOS=watchos +PLATFORM_NAME-watchOS=watchOS VERSION_MIN-watchOS=4.0 # The architecture of the machine doing the build @@ -80,13 +88,13 @@ update-patch: # comparing between the current state of the 3.X branch against the v3.X.Y # tag associated with the release being built. This allows you to # maintain a branch that contains custom patches against the default Python. - # The patch archived in this respository is based on github.com/freakboy3742/cpython + # The patch archived in this repository is based on github.com/freakboy3742/cpython # Requires patchutils (installable via `brew install patchutils`); this # also means we need to re-introduce homebrew to the path for the filterdiff # call if [ -z "$(PYTHON_REPO_DIR)" ]; then echo "\n\nPYTHON_REPO_DIR must be set to the root of your Python github checkout\n\n"; fi cd $(PYTHON_REPO_DIR) && \ - git diff -D v$(PYTHON_VERSION) $(PYTHON_VER)-patched \ + git diff --no-renames -D v$(PYTHON_VERSION) $(PYTHON_VER)-patched \ | PATH="/usr/local/bin:/opt/homebrew/bin:$(PATH)" filterdiff \ -X $(PROJECT_DIR)/patch/Python/diff.exclude -p 1 --clean \ > $(PROJECT_DIR)/patch/Python/Python.patch @@ -128,10 +136,10 @@ ARCH-$(target)=$$(subst .,,$$(suffix $(target))) ifneq ($(os),macOS) ifeq ($$(findstring simulator,$$(SDK-$(target))),) -TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os)) +TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(TRIPLE_OS-$(os))$$(VERSION_MIN-$(os)) IS_SIMULATOR-$(target)=False else -TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os))-simulator +TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(TRIPLE_OS-$(os))$$(VERSION_MIN-$(os))-simulator IS_SIMULATOR-$(target)=True endif endif @@ -278,7 +286,7 @@ $$(PYTHON_SRCDIR-$(target))/configure: \ # Apply target Python patches cd $$(PYTHON_SRCDIR-$(target)) && patch -p1 < $(PROJECT_DIR)/patch/Python/Python.patch # Make sure the binary scripts are executable - chmod 755 $$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin/* + chmod 755 $$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin/* # Touch the configure script to ensure that Make identifies it as up to date. touch $$(PYTHON_SRCDIR-$(target))/configure @@ -286,7 +294,7 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \ $$(PYTHON_SRCDIR-$(target))/configure # Configure target Python cd $$(PYTHON_SRCDIR-$(target)) && \ - PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ + PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \ ./configure \ LIBLZMA_CFLAGS="-I$$(XZ_INSTALL-$(target))/include" \ LIBLZMA_LIBS="-L$$(XZ_INSTALL-$(target))/lib -llzma" \ @@ -308,14 +316,14 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \ $$(PYTHON_SRCDIR-$(target))/python.exe: $$(PYTHON_SRCDIR-$(target))/Makefile @echo ">>> Build Python for $(target)" cd $$(PYTHON_SRCDIR-$(target)) && \ - PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ + PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \ make all \ 2>&1 | tee -a ../python-$(PYTHON_VERSION).build.log $$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe @echo ">>> Install Python for $(target)" cd $$(PYTHON_SRCDIR-$(target)) && \ - PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ + PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \ make install \ 2>&1 | tee -a ../python-$(PYTHON_VERSION).install.log @@ -324,7 +332,7 @@ $$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)): - @echo ">>> Create cross-plaform config for $(target)" + @echo ">>> Create cross-platform config for $(target)" mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(target)) # Create the cross-platform site definition echo "import _cross_$$(ARCH-$(target))_$$(SDK-$(target)); import _cross_venv;" \ @@ -398,15 +406,13 @@ define build-sdk sdk=$1 os=$2 -OS_LOWER-$(sdk)=$(shell echo $(os) | tr '[:upper:]' '[:lower:]') - SDK_TARGETS-$(sdk)=$$(filter $(sdk).%,$$(TARGETS-$(os))) SDK_ARCHES-$(sdk)=$$(sort $$(subst .,,$$(suffix $$(SDK_TARGETS-$(sdk))))) ifeq ($$(findstring simulator,$(sdk)),) -SDK_SLICE-$(sdk)=$$(OS_LOWER-$(sdk))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g") +SDK_SLICE-$(sdk)=$$(TRIPLE_OS-$(os))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g") else -SDK_SLICE-$(sdk)=$$(OS_LOWER-$(sdk))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g")-simulator +SDK_SLICE-$(sdk)=$$(TRIPLE_OS-$(os))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g")-simulator endif # Expand the build-target macro for target on this OS @@ -427,7 +433,6 @@ PYTHON_INSTALL_VERSION-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Versions/$(PYTHON_VER) PYTHON_LIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/Python PYTHON_INCLUDE-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/include/python$(PYTHON_VER) PYTHON_MODULEMAP-$(sdk)=$$(PYTHON_INCLUDE-$(sdk))/module.modulemap -PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/lib/python$(PYTHON_VER) else # Non-macOS builds need to be merged on a per-SDK basis. The merge covers: @@ -442,7 +447,6 @@ PYTHON_FRAMEWORK-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/Python.framework PYTHON_LIB-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Python PYTHON_BIN-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/bin PYTHON_INCLUDE-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Headers -PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/lib/python$(PYTHON_VER) PYTHON_PLATFORM_CONFIG-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/platform-config $$(PYTHON_LIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_LIB-$$(target))) @@ -485,34 +489,31 @@ $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h: $$(PYTHON_LIB-$(sdk)) $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_INCLUDE-$$(target))/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig-$$(ARCH-$$(target)).h; ) # Copy the cross-target header from the source folder of the first target in the $(sdk) SDK - cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h + cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/Apple/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h -$$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target))) +$$(PYTHON_PLATFORM_CONFIG-$(sdk))/sitecustomize.py: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target))) @echo ">>> Build Python stdlib for the $(sdk) SDK" - mkdir -p $$(PYTHON_STDLIB-$(sdk))/lib-dynload - # Copy stdlib from the first target associated with the $(sdk) SDK - cp -r $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/ $$(PYTHON_STDLIB-$(sdk)) + mkdir -p $$(PYTHON_INSTALL-$(sdk))/lib - # Delete the single-SDK parts of the standard library - rm -rf \ - $$(PYTHON_STDLIB-$(sdk))/_sysconfigdata__*.py \ - $$(PYTHON_STDLIB-$(sdk))/config-* \ - $$(PYTHON_STDLIB-$(sdk))/lib-dynload/* + # Create arch-specific stdlib directories + $$(foreach target,$$(SDK_TARGETS-$(sdk)),mkdir -p $$(PYTHON_INSTALL-$(sdk))/lib-$$(ARCH-$$(target))/python$(PYTHON_VER); ) + $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfigdata_* $$(PYTHON_INSTALL-$(sdk))/lib-$$(ARCH-$$(target))/python$(PYTHON_VER)/; ) + $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp -r $$(PYTHON_STDLIB-$$(target))/lib-dynload $$(PYTHON_INSTALL-$(sdk))/lib-$$(ARCH-$$(target))/python$(PYTHON_VER)/; ) - # Copy the individual _sysconfigdata modules into names that include the architecture - $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfigdata_* $$(PYTHON_STDLIB-$(sdk))/; ) + # Copy in known-required xcprivacy files. + # Libraries linking OpenSSL must provide a privacy manifest. The one in this repository + # has been sourced from https://github.com/openssl/openssl/blob/openssl-3.0/os-dep/Apple/PrivacyInfo.xcprivacy + $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$$(target))/lib-dynload/_hashlib.xcprivacy; ) + $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$$(target))/lib-dynload/_ssl.xcprivacy; ) # Copy the platform site folders for each architecture mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(sdk)) $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp -r $$(PYTHON_PLATFORM_CONFIG-$$(target)) $$(PYTHON_PLATFORM_CONFIG-$(sdk)); ) - # Merge the binary modules from each target in the $(sdk) SDK into a single binary - $$(foreach module,$$(wildcard $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/lib-dynload/*),lipo -create -output $$(PYTHON_STDLIB-$(sdk))/lib-dynload/$$(notdir $$(module)) $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_STDLIB-$$(target))/lib-dynload/$$(notdir $$(module))); ) - endif -$(sdk): $$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT +$(sdk): $$(PYTHON_PLATFORM_CONFIG-$(sdk))/sitecustomize.py ########################################################################### # SDK: Debug @@ -529,8 +530,7 @@ vars-$(sdk): @echo "PYTHON_LIB-$(sdk): $$(PYTHON_LIB-$(sdk))" @echo "PYTHON_BIN-$(sdk): $$(PYTHON_BIN-$(sdk))" @echo "PYTHON_INCLUDE-$(sdk): $$(PYTHON_INCLUDE-$(sdk))" - @echo "PYTHON_STDLIB-$(sdk): $$(PYTHON_STDLIB-$(sdk))" - + @echo "PYTHON_PLATFORM_CONFIG-$(sdk): $$(PYTHON_PLATFORM_CONFIG-$(sdk))" @echo endef # build-sdk @@ -635,22 +635,40 @@ dist/Python-$(PYTHON_VER)-macOS-support.$(BUILD_NUMBER).tar.gz: \ else $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ - $$(foreach sdk,$$(SDKS-$(os)),$$(PYTHON_STDLIB-$$(sdk))/LICENSE.TXT) + $$(foreach sdk,$$(SDKS-$(os)),$$(PYTHON_PLATFORM_CONFIG-$$(sdk))/sitecustomize.py) @echo ">>> Create Python.XCFramework on $(os)" mkdir -p $$(dir $$(PYTHON_XCFRAMEWORK-$(os))) xcodebuild -create-xcframework \ -output $$(PYTHON_XCFRAMEWORK-$(os)) $$(foreach sdk,$$(SDKS-$(os)),-framework $$(PYTHON_FRAMEWORK-$$(sdk))) \ 2>&1 | tee -a support/$(PYTHON_VER)/python-$(os).xcframework.log + @echo ">>> Install build tools for $(os)" + mkdir $$(PYTHON_XCFRAMEWORK-$(os))/build + cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Apple/testbed/Python.xcframework/build/utils.sh $$(PYTHON_XCFRAMEWORK-$(os))/build + cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Apple/testbed/Python.xcframework/build/$$(PLATFORM_NAME-$(os))-dylib-Info-template.plist $$(PYTHON_XCFRAMEWORK-$(os))/build + + @echo ">>> Install stdlib for $(os)" + mkdir -p $$(PYTHON_XCFRAMEWORK-$(os))/lib + cp -r $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/ $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER) + + # Delete the single-SDK parts of the standard library + rm -rf \ + $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)/_sysconfigdata__*.py \ + $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)/config-* \ + $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)/lib-dynload + @echo ">>> Install PYTHONHOME for $(os)" $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/include $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/bin $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) - $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/lib $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) + $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/lib* $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/platform-config $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) -ifeq ($(os),iOS) + # Create symlink for dylib + $$(foreach sdk,$$(SDKS-$(os)),ln -si ../Python.framework/Python $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk))/lib/libpython$(PYTHON_VER).dylib; ) + +ifeq ($(filter $(os),iOS tvOS),$(os)) @echo ">>> Clone testbed project for $(os)" - $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/iOS/testbed clone --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed + $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Apple/testbed clone --platform $(os) --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed endif @echo ">>> Create VERSIONS file for $(os)" diff --git a/patch/Python/OpenSSL.xcprivacy b/patch/Python/OpenSSL.xcprivacy new file mode 100644 index 00000000..95780a09 --- /dev/null +++ b/patch/Python/OpenSSL.xcprivacy @@ -0,0 +1,23 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 38610b3a..18f0d95f 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,10668 +1,13170 @@ +diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml +index 04a0d1fe65e..58695d56657 100644 +--- a/.pre-commit-config.yaml ++++ b/.pre-commit-config.yaml +@@ -3,9 +3,29 @@ + rev: v0.3.4 + hooks: + - id: ruff +- name: Run Ruff on Lib/test/ ++ name: Run Ruff (lint) on Apple/ ++ args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml] ++ files: ^Apple/ ++ - id: ruff ++ name: Run Ruff (lint) on Doc/ ++ args: [--exit-non-zero-on-fix] ++ files: ^Doc/ ++ - id: ruff ++ name: Run Ruff (lint) on Lib/test/ + args: [--exit-non-zero-on-fix] + files: ^Lib/test/ ++ - id: ruff ++ name: Run Ruff (lint) on Argument Clinic ++ args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml] ++ files: ^Tools/clinic/|Lib/test/test_clinic.py ++ - id: ruff-format ++ name: Run Ruff (format) on Apple/ ++ args: [--config=Apple/.ruff.toml] ++ files: ^Apple ++ - id: ruff-format ++ name: Run Ruff (format) on Doc/ ++ args: [--check] ++ files: ^Doc/ + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 --- /dev/null -+++ b/Doc/includes/wasm-ios-notavail.rst -@@ -0,0 +1,8 @@ -+.. include for modules that don't work on WASM or iOS ++++ b/.ruff.toml +@@ -0,0 +1,12 @@ ++# Default settings for Ruff in CPython + -+.. availability:: not WASI, not iOS. ++# PYTHON_FOR_REGEN ++target-version = "py310" + -+ This module does not work or is not available on WebAssembly platforms, or -+ on iOS. See :ref:`wasm-availability` for more information on WASM -+ availability; see :ref:`iOS-availability` for more information on iOS -+ availability. -diff --git a/Doc/includes/wasm-notavail.rst b/Doc/includes/wasm-notavail.rst -index e680e1f9b43..c1b79d2a4a0 100644 ---- a/Doc/includes/wasm-notavail.rst -+++ b/Doc/includes/wasm-notavail.rst -@@ -1,7 +1,6 @@ - .. include for modules that don't work on WASM - --.. availability:: not Emscripten, not WASI. -+.. availability:: not WASI. - -- This module does not work or is not available on WebAssembly platforms -- ``wasm32-emscripten`` and ``wasm32-wasi``. See -+ This module does not work or is not available on WebAssembly. See - :ref:`wasm-availability` for more information. -diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst -index 0d743efd28e..dfd71112231 100644 ---- a/Doc/library/curses.rst -+++ b/Doc/library/curses.rst -@@ -21,6 +21,8 @@ - designed to match the API of ncurses, an open-source curses library hosted on - Linux and the BSD variants of Unix. - -+.. include:: ../includes/wasm-ios-notavail.rst ++# PEP 8 ++line-length = 79 + - .. note:: - - Whenever the documentation mentions a *character* it can be specified -diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst -index 74f96b6c433..30b7738a08e 100644 ---- a/Doc/library/dbm.rst -+++ b/Doc/library/dbm.rst -@@ -14,6 +14,7 @@ - is a `third party interface `_ to - the Oracle Berkeley DB. - -+.. include:: ../includes/wasm-ios-notavail.rst - - .. exception:: error - -@@ -398,4 +399,3 @@ - .. method:: dumbdbm.close() - - Close the database. -- -diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst -index de3b93f5e61..168e45cfd6f 100644 ---- a/Doc/library/ensurepip.rst -+++ b/Doc/library/ensurepip.rst -@@ -38,7 +38,7 @@ - :pep:`453`: Explicit bootstrapping of pip in Python installations - The original rationale and specification for this module. - --.. include:: ../includes/wasm-notavail.rst -+.. include:: ../includes/wasm-ios-notavail.rst - - Command line interface - ---------------------- -diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst -index 1836f60ca5c..a36974b1594 100644 ---- a/Doc/library/fcntl.rst -+++ b/Doc/library/fcntl.rst -@@ -18,7 +18,7 @@ - See the :manpage:`fcntl(2)` and :manpage:`ioctl(2)` Unix manual pages - for full details. - --.. availability:: Unix, not Emscripten, not WASI. -+.. availability:: Unix, not WASI. - - All functions in this module take a file descriptor *fd* as their first - argument. This can be an integer file descriptor, such as returned by -diff --git a/Doc/library/grp.rst b/Doc/library/grp.rst -index ee55b12ea86..80260fec8d3 100644 ---- a/Doc/library/grp.rst -+++ b/Doc/library/grp.rst -@@ -10,7 +10,7 @@ - This module provides access to the Unix group database. It is available on all - Unix versions. - --.. availability:: Unix, not Emscripten, not WASI. -+.. availability:: Unix, not WASI, not iOS. - - Group database entries are reported as a tuple-like object, whose attributes - correspond to the members of the ``group`` structure (Attribute field below, see -diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst -index e9200dd1e2d..5f57362e557 100644 ---- a/Doc/library/importlib.rst -+++ b/Doc/library/importlib.rst -@@ -1202,6 +1202,69 @@ - and how the module's :attr:`__file__` is populated. - - -+.. class:: AppleFrameworkLoader(name, path) ++# Enable automatic fixes by default. ++# To override this, use ``fix = false`` in a subdirectory's config file ++# or ``--no-fix`` on the command line. ++fix = true +--- /dev/null ++++ b/Apple/.ruff.toml +@@ -0,0 +1,22 @@ ++extend = "../.ruff.toml" # Inherit the project-wide settings ++ ++[format] ++preview = true ++docstring-code-format = true ++ ++[lint] ++select = [ ++ "C4", # flake8-comprehensions ++ "E", # pycodestyle ++ "F", # pyflakes ++ "I", # isort ++ # "ISC", # flake8-implicit-str-concat ++ "LOG", # flake8-logging ++ "PGH", # pygrep-hooks ++ "PT", # flake8-pytest-style ++ "PYI", # flake8-pyi ++ "RUF100", # Ban unused `# noqa` comments ++ # "UP", # pyupgrade ++ "W", # pycodestyle ++ "YTT", # flake8-2020 ++] +--- /dev/null ++++ b/Apple/__main__.py +@@ -0,0 +1,1111 @@ ++#!/usr/bin/env python3 ++########################################################################## ++# Apple XCframework build script ++# ++# This script simplifies the process of configuring, compiling and packaging an ++# XCframework for an Apple platform. ++# ++# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been ++# constructed so that it could be used on any Apple platform. ++# ++# The simplest entry point is: ++# ++# $ python Apple ci iOS ++# ++# (replace iOS with tvOS, visionOS or watchOS as required.) ++# ++# which will: ++# * Clean any pre-existing build artefacts ++# * Configure and make a Python that can be used for the build ++# * Configure and make a Python for each supported iOS/tvOS/watchOS/visionOS ++# architecture and ABI ++# * Combine the outputs of the builds from the previous step into a single ++# XCframework, merging binaries into a "fat" binary if necessary ++# * Clone a copy of the testbed, configured to use the XCframework ++# * Construct a tarball containing the release artefacts ++# * Run the test suite using the generated XCframework. ++# ++# This is the complete sequence that would be needed in CI to build and test ++# a candidate release artefact. ++# ++# Each individual step can be invoked individually - there are commands to ++# clean, configure-build, make-build, configure-host, make-host, package, and ++# test. ++# ++# There is also a build command that can be used to combine the configure and ++# make steps for the build Python, an individual host, all hosts, or all ++# builds. ++########################################################################## ++from __future__ import annotations + -+ A specialization of :class:`importlib.machinery.ExtensionFileLoader` that -+ is able to load extension modules in Framework format. ++import argparse ++import os ++import platform ++import re ++import shlex ++import shutil ++import signal ++import subprocess ++import sys ++import sysconfig ++import time ++from collections.abc import Callable, Sequence ++from contextlib import contextmanager ++from datetime import datetime, timezone ++from os.path import basename, relpath ++from pathlib import Path ++from subprocess import CalledProcessError ++ ++EnvironmentT = dict[str, str] ++ArgsT = Sequence[str | Path] ++ ++SCRIPT_NAME = Path(__file__).name ++PYTHON_DIR = Path(__file__).resolve().parent.parent ++ ++CROSS_BUILD_DIR = PYTHON_DIR / "cross-build" ++ ++HOSTS: dict[str, dict[str, dict[str, str]]] = { ++ # Structure of this data: ++ # * Platform identifier ++ # * an XCframework slice that must exist for that platform ++ # * a host triple: the multiarch spec for that host ++ "iOS": { ++ "ios-arm64": { ++ "arm64-apple-ios": "arm64-iphoneos", ++ }, ++ "ios-arm64_x86_64-simulator": { ++ "arm64-apple-ios-simulator": "arm64-iphonesimulator", ++ "x86_64-apple-ios-simulator": "x86_64-iphonesimulator", ++ }, ++ }, ++ "tvOS": { ++ "tvos-arm64": { ++ "arm64-apple-tvos": "arm64-appletvos", ++ }, ++ "tvos-arm64_x86_64-simulator": { ++ "arm64-apple-tvos-simulator": "arm64-appletvsimulator", ++ "x86_64-apple-tvos-simulator": "x86_64-appletvsimulator", ++ }, ++ }, ++ "visionOS": { ++ "xros-arm64": { ++ "arm64-apple-xros": "arm64-xros", ++ }, ++ "xros-arm64-simulator": { ++ "arm64-apple-xros-simulator": "arm64-xrsimulator", ++ }, ++ }, ++ "watchOS": { ++ "watchos-arm64_32": { ++ "arm64_32-apple-watchos": "arm64_32-watchos", ++ }, ++ "watchos-arm64_x86_64-simulator": { ++ "arm64-apple-watchos-simulator": "arm64-watchsimulator", ++ "x86_64-apple-watchos-simulator": "x86_64-watchsimulator", ++ }, ++ }, ++} + -+ For compatibility with the iOS App Store, *all* binary modules in an iOS app -+ must be dynamic libraries, contained in a framework with appropriate -+ metadata, stored in the ``Frameworks`` folder of the packaged app. There can -+ be only a single binary per framework, and there can be no executable binary -+ material outside the Frameworks folder. + -+ To accomodate this requirement, when running on iOS, extension module -+ binaries are *not* packaged as ``.so`` files on ``sys.path``, but as -+ individual standalone frameworks. To discover those frameworks, this loader -+ is be registered against the ``.fwork`` file extension, with a ``.fwork`` -+ file acting as a placeholder in the original location of the binary on -+ ``sys.path``. The ``.fwork`` file contains the path of the actual binary in -+ the ``Frameworks`` folder, relative to the app bundle. To allow for -+ resolving a framework-packaged binary back to the original location, the -+ framework is expected to contain a ``.origin`` file that contains the -+ location of the ``.fwork`` file, relative to the app bundle. ++def subdir(name: str, create: bool = False) -> Path: ++ """Ensure that a cross-build directory for the given name exists.""" ++ path = CROSS_BUILD_DIR / name ++ if not path.exists(): ++ if not create: ++ sys.exit( ++ f"{path} does not exist. Create it by running the appropriate " ++ f"`configure` subcommand of {SCRIPT_NAME}." ++ ) ++ else: ++ path.mkdir(parents=True) ++ return path + -+ For example, consider the case of an import ``from foo.bar import _whiz``, -+ where ``_whiz`` is implemented with the binary module -+ ``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location -+ registered on ``sys.path``, relative to the application bundle. This module -+ *must* be distributed as -+ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` (creating the framework -+ name from the full import path of the module), with an ``Info.plist`` file -+ in the ``.framework`` directory identifying the binary as a framework. The -+ ``foo.bar._whiz`` module would be represented in the original location with -+ a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing the path -+ ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also contain -+ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing the -+ path to the ``.fwork`` file. + -+ When a module is loaded with this loader, the ``__file__`` for the module -+ will report as the location of the ``.fwork`` file. This allows code to use -+ the ``__file__`` of a module as an anchor for file system traveral. -+ However, the spec origin will reference the location of the *actual* binary -+ in the ``.framework`` folder. ++def run( ++ command: ArgsT, ++ *, ++ host: str | None = None, ++ env: EnvironmentT | None = None, ++ log: bool | None = True, ++ **kwargs, ++) -> subprocess.CompletedProcess: ++ """Run a command in an Apple development environment. + -+ The Xcode project building the app is responsible for converting any ``.so`` -+ files from wherever they exist in the ``PYTHONPATH`` into frameworks in the -+ ``Frameworks`` folder (including stripping extensions from the module file, -+ the addition of framework metadata, and signing the resulting framework), -+ and creating the ``.fwork`` and ``.origin`` files. This will usually be done -+ with a build step in the Xcode project; see the iOS documentation for -+ details on how to construct this build step. ++ Optionally logs the executed command to the console. ++ """ ++ kwargs.setdefault("check", True) ++ if env is None: ++ env = os.environ.copy() + -+ .. versionadded:: 3.13 ++ if host: ++ host_env = apple_env(host) ++ print_env(host_env) ++ env.update(host_env) + -+ .. availability:: iOS. ++ if log: ++ print(">", join_command(command)) ++ return subprocess.run(command, env=env, **kwargs) + -+ .. attribute:: name + -+ Name of the module the loader supports. ++def join_command(args: str | Path | ArgsT) -> str: ++ """Format a command so it can be copied into a shell. + -+ .. attribute:: path ++ Similar to `shlex.join`, but also accepts arguments which are Paths, or a ++ single string/Path outside of a list. ++ """ ++ if isinstance(args, (str, Path)): ++ return str(args) ++ else: ++ return shlex.join(map(str, args)) ++ ++ ++def print_env(env: EnvironmentT) -> None: ++ """Format the environment so it can be pasted into a shell.""" ++ for key, value in sorted(env.items()): ++ print(f"export {key}={shlex.quote(value)}") ++ ++ ++def platform_for_host(host): ++ """Determine the platform for a given host triple.""" ++ for plat, slices in HOSTS.items(): ++ for _, candidates in slices.items(): ++ for candidate in candidates: ++ if candidate == host: ++ return plat ++ raise KeyError(host) ++ ++ ++def apple_env(host: str) -> EnvironmentT: ++ """Construct an Apple development environment for the given host.""" ++ env = { ++ "PATH": ":".join([ ++ str(PYTHON_DIR / f"Apple/{platform_for_host(host)}/Resources/bin"), ++ str(subdir(host) / "prefix"), ++ "/usr/bin", ++ "/bin", ++ "/usr/sbin", ++ "/sbin", ++ "/Library/Apple/usr/bin", ++ ]), ++ } + -+ Path to the ``.fwork`` file for the extension module. ++ return env + + - :mod:`importlib.util` -- Utility code for importers - --------------------------------------------------- - -diff --git a/Doc/library/intro.rst b/Doc/library/intro.rst -index 5a4c9b8b16a..ffc8939d211 100644 ---- a/Doc/library/intro.rst -+++ b/Doc/library/intro.rst -@@ -58,7 +58,7 @@ - operating system. - - * If not separately noted, all functions that claim "Availability: Unix" are -- supported on macOS, which builds on a Unix core. -+ supported on macOS and iOS, both of which build on a Unix core. - - * If an availability note contains both a minimum Kernel version and a minimum - libc version, then both conditions must hold. For example a feature with note -@@ -119,3 +119,44 @@ - .. _wasmtime: https://wasmtime.dev/ - .. _Pyodide: https://pyodide.org/ - .. _PyScript: https://pyscript.net/ ++def delete_path(name: str) -> None: ++ """Delete the named cross-build directory, if it exists.""" ++ path = CROSS_BUILD_DIR / name ++ if path.exists(): ++ print(f"Deleting {path} ...") ++ shutil.rmtree(path) + -+.. _iOS-availability: + -+iOS -+--- ++def all_host_triples(platform: str) -> list[str]: ++ """Return all host triples for the given platform. + -+iOS is, in most respects, a POSIX operating system. File I/O, socket handling, -+and threading all behave as they would on any POSIX operating system. However, -+there are several major differences between iOS and other POSIX systems. ++ The host triples are the platform definitions used as input to configure ++ (e.g., "arm64-apple-ios-simulator"). ++ """ ++ triples = [] ++ for slice_name, slice_parts in HOSTS[platform].items(): ++ triples.extend(list(slice_parts)) ++ return triples ++ ++ ++def clean(context: argparse.Namespace, target: str = "all") -> None: ++ """The implementation of the "clean" command.""" ++ # If we're explicitly targeting the build, there's no platform or ++ # distribution artefacts. If we're cleaning tests, we keep all built ++ # artefacts. Otherwise, the built artefacts must be dirty, so we remove ++ # them. ++ if target not in {"build", "test"}: ++ paths = ["dist", context.platform] + list(HOSTS[context.platform]) ++ else: ++ paths = [] ++ ++ if target in {"all", "build"}: ++ paths.append("build") ++ ++ if target in {"all", "hosts"}: ++ paths.extend(all_host_triples(context.platform)) ++ elif target not in {"build", "test", "package"}: ++ paths.append(target) ++ ++ if target in {"all", "hosts", "test"}: ++ paths.extend([ ++ path.name ++ for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*") ++ ]) ++ ++ for path in paths: ++ delete_path(path) ++ ++ ++def build_python_path() -> Path: ++ """The path to the build Python binary.""" ++ build_dir = subdir("build") ++ binary = build_dir / "python" ++ if not binary.is_file(): ++ binary = binary.with_suffix(".exe") ++ if not binary.is_file(): ++ raise FileNotFoundError( ++ f"Unable to find `python(.exe)` in {build_dir}" ++ ) + -+* iOS can only use Python in "embedded" mode. There is no Python REPL, and no -+ ability to execute binaries that are part of the normal Python developer -+ experience, such as :program:`pip`. To add Python code to your iOS app, you must use -+ the :ref:`Python embedding API ` to add a Python interpreter to an -+ iOS app created with Xcode. See the :ref:`iOS usage guide ` for -+ more details. ++ return binary + -+* An iOS app cannot use any form of subprocessing, background processing, or -+ inter-process communication. If an iOS app attempts to create a subprocess, -+ the process creating the subprocess will either lock up, or crash. An iOS app -+ has no visibility of other applications that are running, nor any ability to -+ communicate with other running applications, outside of the iOS-specific APIs -+ that exist for this purpose. + -+* iOS apps have limited access to modify system resources (such as the system -+ clock). These resources will often be *readable*, but attempts to modify -+ those resources will usually fail. ++@contextmanager ++def group(text: str): ++ """A context manager that outputs a log marker around a section of a build. + -+* iOS apps have a limited concept of console input and output. ``stdout`` and -+ ``stderr`` *exist*, and content written to ``stdout`` and ``stderr`` will be -+ visible in logs when running in Xcode, but this content *won't* be recorded -+ in the system log. If a user who has installed your app provides their app -+ logs as a diagnostic aid, they will not include any detail written to -+ ``stdout`` or ``stderr``. ++ If running in a GitHub Actions environment, the GitHub syntax for ++ collapsible log sections is used. ++ """ ++ if "GITHUB_ACTIONS" in os.environ: ++ print(f"::group::{text}") ++ else: ++ print(f"===== {text} " + "=" * (70 - len(text))) + -+ iOS apps have no concept of ``stdin`` at all. While iOS apps can have a -+ keyboard, this is a software feature, not something that is attached to -+ ``stdin``. ++ yield + -+ As a result, Python library that involve console manipulation (such as -+ :mod:`curses` and :mod:`readline`) are not available on iOS. -diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst -index ffd690148f1..4173d76d4fb 100644 ---- a/Doc/library/multiprocessing.rst -+++ b/Doc/library/multiprocessing.rst -@@ -8,7 +8,7 @@ - - -------------- - --.. include:: ../includes/wasm-notavail.rst -+.. include:: ../includes/wasm-ios-notavail.rst - - Introduction - ------------ -diff --git a/Doc/library/os.rst b/Doc/library/os.rst -index 1af9e79db57..6cd6fb018ff 100644 ---- a/Doc/library/os.rst -+++ b/Doc/library/os.rst -@@ -34,12 +34,13 @@ - - * On VxWorks, os.popen, os.fork, os.execv and os.spawn*p* are not supported. - --* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, large -- parts of the :mod:`os` module are not available or behave differently. API -- related to processes (e.g. :func:`~os.fork`, :func:`~os.execve`), signals -- (e.g. :func:`~os.kill`, :func:`~os.wait`), and resources -- (e.g. :func:`~os.nice`) are not available. Others like :func:`~os.getuid` -- and :func:`~os.getpid` are emulated or stubs. -+* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, and on -+ iOS, large parts of the :mod:`os` module are not available or behave -+ differently. API related to processes (e.g. :func:`~os.fork`, -+ :func:`~os.execve`) and resources (e.g. :func:`~os.nice`) are not available. -+ Others like :func:`~os.getuid` and :func:`~os.getpid` are emulated or stubs. -+ WebAssembly platforms also lack support for signals (e.g. :func:`~os.kill`, -+ :func:`~os.wait`). - - - .. note:: -@@ -735,6 +736,11 @@ - :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). :func:`platform.uname()` -+ can be used to get the user-facing operating system name and version on iOS and -+ Android. ++ if "GITHUB_ACTIONS" in os.environ: ++ print("::endgroup::") ++ else: ++ print() + - .. availability:: Unix. - - .. versionchanged:: 3.3 -@@ -3768,7 +3774,7 @@ - - .. audit-event:: os.exec path,args,env os.execl - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. - - .. versionchanged:: 3.3 - Added support for specifying *path* as an open file descriptor -@@ -3948,7 +3954,16 @@ - - See :mod:`ssl` for applications that use the SSL module with fork(). - -- .. availability:: Unix, not Emscripten, not WASI. -+ Users of macOS or users of libc or malloc implementations other -+ than those typically found in glibc to date are among those -+ already more likely to experience deadlocks running such code. + -+ See `this discussion on fork being incompatible with threads -+ `_ -+ for technical details of why we're surfacing this longstanding -+ platform compatibility problem to developers. ++@contextmanager ++def cwd(subdir: Path): ++ """A context manager that sets the current working directory.""" ++ orig = os.getcwd() ++ os.chdir(subdir) ++ yield ++ os.chdir(orig) + -+ .. availability:: POSIX, not Emscripten, not WASI, not iOS. - - - .. function:: forkpty() -@@ -3970,7 +3985,12 @@ - Calling ``forkpty()`` in a subinterpreter is no longer supported - (:exc:`RuntimeError` is raised). - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. versionchanged:: 3.12 -+ If Python is able to detect that your process has multiple -+ threads, this now raises a :exc:`DeprecationWarning`. See the -+ longer explanation on :func:`os.fork`. + -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: kill(pid, sig, /) -@@ -3994,7 +4014,7 @@ - - .. audit-event:: os.kill pid,sig os.kill - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. - - .. versionchanged:: 3.2 - Added Windows support. -@@ -4010,7 +4030,7 @@ - - .. audit-event:: os.killpg pgid,sig os.killpg - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: nice(increment, /) -@@ -4038,7 +4058,7 @@ - Lock program segments into memory. The value of *op* (defined in - ````) determines which segments are locked. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: popen(cmd, mode='r', buffering=-1) -@@ -4070,7 +4090,7 @@ - documentation for more powerful ways to manage and communicate with - subprocesses. - -- .. availability:: not Emscripten, not WASI. -+ .. availability:: not Emscripten, not WASI, not iOS. - - .. note:: - The :ref:`Python UTF-8 Mode ` affects encodings used -@@ -4165,7 +4185,7 @@ - - .. versionadded:: 3.8 - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ - setpgroup=None, resetids=False, setsid=False, setsigmask=(), \ -@@ -4181,7 +4201,7 @@ - - .. versionadded:: 3.8 - -- .. availability:: POSIX, not Emscripten, not WASI. -+ .. availability:: POSIX, not Emscripten, not WASI, not iOS. - - See :func:`posix_spawn` documentation. - -@@ -4214,7 +4234,7 @@ - - There is no way to unregister a function. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. versionadded:: 3.7 - -@@ -4283,7 +4303,7 @@ - - .. audit-event:: os.spawn mode,path,args,env os.spawnl - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. - - :func:`spawnlp`, :func:`spawnlpe`, :func:`spawnvp` - and :func:`spawnvpe` are not available on Windows. :func:`spawnle` and -@@ -4407,7 +4427,7 @@ - - .. audit-event:: os.system command os.system - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. - - - .. function:: times() -@@ -4451,7 +4471,7 @@ - :func:`waitstatus_to_exitcode` can be used to convert the exit status into an - exit code. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. seealso:: - -@@ -4485,7 +4505,10 @@ - Otherwise, if there are no matching children - that could be waited for, :exc:`ChildProcessError` is raised. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. ++def configure_build_python(context: argparse.Namespace) -> None: ++ """The implementation of the "configure-build" command.""" ++ if context.clean: ++ clean(context, "build") + -+ .. note:: -+ This function is not available on macOS. - - .. note:: - This function is not available on macOS. -@@ -4526,7 +4549,7 @@ - :func:`waitstatus_to_exitcode` can be used to convert the exit status into an - exit code. - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. - - .. versionchanged:: 3.5 - If the system call is interrupted and the signal handler does not raise an -@@ -4546,7 +4569,7 @@ - :func:`waitstatus_to_exitcode` can be used to convert the exit status into an - exitcode. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: wait4(pid, options) -@@ -4560,7 +4583,7 @@ - :func:`waitstatus_to_exitcode` can be used to convert the exit status into an - exitcode. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. data:: P_PID -@@ -4577,7 +4600,7 @@ - * :data:`!P_PIDFD` - wait for the child identified by the file descriptor - *id* (a process file descriptor created with :func:`pidfd_open`). - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. note:: :data:`!P_PIDFD` is only available on Linux >= 5.4. - -@@ -4592,7 +4615,7 @@ - :func:`waitid` causes child processes to be reported if they have been - continued from a job control stop since they were last reported. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. data:: WEXITED -@@ -4603,7 +4626,7 @@ - The other ``wait*`` functions always report children that have terminated, - so this option is not available for them. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. versionadded:: 3.3 - -@@ -4615,7 +4638,7 @@ - - This option is not available for the other ``wait*`` functions. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. versionadded:: 3.3 - -@@ -4628,7 +4651,7 @@ - - This option is not available for :func:`waitid`. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. data:: WNOHANG -@@ -4637,7 +4660,7 @@ - :func:`waitid` to return right away if no child process status is available - immediately. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. data:: WNOWAIT -@@ -4647,7 +4670,7 @@ - - This option is not available for the other ``wait*`` functions. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. data:: CLD_EXITED -@@ -4660,7 +4683,7 @@ - These are the possible values for :attr:`!si_code` in the result returned by - :func:`waitid`. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. versionadded:: 3.3 - -@@ -4695,7 +4718,7 @@ - :func:`WIFEXITED`, :func:`WEXITSTATUS`, :func:`WIFSIGNALED`, - :func:`WTERMSIG`, :func:`WIFSTOPPED`, :func:`WSTOPSIG` functions. - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. - - .. versionadded:: 3.9 - -@@ -4711,7 +4734,7 @@ - - This function should be employed only if :func:`WIFSIGNALED` is true. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: WIFCONTINUED(status) -@@ -4722,7 +4745,7 @@ - - See :data:`WCONTINUED` option. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: WIFSTOPPED(status) -@@ -4734,14 +4757,14 @@ - done using :data:`WUNTRACED` option or when the process is being traced (see - :manpage:`ptrace(2)`). - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - .. function:: WIFSIGNALED(status) - - Return ``True`` if the process was terminated by a signal, otherwise return - ``False``. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: WIFEXITED(status) -@@ -4750,7 +4773,7 @@ - by calling ``exit()`` or ``_exit()``, or by returning from ``main()``; - otherwise return ``False``. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: WEXITSTATUS(status) -@@ -4759,7 +4782,7 @@ - - This function should be employed only if :func:`WIFEXITED` is true. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: WSTOPSIG(status) -@@ -4768,7 +4791,7 @@ - - This function should be employed only if :func:`WIFSTOPPED` is true. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - .. function:: WTERMSIG(status) -@@ -4777,7 +4800,7 @@ - - This function should be employed only if :func:`WIFSIGNALED` is true. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not Emscripten, not WASI, not iOS. - - - Interface to the scheduler -diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst -index 60cd6b88084..316b693eece 100644 ---- a/Doc/library/platform.rst -+++ b/Doc/library/platform.rst -@@ -148,6 +148,9 @@ - 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 -+ ``'Linux'``), use :func:`os.uname()`. - - .. function:: system_alias(system, release, version) - -@@ -161,6 +164,8 @@ - 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 -+ Darwin or Linux kernel version, use :func:`os.uname()`. - - .. function:: uname() - -@@ -230,7 +235,6 @@ - macOS Platform - -------------- - -- - .. function:: mac_ver(release='', versioninfo=('','',''), machine='') - - Get macOS version information and return it as tuple ``(release, versioninfo, -@@ -240,6 +244,24 @@ - Entries which cannot be determined are set to ``''``. All tuple entries are - strings. - -+iOS Platform -+------------ ++ with ( ++ group("Configuring build Python"), ++ cwd(subdir("build", create=True)), ++ ): ++ command = [relpath(PYTHON_DIR / "configure")] ++ if context.args: ++ command.extend(context.args) ++ run(command) + -+.. function:: ios_ver(system='', release='', model='', is_simulator=False) + -+ Get iOS version information and return it as a -+ :func:`~collections.namedtuple` with the following attributes: ++def make_build_python(context: argparse.Namespace) -> None: ++ """The implementation of the "make-build" command.""" ++ with ( ++ group("Compiling build Python"), ++ cwd(subdir("build")), ++ ): ++ run(["make", "-j", str(os.cpu_count())]) + -+ * ``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. -+ * ``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 the defaults given as -+ parameters. ++def apple_target(host: str) -> str: ++ """Return the Apple platform identifier for a given host triple.""" ++ for _, platform_slices in HOSTS.items(): ++ for slice_name, slice_parts in platform_slices.items(): ++ for host_triple, multiarch in slice_parts.items(): ++ if host == host_triple: ++ return ".".join(multiarch.split("-")[::-1]) + - - Unix Platforms - -------------- -diff --git a/Doc/library/pwd.rst b/Doc/library/pwd.rst -index 755f0d29ac7..b6d212e93b5 100644 ---- a/Doc/library/pwd.rst -+++ b/Doc/library/pwd.rst -@@ -10,7 +10,7 @@ - This module provides access to the Unix user account and password database. It - is available on all Unix versions. - --.. availability:: Unix, not Emscripten, not WASI. -+.. availability:: Unix, not WASI, not iOS. - - Password database entries are reported as a tuple-like object, whose attributes - correspond to the members of the ``passwd`` structure (Attribute field below, -diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst -index 3fb5ceef086..758b89b4612 100644 ---- a/Doc/library/readline.rst -+++ b/Doc/library/readline.rst -@@ -24,6 +24,8 @@ - allowable constructs of that file, and the capabilities of the - Readline library in general. - -+.. include:: ../includes/wasm-ios-notavail.rst ++ raise KeyError(host) + - .. note:: - - The underlying Readline library API may be implemented by -diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst -index 389a63f089d..4fea8d5cb71 100644 ---- a/Doc/library/resource.rst -+++ b/Doc/library/resource.rst -@@ -13,7 +13,7 @@ - This module provides basic mechanisms for measuring and controlling system - resources utilized by a program. - --.. availability:: Unix, not Emscripten, not WASI. -+.. availability:: Unix, not WASI. - - Symbolic constants are used to specify particular system resources and to - request usage information about either the current process or its children. -diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst -index 85a073aad23..05ef45c123b 100644 ---- a/Doc/library/signal.rst -+++ b/Doc/library/signal.rst -@@ -26,9 +26,9 @@ - underlying implementation), with the exception of the handler for - :const:`SIGCHLD`, which follows the underlying implementation. - --On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, signals --are emulated and therefore behave differently. Several functions and signals --are not available on these platforms. -+On WebAssembly platforms, signals are emulated and therefore behave -+differently. Several functions and signals are not available on these -+platforms. - - Execution of Python signal handlers - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst -index 3a5368717ac..de47e1f6e5f 100644 ---- a/Doc/library/socket.rst -+++ b/Doc/library/socket.rst -@@ -1109,7 +1109,7 @@ - buffer. Raises :exc:`OverflowError` if *length* is outside the - permissible range of values. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not WASI. - - Most Unix platforms. - -@@ -1132,7 +1132,7 @@ - amount of ancillary data that can be received, since additional - data may be able to fit into the padding area. - -- .. availability:: Unix, not Emscripten, not WASI. -+ .. availability:: Unix, not WASI. - - most Unix platforms. - -@@ -1172,7 +1172,7 @@ - (index int, name string) tuples. - :exc:`OSError` if the system call fails. - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not WASI. - - .. versionadded:: 3.3 - -@@ -1199,7 +1199,7 @@ - interface name. - :exc:`OSError` if no interface with the given name exists. - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not WASI. - - .. versionadded:: 3.3 - -@@ -1216,7 +1216,7 @@ - interface index number. - :exc:`OSError` if no interface with the given index exists. - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not WASI. - - .. versionadded:: 3.3 - -@@ -1233,7 +1233,7 @@ - The *fds* parameter is a sequence of file descriptors. - Consult :meth:`~socket.sendmsg` for the documentation of these parameters. - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not WASI. - - Unix platforms supporting :meth:`~socket.sendmsg` - and :const:`SCM_RIGHTS` mechanism. -@@ -1247,7 +1247,7 @@ - Return ``(msg, list(fds), flags, addr)``. - Consult :meth:`~socket.recvmsg` for the documentation of these parameters. - -- .. availability:: Unix, Windows, not Emscripten, not WASI. -+ .. availability:: Unix, Windows, not WASI. - - Unix platforms supporting :meth:`~socket.sendmsg` - and :const:`SCM_RIGHTS` mechanism. -diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst -index c0ae9e5d7aa..7009a7e8314 100644 ---- a/Doc/library/subprocess.rst -+++ b/Doc/library/subprocess.rst -@@ -25,7 +25,7 @@ - - :pep:`324` -- PEP proposing the subprocess module - --.. include:: ../includes/wasm-notavail.rst -+.. include:: ../includes/wasm-ios-notavail.rst - - Using the :mod:`subprocess` Module - ---------------------------------- -diff --git a/Doc/library/syslog.rst b/Doc/library/syslog.rst -index 889bbb39d58..6c7c0907143 100644 ---- a/Doc/library/syslog.rst -+++ b/Doc/library/syslog.rst -@@ -11,7 +11,7 @@ - Refer to the Unix manual pages for a detailed description of the ``syslog`` - facility. - --.. availability:: Unix, not Emscripten, not WASI. -+.. availability:: Unix, not WASI, not iOS. - - This module wraps the system ``syslog`` family of routines. A pure Python - library that can speak to a syslog server is available in the -diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst -index 9f90418bc96..c198c5798e9 100644 ---- a/Doc/library/urllib.parse.rst -+++ b/Doc/library/urllib.parse.rst -@@ -22,11 +22,19 @@ - - The module has been designed to match the internet RFC on Relative Uniform - Resource Locators. It supports the following URL schemes: ``file``, ``ftp``, --``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``mailto``, ``mms``, -+``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``itms-services``, ``mailto``, ``mms``, - ``news``, ``nntp``, ``prospero``, ``rsync``, ``rtsp``, ``rtsps``, ``rtspu``, - ``sftp``, ``shttp``, ``sip``, ``sips``, ``snews``, ``svn``, ``svn+ssh``, - ``telnet``, ``wais``, ``ws``, ``wss``. - -+.. impl-detail:: + -+ The inclusion of the ``itms-services`` URL scheme can prevent an app from -+ passing Apple's App Store review process for the macOS and iOS App Stores. -+ Handling for the ``itms-services`` scheme is always removed on iOS; on -+ macOS, it *may* be removed if CPython has been built with the -+ :option:`--with-app-store-compliance` option. ++def apple_multiarch(host: str) -> str: ++ """Return the multiarch descriptor for a given host triple.""" ++ for _, platform_slices in HOSTS.items(): ++ for slice_name, slice_parts in platform_slices.items(): ++ for host_triple, multiarch in slice_parts.items(): ++ if host == host_triple: ++ return multiarch + - The :mod:`urllib.parse` module defines functions that fall into two broad - categories: URL parsing and URL quoting. These are covered in detail in - the following sections. -diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst -index ce6d093b741..822cead8050 100644 ---- a/Doc/library/venv.rst -+++ b/Doc/library/venv.rst -@@ -56,7 +56,7 @@ - `Python Packaging User Guide: Creating and using virtual environments - `__ - --.. include:: ../includes/wasm-notavail.rst -+.. include:: ../includes/wasm-ios-notavail.rst - - Creating virtual environments - ----------------------------- -diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst -index 734b6321e5a..b709e511afa 100644 ---- a/Doc/library/webbrowser.rst -+++ b/Doc/library/webbrowser.rst -@@ -33,6 +33,13 @@ - 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. ++ raise KeyError(host) + - 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; -@@ -157,6 +164,8 @@ - +------------------------+-----------------------------------------+-------+ - | ``'chromium-browser'`` | :class:`Chromium('chromium-browser')` | | - +------------------------+-----------------------------------------+-------+ -+| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) | -++------------------------+-----------------------------------------+-------+ - - Notes: - -@@ -171,7 +180,11 @@ - Only on Windows platforms. - - (3) -- Only on macOS platform. -+ Only on macOS. + -+(4) -+ Only on iOS. ++def unpack_deps( ++ platform: str, ++ host: str, ++ prefix_dir: Path, ++ cache_dir: Path, ++) -> None: ++ """Unpack binary dependencies into a provided directory. + - - .. versionadded:: 3.3 - Support for Chrome/Chromium has been added. -@@ -179,6 +192,9 @@ - .. deprecated-removed:: 3.11 3.13 - :class:`MacOSX` is deprecated, use :class:`MacOSXOSAScript` instead. - -+.. versionchanged:: 3.13 -+ Support for iOS has been added. ++ Downloads binaries if they aren't already present. Downloads will be stored ++ in provided cache directory. + - Here are some simple examples:: - - url = 'https://docs.python.org/' -diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py -index c849a4d3875..4b8252cb590 100644 ---- a/Doc/tools/extensions/pyspecific.py -+++ b/Doc/tools/extensions/pyspecific.py -@@ -138,7 +138,7 @@ - known_platforms = frozenset({ - "AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD", - "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", "Unix", "VxWorks", -- "WASI", "Windows", "macOS", -+ "WASI", "Windows", "macOS", "iOS", - # libc - "BSD libc", "glibc", "musl", - # POSIX platforms with pthreads -@@ -169,7 +169,7 @@ - - Example:: - -- .. availability:: Windows, Linux >= 4.2, not Emscripten, not WASI -+ .. availability:: Windows, Linux >= 4.2, not WASI - - Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not - parsed into separate tokens. -diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst -index e45a5a4791e..26ac341bc98 100644 ---- a/Doc/using/configure.rst -+++ b/Doc/using/configure.rst -@@ -527,7 +527,7 @@ - macOS Options - ------------- - --See ``Mac/README.rst``. -+See :source:`Mac/README.rst`. - - .. option:: --enable-universalsdk - .. option:: --enable-universalsdk=SDKDIR -@@ -562,6 +562,31 @@ - Specify the name for the python framework on macOS only valid when - :option:`--enable-framework` is set (default: ``Python``). - -+.. option:: --with-app-store-compliance -+.. option:: --with-app-store-compliance=PATCH-FILE ++ On non-macOS platforms, as a safety mechanism, any dynamic libraries will ++ be purged from the unpacked dependencies. ++ """ ++ # To create new builds of these dependencies, usually all that's necessary ++ # is to push a tag to the cpython-apple-source-deps repository, and GitHub ++ # Actions will do the rest. ++ # ++ # If you're a member of the Python core team, and you'd like to be able to ++ # push these tags yourself, please contact Malcolm Smith or Russell ++ # Keith-Magee. ++ deps_url = "https://github.com/beeware/cpython-apple-source-deps/releases/download" ++ for name_ver in [ ++ "BZip2-1.0.8-2", ++ "libFFI-3.4.7-2", ++ "OpenSSL-3.0.18-1", ++ "XZ-5.6.4-2", ++ "mpdecimal-4.0.0-2", ++ "zstd-1.5.7-1", ++ ]: ++ filename = f"{name_ver.lower()}-{apple_target(host)}.tar.gz" ++ archive_path = download( ++ f"{deps_url}/{name_ver}/{filename}", ++ target_dir=cache_dir, ++ ) ++ shutil.unpack_archive(archive_path, prefix_dir) + -+ The Python standard library contains strings that are known to trigger -+ automated inspection tool errors when submitted for distribution by -+ the macOS and iOS App Stores. If enabled, this option will apply the list of -+ patches that are known to correct app store compliance. A custom patch -+ file can also be specified. This option is disabled by default. ++ # Dynamic libraries will be preferentially linked over static; ++ # On iOS, ensure that no dylibs are available in the prefix folder. ++ if platform == "iOS": ++ for dylib in prefix_dir.glob("**/*.dylib"): ++ dylib.unlink() + -+ .. versionadded:: 3.13 + -+iOS Options -+----------- ++def download(url: str, target_dir: Path) -> Path: ++ """Download the specified URL into the given directory. + -+See :source:`iOS/README.rst`. ++ :return: The path to the downloaded archive. ++ """ ++ target_path = Path(target_dir).resolve() ++ target_path.mkdir(exist_ok=True, parents=True) ++ ++ out_path = target_path / basename(url) ++ if not Path(out_path).is_file(): ++ run([ ++ "curl", ++ "-Lf", ++ "--retry", ++ "5", ++ "--retry-all-errors", ++ "-o", ++ out_path, ++ url, ++ ]) ++ else: ++ print(f"Using cached version of {basename(url)}") ++ return out_path + -+.. option:: --enable-framework=INSTALLDIR + -+ Create a Python.framework. Unlike macOS, the *INSTALLDIR* argument -+ specifying the installation path is mandatory. ++def configure_host_python( ++ context: argparse.Namespace, ++ host: str | None = None, ++) -> None: ++ """The implementation of the "configure-host" command.""" ++ if host is None: ++ host = context.host + -+.. option:: --with-framework-name=FRAMEWORK ++ if context.clean: ++ clean(context, host) + -+ Specify the name for the framework (default: ``Python``). ++ host_dir = subdir(host, create=True) ++ prefix_dir = host_dir / "prefix" + - - Cross Compiling Options - ----------------------- -diff --git a/Doc/using/index.rst b/Doc/using/index.rst -index e1a3111f36a..f55a12f1ab8 100644 ---- a/Doc/using/index.rst -+++ b/Doc/using/index.rst -@@ -18,4 +18,5 @@ - configure.rst - windows.rst - mac.rst -+ ios.rst - editors.rst ---- /dev/null -+++ b/Doc/using/ios.rst -@@ -0,0 +1,386 @@ -+.. _using-ios: ++ with group(f"Downloading dependencies ({host})"): ++ if not prefix_dir.exists(): ++ prefix_dir.mkdir() ++ unpack_deps(context.platform, host, prefix_dir, context.cache_dir) ++ else: ++ print("Dependencies already installed") + -+=================== -+Using Python on iOS -+=================== ++ with ( ++ group(f"Configuring host Python ({host})"), ++ cwd(host_dir), ++ ): ++ command = [ ++ # Basic cross-compiling configuration ++ relpath(PYTHON_DIR / "configure"), ++ f"--host={host}", ++ f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}", ++ f"--with-build-python={build_python_path()}", ++ "--with-system-libmpdec", ++ "--enable-framework", ++ # Dependent libraries. ++ f"--with-openssl={prefix_dir}", ++ f"LIBLZMA_CFLAGS=-I{prefix_dir}/include", ++ f"LIBLZMA_LIBS=-L{prefix_dir}/lib -llzma", ++ f"LIBFFI_INCLUDEDIR={prefix_dir}/include", ++ f"LIBFFI_LIBDIR={prefix_dir}/lib", ++ "LIBFFI_LIB=ffi", ++ f"LIBMPDEC_CFLAGS=-I{prefix_dir}/include", ++ f"LIBMPDEC_LDFLAGS=-L{prefix_dir}/lib -lmpdec", ++ ] + -+:Authors: -+ Russell Keith-Magee (2024-03) ++ if context.args: ++ command.extend(context.args) ++ run(command, host=host) + -+Python on iOS is unlike Python on desktop platforms. On a desktop platform, -+Python is generally installed as a system resource that can be used by any user -+of that computer. Users then interact with Python by running a :program:`python` -+executable and entering commands at an interactive prompt, or by running a -+Python script. + -+On iOS, there is no concept of installing as a system resource. The only unit -+of software distribution is an "app". There is also no console where you could -+run a :program:`python` executable, or interact with a Python REPL. ++def make_host_python( ++ context: argparse.Namespace, ++ host: str | None = None, ++) -> None: ++ """The implementation of the "make-host" command.""" ++ if host is None: ++ host = context.host + -+As a result, the only way you can use Python on iOS is in embedded mode - that -+is, by writing a native iOS application, and embedding a Python interpreter -+using ``libPython``, and invoking Python code using the :ref:`Python embedding -+API `. The full Python interpreter, the standard library, and all -+your Python code is then packaged as a standalone bundle that can be -+distributed via the iOS App Store. ++ with ( ++ group(f"Compiling host Python ({host})"), ++ cwd(subdir(host)), ++ ): ++ run(["make"], host=host) ++ run(["make", "install"], host=host) + -+If you're looking to experiment for the first time with writing an iOS app in -+Python, projects such as `BeeWare `__ and `Kivy -+`__ will provide a much more approachable user experience. -+These projects manage the complexities associated with getting an iOS project -+running, so you only need to deal with the Python code itself. + -+Python at runtime on iOS -+======================== ++def framework_path(host_triple: str, multiarch: str) -> Path: ++ """The path to a built single-architecture framework product. + -+iOS version compatibility -+------------------------- ++ :param host_triple: The host triple (e.g., arm64-apple-ios-simulator) ++ :param multiarch: The multiarch identifier (e.g., arm64-simulator) ++ """ ++ return ( ++ CROSS_BUILD_DIR ++ / f"{host_triple}/Apple/{platform_for_host(host_triple)}" ++ / f"Frameworks/{multiarch}" ++ ) + -+The minimum supported iOS version is specified at compile time, using the -+:option:`--host` option to ``configure``. By default, when compiled for iOS, -+Python will be compiled with a minimum supported iOS version of 13.0. To use a -+different miniumum iOS version, provide the version number as part of the -+:option:`!--host` argument - for example, -+``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 simulator build -+with a deployment target of 15.4. + -+Platform identification -+----------------------- ++def package_version(prefix_path: Path) -> str: ++ """Extract the Python version being built from patchlevel.h.""" ++ for path in prefix_path.glob("**/patchlevel.h"): ++ text = path.read_text(encoding="utf-8") ++ if match := re.search( ++ r'\n\s*#define\s+PY_VERSION\s+"(.+)"\s*\n', text ++ ): ++ version = match[1] ++ # If not building against a tagged commit, add a timestamp to the ++ # version. Follow the PyPA version number rules, as this will make ++ # it easier to process with other tools. The version will have a ++ # `+` suffix once any official release has been made; a freshly ++ # forked main branch will have a version of 3.X.0a0. ++ if version.endswith("a0"): ++ version += "+" ++ if version.endswith("+"): ++ version += datetime.now(timezone.utc).strftime("%Y%m%d.%H%M%S") ++ ++ return version ++ ++ sys.exit("Unable to determine Python version being packaged.") ++ ++ ++def lib_platform_files(dirname, names): ++ """A file filter that ignores platform-specific files in lib.""" ++ path = Path(dirname) ++ if ( ++ path.parts[-3] == "lib" ++ and path.parts[-2].startswith("python") ++ and path.parts[-1] == "lib-dynload" ++ ): ++ return names ++ elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"): ++ ignored_names = { ++ name ++ for name in names ++ if ( ++ name.startswith("_sysconfigdata_") ++ or name.startswith("_sysconfig_vars_") ++ or name == "build-details.json" ++ ) ++ } ++ elif path.parts[-1] == "lib": ++ ignored_names = { ++ name ++ for name in names ++ if name.startswith("libpython") and name.endswith(".dylib") ++ } ++ else: ++ ignored_names = set() + -+When executing on iOS, ``sys.platform`` will report as ``ios``. This value will -+be returned on an iPhone or iPad, regardless of whether the app is running on -+the simulator or a physical device. ++ return ignored_names + -+Information about the specific runtime environment, including the iOS version, -+device model, and whether the device is a simulator, can be obtained using -+:func:`platform.ios_ver()`. :func:`platform.system()` will report ``iOS`` or -+``iPadOS``, depending on the device. + -+:func:`os.uname()` reports kernel-level details; it will report a name of -+``Darwin``. ++def lib_non_platform_files(dirname, names): ++ """A file filter that ignores anything *except* platform-specific files ++ in the lib directory. ++ """ ++ path = Path(dirname) ++ if path.parts[-2] == "lib" and path.parts[-1].startswith("python"): ++ return ( ++ set(names) - lib_platform_files(dirname, names) - {"lib-dynload"} ++ ) ++ else: ++ return set() + -+Standard library availability -+----------------------------- + -+The Python standard library has some notable omissions and restrictions on -+iOS. See the :ref:`API availability guide for iOS ` for -+details. ++def create_xcframework(platform: str) -> str: ++ """Build an XCframework from the component parts for the platform. + -+Binary extension modules -+------------------------ ++ :return: The version number of the Python version that was packaged. ++ """ ++ package_path = CROSS_BUILD_DIR / platform ++ try: ++ package_path.mkdir() ++ except FileExistsError: ++ raise RuntimeError( ++ f"{platform} XCframework already exists; do you need to run " ++ "with --clean?" ++ ) from None ++ ++ frameworks = [] ++ # Merge Frameworks for each component SDK. If there's only one architecture ++ # for the SDK, we can use the compiled Python.framework as-is. However, if ++ # there's more than architecture, we need to merge the individual built ++ # frameworks into a merged "fat" framework. ++ for slice_name, slice_parts in HOSTS[platform].items(): ++ # Some parts are the same across all slices, so we use can any of the ++ # host frameworks as the source for the merged version. Use the first ++ # one on the list, as it's as representative as any other. ++ first_host_triple, first_multiarch = next(iter(slice_parts.items())) ++ first_framework = ( ++ framework_path(first_host_triple, first_multiarch) ++ / "Python.framework" ++ ) + -+One notable difference about iOS as a platform is that App Store distribution -+imposes hard requirements on the packaging of an application. One of these -+requirements governs how binary extension modules are distributed. ++ if len(slice_parts) == 1: ++ # The first framework is the only framework, so copy it. ++ print(f"Copying framework for {slice_name}...") ++ frameworks.append(first_framework) ++ else: ++ print(f"Merging framework for {slice_name}...") ++ slice_path = CROSS_BUILD_DIR / slice_name ++ slice_framework = slice_path / "Python.framework" ++ slice_framework.mkdir(exist_ok=True, parents=True) ++ ++ # Copy the Info.plist ++ shutil.copy( ++ first_framework / "Info.plist", ++ slice_framework / "Info.plist", ++ ) + -+The iOS App Store requires that *all* binary modules in an iOS app must be -+dynamic libraries, contained in a framework with appropriate metadata, stored -+in the ``Frameworks`` folder of the packaged app. There can be only a single -+binary per framework, and there can be no executable binary material outside -+the ``Frameworks`` folder. ++ # Copy the headers ++ shutil.copytree( ++ first_framework / "Headers", ++ slice_framework / "Headers", ++ ) + -+This conflicts with the usual Python approach for distributing binaries, which -+allows a binary extension module to be loaded from any location on -+``sys.path``. To ensure compliance with App Store policies, an iOS project must -+post-process any Python packages, converting ``.so`` binary modules into -+individual standalone frameworks with appropriate metadata and signing. For -+details on how to perform this post-processing, see the guide for :ref:`adding -+Python to your project `. ++ # Create the "fat" library binary for the slice ++ run( ++ ["lipo", "-create", "-output", slice_framework / "Python"] ++ + [ ++ ( ++ framework_path(host_triple, multiarch) ++ / "Python.framework/Python" ++ ) ++ for host_triple, multiarch in slice_parts.items() ++ ] ++ ) + -+To help Python discover binaries in their new location, the original ``.so`` -+file on ``sys.path`` is replaced with a ``.fwork`` file. This file is a text -+file containing the location of the framework binary, relative to the app -+bundle. To allow the framework to resolve back to the original location, the -+framework must contain a ``.origin`` file that contains the location of the -+``.fwork`` file, relative to the app bundle. ++ # Add this merged slice to the list to be added to the XCframework ++ frameworks.append(slice_framework) + -+For example, consider the case of an import ``from foo.bar import _whiz``, -+where ``_whiz`` is implemented with the binary module -+``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location -+registered on ``sys.path``, relative to the application bundle. This module -+*must* be distributed as ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` -+(creating the framework name from the full import path of the module), with an -+``Info.plist`` file in the ``.framework`` directory identifying the binary as a -+framework. The ``foo.bar._whiz`` module would be represented in the original -+location with a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing -+the path ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also -+contain ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing -+the path to the ``.fwork`` file. ++ print() ++ print("Build XCframework...") ++ cmd = [ ++ "xcodebuild", ++ "-create-xcframework", ++ "-output", ++ package_path / "Python.xcframework", ++ ] ++ for framework in frameworks: ++ cmd.extend(["-framework", framework]) ++ ++ run(cmd) ++ ++ # Extract the package version from the merged framework ++ version = package_version(package_path / "Python.xcframework") ++ version_tag = ".".join(version.split(".")[:2]) ++ ++ # On non-macOS platforms, each framework in XCframework only contains the ++ # headers, libPython, plus an Info.plist. Other resources like the standard ++ # library and binary shims aren't allowed to live in framework; they need ++ # to be copied in separately. ++ print() ++ print("Copy additional resources...") ++ has_common_stdlib = False ++ for slice_name, slice_parts in HOSTS[platform].items(): ++ # Some parts are the same across all slices, so we can any of the ++ # host frameworks as the source for the merged version. ++ first_host_triple, first_multiarch = next(iter(slice_parts.items())) ++ first_path = framework_path(first_host_triple, first_multiarch) ++ first_framework = first_path / "Python.framework" ++ ++ slice_path = package_path / f"Python.xcframework/{slice_name}" ++ slice_framework = slice_path / "Python.framework" ++ ++ # Copy the binary helpers ++ print(f" - {slice_name} binaries") ++ shutil.copytree(first_path / "bin", slice_path / "bin") ++ ++ # Copy the include path (a symlink to the framework headers) ++ print(f" - {slice_name} include files") ++ shutil.copytree( ++ first_path / "include", ++ slice_path / "include", ++ symlinks=True, ++ ) + -+When running on iOS, the Python interpreter will install an -+:class:`~importlib.machinery.AppleFrameworkLoader` that is able to read and -+import ``.fwork`` files. Once imported, the ``__file__`` attribute of the -+binary module will report as the location of the ``.fwork`` file. However, the -+:class:`~importlib.machinery.ModuleSpec` for the loaded module will report the -+``origin`` as the location of the binary in the framework folder. ++ # Copy in the cross-architecture pyconfig.h ++ shutil.copy( ++ PYTHON_DIR / f"Apple/{platform}/Resources/pyconfig.h", ++ slice_framework / "Headers/pyconfig.h", ++ ) + -+Compiler stub binaries -+---------------------- ++ print(f" - {slice_name} shared library") ++ # Create a simlink for the fat library ++ shared_lib = slice_path / f"lib/libpython{version_tag}.dylib" ++ shared_lib.parent.mkdir() ++ shared_lib.symlink_to("../Python.framework/Python") ++ ++ print(f" - {slice_name} architecture-specific files") ++ for host_triple, multiarch in slice_parts.items(): ++ print(f" - {multiarch} standard library") ++ arch, _ = multiarch.split("-", 1) ++ ++ if not has_common_stdlib: ++ print(" - using this architecture as the common stdlib") ++ shutil.copytree( ++ framework_path(host_triple, multiarch) / "lib", ++ package_path / "Python.xcframework/lib", ++ ignore=lib_platform_files, ++ symlinks=True, ++ ) ++ has_common_stdlib = True + -+Xcode doesn't expose explicit compilers for iOS; instead, it uses an ``xcrun`` -+script that resolves to a full compiler path (e.g., ``xcrun --sdk iphoneos -+clang`` to get the ``clang`` for an iPhone device). However, using this script -+poses two problems: ++ shutil.copytree( ++ framework_path(host_triple, multiarch) / "lib", ++ slice_path / f"lib-{arch}", ++ ignore=lib_non_platform_files, ++ symlinks=True, ++ ) + -+* The output of ``xcrun`` includes paths that are machine specific, resulting -+ in a sysconfig module that cannot be shared between users; and ++ # Copy the host's pyconfig.h to an architecture-specific name. ++ arch = multiarch.split("-")[0] ++ host_path = ( ++ CROSS_BUILD_DIR ++ / host_triple ++ / f"Apple/{platform}/Frameworks" ++ / multiarch ++ ) ++ host_framework = host_path / "Python.framework" ++ shutil.copy( ++ host_framework / "Headers/pyconfig.h", ++ slice_framework / f"Headers/pyconfig-{arch}.h", ++ ) + -+* It results in ``CC``/``CPP``/``LD``/``AR`` definitions that include spaces. -+ There is a lot of C ecosystem tooling that assumes that you can split a -+ command line at the first space to get the path to the compiler executable; -+ this isn't the case when using ``xcrun``. ++ # Apple identifies certain libraries as "security risks"; if you ++ # statically link those libraries into a Framework, you become ++ # responsible for providing a privacy manifest for that framework. ++ xcprivacy_file = { ++ "OpenSSL": subdir(host_triple) ++ / "prefix/share/OpenSSL.xcprivacy" ++ } ++ print(f" - {multiarch} xcprivacy files") ++ for module, lib in [ ++ ("_hashlib", "OpenSSL"), ++ ("_ssl", "OpenSSL"), ++ ]: ++ shutil.copy( ++ xcprivacy_file[lib], ++ slice_path ++ / f"lib-{arch}/python{version_tag}" ++ / f"lib-dynload/{module}.xcprivacy", ++ ) + -+To avoid these problems, Python provided stubs for these tools. These stubs are -+shell script wrappers around the underingly ``xcrun`` tools, distributed in a -+``bin`` folder distributed alongside the compiled iOS framework. These scripts -+are relocatable, and will always resolve to the appropriate local system paths. -+By including these scripts in the bin folder that accompanies a framework, the -+contents of the ``sysconfig`` module becomes useful for end-users to compile -+their own modules. When compiling third-party Python modules for iOS, you -+should ensure these stub binaries are on your path. ++ print(" - build tools") ++ shutil.copytree( ++ PYTHON_DIR / "Apple/testbed/Python.xcframework/build", ++ package_path / "Python.xcframework/build", ++ ) + -+Installing Python on iOS -+======================== ++ return version + -+Tools for building iOS apps -+--------------------------- + -+Building for iOS requires the use of Apple's Xcode tooling. It is strongly -+recommended that you use the most recent stable release of Xcode. This will -+require the use of the most (or second-most) recently released macOS version, -+as Apple does not maintain Xcode for older macOS versions. The Xcode Command -+Line Tools are not sufficient for iOS development; you need a *full* Xcode -+install. ++def package(context: argparse.Namespace) -> None: ++ """The implementation of the "package" command.""" ++ if context.clean: ++ clean(context, "package") + -+If you want to run your code on the iOS simulator, you'll also need to install -+an iOS Simulator Platform. You should be prompted to select an iOS Simulator -+Platform when you first run Xcode. Alternatively, you can add an iOS Simulator -+Platform by selecting from the Platforms tab of the Xcode Settings panel. ++ with group("Building package"): ++ # Create an XCframework ++ version = create_xcframework(context.platform) + -+.. _adding-ios: ++ # watchOS doesn't have a testbed (yet!) ++ if context.platform != "watchOS": ++ # Clone testbed ++ print() ++ run([ ++ sys.executable, ++ "Apple/testbed", ++ "clone", ++ "--platform", ++ context.platform, ++ "--framework", ++ CROSS_BUILD_DIR / context.platform / "Python.xcframework", ++ CROSS_BUILD_DIR / context.platform / "testbed", ++ ]) ++ ++ # Build the final archive ++ archive_name = ( ++ CROSS_BUILD_DIR ++ / "dist" ++ / f"python-{version}-{context.platform}-XCframework" ++ ) + -+Adding Python to an iOS project -+------------------------------- ++ print() ++ print("Create package archive...") ++ shutil.make_archive( ++ str(CROSS_BUILD_DIR / archive_name), ++ format="gztar", ++ root_dir=CROSS_BUILD_DIR / context.platform, ++ base_dir=".", ++ ) ++ print() ++ print(f"{archive_name.relative_to(PYTHON_DIR)}.tar.gz created.") + -+Python can be added to any iOS project, using either Swift or Objective C. The -+following examples will use Objective C; if you are using Swift, you may find a -+library like `PythonKit `__ to be -+helpful. + -+To add Python to an iOS Xcode project: ++def build(context: argparse.Namespace, host: str | None = None) -> None: ++ """The implementation of the "build" command.""" ++ if host is None: ++ host = context.host + -+1. Build or obtain a Python ``XCFramework``. See the instructions in -+ :source:`iOS/README.rst` (in the CPython source distribution) for details on -+ how to build a Python ``XCFramework``. At a minimum, you will need a build -+ that supports ``arm64-apple-ios``, plus one of either -+ ``arm64-apple-ios-simulator`` or ``x86_64-apple-ios-simulator``. ++ if context.clean: ++ clean(context, host) + -+2. Drag the ``XCframework`` into your iOS project. In the following -+ instructions, we'll assume you've dropped the ``XCframework`` into the root -+ of your project; however, you can use any other location that you want by -+ adjusting paths as needed. ++ if host in {"all", "build"}: ++ for step in [ ++ configure_build_python, ++ make_build_python, ++ ]: ++ step(context) + -+3. Drag the ``iOS/Resources/dylib-Info-template.plist`` file into your project, -+ and ensure it is associated with the app target. ++ if host == "build": ++ hosts = [] ++ elif host in {"all", "hosts"}: ++ hosts = all_host_triples(context.platform) ++ else: ++ hosts = [host] + -+4. Add your application code as a folder in your Xcode project. In the -+ following instructions, we'll assume that your user code is in a folder -+ named ``app`` in the root of your project; you can use any other location by -+ adjusting paths as needed. Ensure that this folder is associated with your -+ app target. ++ for step_host in hosts: ++ for step in [ ++ configure_host_python, ++ make_host_python, ++ ]: ++ step(context, host=step_host) + -+5. Select the app target by selecting the root node of your Xcode project, then -+ the target name in the sidebar that appears. ++ if host in {"all", "hosts"}: ++ package(context) + -+6. In the "General" settings, under "Frameworks, Libraries and Embedded -+ Content", add ``Python.xcframework``, with "Embed & Sign" selected. + -+7. In the "Build Settings" tab, modify the following: ++def test(context: argparse.Namespace, host: str | None = None) -> None: ++ """The implementation of the "test" command.""" ++ if host is None: ++ host = context.host + -+ - Build Options ++ if context.clean: ++ clean(context, "test") + -+ * User Script Sandboxing: No -+ * Enable Testability: Yes ++ with group(f"Test {'XCframework' if host in {'all', 'hosts'} else host}"): ++ timestamp = str(time.time_ns())[:-6] ++ testbed_dir = ( ++ CROSS_BUILD_DIR / f"{context.platform}-testbed.{timestamp}" ++ ) ++ if host in {"all", "hosts"}: ++ framework_path = ( ++ CROSS_BUILD_DIR / context.platform / "Python.xcframework" ++ ) ++ else: ++ build_arch = platform.machine() ++ host_arch = host.split("-")[0] + -+ - Search Paths ++ if not host.endswith("-simulator"): ++ print("Skipping test suite non-simulator build.") ++ return ++ elif build_arch != host_arch: ++ print( ++ f"Skipping test suite for an {host_arch} build " ++ f"on an {build_arch} machine." ++ ) ++ return ++ else: ++ framework_path = ( ++ CROSS_BUILD_DIR ++ / host ++ / f"Apple/{context.platform}" ++ / f"Frameworks/{apple_multiarch(host)}" ++ ) + -+ * Framework Search Paths: ``$(PROJECT_DIR)`` -+ * Header Search Paths: ``"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"`` ++ run([ ++ sys.executable, ++ "Apple/testbed", ++ "clone", ++ "--platform", ++ context.platform, ++ "--framework", ++ framework_path, ++ testbed_dir, ++ ]) ++ ++ run( ++ [ ++ sys.executable, ++ testbed_dir, ++ "run", ++ "--verbose", ++ ] ++ + ( ++ ["--simulator", str(context.simulator)] ++ if context.simulator ++ else [] ++ ) ++ + [ ++ "--", ++ "test", ++ # f"--{context.ci_mode}-ci", ++ "-uall", ++ "--rerun", ++ # "--single-process", ++ "-W", ++ # Timeout handling requires subprocesses; explicitly setting ++ # the timeout to -1 disables the faulthandler. ++ "--timeout=-1", ++ # Adding Python options requires the use of a subprocess to ++ # start a new Python interpreter. ++ "--dont-add-python-opts", ++ ] ++ ) + -+ - Apple Clang - Warnings - All languages + -+ * Quoted Include In Framework Header: No ++def apple_sim_host(platform_name: str) -> str: ++ """Determine the native simulator target for this platform.""" ++ for _, slice_parts in HOSTS[platform_name].items(): ++ for host_triple in slice_parts: ++ parts = host_triple.split("-") ++ if parts[0] == platform.machine() and parts[-1] == "simulator": ++ return host_triple + -+8. Add a build step that copies the Python standard library into your app. In -+ the "Build Phases" tab, add a new "Run Script" build step *before* the -+ "Embed Frameworks" step, but *after* the "Copy Bundle Resources" step. Name -+ the step "Install Target Specific Python Standard Library", disable the -+ "Based on dependency analysis" checkbox, and set the script content to: ++ raise KeyError(platform_name) + -+ .. code-block:: bash + -+ set -e ++def ci(context: argparse.Namespace) -> None: ++ """The implementation of the "ci" command. + -+ mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" -+ if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then -+ echo "Installing Python modules for iOS Simulator" -+ rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" -+ else -+ echo "Installing Python modules for iOS Device" -+ rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" -+ fi ++ In "Fast" mode, this compiles the build python, and the simulator for the ++ build machine's architecture; and runs the test suite with `--fast-ci` ++ configuration. + -+ Note that the name of the simulator "slice" in the XCframework may be -+ different, depending the CPU architectures your ``XCFramework`` supports. ++ In "Slow" mode, it compiles the build python, plus all candidate ++ architectures (both device and simulator); then runs the test suite with ++ `--slow-ci` configuration. ++ """ ++ clean(context, "all") ++ if context.ci_mode == "slow": ++ # In slow mode, build and test the full XCframework ++ build(context, host="all") ++ test(context, host="all") ++ else: ++ # In fast mode, just build the simulator platform. ++ sim_host = apple_sim_host(context.platform) ++ build(context, host="build") ++ build(context, host=sim_host) ++ test(context, host=sim_host) + -+9. Add a second build step that processes the binary extension modules in the -+ standard library into "Framework" format. Add a "Run Script" build step -+ *directly after* the one you added in step 8, named "Prepare Python Binary -+ Modules". It should also have "Based on dependency analysis" unchecked, with -+ the following script content: + -+ .. code-block:: bash ++def parse_args() -> argparse.Namespace: ++ parser = argparse.ArgumentParser( ++ description=( ++ "A tool for managing the build, package and test process of " ++ "CPython on Apple platforms." ++ ), ++ ) ++ parser.suggest_on_error = True ++ subcommands = parser.add_subparsers(dest="subcommand", required=True) + -+ set -e -+ -+ install_dylib () { -+ INSTALL_BASE=$1 -+ FULL_EXT=$2 -+ -+ # The name of the extension file -+ EXT=$(basename "$FULL_EXT") -+ # The location of the extension file, relative to the bundle -+ RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} -+ # The path to the extension file, relative to the install base -+ PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} -+ # The full dotted name of the extension module, constructed from the file path. -+ FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); -+ # A bundle identifier; not actually used, but required by Xcode framework packaging -+ FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") -+ # The name of the framework folder. -+ FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" -+ -+ # If the framework folder doesn't exist, create it. -+ if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then -+ echo "Creating framework for $RELATIVE_EXT" -+ mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" -+ cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" -+ plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" -+ plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" -+ fi -+ -+ echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" -+ mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" -+ # Create a placeholder .fwork file where the .so was -+ echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork -+ # Create a back reference to the .so file location in the framework -+ echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" -+ } ++ clean = subcommands.add_parser( ++ "clean", ++ help="Delete all build directories", ++ ) + -+ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") -+ echo "Install Python $PYTHON_VER standard library extension modules..." -+ find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do -+ install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" -+ done ++ configure_build = subcommands.add_parser( ++ "configure-build", help="Run `configure` for the build Python" ++ ) ++ subcommands.add_parser( ++ "make-build", help="Run `make` for the build Python" ++ ) ++ configure_host = subcommands.add_parser( ++ "configure-host", ++ help="Run `configure` for a specific platform and target", ++ ) ++ make_host = subcommands.add_parser( ++ "make-host", ++ help="Run `make` for a specific platform and target", ++ ) ++ package = subcommands.add_parser( ++ "package", ++ help="Create a release package for the platform", ++ ) ++ build = subcommands.add_parser( ++ "build", ++ help="Build all platform targets and create the XCframework", ++ ) ++ test = subcommands.add_parser( ++ "test", ++ help="Run the testbed for a specific platform", ++ ) ++ ci = subcommands.add_parser( ++ "ci", ++ help="Run build, package, and test", ++ ) + -+ # Clean up dylib template -+ rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" ++ # platform argument ++ for cmd in [clean, configure_host, make_host, package, build, test, ci]: ++ cmd.add_argument( ++ "platform", ++ choices=HOSTS.keys(), ++ help="The target platform to build", ++ ) + -+ echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." -+ find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \; ++ # host triple argument ++ for cmd in [configure_host, make_host]: ++ cmd.add_argument( ++ "host", ++ help="The host triple to build (e.g., arm64-apple-ios-simulator)", ++ ) ++ # optional host triple argument ++ for cmd in [clean, build, test]: ++ cmd.add_argument( ++ "host", ++ nargs="?", ++ default="all", ++ help=( ++ "The host triple to build (e.g., arm64-apple-ios-simulator), " ++ "or 'build' for just the build platform, or 'hosts' for all " ++ "host platforms, or 'all' for the build platform and all " ++ "hosts. Defaults to 'all'" ++ ), ++ ) + -+10. Add Objective C code to initialize and use a Python interpreter in embedded -+ mode. You should ensure that: ++ # --clean option ++ for cmd in [configure_build, configure_host, build, package, test, ci]: ++ cmd.add_argument( ++ "--clean", ++ action="store_true", ++ default=False, ++ dest="clean", ++ help="Delete the relevant build directories first", ++ ) + -+ * UTF-8 mode (:c:member:`PyPreConfig.utf8_mode`) is *enabled*; -+ * Buffered stdio (:c:member:`PyConfig.buffered_stdio`) is *disabled*; -+ * Writing bytecode (:c:member:`PyConfig.write_bytecode`) is *disabled*; -+ * Signal handlers (:c:member:`PyConfig.install_signal_handlers`) are *enabled*; -+ * ``PYTHONHOME`` for the interpreter is configured to point at the -+ ``python`` subfolder of your app's bundle; and -+ * The ``PYTHONPATH`` for the interpreter includes: ++ # --cache-dir option ++ for cmd in [configure_host, build, ci]: ++ cmd.add_argument( ++ "--cache-dir", ++ default="./cross-build/downloads", ++ help="The directory to store cached downloads.", ++ ) + -+ - the ``python/lib/python3.X`` subfolder of your app's bundle, -+ - the ``python/lib/python3.X/lib-dynload`` subfolder of your app's bundle, and -+ - the ``app`` subfolder of your app's bundle ++ # --simulator option ++ for cmd in [test, ci]: ++ cmd.add_argument( ++ "--simulator", ++ help=( ++ "The name of the simulator to use (eg: 'iPhone 16e'). " ++ "Defaults to the most recently released 'entry level' " ++ "iPhone device. Device architecture and OS version can also " ++ "be specified; e.g., " ++ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would " ++ "run on an ARM64 iPhone 16 Pro simulator running iOS 26.0." ++ ), ++ ) ++ group = cmd.add_mutually_exclusive_group() ++ group.add_argument( ++ "--fast-ci", ++ action="store_const", ++ dest="ci_mode", ++ const="fast", ++ help="Add test arguments for GitHub Actions", ++ ) ++ group.add_argument( ++ "--slow-ci", ++ action="store_const", ++ dest="ci_mode", ++ const="slow", ++ help="Add test arguments for buildbots", ++ ) + -+ Your app's bundle location can be determined using ``[[NSBundle mainBundle] -+ resourcePath]``. ++ for subcommand in [configure_build, configure_host, build, ci]: ++ subcommand.add_argument( ++ "args", nargs="*", help="Extra arguments to pass to `configure`" ++ ) + -+Steps 8, 9 and 10 of these instructions assume that you have a single folder of -+pure Python application code, named ``app``. If you have third-party binary -+modules in your app, some additional steps will be required: ++ return parser.parse_args() + -+* You need to ensure that any folders containing third-party binaries are -+ either associated with the app target, or copied in as part of step 8. Step 8 -+ should also purge any binaries that are not appropriate for the platform a -+ specific build is targetting (i.e., delete any device binaries if you're -+ building app app targeting the simulator). + -+* Any folders that contain third-party binaries must be processed into -+ framework form by step 9. The invocation of ``install_dylib`` that processes -+ the ``lib-dynload`` folder can be copied and adapted for this purpose. ++def print_called_process_error(e: subprocess.CalledProcessError) -> None: ++ for stream_name in ["stdout", "stderr"]: ++ content = getattr(e, stream_name) ++ stream = getattr(sys, stream_name) ++ if content: ++ stream.write(content) ++ if not content.endswith("\n"): ++ stream.write("\n") + -+* If you're using a separate folder for third-party packages, ensure that folder -+ is included as part of the ``PYTHONPATH`` configuration in step 10. ++ # shlex uses single quotes, so we surround the command with double quotes. ++ print( ++ f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}' ++ ) + -+Testing a Python package -+------------------------ + -+The CPython source tree contains :source:`a testbed project ` that -+is used to run the CPython test suite on the iOS simulator. This testbed can also -+be used as a testbed project for running your Python library's test suite on iOS. ++def main() -> None: ++ # Handle SIGTERM the same way as SIGINT. This ensures that if we're ++ # terminated by the buildbot worker, we'll make an attempt to clean up our ++ # subprocesses. ++ def signal_handler(*args): ++ os.kill(os.getpid(), signal.SIGINT) ++ ++ signal.signal(signal.SIGTERM, signal_handler) ++ ++ # Process command line arguments ++ context = parse_args() ++ dispatch: dict[str, Callable] = { ++ "clean": clean, ++ "configure-build": configure_build_python, ++ "make-build": make_build_python, ++ "configure-host": configure_host_python, ++ "make-host": make_host_python, ++ "package": package, ++ "build": build, ++ "test": test, ++ "ci": ci, ++ } + -+After building or obtaining an iOS XCFramework (See :source:`iOS/README.rst` -+for details), create a clone of the Python iOS testbed project by running: ++ try: ++ dispatch[context.subcommand](context) ++ except CalledProcessError as e: ++ print() ++ print_called_process_error(e) ++ sys.exit(1) ++ except RuntimeError as e: ++ print() ++ print(e) ++ sys.exit(2) + -+.. code-block:: bash + -+ $ python iOS/testbed clone --framework --app --app app-testbed ++if __name__ == "__main__": ++ # Under the buildbot, stdout is not a TTY, but we must still flush after ++ # every line to make sure our output appears in the correct order relative ++ # to the output of our subprocesses. ++ for stream in [sys.stdout, sys.stderr]: ++ stream.reconfigure(line_buffering=True) + -+You will need to modify the ``iOS/testbed`` reference to point to that -+directory in the CPython source tree; any folders specified with the ``--app`` -+flag will be copied into the cloned testbed project. The resulting testbed will -+be created in the ``app-testbed`` folder. In this example, the ``module1`` and -+``module2`` would be importable modules at runtime. If your project has -+additional dependencies, they can be installed into the -+``app-testbed/iOSTestbed/app_packages`` folder (using ``pip install --target -+app-testbed/iOSTestbed/app_packages`` or similar). ++ main() +--- /dev/null ++++ b/Apple/iOS/README.md +@@ -0,0 +1,339 @@ ++# Python on iOS README + -+You can then use the ``app-testbed`` folder to run the test suite for your app, -+For example, if ``module1.tests`` was the entry point to your test suite, you -+could run: ++**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).** + -+.. code-block:: bash ++This document provides a quick overview of some iOS specific features in the ++Python distribution. + -+ $ python app-testbed run -- module1.tests ++These instructions are only needed if you're planning to compile Python for iOS ++yourself. Most users should *not* need to do this. If you're looking to ++experiment with writing an iOS app in Python, tools such as [BeeWare's ++Briefcase](https://briefcase.readthedocs.io) and [Kivy's ++Buildozer](https://buildozer.readthedocs.io) will provide a much more ++approachable user experience. + -+This is the equivalent of running ``python -m module1.tests`` on a desktop -+Python build. Any arguments after the ``--`` will be passed to the testbed as -+if they were arguments to ``python -m`` on a desktop machine. ++## Compilers for building on iOS + -+You can also open the testbed project in Xcode by running: ++Building for iOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode. This will ++require the use of the most (or second-most) recently released macOS version, ++as Apple does not maintain Xcode for older macOS versions. The Xcode Command ++Line Tools are not sufficient for iOS development; you need a *full* Xcode ++install. + -+.. code-block:: bash ++If you want to run your code on the iOS simulator, you'll also need to install ++an iOS Simulator Platform. You should be prompted to select an iOS Simulator ++Platform when you first run Xcode. Alternatively, you can add an iOS Simulator ++Platform by selecting an open the Platforms tab of the Xcode Settings panel. + -+ $ open app-testbed/iOSTestbed.xcodeproj ++## Building Python on iOS + -+This will allow you to use the full Xcode suite of tools for debugging. ++### ABIs and Architectures + -+App Store Compliance -+==================== ++iOS apps can be deployed on physical devices, and on the iOS simulator. Although ++the API used on these devices is identical, the ABI is different - you need to ++link against different libraries for an iOS device build (`iphoneos`) or an ++iOS simulator build (`iphonesimulator`). + -+The only mechanism for distributing apps to third-party iOS devices is to -+submit the app to the iOS App Store; apps submitted for distribution must pass -+Apple's app review process. This process includes a set of automated validation -+rules that inspect the submitted application bundle for problematic code. ++Apple uses the `XCframework` format to allow specifying a single dependency ++that supports multiple ABIs. An `XCframework` is a wrapper around multiple ++ABI-specific frameworks that share a common API. + -+The Python standard library contains some code that is known to violate these -+automated rules. While these violations appear to be false positives, Apple's -+review rules cannot be challenged; so, it is necessary to modify the Python -+standard library for an app to pass App Store review. ++iOS can also support different CPU architectures within each ABI. At present, ++there is only a single supported architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines). + -+The Python source tree contains -+:source:`a patch file ` that will remove -+all code that is known to cause issues with the App Store review process. This -+patch is applied automatically when building for iOS. -diff --git a/Doc/using/mac.rst b/Doc/using/mac.rst -index 986d693d03f..da45aabca28 100644 ---- a/Doc/using/mac.rst -+++ b/Doc/using/mac.rst -@@ -188,6 +188,28 @@ - * `PyInstaller `__: A cross-platform packaging tool that creates - a single file or folder as a distributable artifact. - -+App Store Compliance -+-------------------- ++To support multiple CPU architectures on a single platform, Apple uses a "fat ++binary" format - a single physical file that contains support for multiple ++architectures. It is possible to compile and use a "thin" single architecture ++version of a binary for testing purposes; however, the "thin" binary will not be ++portable to machines using other architectures. + -+Apps submitted for distribution through the macOS App Store must pass Apple's -+app review process. This process includes a set of automated validation rules -+that inspect the submitted application bundle for problematic code. ++### Building a multi-architecture iOS XCframework + -+The Python standard library contains some code that is known to violate these -+automated rules. While these violations appear to be false positives, Apple's -+review rules cannot be challenged. Therefore, it is necessary to modify the -+Python standard library for an app to pass App Store review. ++The `Apple` subfolder of the Python repository acts as a build script that ++can be used to coordinate the compilation of a complete iOS XCframework. To use ++it, run:: + -+The Python source tree contains -+:source:`a patch file ` that will remove -+all code that is known to cause issues with the App Store review process. This -+patch is applied automatically when CPython is configured with the -+:option:`--with-app-store-compliance` option. ++ python Apple build iOS + -+This patch is not normally required to use CPython on a Mac; nor is it required -+if you are distributing an app *outside* the macOS App Store. It is *only* -+required if you are using the macOS App Store as a distribution channel. ++This will: + - Other Resources - =============== - ---- /dev/null -+++ b/Lib/_apple_support.py -@@ -0,0 +1,66 @@ -+import io -+import sys ++* Configure and compile a version of Python to run on the build machine ++* Download pre-compiled binary dependencies for each platform ++* Configure and build a `Python.framework` for each required architecture and ++ iOS SDK ++* Merge the multiple `Python.framework` folders into a single `Python.xcframework` ++* Produce a `.tar.gz` archive in the `cross-build/dist` folder containing ++ the `Python.xcframework`, plus a copy of the Testbed app pre-configured to ++ use the XCframework. + ++The `Apple` build script has other entry points that will perform the ++individual parts of the overall `build` target, plus targets to test the ++build, clean the `cross-build` folder of iOS build products, and perform a ++complete "build and test" CI run. The `--clean` flag can also be used on ++individual commands to ensure that a stale build product are removed before ++building. + -+def init_streams(log_write, stdout_level, stderr_level): -+ # Redirect stdout and stderr to the Apple system log. This method is -+ # invoked by init_apple_streams() (initconfig.c) if config->use_system_logger -+ # is enabled. -+ sys.stdout = SystemLog(log_write, stdout_level, errors=sys.stderr.errors) -+ sys.stderr = SystemLog(log_write, stderr_level, errors=sys.stderr.errors) ++### Building a single-architecture framework + ++If you're using the `Apple` build script, you won't need to build ++individual frameworks. However, if you do need to manually configure an iOS ++Python build for a single framework, the following options are available. + -+class SystemLog(io.TextIOWrapper): -+ def __init__(self, log_write, level, **kwargs): -+ kwargs.setdefault("encoding", "UTF-8") -+ kwargs.setdefault("line_buffering", True) -+ super().__init__(LogStream(log_write, level), **kwargs) ++#### iOS specific arguments to configure + -+ def __repr__(self): -+ return f"" ++* `--enable-framework[=DIR]` + -+ def write(self, s): -+ if not isinstance(s, str): -+ raise TypeError( -+ f"write() argument must be str, not {type(s).__name__}") ++ This argument specifies the location where the Python.framework will be ++ installed. If `DIR` is not specified, the framework will be installed into ++ a subdirectory of the `iOS/Frameworks` folder. + -+ # In case `s` is a str subclass that writes itself to stdout or stderr -+ # when we call its methods, convert it to an actual str. -+ s = str.__str__(s) ++ This argument *must* be provided when configuring iOS builds. iOS does not ++ support non-framework builds. + -+ # We want to emit one log message per line, so split -+ # the string before sending it to the superclass. -+ for line in s.splitlines(keepends=True): -+ super().write(line) ++* `--with-framework-name=NAME` + -+ return len(s) ++ Specify the name for the Python framework; defaults to `Python`. + ++ > [!NOTE] ++ > Unless you know what you're doing, changing the name of the Python ++ > framework on iOS is not advised. If you use this option, you won't be able ++ > to run the `Apple` build script without making significant manual ++ > alterations, and you won't be able to use any binary packages unless you ++ > compile them yourself using your own framework name. + -+class LogStream(io.RawIOBase): -+ def __init__(self, log_write, level): -+ self.log_write = log_write -+ self.level = level ++#### Building Python for iOS + -+ def __repr__(self): -+ return f"" ++The Python build system will create a `Python.framework` that supports a ++*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a ++framework to contain non-library content, so the iOS build will produce a ++`bin` and `lib` folder in the same output folder as `Python.framework`. ++The `lib` folder will be needed at runtime to support the Python library. + -+ def writable(self): -+ return True ++If you want to use Python in a real iOS project, you need to produce multiple ++`Python.framework` builds, one for each ABI and architecture. iOS builds of ++Python *must* be constructed as framework builds. To support this, you must ++provide the `--enable-framework` flag when configuring the build. The build ++also requires the use of cross-compilation. The minimal commands for building ++Python for the ARM64 iOS simulator will look something like: ++``` ++export PATH="$(pwd)/Apple/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" ++./configure \ ++ --enable-framework \ ++ --host=arm64-apple-ios-simulator \ ++ --build=arm64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++make ++make install ++``` + -+ def write(self, b): -+ if type(b) is not bytes: -+ try: -+ b = bytes(memoryview(b)) -+ except TypeError: -+ raise TypeError( -+ f"write() argument must be bytes-like, not {type(b).__name__}" -+ ) from None ++In this invocation: + -+ # Writing an empty string to the stream should have no effect. -+ if b: -+ # Encode null bytes using "modified UTF-8" to avoid truncating the -+ # message. This should not affect the return value, as the caller -+ # may be expecting it to match the length of the input. -+ self.log_write(self.level, b.replace(b"\x00", b"\xc0\x80")) ++* `Apple/iOS/Resources/bin` has been added to the path, providing some shims for the ++ compilers and linkers needed by the build. Xcode requires the use of `xcrun` ++ to invoke compiler tooling. However, if `xcrun` is pre-evaluated and the ++ result passed to `configure`, these results can embed user- and ++ version-specific paths into the sysconfig data, which limits the portability ++ of the compiled Python. Alternatively, if `xcrun` is used *as* the compiler, ++ it requires that compiler variables like `CC` include spaces, which can ++ cause significant problems with many C configuration systems which assume that ++ `CC` will be a single executable. + -+ return len(b) ---- /dev/null -+++ b/Lib/_ios_support.py -@@ -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") ++ To work around this problem, the `Apple/iOS/Resources/bin` folder contains some ++ wrapper scripts that present as simple compilers and linkers, but wrap ++ underlying calls to `xcrun`. This allows configure to use a `CC` ++ definition without spaces, and without user- or version-specific paths, while ++ retaining the ability to adapt to the local Xcode install. These scripts are ++ included in the `bin` directory of an iOS install. + -+ 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] ++ These scripts will, by default, use the currently active Xcode installation. ++ If you want to use a different Xcode installation, you can use ++ `xcode-select` to set a new default Xcode globally, or you can use the ++ `DEVELOPER_DIR` environment variable to specify an Xcode install. The ++ scripts will use the default `iphoneos`/`iphonesimulator` SDK version for ++ the select Xcode install; if you want to use a different SDK, you can set the ++ `IOS_SDK_VERSION` environment variable. (e.g, setting ++ `IOS_SDK_VERSION=17.1` would cause the scripts to use the `iphoneos17.1` ++ and `iphonesimulator17.1` SDKs, regardless of the Xcode default.) + ++ The path has also been cleared of any user customizations. A common source of ++ bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS ++ build. Resetting the path to a known "bare bones" value is the easiest way to ++ avoid these problems. + -+def get_platform_ios(): -+ # Determine if this is a simulator using the multiarch value -+ is_simulator = sys.implementation._multiarch.endswith("simulator") ++* `--host` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: + -+ # We can't use ctypes; abort -+ if not objc: -+ return None ++ - `arm64-apple-ios` for ARM64 iOS devices. ++ - `arm64-apple-ios-simulator` for the iOS simulator running on Apple ++ Silicon devices. ++ - `x86_64-apple-ios-simulator` for the iOS simulator running on Intel ++ devices. + -+ # 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] ++* `--build` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: + -+ # 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) ++ - `arm64-apple-darwin` for Apple Silicon devices. ++ - `x86_64-apple-darwin` for Intel devices. + -+ # Equivalent of: -+ # device_systemVersion = [device systemVersion] -+ SEL_systemVersion = objc.sel_registerName(b"systemVersion") -+ device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion) ++* `/path/to/python.exe` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for iOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the same version as the Python that is being compiled. To be completely safe, ++ this should be the *exact* same commit hash. However, the longer a Python ++ release has been stable, the more likely it is that this constraint can be ++ relaxed - the same micro version will often be sufficient. + -+ # Equivalent of: -+ # device_systemName = [device systemName] -+ SEL_systemName = objc.sel_registerName(b"systemName") -+ device_systemName = objc.objc_msgSend(device, SEL_systemName) ++* The `install` target for iOS builds is slightly different to other ++ platforms. On most platforms, `make install` will install the build into ++ the final runtime location. This won't be the case for iOS, as the final ++ runtime location will be on a physical device. + -+ # Equivalent of: -+ # device_model = [device model] -+ SEL_model = objc.sel_registerName(b"model") -+ device_model = objc.objc_msgSend(device, SEL_model) ++ However, you still need to run the `install` target for iOS builds, as it ++ performs some final framework assembly steps. The location specified with ++ `--enable-framework` will be the location where `make install` will ++ assemble the complete iOS framework. This completed framework can then ++ be copied and relocated as required. + -+ # UTF8String returns a const char*; -+ SEL_UTF8String = objc.sel_registerName(b"UTF8String") -+ objc.objc_msgSend.restype = c_char_p ++For a full CPython build, you also need to specify the paths to iOS builds of ++the binary libraries that CPython depends on (such as XZ, LibFFI and OpenSSL). ++This can be done by defining library specific environment variables (such as ++`LIBLZMA_CFLAGS`, `LIBLZMA_LIBS`), and the `--with-openssl` configure ++option. Versions of these libraries pre-compiled for iOS can be found in [this ++repository](https://github.com/beeware/cpython-apple-source-deps/releases). ++LibFFI is especially important, as many parts of the standard library ++(including the `platform`, `sysconfig` and `webbrowser` modules) require ++the use of the `ctypes` module at runtime. + -+ # 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() ++By default, Python will be compiled with an iOS deployment target (i.e., the ++minimum supported iOS version) of 13.0. To specify a different deployment ++target, provide the version number as part of the `--host` argument - for ++example, `--host=arm64-apple-ios15.4-simulator` would compile an ARM64 ++simulator build with a deployment target of 15.4. + -+ return system, release, model, is_simulator -diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py -index 26135ad9629..315c725e547 100644 ---- a/Lib/ctypes/__init__.py -+++ b/Lib/ctypes/__init__.py -@@ -343,6 +343,19 @@ - use_errno=False, - use_last_error=False, - winmode=None): -+ if name: -+ name = _os.fspath(name) ++## Testing Python on iOS + -+ # If the filename that has been provided is an iOS/tvOS/watchOS -+ # .fwork file, dereference the location to the true origin of the -+ # binary. -+ if name.endswith(".fwork"): -+ with open(name) as f: -+ name = _os.path.join( -+ _os.path.dirname(_sys.executable), -+ f.read().strip() -+ ) ++### Testing a multi-architecture framework + - self._name = name - flags = self._func_flags_ - if use_errno: -diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py -index c550883e7c7..12d7428fe9a 100644 ---- a/Lib/ctypes/util.py -+++ b/Lib/ctypes/util.py -@@ -67,7 +67,7 @@ - return fname - return None - --elif os.name == "posix" and sys.platform == "darwin": -+elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}: - from ctypes.macholib.dyld import dyld_find as _dyld_find - def find_library(name): - possible = ['lib%s.dylib' % name, -diff --git a/Lib/distutils/tests/test_cygwinccompiler.py b/Lib/distutils/tests/test_cygwinccompiler.py -index 633d3041a1b..a91ee9e0f37 100644 ---- a/Lib/distutils/tests/test_cygwinccompiler.py -+++ b/Lib/distutils/tests/test_cygwinccompiler.py -@@ -4,6 +4,9 @@ - import os - from io import BytesIO - -+if sys.platform != 'win32': -+ raise unittest.SkipTest("Cygwin tests only needed on Windows") ++Once you have a built an XCframework, you can test that framework by running: + - from distutils import cygwinccompiler - from distutils.cygwinccompiler import (check_config_h, - CONFIG_H_OK, CONFIG_H_NOTOK, -diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py -index 363834fe8b3..c67b7a130b5 100644 ---- a/Lib/distutils/tests/test_sysconfig.py -+++ b/Lib/distutils/tests/test_sysconfig.py -@@ -10,7 +10,7 @@ - from distutils import sysconfig - from distutils.ccompiler import get_default_compiler - from distutils.tests import support --from test.support import swap_item, requires_subprocess, is_wasi -+from test.support import swap_item, requires_subprocess, is_apple_mobile, is_wasi - from test.support.os_helper import TESTFN - from test.support.warnings_helper import check_warnings - -@@ -33,6 +33,7 @@ - shutil.rmtree(TESTFN) - - @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") -+ @unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile") - def test_get_config_h_filename(self): - config_h = sysconfig.get_config_h_filename() - self.assertTrue(os.path.isfile(config_h), config_h) -@@ -50,6 +51,7 @@ - self.assertTrue(cvars) - - @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") -+ @unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile") - def test_srcdir(self): - # See Issues #15322, #15364. - srcdir = sysconfig.get_config_var('srcdir') -diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py -index d00c48981eb..5d12b4779db 100644 ---- a/Lib/distutils/unixccompiler.py -+++ b/Lib/distutils/unixccompiler.py -@@ -270,9 +270,9 @@ - static_f = self.library_filename(lib, lib_type='static') - - if sys.platform == 'darwin': -- # On OSX users can specify an alternate SDK using -- # '-isysroot', calculate the SDK root if it is specified -- # (and use it further on) -+ # On macOS users can specify an alternate SDK using -+ # '-isysroot ' or --sysroot=, calculate the SDK root -+ # if it is specified (and use it further on) - # - # Note that, as of Xcode 7, Apple SDKs may contain textual stub - # libraries with .tbd extensions rather than the normal .dylib -@@ -291,12 +291,14 @@ - cflags = sysconfig.get_config_var('CFLAGS') - m = re.search(r'-isysroot\s*(\S+)', cflags) - if m is None: -- sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC')) -+ m = re.search(r'--sysroot=(\S+)', cflags) -+ if m is None: -+ sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC')) -+ else: -+ sysroot = m.group(1) - else: - sysroot = m.group(1) - -- -- - for dir in dirs: - shared = os.path.join(dir, shared_f) - dylib = os.path.join(dir, dylib_f) -diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py -index 2ce5c5b64d6..e927f4af938 100644 ---- a/Lib/distutils/util.py -+++ b/Lib/distutils/util.py -@@ -89,10 +89,25 @@ - if m: - release = m.group() - elif osname[:6] == "darwin": -- import _osx_support, distutils.sysconfig -- osname, release, machine = _osx_support.get_platform_osx( -- distutils.sysconfig.get_config_vars(), -- osname, release, machine) -+ import distutils.sysconfig -+ config_vars = distutils.sysconfig.get_config_vars() -+ if sys.platform == "ios": -+ release = config_vars.get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") -+ osname = sys.platform -+ machine = sys.implementation._multiarch -+ elif sys.platform == "tvos": -+ release = config_vars.get("TVOS_DEPLOYMENT_TARGET", "9.0") -+ osname = sys.platform -+ machine = sys.implementation._multiarch -+ elif sys.platform == "watchos": -+ release = config_vars.get("WATCHOS_DEPLOYMENT_TARGET", "4.0") -+ osname = sys.platform -+ machine = sys.implementation._multiarch -+ else: -+ import _osx_support -+ osname, release, machine = _osx_support.get_platform_osx( -+ config_vars, -+ osname, release, machine) - - return "%s-%s-%s" % (osname, release, machine) - -@@ -170,7 +185,7 @@ - if _environ_checked: - return - -- if os.name == 'posix' and 'HOME' not in os.environ: -+ if os.name == 'posix' and 'HOME' not in os.environ and sys.platform not in {"ios", "tvos", "watchos"}: - try: - import pwd - os.environ['HOME'] = pwd.getpwuid(os.getuid())[5] -diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py -index e53f6acf38f..64aa8444410 100644 ---- a/Lib/importlib/_bootstrap_external.py -+++ b/Lib/importlib/_bootstrap_external.py -@@ -52,7 +52,7 @@ - - # Bootstrap-related code ###################################################### - _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', --_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin' -+_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos' - _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY - + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) - -@@ -1708,6 +1708,46 @@ - return 'FileFinder({!r})'.format(self.path) - - -+class AppleFrameworkLoader(ExtensionFileLoader): -+ """A loader for modules that have been packaged as frameworks for -+ compatibility with Apple's iOS App Store policies. -+ """ -+ def create_module(self, spec): -+ # If the ModuleSpec has been created by the FileFinder, it will have -+ # been created with an origin pointing to the .fwork file. We need to -+ # redirect this to the location in the Frameworks folder, using the -+ # content of the .fwork file. -+ if spec.origin.endswith(".fwork"): -+ with _io.FileIO(spec.origin, 'r') as file: -+ framework_binary = file.read().decode().strip() -+ bundle_path = _path_split(sys.executable)[0] -+ spec.origin = _path_join(bundle_path, framework_binary) ++ $ python Apple test iOS + -+ # If the loader is created based on the spec for a loaded module, the -+ # path will be pointing at the Framework location. If this occurs, -+ # get the original .fwork location to use as the module's __file__. -+ if self.path.endswith(".fwork"): -+ path = self.path -+ else: -+ with _io.FileIO(self.path + ".origin", 'r') as file: -+ origin = file.read().decode().strip() -+ bundle_path = _path_split(sys.executable)[0] -+ path = _path_join(bundle_path, origin) ++This test will attempt to find an "SE-class" simulator (i.e., an iPhone SE, or ++iPhone 16e, or similar), and run the test suite on the most recent version of ++iOS that is available. You can specify a simulator using the `--simulator` ++command line argument, providing the name of the simulator (e.g., `--simulator ++'iPhone 16 Pro'`). You can also use this argument to control the OS version used ++for testing; `--simulator 'iPhone 16 Pro,OS=18.2'` would attempt to run the ++tests on an iPhone 16 Pro running iOS 18.2. + -+ module = _bootstrap._call_with_frames_removed(_imp.create_dynamic, spec) ++If the test runner is executed on GitHub Actions, the `GITHUB_ACTIONS` ++environment variable will be exposed to the iOS process at runtime. + -+ _bootstrap._verbose_message( -+ "Apple framework extension module {!r} loaded from {!r} (path {!r})", -+ spec.name, -+ spec.origin, -+ path, -+ ) ++### Testing a single-architecture framework + -+ # Ensure that the __file__ points at the .fwork location -+ module.__file__ = path ++The `Apple/testbed` folder that contains an Xcode project that is able to run ++the Python test suite on Apple platforms. This project converts the Python test ++suite into a single test case in Xcode's XCTest framework. The single XCTest ++passes if the test suite passes. + -+ return module ++To run the test suite, configure a Python build for an iOS simulator (i.e., ++`--host=arm64-apple-ios-simulator` or `--host=x86_64-apple-ios-simulator` ++), specifying a framework build (i.e. `--enable-framework`). Ensure that your ++`PATH` has been configured to include the `Apple/iOS/Resources/bin` folder and ++exclude any non-iOS tools, then run: ++``` ++make all ++make install ++make testios ++``` + - # Import setup ############################################################### - - def _fix_up_module(ns, name, pathname, cpathname=None): -@@ -1738,10 +1778,17 @@ - - Each item is a tuple (loader, suffixes). - """ -- extensions = ExtensionFileLoader, _imp.extension_suffixes() -+ if sys.platform in {"ios", "tvos", "watchos"}: -+ extension_loaders = [(AppleFrameworkLoader, [ -+ suffix.replace(".so", ".fwork") -+ for suffix in _imp.extension_suffixes() -+ ])] -+ else: -+ extension_loaders = [] -+ extension_loaders.append((ExtensionFileLoader, _imp.extension_suffixes())) - source = SourceFileLoader, SOURCE_SUFFIXES - bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES -- return [extensions, source, bytecode] -+ return extension_loaders + [source, bytecode] - - - def _set_bootstrap_module(_bootstrap_module): -diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py -index 3fa151f390b..47a6a5ed5a6 100644 ---- a/Lib/importlib/abc.py -+++ b/Lib/importlib/abc.py -@@ -261,7 +261,11 @@ - else: - return self.source_to_code(source, path) - --_register(ExecutionLoader, machinery.ExtensionFileLoader) -+_register( -+ ExecutionLoader, -+ machinery.ExtensionFileLoader, -+ machinery.AppleFrameworkLoader, -+) - - - class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader): -diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py -index d9a19a13f7b..fbd30b159fb 100644 ---- a/Lib/importlib/machinery.py -+++ b/Lib/importlib/machinery.py -@@ -12,6 +12,7 @@ - from ._bootstrap_external import SourceFileLoader - from ._bootstrap_external import SourcelessFileLoader - from ._bootstrap_external import ExtensionFileLoader -+from ._bootstrap_external import AppleFrameworkLoader - from ._bootstrap_external import NamespaceLoader - - -diff --git a/Lib/inspect.py b/Lib/inspect.py -index 09e6a23652f..0dbe90f470a 100644 ---- a/Lib/inspect.py -+++ b/Lib/inspect.py -@@ -972,6 +972,7 @@ - return object - if hasattr(object, '__module__'): - return sys.modules.get(object.__module__) ++This will: + - # Try the filename to modulename cache - if _filename is not None and _filename in modulesbyfile: - return sys.modules.get(modulesbyfile[_filename]) -@@ -1065,7 +1066,7 @@ - # Allow filenames in form of "" to pass through. - # `doctest` monkeypatches `linecache` module to enable - # inspection, so let `linecache.getlines` to be called. -- if not (file.startswith('<') and file.endswith('>')): -+ if (not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'): - raise OSError('source code not available') - - module = getmodule(object, file) -diff --git a/Lib/lib2to3/tests/test_parser.py b/Lib/lib2to3/tests/test_parser.py -index 8e7773bcae1..7df3e354f6d 100644 ---- a/Lib/lib2to3/tests/test_parser.py -+++ b/Lib/lib2to3/tests/test_parser.py -@@ -62,9 +62,7 @@ - shutil.rmtree(tmpdir) - - @unittest.skipIf(sys.executable is None, 'sys.executable required') -- @unittest.skipIf( -- sys.platform in {'emscripten', 'wasi'}, 'requires working subprocess' -- ) -+ @test.support.requires_subprocess() - def test_load_grammar_from_subprocess(self): - tmpdir = tempfile.mkdtemp() - tmpsubdir = os.path.join(tmpdir, 'subdir') -diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py -index a0a020f9eeb..ac478ee7f51 100644 ---- a/Lib/modulefinder.py -+++ b/Lib/modulefinder.py -@@ -72,7 +72,12 @@ - if isinstance(spec.loader, importlib.machinery.SourceFileLoader): - kind = _PY_SOURCE - -- elif isinstance(spec.loader, importlib.machinery.ExtensionFileLoader): -+ elif isinstance( -+ spec.loader, ( -+ importlib.machinery.ExtensionFileLoader, -+ importlib.machinery.AppleFrameworkLoader, -+ ) -+ ): - kind = _C_EXTENSION - - elif isinstance(spec.loader, importlib.machinery.SourcelessFileLoader): -diff --git a/Lib/platform.py b/Lib/platform.py -index 9b9d88bf584..1bb5fcb96d6 100755 ---- a/Lib/platform.py -+++ b/Lib/platform.py -@@ -452,6 +452,78 @@ - # If that also doesn't work return the default values - return release, versioninfo, machine - ++* Build an iOS framework for your chosen architecture; ++* Finalize the single-platform framework; ++* Make a clean copy of the testbed project; ++* Install the Python iOS framework into the copy of the testbed project; and ++* Run the test suite on an "entry-level device" simulator (i.e., an iPhone SE, ++ iPhone 16e, or a similar). + -+# A namedtuple for iOS version information. -+IOSVersionInfo = collections.namedtuple( -+ "IOSVersionInfo", -+ ["system", "release", "model", "is_simulator"] -+) ++On success, the test suite will exit and report successful completion of the ++test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15 ++minutes to run; a couple of extra minutes is required to compile the testbed ++project, and then boot and prepare the iOS simulator. + ++### Debugging test failures + -+def ios_ver(system="", release="", model="", is_simulator=False): -+ """Get iOS version information, and return it as a namedtuple: -+ (system, release, model, is_simulator). ++Running `python Apple test iOS` generates a standalone version of the ++`Apple/testbed` project, and runs the full test suite. It does this using ++`Apple/testbed` itself - the folder is an executable module that can be used ++to create and run a clone of the testbed project. The standalone version of the ++testbed will be created in a directory named ++`cross-build/iOS-testbed.`. + -+ 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) -+ -+ -+# A namedtuple for tvOS version information. -+TVOSVersionInfo = collections.namedtuple( -+ "TVOSVersionInfo", -+ ["system", "release", "model", "is_simulator"] -+) -+ -+ -+def tvos_ver(system="", release="", model="", is_simulator=False): -+ """Get tvOS version information, and return it as a namedtuple: -+ (system, release, model, is_simulator). ++You can generate your own standalone testbed instance by running: ++``` ++python cross-build/iOS/testbed clone my-testbed ++``` + -+ If values can't be determined, they are set to values provided as -+ parameters. -+ """ -+ if sys.platform == "tvos": -+ # TODO: Can the iOS implementation be used here? -+ import _ios_support -+ result = _ios_support.get_platform_ios() -+ if result is not None: -+ return TVOSVersionInfo(*result) ++In this invocation, `my-testbed` is the name of the folder for the new ++testbed clone. + -+ return TVOSVersionInfo(system, release, model, is_simulator) ++If you've built your own XCframework, or you only want to test a single architecture, ++you can construct a standalone testbed instance by running: ++``` ++python Apple/testbed clone --platform iOS --framework my-testbed ++``` + ++The framework path can be the path path to a `Python.xcframework`, or the ++path to a folder that contains a single-platform `Python.framework`. + -+# A namedtuple for watchOS version information. -+WatchOSVersionInfo = collections.namedtuple( -+ "WatchOSVersionInfo", -+ ["system", "release", "model", "is_simulator"] -+) ++You can then use the `my-testbed` folder to run the Python test suite, ++passing in any command line arguments you may require. For example, if you're ++trying to diagnose a failure in the `os` module, you might run: ++``` ++python my-testbed run -- test -W test_os ++``` + ++This is the equivalent of running `python -m test -W test_os` on a desktop ++Python build. Any arguments after the `--` will be passed to testbed as if ++they were arguments to `python -m` on a desktop machine. + -+def watchos_ver(system="", release="", model="", is_simulator=False): -+ """Get watchOS version information, and return it as a namedtuple: -+ (system, release, model, is_simulator). ++### Testing in Xcode + -+ If values can't be determined, they are set to values provided as -+ parameters. -+ """ -+ if sys.platform == "watchos": -+ # TODO: Can the iOS implementation be used here? -+ import _ios_support -+ result = _ios_support.get_platform_ios() -+ if result is not None: -+ return WatchOSVersionInfo(*result) ++You can also open the testbed project in Xcode by running: ++``` ++open my-testbed/iOSTestbed.xcodeproj ++``` + -+ return WatchOSVersionInfo(system, release, model, is_simulator) ++This will allow you to use the full Xcode suite of tools for debugging. + ++The arguments used to run the test suite are defined as part of the test plan. ++To modify the test plan, select the test plan node of the project tree (it ++should be the first child of the root node), and select the "Configurations" ++tab. Modify the "Arguments Passed On Launch" value to change the testing ++arguments. + - def _java_getprop(name, default): - - from java.lang import System -@@ -567,7 +639,7 @@ - if cleaned == platform: - break - platform = cleaned -- while platform[-1] == '-': -+ while platform and platform[-1] == '-': - platform = platform[:-1] - - return platform -@@ -608,7 +680,7 @@ - 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 - -@@ -750,6 +822,25 @@ - csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) - return 'Alpha' if cpu_number >= 128 else 'VAX' - -+ # On the iOS/tvOS/watchOS 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 devices, so we know the right -+ # answer. -+ def get_ios(): -+ if sys.implementation._multiarch.endswith("simulator"): -+ return os.uname().machine -+ return 'arm64' ++The test plan also disables parallel testing, and specifies the use of the ++`Testbed.lldbinit` file for providing configuration of the debugger. The ++default debugger configuration disables automatic breakpoints on the ++`SIGINT`, `SIGUSR1`, `SIGUSR2`, and `SIGXFSZ` signals. + -+ def get_tvos(): -+ if sys.implementation._multiarch.endswith("simulator"): -+ return os.uname().machine -+ return 'arm64' ++### Testing on an iOS device + -+ def get_watchos(): -+ if sys.implementation._multiarch.endswith("simulator"): -+ return os.uname().machine -+ return 'arm64_32' ++To test on an iOS device, the app needs to be signed with known developer ++credentials. To obtain these credentials, you must have an iOS Developer ++account, and your Xcode install will need to be logged into your account (see ++the Accounts tab of the Preferences dialog). + - def from_subprocess(): - """ - Fall back to `uname -p` -@@ -904,6 +995,14 @@ - system = 'Windows' - release = 'Vista' - -+ # Normalize responses on Apple mobile platforms -+ if sys.platform == 'ios': -+ system, release, _, _ = ios_ver() -+ if sys.platform == 'tvos': -+ system, release, _, _ = tvos_ver() -+ if sys.platform == 'watchos': -+ system, release, _, _ = watchos_ver() ++Once the project is open, and you're signed into your Apple Developer account, ++select the root node of the project tree (labeled "iOSTestbed"), then the ++"Signing & Capabilities" tab in the details page. Select a development team ++(this will likely be your own name), and plug in a physical device to your ++macOS machine with a USB cable. You should then be able to select your physical ++device from the list of targets in the pulldown in the Xcode titlebar. +--- /dev/null ++++ b/Apple/iOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ %VERSION% ++ CFBundleSupportedPlatforms ++ ++ iPhoneOS ++ ++ MinimumOSVersion ++ @IPHONEOS_DEPLOYMENT_TARGET@ ++ ++ +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-ar +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@" +--- /dev/null ++++ b/Apple/iOS/Resources/pyconfig.h +@@ -0,0 +1,7 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif + - vals = system, node, release, version, machine - # Replace 'unknown' values with the more portable '' - _uname_cache = uname_result(*map(_unknown_as_blank, vals)) -@@ -1216,11 +1315,18 @@ - 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() -+ elif sys.platform == "tvos": -+ system, release, _, _ = tvos_ver() -+ elif sys.platform == "watchos": -+ system, release, _, _ = watchos_ver() -+ else: -+ macos_release = mac_ver()[0] -+ if macos_release: -+ system = 'macOS' -+ release = macos_release - - if system == 'Windows': - # MS platforms -diff --git a/Lib/site.py b/Lib/site.py -index 2904e44cffd..371a89ff717 100644 ---- a/Lib/site.py -+++ b/Lib/site.py -@@ -276,8 +276,8 @@ - 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): -diff --git a/Lib/subprocess.py b/Lib/subprocess.py -index 1d17ae3608a..34dfa0019a5 100644 ---- a/Lib/subprocess.py -+++ b/Lib/subprocess.py -@@ -74,8 +74,8 @@ - else: - _mswindows = True - --# wasm32-emscripten and wasm32-wasi do not support processes --_can_fork_exec = sys.platform not in {"emscripten", "wasi"} -+# some platforms do not support subprocesses -+_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"} - - if _mswindows: - import _winapi -@@ -103,18 +103,22 @@ - if _can_fork_exec: - from _posixsubprocess import fork_exec as _fork_exec - # used in methods that are called by __del__ -- _waitpid = os.waitpid -- _waitstatus_to_exitcode = os.waitstatus_to_exitcode -- _WIFSTOPPED = os.WIFSTOPPED -- _WSTOPSIG = os.WSTOPSIG -- _WNOHANG = os.WNOHANG -+ class _del_safe: -+ waitpid = os.waitpid -+ waitstatus_to_exitcode = os.waitstatus_to_exitcode -+ WIFSTOPPED = os.WIFSTOPPED -+ WSTOPSIG = os.WSTOPSIG -+ WNOHANG = os.WNOHANG -+ ECHILD = errno.ECHILD - else: -- _fork_exec = None -- _waitpid = None -- _waitstatus_to_exitcode = None -- _WIFSTOPPED = None -- _WSTOPSIG = None -- _WNOHANG = None -+ class _del_safe: -+ waitpid = None -+ waitstatus_to_exitcode = None -+ WIFSTOPPED = None -+ WSTOPSIG = None -+ WNOHANG = None -+ ECHILD = errno.ECHILD ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/Info.plist +@@ -0,0 +1,106 @@ ++ ++ ++ ++ ++ AvailableLibraries ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ ios-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ ios ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ ios-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ ios ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ tvos-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ tvos ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ tvos-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ tvos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ watchos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_32 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64_32 ++ ++ SupportedPlatform ++ watchos ++ ++ ++ CFBundlePackageType ++ XFWK ++ XCFrameworkFormatVersion ++ 1.0 ++ ++ +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ iPhoneOS ++ ++ MinimumOSVersion ++ 13.0 ++ CFBundleVersion ++ 1 ++ ++ +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ 9.0 ++ CFBundleVersion ++ 1 ++ ++ +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/build/utils.sh +@@ -0,0 +1,174 @@ ++# Utility methods for use in an Xcode project. ++# ++# An iOS XCframework cannot include any content other than the library binary ++# and relevant metadata. However, Python requires a standard library at runtime. ++# Therefore, it is necessary to add a build step to an Xcode app target that ++# processes the standard library and puts the content into the final app. ++# ++# In general, these tools will be invoked after bundle resources have been ++# copied into the app, but before framework embedding (and signing). ++# ++# The following is an example script, assuming that: ++# * Python.xcframework is in the root of the project ++# * There is an `app` folder that contains the app code ++# * There is an `app_packages` folder that contains installed Python packages. ++# ----- ++# set -e ++# source $PROJECT_DIR/Python.xcframework/build/build_utils.sh ++# install_python Python.xcframework app app_packages ++# ----- ++ ++# Copy the standard library from the XCframework into the app bundle. ++# ++# Accepts one argument: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. ++install_stdlib() { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ ++ mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" ++ if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then ++ echo "Installing Python modules for iOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/ios-arm64-simulator" ]; then ++ SLICE_FOLDER="ios-arm64-simulator" ++ else ++ SLICE_FOLDER="ios-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then ++ echo "Installing Python modules for iOS Device" ++ SLICE_FOLDER="ios-arm64" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then ++ echo "Installing Python modules for tvOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then ++ SLICE_FOLDER="tvos-arm64-simulator" ++ else ++ SLICE_FOLDER="tvos-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then ++ echo "Installing Python modules for tvOS Device" ++ SLICE_FOLDER="tvos-arm64" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then ++ echo "Installing Python modules for watchOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then ++ SLICE_FOLDER="watchos-arm64-simulator" ++ else ++ SLICE_FOLDER="watchos-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then ++ echo "Installing Python modules for watchOS Device" ++ SLICE_FOLDER="watchos-arm64" ++ else ++ echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME" ++ exit 1 ++ fi + - import select - import selectors - -@@ -1958,20 +1962,16 @@ - raise child_exception_type(err_msg) - - -- def _handle_exitstatus(self, sts, -- _waitstatus_to_exitcode=_waitstatus_to_exitcode, -- _WIFSTOPPED=_WIFSTOPPED, -- _WSTOPSIG=_WSTOPSIG): -+ def _handle_exitstatus(self, sts, _del_safe=_del_safe): - """All callers to this function MUST hold self._waitpid_lock.""" - # This method is called (indirectly) by __del__, so it cannot - # refer to anything outside of its local scope. -- if _WIFSTOPPED(sts): -- self.returncode = -_WSTOPSIG(sts) -+ if _del_safe.WIFSTOPPED(sts): -+ self.returncode = -_del_safe.WSTOPSIG(sts) - else: -- self.returncode = _waitstatus_to_exitcode(sts) -+ self.returncode = _del_safe.waitstatus_to_exitcode(sts) - -- def _internal_poll(self, _deadstate=None, _waitpid=_waitpid, -- _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD): -+ def _internal_poll(self, _deadstate=None, _del_safe=_del_safe): - """Check if child process has terminated. Returns returncode - attribute. - -@@ -1987,13 +1987,13 @@ - try: - if self.returncode is not None: - return self.returncode # Another thread waited. -- pid, sts = _waitpid(self.pid, _WNOHANG) -+ pid, sts = _del_safe.waitpid(self.pid, _del_safe.WNOHANG) - if pid == self.pid: - self._handle_exitstatus(sts) - except OSError as e: - if _deadstate is not None: - self.returncode = _deadstate -- elif e.errno == _ECHILD: -+ elif e.errno == _del_safe.ECHILD: - # This happens if SIGCLD is set to be ignored or - # waiting for child processes has otherwise been - # disabled for our process. This child is dead, we -diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py -index ebe37118274..edf12d57068 100644 ---- a/Lib/sysconfig.py -+++ b/Lib/sysconfig.py -@@ -20,6 +20,7 @@ - - # Keys for get_config_var() that are never converted to Python integers. - _ALWAYS_STR = { -+ 'IPHONEOS_DEPLOYMENT_TARGET', - 'MACOSX_DEPLOYMENT_TARGET', - } - -@@ -56,6 +57,7 @@ - 'scripts': '{base}/Scripts', - 'data': '{base}', - }, ++ # If the XCframework has a shared lib folder, then it's a full framework. ++ # Copy both the common and slice-specific part of the lib directory. ++ # Otherwise, it's a single-arch framework; use the "full" lib folder. ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then ++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" ++ rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/" ++ else ++ # A single-arch framework will have a libpython symlink; that can't be included at runtime ++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib' ++ fi ++} + - # 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). -@@ -111,8 +113,8 @@ - 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): -@@ -289,6 +291,7 @@ - 'home': 'posix_home', - 'user': 'osx_framework_user', - } ++# Convert a single .so library into a framework that iOS can load. ++# ++# Accepts three arguments: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. ++# 2. The base path, relative to the installed location in the app bundle, that ++# needs to be processed. Any .so file found in this path (or a subdirectory ++# of it) will be processed. ++# 2. The full path to a single .so file to process. This path should include ++# the base path. ++install_dylib () { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ INSTALL_BASE=$2 ++ FULL_EXT=$3 ++ ++ # The name of the extension file ++ EXT=$(basename "$FULL_EXT") ++ # The name and location of the module ++ MODULE_PATH=$(dirname "$FULL_EXT") ++ MODULE_NAME=$(echo $EXT | cut -d "." -f 1) ++ # The location of the extension file, relative to the bundle ++ RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} ++ # The path to the extension file, relative to the install base ++ PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} ++ # The full dotted name of the extension module, constructed from the file path. ++ FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); ++ # A bundle identifier; not actually used, but required by Xcode framework packaging ++ FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") ++ # The name of the framework folder. ++ FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" ++ ++ # If the framework folder doesn't exist, create it. ++ if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then ++ echo "Creating framework for $RELATIVE_EXT" ++ mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ++ cp "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/build/$PLATFORM_FAMILY_NAME-dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ fi + - return { - 'prefix': 'posix_prefix', - 'home': 'posix_home', -@@ -788,10 +791,23 @@ - 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", "13.0") -+ osname = sys.platform -+ machine = sys.implementation._multiarch -+ elif sys.platform == "tvos": -+ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "9.0") -+ osname = sys.platform -+ machine = sys.implementation._multiarch -+ elif sys.platform == "watchos": -+ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "4.0") -+ 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/pythoninfo.py b/Lib/test/pythoninfo.py -index 74ebb5e5b8a..d3e5fddf69b 100644 ---- a/Lib/test/pythoninfo.py -+++ b/Lib/test/pythoninfo.py -@@ -287,6 +287,7 @@ - "HOMEDRIVE", - "HOMEPATH", - "IDLESTARTUP", -+ "IPHONEOS_DEPLOYMENT_TARGET", - "LANG", - "LDFLAGS", - "LDSHARED", -diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 059542c2433..532dff848a4 100644 ---- a/Lib/test/support/__init__.py -+++ b/Lib/test/support/__init__.py -@@ -45,7 +45,7 @@ - "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", - # sys - "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", -- "check_impl_detail", "unix_shell", "setswitchinterval", -+ "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval", - # network - "open_urlresource", - # processes -@@ -517,7 +517,7 @@ - - is_android = hasattr(sys, 'getandroidapilevel') - --if sys.platform not in ('win32', 'vxworks'): -+if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: - unix_shell = '/system/bin/sh' if is_android else '/bin/sh' - else: - unix_shell = None -@@ -527,19 +527,35 @@ - is_emscripten = sys.platform == "emscripten" - is_wasi = sys.platform == "wasi" - --has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi -+# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not -+# have subprocess or fork support. -+is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} -+is_apple = is_apple_mobile or sys.platform == "darwin" ++ echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ # Create a placeholder .fwork file where the .so was ++ echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork ++ # Create a back reference to the .so file location in the framework ++ echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" ++ ++ # If the framework provides an xcprivacy file, install it. ++ if [ -e "$MODULE_PATH/$MODULE_NAME.xcprivacy" ]; then ++ echo "Installing XCPrivacy file for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ XCPRIVACY_FILE="$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/PrivacyInfo.xcprivacy" ++ if [ -e "$XCPRIVACY_FILE" ]; then ++ rm -rf "$XCPRIVACY_FILE" ++ fi ++ mv "$MODULE_PATH/$MODULE_NAME.xcprivacy" "$XCPRIVACY_FILE" ++ fi + -+has_fork_support = hasattr(os, "fork") and not ( -+ is_emscripten -+ or is_wasi -+ or is_apple_mobile -+) - - def requires_fork(): - return unittest.skipUnless(has_fork_support, "requires working os.fork()") - --has_subprocess_support = not is_emscripten and not is_wasi -+has_subprocess_support = not ( -+ is_emscripten -+ or is_wasi -+ or is_apple_mobile -+) - - def requires_subprocess(): - """Used for subprocess, os.spawn calls, fd inheritance""" - return unittest.skipUnless(has_subprocess_support, "requires subprocess support") - - # Emscripten's socket emulation and WASI sockets have limitations. --has_socket_support = not is_emscripten and not is_wasi -+has_socket_support = not ( -+ is_emscripten -+ or is_wasi -+) - - def requires_working_socket(*, module=False): - """Skip tests or modules that require working sockets -diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py -index c1b2995ef37..353a3f589ac 100644 ---- a/Lib/test/support/os_helper.py -+++ b/Lib/test/support/os_helper.py -@@ -9,6 +9,8 @@ - import unittest - import warnings - -+from test import support ++ echo "Signing framework as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." ++ /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ++} + - - # Filename used for testing - if os.name == 'java': -@@ -23,8 +25,8 @@ - - # TESTFN_UNICODE is a non-ascii filename - TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f" --if sys.platform == 'darwin': -- # In Mac OS X's VFS API file names are, by definition, canonically -+if support.is_apple: -+ # On Apple's VFS API file names are, by definition, canonically - # decomposed Unicode, encoded using UTF-8. See QA1173: - # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html - import unicodedata -@@ -49,8 +51,8 @@ - 'encoding (%s). Unicode filename tests may not be effective' - % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) - TESTFN_UNENCODABLE = None --# macOS and Emscripten deny unencodable filenames (invalid utf-8) --elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: -+# Apple and Emscripten deny unencodable filenames (invalid utf-8) -+elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}: - try: - # ascii and utf-8 cannot encode the byte 0xff - b'\xff'.decode(sys.getfilesystemencoding()) -@@ -611,7 +613,8 @@ - if hasattr(os, 'sysconf'): - try: - MAXFD = os.sysconf("SC_OPEN_MAX") -- except OSError: -+ except (OSError, ValueError): -+ # gh-118201: ValueError is raised intermittently on iOS - pass - - old_modes = None -diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py -index ec6d1dee4e9..48900f50508 100644 ---- a/Lib/test/support/socket_helper.py -+++ b/Lib/test/support/socket_helper.py -@@ -1,7 +1,9 @@ - import contextlib - import errno -+import os.path - import socket - import subprocess -+import tempfile - import sys - import unittest - -@@ -273,6 +275,16 @@ - socket.setdefaulttimeout(old_timeout) - - -+def create_unix_domain_name(): -+ """ -+ Create a UNIX domain name: socket.bind() argument of a AF_UNIX socket. ++# Process all the dynamic libraries in a path into Framework format. ++# ++# Accepts two arguments: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. ++# 2. The base path, relative to the installed location in the app bundle, that ++# needs to be processed. Any .so file found in this path (or a subdirectory ++# of it) will be processed. ++process_dylibs () { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ LIB_PATH=$2 ++ find "$CODESIGNING_FOLDER_PATH/$LIB_PATH" -name "*.so" | while read FULL_EXT; do ++ install_dylib $PYTHON_XCFRAMEWORK_PATH "$LIB_PATH/" "$FULL_EXT" ++ done ++} + -+ Return a path relative to the current directory to get a short path -+ (around 27 ASCII characters). -+ """ -+ return tempfile.mktemp(prefix="test_python_", suffix='.sock', -+ dir=os.path.curdir) ++# The entry point for post-processing a Python XCframework. ++# ++# Accepts 1 or more arguments: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. If the XCframework is in the root of the project, ++# 2+. The path of a package, relative to the root of the packaged app, that contains ++# library content that should be processed for binary libraries. ++install_python() { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ shift ++ ++ install_stdlib $PYTHON_XCFRAMEWORK_PATH ++ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") ++ echo "Install Python $PYTHON_VER standard library extension modules..." ++ process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload ++ ++ for package_path in $@; do ++ echo "Installing $package_path extension modules ..." ++ process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path ++ done ++} +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ watchOS ++ ++ MinimumOSVersion ++ 4.0 ++ CFBundleVersion ++ 1 ++ ++ +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/ios-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + - # consider that sysctl values should not change while tests are running - _sysctl_cache = {} - ++It should be used as a target for `--enable-framework` when compiling an iOS on-device ++build for testing purposes. --- /dev/null -+++ b/Lib/test/test_apple.py -@@ -0,0 +1,155 @@ -+import unittest -+from _apple_support import SystemLog -+from test.support import is_apple_mobile -+from unittest.mock import Mock, call ++++ b/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+if not is_apple_mobile: -+ raise unittest.SkipTest("iOS-specific") ++It should be used as a target for `--enable-framework` when compiling an iOS simulator ++build for testing purposes (either x86_64 or ARM64). +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/tvos-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + ++It should be used as a target for `--enable-framework` when compiling a tvOS ++on-device build for testing purposes. +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+# Test redirection of stdout and stderr to the Apple system log. -+class TestAppleSystemLogOutput(unittest.TestCase): -+ maxDiff = None ++It should be used as a target for `--enable-framework` when compiling a tvOS ++simulator build for testing purposes (either x86_64 or ARM64). +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/watchos-arm64_32/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+ def assert_writes(self, output): -+ self.assertEqual( -+ self.log_write.mock_calls, -+ [ -+ call(self.log_level, line) -+ for line in output -+ ] -+ ) ++It should be used as a target for `--enable-framework` when compiling a watchOS on-device ++build for testing purposes. +--- /dev/null ++++ b/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+ self.log_write.reset_mock() ++It should be used as a target for `--enable-framework` when compiling a watchOS ++simulator build for testing purposes (either x86_64 or ARM64). +--- /dev/null ++++ b/Apple/testbed/Testbed.lldbinit +@@ -0,0 +1,4 @@ ++process handle SIGINT -n true -p true -s false ++process handle SIGUSR1 -n true -p true -s false ++process handle SIGUSR2 -n true -p true -s false ++process handle SIGXFSZ -n true -p true -s false +--- /dev/null ++++ b/Apple/testbed/TestbedTests/TestbedTests.m +@@ -0,0 +1,198 @@ ++#import ++#import + -+ def setUp(self): -+ self.log_write = Mock() -+ self.log_level = 42 -+ self.log = SystemLog(self.log_write, self.log_level, errors="replace") ++@interface TestbedTests : XCTestCase + -+ def test_repr(self): -+ self.assertEqual(repr(self.log), "") -+ self.assertEqual(repr(self.log.buffer), "") ++@end + -+ def test_log_config(self): -+ self.assertIs(self.log.writable(), True) -+ self.assertIs(self.log.readable(), False) ++@implementation TestbedTests + -+ self.assertEqual("UTF-8", self.log.encoding) -+ self.assertEqual("replace", self.log.errors) + -+ self.assertIs(self.log.line_buffering, True) -+ self.assertIs(self.log.write_through, False) ++- (void)testPython { ++ const char **argv; ++ int exit_code; ++ int failed; ++ PyStatus status; ++ PyPreConfig preconfig; ++ PyConfig config; ++ PyObject *app_packages_path; ++ PyObject *method_args; ++ PyObject *result; ++ PyObject *site_module; ++ PyObject *site_addsitedir_attr; ++ PyObject *sys_module; ++ PyObject *sys_path_attr; ++ NSArray *test_args; ++ NSString *python_home; ++ NSString *path; ++ wchar_t *wtmp_str; + -+ def test_empty_str(self): -+ self.log.write("") -+ self.log.flush() ++ NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + -+ self.assert_writes([]) ++ // Set some other common environment indicators to disable color, as the ++ // Xcode log can't display color. Stdout will report that it is *not* a ++ // TTY. ++ setenv("NO_COLOR", "1", true); ++ setenv("PYTHON_COLORS", "0", true); + -+ def test_simple_str(self): -+ self.log.write("hello world\n") ++ if (getenv("GITHUB_ACTIONS")) { ++ NSLog(@"Running in a GitHub Actions environment"); ++ } ++ // Arguments to pass into the test suite runner. ++ // argv[0] must identify the process; any subsequent arg ++ // will be handled as if it were an argument to `python -m test` ++ // The processInfo arguments contain the binary that is running, ++ // followed by the arguments defined in the test plan. This means: ++ // run_module = test_args[1] ++ // argv = ["Testbed"] + test_args[2:] ++ test_args = [[NSProcessInfo processInfo] arguments]; ++ if (test_args == NULL) { ++ NSLog(@"Unable to identify test arguments."); ++ } ++ NSLog(@"Test arguments: %@", test_args); ++ argv = malloc(sizeof(char *) * ([test_args count] - 1)); ++ argv[0] = "Testbed"; ++ for (int i = 1; i < [test_args count] - 1; i++) { ++ argv[i] = [[test_args objectAtIndex:i+1] UTF8String]; ++ } + -+ self.assert_writes([b"hello world\n"]) ++ // Generate an isolated Python configuration. ++ NSLog(@"Configuring isolated Python..."); ++ PyPreConfig_InitIsolatedConfig(&preconfig); ++ PyConfig_InitIsolatedConfig(&config); + -+ def test_buffered_str(self): -+ self.log.write("h") -+ self.log.write("ello") -+ self.log.write(" ") -+ self.log.write("world\n") -+ self.log.write("goodbye.") -+ self.log.flush() ++ // Configure the Python interpreter: ++ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. ++ // See https://docs.python.org/3/library/os.html#python-utf-8-mode. ++ preconfig.utf8_mode = 1; ++ // Don't buffer stdio. We want output to appears in the log immediately ++ config.buffered_stdio = 0; ++ // Don't write bytecode; we can't modify the app bundle ++ // after it has been signed. ++ config.write_bytecode = 0; ++ // Ensure that signal handlers are installed ++ config.install_signal_handlers = 1; ++ // Run the test module. ++ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:1] UTF8String], NULL); ++ // For debugging - enable verbose mode. ++ // config.verbose = 1; + -+ self.assert_writes([b"hello world\n", b"goodbye."]) ++ NSLog(@"Pre-initializing Python runtime..."); ++ status = Py_PreInitialize(&preconfig); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } + -+ def test_manual_flush(self): -+ self.log.write("Hello") ++ // Set the home for the Python interpreter ++ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; ++ NSLog(@"PythonHome: %@", python_home); ++ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); ++ status = PyConfig_SetString(&config, &config.home, wtmp_str); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ PyMem_RawFree(wtmp_str); + -+ self.assert_writes([]) ++ // Read the site config ++ status = PyConfig_Read(&config); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to read site config: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } + -+ self.log.write(" world\nHere for a while...\nGoodbye") -+ self.assert_writes([b"Hello world\n", b"Here for a while...\n"]) ++ NSLog(@"Configure argc/argv..."); ++ status = PyConfig_SetBytesArgv(&config, [test_args count] - 1, (char**) argv); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } + -+ self.log.write(" world\nHello again") -+ self.assert_writes([b"Goodbye world\n"]) ++ NSLog(@"Initializing Python runtime..."); ++ status = Py_InitializeFromConfig(&config); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } + -+ self.log.flush() -+ self.assert_writes([b"Hello again"]) ++ // Add app_packages as a site directory. This both adds to sys.path, ++ // and ensures that any .pth files in that directory will be executed. ++ site_module = PyImport_ImportModule("site"); ++ if (site_module == NULL) { ++ XCTFail(@"Could not import site module"); ++ return; ++ } + -+ def test_non_ascii(self): -+ # Spanish -+ self.log.write("ol\u00e9\n") -+ self.assert_writes([b"ol\xc3\xa9\n"]) ++ site_addsitedir_attr = PyObject_GetAttrString(site_module, "addsitedir"); ++ if (site_addsitedir_attr == NULL || !PyCallable_Check(site_addsitedir_attr)) { ++ XCTFail(@"Could not access site.addsitedir"); ++ return; ++ } + -+ # Chinese -+ self.log.write("\u4e2d\u6587\n") -+ self.assert_writes([b"\xe4\xb8\xad\xe6\x96\x87\n"]) ++ path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil]; ++ NSLog(@"App packages path: %@", path); ++ wtmp_str = Py_DecodeLocale([path UTF8String], NULL); ++ app_packages_path = PyUnicode_FromWideChar(wtmp_str, wcslen(wtmp_str)); ++ if (app_packages_path == NULL) { ++ XCTFail(@"Could not convert app_packages path to unicode"); ++ return; ++ } ++ PyMem_RawFree(wtmp_str); + -+ # Printing Non-BMP emoji -+ self.log.write("\U0001f600\n") -+ self.assert_writes([b"\xf0\x9f\x98\x80\n"]) ++ method_args = Py_BuildValue("(O)", app_packages_path); ++ if (method_args == NULL) { ++ XCTFail(@"Could not create arguments for site.addsitedir"); ++ return; ++ } + -+ # Non-encodable surrogates are replaced -+ self.log.write("\ud800\udc00\n") -+ self.assert_writes([b"??\n"]) ++ result = PyObject_CallObject(site_addsitedir_attr, method_args); ++ if (result == NULL) { ++ XCTFail(@"Could not add app_packages directory using site.addsitedir"); ++ return; ++ } + -+ def test_modified_null(self): -+ # Null characters are logged using "modified UTF-8". -+ self.log.write("\u0000\n") -+ self.assert_writes([b"\xc0\x80\n"]) -+ self.log.write("a\u0000\n") -+ self.assert_writes([b"a\xc0\x80\n"]) -+ self.log.write("\u0000b\n") -+ self.assert_writes([b"\xc0\x80b\n"]) -+ self.log.write("a\u0000b\n") -+ self.assert_writes([b"a\xc0\x80b\n"]) ++ // Add test code to sys.path ++ sys_module = PyImport_ImportModule("sys"); ++ if (sys_module == NULL) { ++ XCTFail(@"Could not import sys module"); ++ return; ++ } + -+ def test_nonstandard_str(self): -+ # String subclasses are accepted, but they should be converted -+ # to a standard str without calling any of their methods. -+ class CustomStr(str): -+ def splitlines(self, *args, **kwargs): -+ raise AssertionError() ++ sys_path_attr = PyObject_GetAttrString(sys_module, "path"); ++ if (sys_path_attr == NULL) { ++ XCTFail(@"Could not access sys.path"); ++ return; ++ } + -+ def __len__(self): -+ raise AssertionError() ++ path = [NSString stringWithFormat:@"%@/app", resourcePath, nil]; ++ NSLog(@"App path: %@", path); ++ wtmp_str = Py_DecodeLocale([path UTF8String], NULL); ++ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String])); ++ if (failed) { ++ XCTFail(@"Unable to add app to sys.path"); ++ return; ++ } ++ PyMem_RawFree(wtmp_str); + -+ def __str__(self): -+ raise AssertionError() ++ // Ensure the working directory is the app folder. ++ chdir([path UTF8String]); + -+ self.log.write(CustomStr("custom\n")) -+ self.assert_writes([b"custom\n"]) ++ // Start the test suite. Print a separator to differentiate Python startup logs from app logs ++ NSLog(@"---------------------------------------------------------------------------"); + -+ def test_non_str(self): -+ # Non-string classes are not accepted. -+ for obj in [b"", b"hello", None, 42]: -+ with self.subTest(obj=obj): -+ with self.assertRaisesRegex( -+ TypeError, -+ fr"write\(\) argument must be str, not " -+ fr"{type(obj).__name__}" -+ ): -+ self.log.write(obj) ++ exit_code = Py_RunMain(); ++ XCTAssertEqual(exit_code, 0, @"Test suite did not pass"); + -+ def test_byteslike_in_buffer(self): -+ # The underlying buffer *can* accept bytes-like objects -+ self.log.buffer.write(bytearray(b"hello")) -+ self.log.flush() ++ NSLog(@"---------------------------------------------------------------------------"); + -+ self.log.buffer.write(b"") -+ self.log.flush() ++ Py_Finalize(); ++} + -+ self.log.buffer.write(b"goodbye") -+ self.log.flush() + -+ self.assert_writes([b"hello", b"goodbye"]) ++@end +--- /dev/null ++++ b/Apple/testbed/__main__.py +@@ -0,0 +1,472 @@ ++import argparse ++import json ++import os ++import re ++import shlex ++import shutil ++import subprocess ++import sys ++from itertools import chain ++from pathlib import Path + -+ def test_non_byteslike_in_buffer(self): -+ for obj in ["hello", None, 42]: -+ with self.subTest(obj=obj): -+ with self.assertRaisesRegex( -+ TypeError, -+ fr"write\(\) argument must be bytes-like, not " -+ fr"{type(obj).__name__}" -+ ): -+ self.log.buffer.write(obj) -diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py -index 8e3c68db550..0e4028a31ae 100644 ---- a/Lib/test/test_asyncio/test_events.py -+++ b/Lib/test/test_asyncio/test_events.py -@@ -1867,6 +1867,7 @@ - else: - self.assertEqual(-signal.SIGKILL, returncode) - -+ @support.requires_subprocess() - def test_subprocess_exec(self): - prog = os.path.join(os.path.dirname(__file__), 'echo.py') - -@@ -1888,6 +1889,7 @@ - self.check_killed(proto.returncode) - self.assertEqual(b'Python The Winner', proto.data[1]) - -+ @support.requires_subprocess() - def test_subprocess_interactive(self): - prog = os.path.join(os.path.dirname(__file__), 'echo.py') - -@@ -1915,6 +1917,7 @@ - self.loop.run_until_complete(proto.completed) - self.check_killed(proto.returncode) - -+ @support.requires_subprocess() - def test_subprocess_shell(self): - connect = self.loop.subprocess_shell( - functools.partial(MySubprocessProtocol, self.loop), -@@ -1931,6 +1934,7 @@ - self.assertEqual(proto.data[2], b'') - transp.close() - -+ @support.requires_subprocess() - def test_subprocess_exitcode(self): - connect = self.loop.subprocess_shell( - functools.partial(MySubprocessProtocol, self.loop), -@@ -1942,6 +1946,7 @@ - self.assertEqual(7, proto.returncode) - transp.close() - -+ @support.requires_subprocess() - def test_subprocess_close_after_finish(self): - connect = self.loop.subprocess_shell( - functools.partial(MySubprocessProtocol, self.loop), -@@ -1956,6 +1961,7 @@ - self.assertEqual(7, proto.returncode) - self.assertIsNone(transp.close()) - -+ @support.requires_subprocess() - def test_subprocess_kill(self): - prog = os.path.join(os.path.dirname(__file__), 'echo.py') - -@@ -1972,6 +1978,7 @@ - self.check_killed(proto.returncode) - transp.close() - -+ @support.requires_subprocess() - def test_subprocess_terminate(self): - prog = os.path.join(os.path.dirname(__file__), 'echo.py') - -@@ -1989,6 +1996,7 @@ - transp.close() - - @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP") -+ @support.requires_subprocess() - def test_subprocess_send_signal(self): - # bpo-31034: Make sure that we get the default signal handler (killing - # the process). The parent process may have decided to ignore SIGHUP, -@@ -2013,6 +2021,7 @@ - finally: - signal.signal(signal.SIGHUP, old_handler) - -+ @support.requires_subprocess() - def test_subprocess_stderr(self): - prog = os.path.join(os.path.dirname(__file__), 'echo2.py') - -@@ -2034,6 +2043,7 @@ - self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2]) - self.assertEqual(0, proto.returncode) - -+ @support.requires_subprocess() - def test_subprocess_stderr_redirect_to_stdout(self): - prog = os.path.join(os.path.dirname(__file__), 'echo2.py') - -@@ -2059,6 +2069,7 @@ - transp.close() - self.assertEqual(0, proto.returncode) - -+ @support.requires_subprocess() - def test_subprocess_close_client_stream(self): - prog = os.path.join(os.path.dirname(__file__), 'echo3.py') - -@@ -2093,6 +2104,7 @@ - self.loop.run_until_complete(proto.completed) - self.check_killed(proto.returncode) - -+ @support.requires_subprocess() - def test_subprocess_wait_no_same_group(self): - # start the new process in a new session - connect = self.loop.subprocess_shell( -@@ -2105,6 +2117,7 @@ - self.assertEqual(7, proto.returncode) - transp.close() - -+ @support.requires_subprocess() - def test_subprocess_exec_invalid_args(self): - async def connect(**kwds): - await self.loop.subprocess_exec( -@@ -2118,6 +2131,7 @@ - with self.assertRaises(ValueError): - self.loop.run_until_complete(connect(shell=True)) - -+ @support.requires_subprocess() - def test_subprocess_shell_invalid_args(self): - - async def connect(cmd=None, **kwds): -diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py -index 59ff89e7734..320affdfdec 100644 ---- a/Lib/test/test_asyncio/test_streams.py -+++ b/Lib/test/test_asyncio/test_streams.py -@@ -9,7 +9,7 @@ - import threading - import unittest - from unittest import mock --from test.support import socket_helper -+from test.support import requires_subprocess, socket_helper - try: - import ssl - except ImportError: -@@ -769,6 +769,7 @@ - self.assertEqual(msg2, b"hello world 2!\n") - - @unittest.skipIf(sys.platform == 'win32', "Don't have pipes") -+ @requires_subprocess() - def test_read_all_from_pipe_reader(self): - # See asyncio issue 168. This test is derived from the example - # subprocess_attach_read_pipe.py, but we configure the -diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py -index f0d1d95d2c1..b07afb97adb 100644 ---- a/Lib/test/test_asyncio/test_subprocess.py -+++ b/Lib/test/test_asyncio/test_subprocess.py -@@ -48,6 +48,7 @@ - self._proc.pid = -1 - - -+@support.requires_subprocess() - class SubprocessTransportTests(test_utils.TestCase): - def setUp(self): - super().setUp() -@@ -111,6 +112,7 @@ - transport.close() - - -+@support.requires_subprocess() - class SubprocessMixin: - - def test_stdin_stdout(self): -diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py -index 01c1214c7f7..7e16e2a369d 100644 ---- a/Lib/test/test_asyncio/test_unix_events.py -+++ b/Lib/test/test_asyncio/test_unix_events.py -@@ -315,11 +315,15 @@ - self.loop.run_until_complete(coro) - - def test_create_unix_server_existing_path_nonsock(self): -- with tempfile.NamedTemporaryFile() as file: -- coro = self.loop.create_unix_server(lambda: None, file.name) -- with self.assertRaisesRegex(OSError, -- 'Address.*is already in use'): -- self.loop.run_until_complete(coro) -+ path = test_utils.gen_unix_socket_path() -+ self.addCleanup(os_helper.unlink, path) -+ # create the file -+ open(path, "wb").close() ++TEST_SLICES = { ++ "iOS": "ios-arm64_x86_64-simulator", ++ "tvOS": "tvos-arm64_x86_64-simulator", ++ "visionOS": "xros-arm64-simulator", ++ "watchOS": "watchos-arm64_x86_64-simulator", ++} + -+ coro = self.loop.create_unix_server(lambda: None, path) -+ with self.assertRaisesRegex(OSError, -+ 'Address.*is already in use'): -+ self.loop.run_until_complete(coro) - - def test_create_unix_server_ssl_bool(self): - coro = self.loop.create_unix_server(lambda: None, path='spam', -diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py -index 045e385511b..c39fb20ed7c 100644 ---- a/Lib/test/test_asyncio/utils.py -+++ b/Lib/test/test_asyncio/utils.py -@@ -11,7 +11,6 @@ - import socket - import socketserver - import sys --import tempfile - import threading - import time - import unittest -@@ -34,7 +33,7 @@ - from asyncio import tasks - from asyncio.log import logger - from test import support --from test.support import threading_helper -+from test.support import threading_helper, socket_helper - - - # Use the maximum known clock resolution (gh-75191, gh-110088): Windows -@@ -256,8 +255,7 @@ - - - def gen_unix_socket_path(): -- with tempfile.NamedTemporaryFile() as file: -- return file.name -+ return socket_helper.create_unix_domain_name() - - - @contextlib.contextmanager -diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py -index 98ccd3a9304..d7860853eba 100644 ---- a/Lib/test/test_asyncore.py -+++ b/Lib/test/test_asyncore.py -@@ -9,6 +9,7 @@ - import threading - - from test import support -+from test.support import is_apple_mobile - from test.support import os_helper - from test.support import socket_helper - from test.support import threading_helper -@@ -657,6 +658,7 @@ - - @unittest.skipIf(sys.platform.startswith("sunos"), - "OOB support is broken on Solaris") -+ @unittest.skipIf(is_apple_mobile, "FIXME: edge case in removed test module") - def test_handle_expt(self): - # Make sure handle_expt is called on OOB data received. - # Note: this might fail on some platforms as OOB data is -diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py -index cf28cf35159..54600eafefc 100644 ---- a/Lib/test/test_capi/test_misc.py -+++ b/Lib/test/test_capi/test_misc.py -@@ -1099,6 +1099,13 @@ - self.addCleanup(os.close, r) - self.addCleanup(os.close, w) - -+ # Apple extensions must be distributed as frameworks. This requires -+ # a specialist loader. -+ if support.is_apple_mobile: -+ loader = "AppleFrameworkLoader" -+ else: -+ loader = "ExtensionFileLoader" ++DECODE_ARGS = ("UTF-8", "backslashreplace") + - script = textwrap.dedent(f""" - import importlib.machinery - import importlib.util -@@ -1106,7 +1113,7 @@ - - fullname = '_test_module_state_shared' - origin = importlib.util.find_spec('_testmultiphase').origin -- loader = importlib.machinery.ExtensionFileLoader(fullname, origin) -+ loader = importlib.machinery.{loader}(fullname, origin) - spec = importlib.util.spec_from_loader(fullname, loader) - module = importlib.util.module_from_spec(spec) - attr_id = str(id(module.Error)).encode() -@@ -1311,7 +1318,12 @@ - def setUp(self): - fullname = '_testmultiphase_meth_state_access' # XXX - origin = importlib.util.find_spec('_testmultiphase').origin -- loader = importlib.machinery.ExtensionFileLoader(fullname, origin) -+ # Apple extensions must be distributed as frameworks. This requires -+ # a specialist loader. -+ if support.is_apple_mobile: -+ loader = importlib.machinery.AppleFrameworkLoader(fullname, origin) ++# The system log prefixes each line: ++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ... ++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ... ++ ++LOG_PREFIX_REGEX = re.compile( ++ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD ++ r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ ++ r"\s+.*Testbed\[\d+:\w+\]" # Process/thread ID ++) ++ ++ ++# Select a simulator device to use. ++def select_simulator_device(platform): ++ # List the testing simulators, in JSON format ++ raw_json = subprocess.check_output(["xcrun", "simctl", "list", "-j"]) ++ json_data = json.loads(raw_json) ++ ++ if platform == "iOS": ++ # Any iOS device will do; we'll look for "SE" devices - but the name ++ # isn't consistent over time. Older Xcode versions will use "iPhone SE ++ # (Nth generation)"; As of 2025, they've started using "iPhone 16e". ++ # ++ # When Xcode is updated after a new release, new devices will be ++ # available and old ones will be dropped from the set available on the ++ # latest iOS version. Select the one with the highest minimum runtime ++ # version - this is an indicator of the "newest" released device, which ++ # should always be supported on the "most recent" iOS version. ++ se_simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "iPhone" ++ and ( ++ ( ++ "iPhone " in devicetype["name"] ++ and devicetype["name"].endswith("e") ++ ) ++ or "iPhone SE " in devicetype["name"] ++ ) ++ ) ++ simulator = se_simulators[-1][1] ++ elif platform == "tvOS": ++ # Find the most recent tvOS release. ++ simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple TV" ++ ) ++ simulator = simulators[-1][1] ++ elif platform == "visionOS": ++ # Find the most recent visionOS release. ++ simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple Vision" ++ ) ++ simulator = simulators[-1][1] ++ elif platform == "watchOS": ++ raise NotImplementedError("Don't know how to launch watchOS (yet)") ++ else: ++ raise ValueError(f"Unknown platform {platform}") ++ ++ return simulator ++ ++ ++# A backport of Path.relative_to(*, walk_up=True) ++def relative_to(target, other): ++ for step, path in enumerate(chain([other], other.parents)): ++ if path == target or path in target.parents: ++ break ++ else: ++ raise ValueError( ++ f"{str(target)!r} and {str(other)!r} have different anchors" ++ ) ++ parts = [".."] * step + list(target.parts[len(path.parts) :]) ++ return Path("/".join(parts)) ++ ++ ++def xcode_test(location: Path, platform: str, simulator: str, verbose: bool): ++ # Build and run the test suite on the named simulator. ++ args = [ ++ "-project", ++ str(location / f"{platform}Testbed.xcodeproj"), ++ "-scheme", ++ f"{platform}Testbed", ++ "-destination", ++ f"platform={platform} Simulator,name={simulator}", ++ "-derivedDataPath", ++ str(location / "DerivedData"), ++ ] ++ verbosity_args = [] if verbose else ["-quiet"] ++ ++ print("Building test project...") ++ subprocess.run( ++ ["xcodebuild", "build-for-testing"] + args + verbosity_args, ++ check=True, ++ ) ++ ++ # Any environment variable prefixed with TEST_RUNNER_ is exposed into the ++ # test runner environment. There are some variables (like those identifying ++ # CI platforms) that can be useful to have access to. ++ test_env = os.environ.copy() ++ if "GITHUB_ACTIONS" in os.environ: ++ test_env["TEST_RUNNER_GITHUB_ACTIONS"] = os.environ["GITHUB_ACTIONS"] ++ ++ print("Running test project...") ++ # Test execution *can't* be run -quiet; verbose mode ++ # is how we see the output of the test output. ++ process = subprocess.Popen( ++ ["xcodebuild", "test-without-building"] + args, ++ stdout=subprocess.PIPE, ++ stderr=subprocess.STDOUT, ++ env=test_env, ++ ) ++ while line := (process.stdout.readline()).decode(*DECODE_ARGS): ++ # Strip the timestamp/process prefix from each log line ++ line = LOG_PREFIX_REGEX.sub("", line) ++ sys.stdout.write(line) ++ sys.stdout.flush() ++ ++ status = process.wait(timeout=5) ++ exit(status) ++ ++ ++def copy(src, tgt): ++ """An all-purpose copy. ++ ++ If src is a file, it is copied. If src is a symlink, it is copied *as a ++ symlink*. If src is a directory, the full tree is duplicated, with symlinks ++ being preserved. ++ """ ++ if src.is_file() or src.is_symlink(): ++ shutil.copyfile(src, tgt, follow_symlinks=False) ++ else: ++ shutil.copytree(src, tgt, symlinks=True) ++ ++ ++def clone_testbed( ++ source: Path, ++ target: Path, ++ framework: Path, ++ platform: str, ++ apps: list[Path], ++) -> None: ++ if target.exists(): ++ print(f"{target} already exists; aborting without creating project.") ++ sys.exit(10) ++ ++ if framework is None: ++ if not ( ++ source / "Python.xcframework" / TEST_SLICES[platform] / "bin" ++ ).is_dir(): ++ print( ++ f"The testbed being cloned ({source}) does not contain " ++ "a framework with slices. Re-run with --framework" ++ ) ++ sys.exit(11) ++ else: ++ if not framework.is_dir(): ++ print(f"{framework} does not exist.") ++ sys.exit(12) ++ elif not ( ++ framework.suffix == ".xcframework" ++ or (framework / "Python.framework").is_dir() ++ ): ++ print( ++ f"{framework} is not an XCframework, " ++ f"or a simulator slice of a framework build." ++ ) ++ sys.exit(13) ++ ++ print("Cloning testbed project:") ++ print(f" Cloning {source}...", end="") ++ # Only copy the files for the platform being cloned plus the files common ++ # to all platforms. The XCframework will be copied later, if needed. ++ target.mkdir(parents=True) ++ ++ for name in [ ++ "__main__.py", ++ "TestbedTests", ++ "Testbed.lldbinit", ++ f"{platform}Testbed", ++ f"{platform}Testbed.xcodeproj", ++ f"{platform}Testbed.xctestplan", ++ ]: ++ copy(source / name, target / name) ++ ++ print(" done") ++ ++ orig_xc_framework_path = source / "Python.xcframework" ++ xc_framework_path = target / "Python.xcframework" ++ test_framework_path = xc_framework_path / TEST_SLICES[platform] ++ if framework is not None: ++ if framework.suffix == ".xcframework": ++ print(" Installing XCFramework...", end="") ++ xc_framework_path.symlink_to( ++ relative_to(framework, xc_framework_path.parent) ++ ) ++ print(" done") + else: -+ loader = importlib.machinery.ExtensionFileLoader(fullname, origin) - spec = importlib.util.spec_from_loader(fullname, loader) - module = importlib.util.module_from_spec(spec) - loader.exec_module(module) -diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py -index 7fcd563be27..2945b3f56b5 100644 ---- a/Lib/test/test_cmd_line_script.py -+++ b/Lib/test/test_cmd_line_script.py -@@ -14,8 +14,7 @@ - - import textwrap - from test import support --from test.support import import_helper --from test.support import os_helper -+from test.support import import_helper, is_apple, os_helper - from test.support.script_helper import ( - make_pkg, make_script, make_zip_pkg, make_zip_script, - assert_python_ok, assert_python_failure, spawn_python, kill_python) -@@ -555,12 +554,17 @@ - self.assertTrue(text[3].startswith('NameError')) - - def test_non_ascii(self): -- # Mac OS X denies the creation of a file with an invalid UTF-8 name. -+ # Apple platforms deny the creation of a file with an invalid UTF-8 name. - # Windows allows creating a name with an arbitrary bytes name, but - # Python cannot a undecodable bytes argument to a subprocess. -- # WASI does not permit invalid UTF-8 names. -- if (os_helper.TESTFN_UNDECODABLE -- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): -+ # Emscripten/WASI does not permit invalid UTF-8 names. ++ print(" Installing simulator framework...", end="") ++ # We're only installing a slice of a framework; we need ++ # to do a full tree copy to make sure we don't damage ++ # symlinked content. ++ shutil.copytree(orig_xc_framework_path, xc_framework_path) ++ if test_framework_path.is_dir(): ++ shutil.rmtree(test_framework_path) ++ else: ++ test_framework_path.unlink(missing_ok=True) ++ test_framework_path.symlink_to( ++ relative_to(framework, test_framework_path.parent) ++ ) ++ print(" done") ++ else: ++ copy(orig_xc_framework_path, xc_framework_path) ++ + if ( -+ os_helper.TESTFN_UNDECODABLE -+ and sys.platform not in { -+ "win32", "emscripten", "wasi" -+ } -+ and not is_apple ++ xc_framework_path.is_symlink() ++ and not xc_framework_path.readlink().is_absolute() + ): - name = os.fsdecode(os_helper.TESTFN_UNDECODABLE) - elif os_helper.TESTFN_NONASCII: - name = os_helper.TESTFN_NONASCII -diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py -index 812f989d8f3..dfcf9e16e40 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 @@ - 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): -diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py -index 8e98256a62c..7681e829d8b 100644 ---- a/Lib/test/test_fcntl.py -+++ b/Lib/test/test_fcntl.py -@@ -6,7 +6,7 @@ - import sys - import unittest - from multiprocessing import Process --from test.support import verbose, cpython_only -+from test.support import cpython_only, is_apple, requires_subprocess, verbose - from test.support.import_helper import import_module - from test.support.os_helper import TESTFN, unlink - -@@ -25,7 +25,7 @@ - start_len = "qq" - - if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd')) -- or sys.platform == 'darwin'): -+ or is_apple): - if struct.calcsize('l') == 8: - off_t = 'l' - pid_t = 'i' -@@ -156,6 +156,7 @@ - self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH) - - @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") -+ @requires_subprocess() - def test_lockf_exclusive(self): - self.f = open(TESTFN, 'wb+') - cmd = fcntl.LOCK_EX | fcntl.LOCK_NB -@@ -167,6 +168,7 @@ - self.assertEqual(p.exitcode, 0) - - @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") -+ @requires_subprocess() - def test_lockf_share(self): - self.f = open(TESTFN, 'wb+') - cmd = fcntl.LOCK_SH | fcntl.LOCK_NB -diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py -index d813ecdcd6f..38e405933b2 100644 ---- a/Lib/test/test_ftplib.py -+++ b/Lib/test/test_ftplib.py -@@ -18,6 +18,7 @@ - - from unittest import TestCase, skipUnless - from test import support -+from test.support import requires_subprocess - from test.support import threading_helper - from test.support import socket_helper - from test.support import warnings_helper -@@ -902,6 +903,7 @@ - - - @skipUnless(ssl, "SSL not available") -+@requires_subprocess() - class TestTLS_FTPClassMixin(TestFTPClass): - """Repeat TestFTPClass tests starting the TLS layer for both control - and data connections first. -@@ -918,6 +920,7 @@ - - - @skipUnless(ssl, "SSL not available") -+@requires_subprocess() - class TestTLS_FTPClass(TestCase): - """Specific TLS_FTP class tests.""" - -diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py -index 01578539eb3..90669f5ff45 100644 ---- a/Lib/test/test_gc.py -+++ b/Lib/test/test_gc.py -@@ -1188,6 +1188,7 @@ - self.assertEqual(len(gc.garbage), 0) - - -+ @requires_subprocess() - @unittest.skipIf(BUILD_WITH_NDEBUG, - 'built with -NDEBUG') - def test_refcount_errors(self): -diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py -index 4f311c2d498..b77cd4c67d6 100644 ---- a/Lib/test/test_genericpath.py -+++ b/Lib/test/test_genericpath.py -@@ -7,9 +7,9 @@ - import sys - import unittest - import warnings --from test.support import is_emscripten --from test.support import os_helper --from test.support import warnings_helper -+from test.support import ( -+ is_apple, is_emscripten, os_helper, warnings_helper -+) - from test.support.script_helper import assert_python_ok - from test.support.os_helper import FakePath - -@@ -483,12 +483,16 @@ - self.assertIsInstance(abspath(path), str) - - def test_nonascii_abspath(self): -- if (os_helper.TESTFN_UNDECODABLE -- # macOS and Emscripten deny the creation of a directory with an -- # invalid UTF-8 name. Windows allows creating a directory with an -- # arbitrary bytes name, but fails to enter this directory -- # (when the bytes name is used). -- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): -+ if ( -+ os_helper.TESTFN_UNDECODABLE -+ # Apple platforms and Emscripten/WASI deny the creation of a -+ # directory with an invalid UTF-8 name. Windows allows creating a -+ # directory with an arbitrary bytes name, but fails to enter this -+ # directory (when the bytes name is used). -+ and sys.platform not in { -+ "win32", "emscripten", "wasi" -+ } and not is_apple ++ # XCFramework is a relative symlink. Rewrite the symlink relative ++ # to the new location. ++ print(" Rewriting symlink to XCframework...", end="") ++ resolved_xc_framework_path = ( ++ source / xc_framework_path.readlink() ++ ).resolve() ++ xc_framework_path.unlink() ++ xc_framework_path.symlink_to( ++ relative_to( ++ resolved_xc_framework_path, ++ xc_framework_path.parent, ++ ) ++ ) ++ print(" done") ++ elif ( ++ test_framework_path.is_symlink() ++ and not test_framework_path.readlink().is_absolute() + ): - name = os_helper.TESTFN_UNDECODABLE - elif os_helper.TESTFN_NONASCII: - name = os_helper.TESTFN_NONASCII -diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py -index cfd8a101dcc..b8ac2544be5 100644 ---- a/Lib/test/test_httpservers.py -+++ b/Lib/test/test_httpservers.py -@@ -30,8 +30,9 @@ - - import unittest - from test import support --from test.support import os_helper --from test.support import threading_helper -+from test.support import ( -+ is_apple, os_helper, requires_subprocess, threading_helper -+) - - support.requires_working_socket(module=True) - -@@ -410,8 +411,8 @@ - reader.close() - return body - -- @unittest.skipIf(sys.platform == 'darwin', -- 'undecodable name cannot always be decoded on macOS') -+ @unittest.skipIf(is_apple, -+ 'undecodable name cannot always be decoded on Apple platforms') - @unittest.skipIf(sys.platform == 'win32', - 'undecodable name cannot be decoded on win32') - @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, -@@ -422,11 +423,11 @@ - with open(os.path.join(self.tempdir, filename), 'wb') as f: - f.write(os_helper.TESTFN_UNDECODABLE) - response = self.request(self.base_url + '/') -- if sys.platform == 'darwin': -- # On Mac OS the HFS+ filesystem replaces bytes that aren't valid -- # UTF-8 into a percent-encoded value. -+ if is_apple: -+ # On Apple platforms the HFS+ filesystem replaces bytes that -+ # aren't valid UTF-8 into a percent-encoded value. - for name in os.listdir(self.tempdir): -- if name != 'test': # Ignore a filename created in setUp(). -+ if name != 'test': # Ignore a filename created in setUp(). - filename = name - break - body = self.check_status_and_reason(response, HTTPStatus.OK) -@@ -697,6 +698,7 @@ - - @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, - "This test can't be run reliably as root (issue #13308).") -+@requires_subprocess() - class CGIHTTPServerTestCase(BaseTestCase): - class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler): - pass -diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py -index aa67cc3514e..7c684c5799f 100644 ---- a/Lib/test/test_imp.py -+++ b/Lib/test/test_imp.py -@@ -10,6 +10,7 @@ - from test.support import os_helper - from test.support import script_helper - from test.support import warnings_helper -+from test.support import is_apple_mobile - from test.support import is_wasi - import unittest - import warnings -@@ -234,6 +235,7 @@ - self.assertIsNot(orig_getenv, new_os.getenv) - - @requires_load_dynamic -+ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") - def test_issue15828_load_extensions(self): - # Issue 15828 picked up that the adapter between the old imp API - # and importlib couldn't handle C extensions -@@ -246,6 +248,7 @@ - self.assertEqual(mod.__name__, example) - - @requires_load_dynamic -+ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") - def test_issue16421_multiple_modules_in_one_dll(self): - # Issue 16421: loading several modules from the same compiled file fails - m = '_testimportmultiple' -@@ -273,6 +276,7 @@ - self.assertEqual(name, err.exception.name) - - @requires_load_dynamic -+ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") - def test_load_module_extension_file_is_None(self): - # When loading an extension module and the file is None, open one - # on the behalf of imp.load_dynamic(). -@@ -286,6 +290,7 @@ - imp.load_module(name, None, *found[1:]) - - @requires_load_dynamic -+ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") - def test_issue24748_load_module_skips_sys_modules_check(self): - name = 'test.imp_dummy' - try: -diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py -index f991f323573..190dbaa9d2e 100644 ---- a/Lib/test/test_import/__init__.py -+++ b/Lib/test/test_import/__init__.py -@@ -20,7 +20,7 @@ - - from test.support import os_helper - from test.support import ( -- STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only, is_emscripten, -+ STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only, is_apple_mobile, is_emscripten, - is_wasi) - from test.support.import_helper import ( - forget, make_legacy_pyc, unlink, unload, ready_to_import, -@@ -83,10 +83,14 @@ - from _testcapi import i_dont_exist - self.assertEqual(cm.exception.name, '_testcapi') - if hasattr(_testcapi, "__file__"): -- self.assertEqual(cm.exception.path, _testcapi.__file__) -+ # The path on the exception is strictly the spec origin, not the -+ # module's __file__. For most cases, these are the same; but on -+ # iOS, the Framework relocation process results in the exception -+ # being raised from the spec location. -+ self.assertEqual(cm.exception.path, _testcapi.__spec__.origin) - self.assertRegex( - str(cm.exception), -- r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|pyd)\)" -+ r"cannot import name 'i_dont_exist' from '_testcapi' \(.*(\.(so|pyd))?\)" - ) - else: - self.assertEqual( -diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py -index 3de120958fd..cdc8884d668 100644 ---- a/Lib/test/test_importlib/extension/test_finder.py -+++ b/Lib/test/test_importlib/extension/test_finder.py -@@ -1,3 +1,4 @@ -+from test.support import is_apple_mobile - from test.test_importlib import abc, util - - machinery = util.import_importlib('importlib.machinery') -@@ -19,9 +20,27 @@ - ) - - def find_spec(self, fullname): -- importer = self.machinery.FileFinder(util.EXTENSIONS.path, -- (self.machinery.ExtensionFileLoader, -- self.machinery.EXTENSION_SUFFIXES)) -+ if is_apple_mobile: -+ # Apple mobile platforms require a specialist loader that uses -+ # .fwork files as placeholders for the true `.so` files. -+ loaders = [ -+ ( -+ self.machinery.AppleFrameworkLoader, -+ [ -+ ext.replace(".so", ".fwork") -+ for ext in self.machinery.EXTENSION_SUFFIXES -+ ] ++ print(" Rewriting symlink to simulator framework...", end="") ++ # Simulator framework is a relative symlink. Rewrite the symlink ++ # relative to the new location. ++ orig_test_framework_path = ( ++ source / "Python.XCframework" / test_framework_path.readlink() ++ ).resolve() ++ test_framework_path.unlink() ++ test_framework_path.symlink_to( ++ relative_to( ++ orig_test_framework_path, ++ test_framework_path.parent, + ) -+ ] ++ ) ++ print(" done") + else: -+ loaders = [ -+ ( -+ self.machinery.ExtensionFileLoader, -+ self.machinery.EXTENSION_SUFFIXES -+ ) -+ ] ++ print(" Using pre-existing Python framework.") + -+ importer = self.machinery.FileFinder(util.EXTENSIONS.path, *loaders) - - return importer.find_spec(fullname) - -diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py -index 2519149c071..828b6f0dfd3 100644 ---- a/Lib/test/test_importlib/extension/test_loader.py -+++ b/Lib/test/test_importlib/extension/test_loader.py -@@ -1,3 +1,4 @@ -+from test.support import is_apple_mobile - from warnings import catch_warnings - from test.test_importlib import abc, util - -@@ -25,8 +26,15 @@ - raise unittest.SkipTest( - f"{util.EXTENSIONS.name} is a builtin module" - ) -- self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name, -- util.EXTENSIONS.file_path) ++ for app_src in apps: ++ print(f" Installing app {app_src.name!r}...", end="") ++ app_target = target / f"Testbed/app/{app_src.name}" ++ if app_target.is_dir(): ++ shutil.rmtree(app_target) ++ shutil.copytree(app_src, app_target) ++ print(" done") + -+ # Apple extensions must be distributed as frameworks. This requires -+ # a specialist loader. -+ if is_apple_mobile: -+ self.LoaderClass = self.machinery.AppleFrameworkLoader -+ else: -+ self.LoaderClass = self.machinery.ExtensionFileLoader ++ print(f"Successfully cloned testbed: {target.resolve()}") + -+ self.loader = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path) - - def load_module(self, fullname): - with warnings.catch_warnings(): -@@ -43,13 +51,11 @@ - self.load_module('XXX') - - def test_equality(self): -- other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name, -- util.EXTENSIONS.file_path) -+ other = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path) - self.assertEqual(self.loader, other) - - def test_inequality(self): -- other = self.machinery.ExtensionFileLoader('_' + util.EXTENSIONS.name, -- util.EXTENSIONS.file_path) -+ other = self.LoaderClass('_' + util.EXTENSIONS.name, util.EXTENSIONS.file_path) - self.assertNotEqual(self.loader, other) - - def test_module(self): -@@ -60,8 +66,7 @@ - ('__package__', '')]: - self.assertEqual(getattr(module, attr), value) - self.assertIn(util.EXTENSIONS.name, sys.modules) -- self.assertIsInstance(module.__loader__, -- self.machinery.ExtensionFileLoader) -+ self.assertIsInstance(module.__loader__, self.LoaderClass) - - # No extension module as __init__ available for testing. - test_package = None -@@ -88,7 +93,7 @@ - self.assertFalse(self.loader.is_package(util.EXTENSIONS.name)) - for suffix in self.machinery.EXTENSION_SUFFIXES: - path = os.path.join('some', 'path', 'pkg', '__init__' + suffix) -- loader = self.machinery.ExtensionFileLoader('pkg', path) -+ loader = self.LoaderClass('pkg', path) - self.assertTrue(loader.is_package('pkg')) - - (Frozen_LoaderTests, -@@ -101,6 +106,14 @@ - def setUp(self): - if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS: - raise unittest.SkipTest("Requires dynamic loading support.") + -+ # Apple extensions must be distributed as frameworks. This requires -+ # a specialist loader. -+ if is_apple_mobile: -+ self.LoaderClass = self.machinery.AppleFrameworkLoader -+ else: -+ self.LoaderClass = self.machinery.ExtensionFileLoader ++def update_test_plan(testbed_path, platform, args): ++ # Modify the test plan to use the requested test arguments. ++ test_plan_path = testbed_path / f"{platform}Testbed.xctestplan" ++ with test_plan_path.open("r", encoding="utf-8") as f: ++ test_plan = json.load(f) + - self.name = '_testmultiphase' - if self.name in sys.builtin_module_names: - raise unittest.SkipTest( -@@ -109,8 +122,7 @@ - finder = self.machinery.FileFinder(None) - self.spec = importlib.util.find_spec(self.name) - assert self.spec -- self.loader = self.machinery.ExtensionFileLoader( -- self.name, self.spec.origin) -+ self.loader = self.LoaderClass(self.name, self.spec.origin) - - def load_module(self): - # Load the module from the test extension. -@@ -121,7 +133,7 @@ - def load_module_by_name(self, fullname): - # Load a module from the test extension by name. - origin = self.spec.origin -- loader = self.machinery.ExtensionFileLoader(fullname, origin) -+ loader = self.LoaderClass(fullname, origin) - spec = importlib.util.spec_from_loader(fullname, loader) - module = importlib.util.module_from_spec(spec) - loader.exec_module(module) -@@ -147,8 +159,7 @@ - with self.assertRaises(AttributeError): - module.__path__ - self.assertIs(module, sys.modules[self.name]) -- self.assertIsInstance(module.__loader__, -- self.machinery.ExtensionFileLoader) -+ self.assertIsInstance(module.__loader__, self.LoaderClass) - - def test_functionality(self): - # Test basic functionality of stuff defined in an extension module. -diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py -index 4f1b190f4cc..12eb544912f 100644 ---- a/Lib/test/test_importlib/util.py -+++ b/Lib/test/test_importlib/util.py -@@ -8,6 +8,7 @@ - import os.path - from test import support - from test.support import import_helper -+from test.support import is_apple_mobile - from test.support import os_helper - import unittest - import sys -@@ -43,6 +44,11 @@ - global EXTENSIONS - for path in sys.path: - for ext in machinery.EXTENSION_SUFFIXES: -+ # Apple mobile platforms mechanically load .so files, -+ # but the findable files are labelled .fwork -+ if is_apple_mobile: -+ ext = ext.replace(".so", ".fwork") ++ test_plan["defaultOptions"]["commandLineArgumentEntries"] = [ ++ {"argument": shlex.quote(arg)} for arg in args ++ ] + - filename = EXTENSIONS.name + ext - file_path = os.path.join(path, filename) - if os.path.exists(file_path): -diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py -index 5abb66c7ae4..f69f9bd38b8 100644 ---- a/Lib/test/test_inspect/test_inspect.py -+++ b/Lib/test/test_inspect/test_inspect.py -@@ -25,7 +25,7 @@ - except ImportError: - ThreadPoolExecutor = None - --from test.support import cpython_only -+from test.support import cpython_only, is_apple_mobile - from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ - from test.support.import_helper import DirsOnSysPath, ready_to_import - from test.support.os_helper import TESTFN -@@ -783,6 +783,7 @@ - @unittest.skipIf(not hasattr(unicodedata, '__file__') or - unicodedata.__file__.endswith('.py'), - "unicodedata is not an external binary module") -+ @unittest.skipIf(is_apple_mobile, "FIXME: Edge case of module loader") - def test_findsource_binary(self): - self.assertRaises(OSError, inspect.getsource, unicodedata) - self.assertRaises(OSError, inspect.findsource, unicodedata) -diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py -index d528b4aa287..2d2351a0319 100644 ---- a/Lib/test/test_io.py -+++ b/Lib/test/test_io.py -@@ -39,11 +39,9 @@ - from test import support - from test.support.script_helper import ( - assert_python_ok, assert_python_failure, run_python_until_end) --from test.support import import_helper --from test.support import os_helper --from test.support import threading_helper --from test.support import warnings_helper --from test.support import skip_if_sanitizer -+from test.support import ( -+ import_helper, is_apple, os_helper, skip_if_sanitizer, threading_helper, warnings_helper -+) - from test.support.os_helper import FakePath - - import codecs -@@ -631,10 +629,10 @@ - self.read_ops(f, True) - - def test_large_file_ops(self): -- # On Windows and Mac OSX this test consumes large resources; It takes -- # a long time to build the >2 GiB file and takes >2 GiB of disk space -- # therefore the resource must be enabled to run this test. -- if sys.platform[:3] == 'win' or sys.platform == 'darwin': -+ # On Windows and Apple platforms this test consumes large resources; It -+ # takes a long time to build the >2 GiB file and takes >2 GiB of disk -+ # space therefore the resource must be enabled to run this test. -+ if sys.platform[:3] == 'win' or is_apple: - support.requires( - 'largefile', - 'test requires %s bytes and a long time to run' % self.LARGE) -diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py -index fbaafde881c..7117b160949 100644 ---- a/Lib/test/test_logging.py -+++ b/Lib/test/test_logging.py -@@ -43,6 +43,7 @@ - import tempfile - from test.support.script_helper import assert_python_ok, assert_python_failure - from test import support -+from test.support import is_apple_mobile - from test.support import os_helper - from test.support import socket_helper - from test.support import threading_helper -@@ -2003,6 +2004,7 @@ - self.handled.wait(support.LONG_TIMEOUT) - self.assertEqual(self.log_output, b'<11>h\xc3\xa4m-sp\xc3\xa4m') - -+ @unittest.skipIf(is_apple_mobile, "FIXME: Edge case of logging setup") - def test_udp_reconnection(self): - logger = logging.getLogger("slh") - self.sl_hdlr.close() -diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py -index a1a91f661ba..dde44aeffb6 100644 ---- a/Lib/test/test_marshal.py -+++ b/Lib/test/test_marshal.py -@@ -1,5 +1,5 @@ - from test import support --from test.support import os_helper, requires_debug_ranges -+from test.support import is_apple_mobile, os_helper, requires_debug_ranges - from test.support.script_helper import assert_python_ok - import array - import io -@@ -259,7 +259,7 @@ - #if os.name == 'nt' and hasattr(sys, 'gettotalrefcount'): - if os.name == 'nt': - MAX_MARSHAL_STACK_DEPTH = 1000 -- elif sys.platform == 'wasi': -+ elif sys.platform == 'wasi' or is_apple_mobile: - MAX_MARSHAL_STACK_DEPTH = 1500 - else: - MAX_MARSHAL_STACK_DEPTH = 2000 -diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py -index 1867e8c957f..f75e40940e4 100644 ---- a/Lib/test/test_mmap.py -+++ b/Lib/test/test_mmap.py -@@ -1,5 +1,5 @@ - from test.support import ( -- requires, _2G, _4G, gc_collect, cpython_only, is_emscripten -+ requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple, - ) - from test.support.import_helper import import_module - from test.support.os_helper import TESTFN, unlink -@@ -1009,7 +1009,7 @@ - unlink(TESTFN) - - def _make_test_file(self, num_zeroes, tail): -- if sys.platform[:3] == 'win' or sys.platform == 'darwin': -+ if sys.platform[:3] == 'win' or is_apple: - requires('largefile', - 'test requires %s bytes and a long time to run' % str(0x180000000)) - f = open(TESTFN, 'w+b') -diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py -index 2d5b76ec826..7b6497a2619 100644 ---- a/Lib/test/test_os.py -+++ b/Lib/test/test_os.py -@@ -2298,6 +2298,7 @@ - support.is_emscripten or support.is_wasi, - "musl libc issue on Emscripten/WASI, bpo-46390" - ) -+ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS") - def test_fpathconf(self): - self.check(os.pathconf, "PC_NAME_MAX") - self.check(os.fpathconf, "PC_NAME_MAX") -@@ -3762,6 +3763,7 @@ - self.assertGreaterEqual(size.columns, 0) - self.assertGreaterEqual(size.lines, 0) - -+ @support.requires_subprocess() - def test_stty_match(self): - """Check if stty returns the same results - -diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py -index 4569e30ce86..fceeb4cbfa3 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 ++ with test_plan_path.open("w", encoding="utf-8") as f: ++ json.dump(test_plan, f, indent=2) + - FEDORA_OS_RELEASE = """\ - NAME=Fedora - VERSION="32 (Thirty Two)" -@@ -229,6 +237,29 @@ - self.assertEqual(res[-1], res.processor) - self.assertEqual(len(res), 6) - -+ if os.name == "posix": -+ uname = os.uname() -+ self.assertEqual(res.node, uname.nodename) -+ self.assertEqual(res.version, uname.version) -+ self.assertEqual(res.machine, uname.machine) + -+ if sys.platform == "android": -+ self.assertEqual(res.system, "Android") -+ self.assertEqual(res.release, platform.android_ver().release) -+ elif sys.platform == "ios": -+ # 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) ++def run_testbed( ++ platform: str, ++ simulator: str | None, ++ args: list[str], ++ verbose: bool = False, ++): ++ location = Path(__file__).parent ++ print("Updating test plan...", end="") ++ update_test_plan(location, platform, args) ++ print(" done.") + - def test_uname_cast_to_tuple(self): - res = platform.uname() - expected = ( -@@ -400,6 +431,56 @@ - # parent - support.wait_process(pid, exitcode=0) - -+ def test_ios_ver(self): -+ result = platform.ios_ver() ++ if simulator is None: ++ simulator = select_simulator_device(platform) ++ print(f"Running test on {simulator}") + -+ # 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) -+ self.assertEqual(result.model, model) -+ self.assertEqual(result.is_simulator, is_simulator) ++ xcode_test( ++ location, ++ platform=platform, ++ simulator=simulator, ++ verbose=verbose, ++ ) + -+ # 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"}) ++def main(): ++ # Look for directories like `iOSTestbed` as an indicator of the platforms ++ # that the testbed folder supports. The original source testbed can support ++ # many platforms, but when cloned, only one platform is preserved. ++ available_platforms = [ ++ platform ++ for platform in ["iOS", "tvOS", "visionOS", "watchOS"] ++ if (Path(__file__).parent / f"{platform}Testbed").is_dir() ++ ] + -+ # 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)) ++ parser = argparse.ArgumentParser( ++ description=( ++ "Manages the process of testing an Apple Python project " ++ "through Xcode." ++ ), ++ ) + -+ # 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 ++ subcommands = parser.add_subparsers(dest="subcommand") ++ clone = subcommands.add_parser( ++ "clone", ++ description=( ++ "Clone the testbed project, copying in a Python framework and" ++ "any specified application code." ++ ), ++ help="Clone a testbed project to a new location.", ++ ) ++ clone.add_argument( ++ "--framework", ++ help=( ++ "The location of the XCFramework (or simulator-only slice of an " ++ "XCFramework) to use when running the testbed" ++ ), ++ ) ++ clone.add_argument( ++ "--platform", ++ dest="platform", ++ choices=available_platforms, ++ default=available_platforms[0], ++ help=f"The platform to target (default: {available_platforms[0]})", ++ ) ++ clone.add_argument( ++ "--app", ++ dest="apps", ++ action="append", ++ default=[], ++ help="The location of any code to include in the testbed project", ++ ) ++ clone.add_argument( ++ "location", ++ help="The path where the testbed will be cloned.", ++ ) ++ ++ run = subcommands.add_parser( ++ "run", ++ usage=( ++ "%(prog)s [-h] [--simulator SIMULATOR] -- " ++ " [ ...]" ++ ), ++ description=( ++ "Run a testbed project. The arguments provided after `--` will be " ++ "passed to the running test process as if they were arguments to " ++ "`python -m`." ++ ), ++ help="Run a testbed project", ++ ) ++ run.add_argument( ++ "--platform", ++ dest="platform", ++ choices=available_platforms, ++ default=available_platforms[0], ++ help=f"The platform to target (default: {available_platforms[0]})", ++ ) ++ run.add_argument( ++ "--simulator", ++ help=( ++ "The name of the simulator to use (eg: 'iPhone 16e'). Defaults to " ++ "the most recently released 'entry level' iPhone device. Device " ++ "architecture and OS version can also be specified; e.g., " ++ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would run on " ++ "an ARM64 iPhone 16 Pro simulator running iOS 26.0." ++ ), ++ ) ++ run.add_argument( ++ "-v", ++ "--verbose", ++ action="store_true", ++ help="Enable verbose output", ++ ) ++ ++ try: ++ pos = sys.argv.index("--") ++ testbed_args = sys.argv[1:pos] ++ test_args = sys.argv[pos + 1 :] ++ except ValueError: ++ testbed_args = sys.argv[1:] ++ test_args = [] ++ ++ context = parser.parse_args(testbed_args) ++ ++ if context.subcommand == "clone": ++ clone_testbed( ++ source=Path(__file__).parent.resolve(), ++ target=Path(context.location).resolve(), ++ framework=Path(context.framework).resolve() ++ if context.framework ++ else None, ++ platform=context.platform, ++ apps=[Path(app) for app in context.apps], ++ ) ++ elif context.subcommand == "run": ++ if test_args: ++ if not ( ++ Path(__file__).parent ++ / "Python.xcframework" ++ / TEST_SLICES[context.platform] ++ / "bin" ++ ).is_dir(): ++ print( ++ "Testbed does not contain a compiled Python framework. " ++ f"Use `python {sys.argv[0]} clone ...` to create a " ++ "runnable clone of this testbed." + ) ++ sys.exit(20) + -+ self.assertEqual(type(is_simulator), bool) ++ run_testbed( ++ platform=context.platform, ++ simulator=context.simulator, ++ verbose=context.verbose, ++ args=test_args, ++ ) + else: -+ # On non-iOS platforms, calling ios_ver doesn't fail; you get -+ # default values -+ self.assertEqual(result.system, "") -+ self.assertEqual(result.release, "") -+ self.assertEqual(result.model, "") -+ self.assertFalse(result.is_simulator) ++ print( ++ "Must specify test arguments " ++ f"(e.g., {sys.argv[0]} run -- test)" ++ ) ++ print() ++ parser.print_help(sys.stderr) ++ sys.exit(21) ++ else: ++ parser.print_help(sys.stderr) ++ sys.exit(1) + -+ # 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 -@@ -495,7 +576,8 @@ - 'root:xnu-4570.71.2~1/RELEASE_X86_64'), - 'x86_64', 'i386') - arch = ('64bit', '') -- with mock.patch.object(platform, 'uname', return_value=uname), \ -+ with mock.patch.object(sys, "platform", "darwin"), \ -+ mock.patch.object(platform, 'uname', return_value=uname), \ - mock.patch.object(platform, 'architecture', return_value=arch): - for mac_ver, expected_terse, expected in [ - # darwin: mac_ver() returns empty strings -diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py -index 221d25a4d62..a4f473b58ec 100644 ---- a/Lib/test/test_posix.py -+++ b/Lib/test/test_posix.py -@@ -1,7 +1,7 @@ - "Test posix functions" - - from test import support --from test.support import import_helper -+from test.support import is_apple - from test.support import os_helper - from test.support import warnings_helper - from test.support.script_helper import assert_python_ok -@@ -561,6 +561,7 @@ - - @unittest.skipUnless(hasattr(posix, 'confstr'), - 'test needs posix.confstr()') -+ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS") - def test_confstr(self): - self.assertRaises(ValueError, posix.confstr, "CS_garbage") - self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) -@@ -778,9 +779,10 @@ - check_stat(uid, gid) - self.assertRaises(OSError, chown_func, first_param, 0, -1) - check_stat(uid, gid) -- if 0 not in os.getgroups(): -- self.assertRaises(OSError, chown_func, first_param, -1, 0) -- check_stat(uid, gid) -+ if hasattr(os, 'getgroups'): -+ if 0 not in os.getgroups(): -+ self.assertRaises(OSError, chown_func, first_param, -1, 0) -+ check_stat(uid, gid) - # test illegal types - for t in str, float: - self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) -@@ -1249,8 +1251,8 @@ - self.assertIsInstance(lo, int) - self.assertIsInstance(hi, int) - self.assertGreaterEqual(hi, lo) -- # OSX evidently just returns 15 without checking the argument. -- if sys.platform != "darwin": -+ # Apple plaforms return 15 without checking the argument. -+ if not is_apple: - self.assertRaises(OSError, posix.sched_get_priority_min, -23) - self.assertRaises(OSError, posix.sched_get_priority_max, -23) - -@@ -2036,11 +2038,13 @@ - - - @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") -+@support.requires_subprocess() - class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): - spawn_func = getattr(posix, 'posix_spawn', None) - - - @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") -+@support.requires_subprocess() - class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): - spawn_func = getattr(posix, 'posix_spawnp', None) - -diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py -index 51e3a46d0df..3f2bac0155f 100644 ---- a/Lib/test/test_pty.py -+++ b/Lib/test/test_pty.py -@@ -1,12 +1,17 @@ --from test.support import verbose, reap_children --from test.support.os_helper import TESTFN, unlink -+import sys -+import unittest -+from test.support import ( -+ is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose -+) - from test.support.import_helper import import_module -+from test.support.os_helper import TESTFN, unlink - --# Skip these tests if termios or fcntl are not available -+# Skip these tests if termios is not available - import_module('termios') --# fcntl is a proxy for not being one of the wasm32 platforms even though we --# don't use this module... a proper check for what crashes those is needed. --import_module("fcntl") ++if __name__ == "__main__": ++ # Under the buildbot, stdout is not a TTY, but we must still flush after ++ # every line to make sure our output appears in the correct order relative ++ # to the output of our subprocesses. ++ for stream in [sys.stdout, sys.stderr]: ++ stream.reconfigure(line_buffering=True) ++ main() +--- /dev/null ++++ b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,557 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 56; ++ objects = { + -+# Skip tests on WASM platforms, plus iOS/tvOS/watchOS -+if is_apple_mobile or is_emscripten or is_wasi: -+ raise unittest.SkipTest(f"pty tests not required on {sys.platform}") - - import errno - import os -@@ -17,7 +22,6 @@ - import signal - import socket - import io # readline --import unittest - import warnings - - TEST_STRING_1 = b"I wish to buy a fish license.\n" -diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py -index 31757205ca3..6b88b121580 100644 ---- a/Lib/test/test_selectors.py -+++ b/Lib/test/test_selectors.py -@@ -6,8 +6,7 @@ - import socket - import sys - from test import support --from test.support import os_helper --from test.support import socket_helper -+from test.support import is_apple, os_helper, socket_helper - from time import sleep - import unittest - import unittest.mock -@@ -520,7 +519,7 @@ - try: - fds = s.select() - except OSError as e: -- if e.errno == errno.EINVAL and sys.platform == 'darwin': -+ if e.errno == errno.EINVAL and is_apple: - # unexplainable errors on macOS don't need to fail the test - self.skipTest("Invalid argument error calling poll()") - raise -diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py -index 6728d309589..e85ff0fdcfa 100644 ---- a/Lib/test/test_shutil.py -+++ b/Lib/test/test_shutil.py -@@ -1906,6 +1906,7 @@ - check_chown(dirname, uid, gid) - - -+@support.requires_subprocess() - class TestWhich(BaseTest, unittest.TestCase): - - def setUp(self): -@@ -2801,6 +2802,7 @@ - self.assertGreaterEqual(size.lines, 0) - - @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty") -+ @support.requires_subprocess() - @unittest.skipUnless(hasattr(os, 'get_terminal_size'), - 'need os.get_terminal_size()') - def test_stty_match(self): -diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py -index 3711e7d1c6c..8003f3a226c 100644 ---- a/Lib/test/test_signal.py -+++ b/Lib/test/test_signal.py -@@ -13,9 +13,10 @@ - import time - import unittest - from test import support --from test.support import os_helper -+from test.support import ( -+ is_apple, is_apple_mobile, os_helper, threading_helper -+) - from test.support.script_helper import assert_python_ok, spawn_python --from test.support import threading_helper - try: - import _testcapi - except ImportError: -@@ -832,7 +833,7 @@ - self.assertEqual(self.hndl_called, True) - - # Issue 3864, unknown if this affects earlier versions of freebsd also -- @unittest.skipIf(sys.platform in ('netbsd5',), -+ @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile, - 'itimer not reliable (does not mix well with threading) on some BSDs.') - def test_itimer_virtual(self): - self.itimer = signal.ITIMER_VIRTUAL -@@ -1352,7 +1353,7 @@ - # Python handler - self.assertEqual(len(sigs), N, "Some signals were lost") - -- @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)") -+ @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)") - @unittest.skipUnless(hasattr(signal, "SIGUSR1"), - "test needs SIGUSR1") - @threading_helper.requires_working_threading() -diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py -index cc803d8753b..92ffda88617 100644 ---- a/Lib/test/test_socket.py -+++ b/Lib/test/test_socket.py -@@ -3,6 +3,7 @@ - from test.support import os_helper - from test.support import socket_helper - from test.support import threading_helper -+from test.support import is_apple - - import errno - import io -@@ -684,7 +685,7 @@ - super().setUp() - - def bindSock(self, sock): -- path = tempfile.mktemp(dir=self.dir_path) -+ path = socket_helper.create_unix_domain_name() - socket_helper.bind_unix_socket(sock, path) - self.addCleanup(os_helper.unlink, path) - -@@ -1161,8 +1162,11 @@ - # Find one service that exists, then check all the related interfaces. - # I've ordered this by protocols that have both a tcp and udp - # protocol, at least for modern Linuxes. -- if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) -- or sys.platform in ('linux', 'darwin')): -+ if ( -+ sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) -+ or sys.platform == 'linux' -+ or is_apple -+ ): - # avoid the 'echo' service on this platform, as there is an - # assumption breaking non-standard port/protocol entry - services = ('daytime', 'qotd', 'domain') -@@ -1924,12 +1928,13 @@ - self._test_socket_fileno(s, socket.AF_INET6, socket.SOCK_STREAM) - - if hasattr(socket, "AF_UNIX"): -- tmpdir = tempfile.mkdtemp() -- self.addCleanup(shutil.rmtree, tmpdir) -+ unix_name = socket_helper.create_unix_domain_name() -+ self.addCleanup(os_helper.unlink, unix_name) ++/* Begin PBXBuildFile section */ ++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; }; ++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; }; ++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; }; ++ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; }; ++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */; }; ++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; ++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; ++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; }; ++ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; }; ++/* End PBXBuildFile section */ + - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.addCleanup(s.close) - try: -- s.bind(os.path.join(tmpdir, 'socket')) -+ s.bind(unix_name) - except PermissionError: - pass - else: -@@ -3572,7 +3577,7 @@ - def _testFDPassCMSG_LEN(self): - self.createAndSendFDs(1) - -- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(is_apple, "skipping, see issue #12958") - @unittest.skipIf(AIX, "skipping, see issue #22397") - @requireAttrs(socket, "CMSG_SPACE") - def testFDPassSeparate(self): -@@ -3583,7 +3588,7 @@ - maxcmsgs=2) - - @testFDPassSeparate.client_skip -- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(is_apple, "skipping, see issue #12958") - @unittest.skipIf(AIX, "skipping, see issue #22397") - def _testFDPassSeparate(self): - fd0, fd1 = self.newFDs(2) -@@ -3596,7 +3601,7 @@ - array.array("i", [fd1]))]), - len(MSG)) - -- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(is_apple, "skipping, see issue #12958") - @unittest.skipIf(AIX, "skipping, see issue #22397") - @requireAttrs(socket, "CMSG_SPACE") - def testFDPassSeparateMinSpace(self): -@@ -3610,7 +3615,7 @@ - maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC) - - @testFDPassSeparateMinSpace.client_skip -- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") -+ @unittest.skipIf(is_apple, "skipping, see issue #12958") - @unittest.skipIf(AIX, "skipping, see issue #22397") - def _testFDPassSeparateMinSpace(self): - fd0, fd1 = self.newFDs(2) -@@ -3634,7 +3639,7 @@ - nbytes = self.sendmsgToServer([msg]) - self.assertEqual(nbytes, len(msg)) - -- @unittest.skipIf(sys.platform == "darwin", "see issue #24725") -+ @unittest.skipIf(is_apple, "skipping, see issue #12958") - def testFDPassEmpty(self): - # Try to pass an empty FD array. Can receive either no array - # or an empty array. -diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py -index 80e1968c0bf..529b6405f28 100644 ---- a/Lib/test/test_socketserver.py -+++ b/Lib/test/test_socketserver.py -@@ -91,8 +91,7 @@ - else: - # XXX: We need a way to tell AF_UNIX to pick its own name - # like AF_INET provides port==0. -- dir = None -- fn = tempfile.mktemp(prefix='unix_socket.', dir=dir) -+ fn = socket_helper.create_unix_domain_name() - self.test_files.append(fn) - return fn - -diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py -index ff86291bc57..59370264ab3 100644 ---- a/Lib/test/test_sqlite3/test_dbapi.py -+++ b/Lib/test/test_sqlite3/test_dbapi.py -@@ -31,7 +31,7 @@ - - from test.support import ( - SHORT_TIMEOUT, bigmemtest, check_disallow_instantiation, requires_subprocess, -- is_emscripten, is_wasi -+ is_apple, is_emscripten, is_wasi - ) - from test.support import threading_helper - from _testcapi import INT_MAX, ULLONG_MAX -@@ -335,7 +335,7 @@ - - # sqlite3_enable_shared_cache() is deprecated on macOS and calling it may raise - # OperationalError on some buildbots. -- @unittest.skipIf(sys.platform == "darwin", "shared cache is deprecated on macOS") -+ @unittest.skipIf(is_apple, "shared cache is deprecated on Apple platforms") - def test_shared_cache_deprecated(self): - for enable in (True, False): - with self.assertWarns(DeprecationWarning) as cm: -@@ -659,7 +659,7 @@ - cx.execute(self._sql) - - @unittest.skipIf(sys.platform == "win32", "skipped on Windows") -- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") -+ @unittest.skipIf(is_apple, "skipped on Apple platforms") - @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") - @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") - def test_open_with_undecodable_path(self): -@@ -705,7 +705,7 @@ - cx.execute(self._sql) - - @unittest.skipIf(sys.platform == "win32", "skipped on Windows") -- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") -+ @unittest.skipIf(is_apple, "skipped on Apple platforms") - @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") - @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") - def test_open_undecodable_uri(self): -diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py -index c77fec3d39d..ca55d429aec 100644 ---- a/Lib/test/test_stat.py -+++ b/Lib/test/test_stat.py -@@ -2,8 +2,7 @@ - import os - import socket - import sys --from test.support import os_helper --from test.support import socket_helper -+from test.support import is_apple, os_helper, socket_helper - from test.support.import_helper import import_fresh_module - from test.support.os_helper import TESTFN - -diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py -index de2e7305ccc..3b247e06442 100644 ---- a/Lib/test/test_sundry.py -+++ b/Lib/test/test_sundry.py -@@ -1,5 +1,6 @@ - """Do a minimal test of all the modules that aren't otherwise tested.""" - import importlib -+import sys - from test import support - from test.support import import_helper - from test.support import warnings_helper -@@ -20,7 +21,8 @@ - - import distutils.bcppcompiler - import distutils.ccompiler -- import distutils.cygwinccompiler -+ if sys.platform.startswith("win"): -+ import distutils.cygwinccompiler - import distutils.filelist - import distutils.text_file - import distutils.unixccompiler -diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py -index d3bb0d25cac..f487fb5313b 100644 ---- a/Lib/test/test_sysconfig.py -+++ b/Lib/test/test_sysconfig.py -@@ -6,7 +6,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, -@@ -340,6 +344,8 @@ - # 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 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) -@@ -449,6 +455,8 @@ - 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 include config folder at runtime") - def test_srcdir(self): - # See Issues #15322, #15364. - srcdir = sysconfig.get_config_var('srcdir') -@@ -517,6 +525,8 @@ - @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 include config folder at runtime") - def test_get_makefile_filename(self): - makefile = sysconfig.get_makefile_filename() - self.assertTrue(os.path.isfile(makefile), makefile) -diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py -index 47619c8807b..25c16e3a0b7 100644 ---- a/Lib/test/test_unicode_file_functions.py -+++ b/Lib/test/test_unicode_file_functions.py -@@ -5,7 +5,7 @@ - import unittest - import warnings - from unicodedata import normalize --from test.support import os_helper -+from test.support import is_apple, os_helper - from test import support - - -@@ -23,13 +23,13 @@ - '10_\u1fee\u1ffd', - ] - --# Mac OS X decomposes Unicode names, using Normal Form D. -+# Apple platforms decompose Unicode names, using Normal Form D. - # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html - # "However, most volume formats do not follow the exact specification for - # these normal forms. For example, HFS Plus uses a variant of Normal Form D - # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through - # U+2FAFF are not decomposed." --if sys.platform != 'darwin': -+if not is_apple: - filenames.extend([ - # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different - '11_\u0385\u03d3\u03d4', -@@ -119,11 +119,11 @@ - os.stat(name) - self._apply_failure(os.listdir, name, self._listdir_failure) - -- # Skip the test on darwin, because darwin does normalize the filename to -+ # Skip the test on Apple platforms, because they don't normalize the filename to - # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC, - # NFKD in Python is useless, because darwin will normalize it later and so - # open(), os.stat(), etc. don't raise any exception. -- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') -+ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') - @unittest.skipIf( - support.is_emscripten or support.is_wasi, - "test fails on Emscripten/WASI when host platform is macOS." -@@ -142,10 +142,10 @@ - self._apply_failure(os.remove, name) - self._apply_failure(os.listdir, name) - -- # Skip the test on darwin, because darwin uses a normalization different -+ # Skip the test on Apple platforms, because they use a normalization different - # than Python NFD normalization: filenames are different even if we use - # Python NFD normalization. -- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') -+ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') - def test_listdir(self): - sf0 = set(self.files) - with warnings.catch_warnings(): -diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py -index 94cbc8a62f9..06a553f093f 100644 ---- a/Lib/test/test_urllib2.py -+++ b/Lib/test/test_urllib2.py -@@ -1,7 +1,7 @@ - import unittest - from test import support - from test.support import os_helper --from test.support import socket_helper -+from test.support import requires_subprocess - from test.support import warnings_helper - from test import test_urllib - -@@ -987,6 +987,7 @@ - - file_obj.close() - -+ @requires_subprocess() - def test_http_body_pipe(self): - # A file reading from a pipe. - # A pipe cannot be seek'ed. There is no way to determine the -diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py -index d3abb77f40f..3e4c0d583a9 100644 ---- a/Lib/test/test_venv.py -+++ b/Lib/test/test_venv.py -@@ -20,8 +20,8 @@ - import shlex - from test.support import (captured_stdout, captured_stderr, - skip_if_broken_multiprocessing_synchronize, verbose, -- requires_subprocess, is_emscripten, is_wasi, -- requires_venv_with_pip, TEST_HOME_DIR, -+ requires_subprocess, is_apple_mobile, is_emscripten, -+ is_wasi, requires_venv_with_pip, TEST_HOME_DIR, - requires_resource, copy_python_src_ignore) - from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) - import unittest -@@ -40,8 +40,10 @@ - or sys._base_executable != sys.executable, - 'cannot run venv.create from within a venv on this platform') - --if is_emscripten or is_wasi: -- raise unittest.SkipTest("venv is not available on Emscripten/WASI.") -+# Skip tests on WASM platforms, plus iOS/tvOS/watchOS -+if is_apple_mobile or is_emscripten or is_wasi: -+ raise unittest.SkipTest(f"venv tests not required on {sys.platform}") -+ - - @requires_subprocess() - def check_output(cmd, encoding=None): -diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py -index 9d608d63a01..24b1103f500 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 = 'http://www.example.com' - CMD_NAME = 'test' -@@ -24,6 +27,7 @@ - return 0 - - -+@requires_subprocess() - class CommandTestMixin: - - def _test(self, meth, *, args=[URL], kw={}, options, arguments): -@@ -219,6 +223,73 @@ - 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) -+ -+ @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 -+ # wrap msgSend to return stringified object references -+ self.orig_objc = webbrowser.objc ++/* Begin PBXContainerItemProxy section */ ++ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = 607A66112B0EFA380010BFC8; ++ remoteInfo = iOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ + -+ 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 ++/* Begin PBXCopyFilesBuildPhase section */ ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ + -+ def tearDown(self): -+ webbrowser.objc = self.orig_objc ++/* Begin PBXFileReference section */ ++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; ++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; ++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; ++ 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; ++ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; ++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestbedTests.m; sourceTree = ""; }; ++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = ""; }; ++ 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; }; ++ 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; }; ++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = iOSTestbed.xctestplan; sourceTree = ""; }; ++/* End PBXFileReference section */ + -+ 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) ++/* Begin PBXFrameworksBuildPhase section */ ++ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ + -+ # The ObjC String version of the URL is created with UTF-8 encoding -+ url_string_args = [ -+ "C#NSString", -+ "S#stringWithCString:encoding:", -+ b'http://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"] ++/* Begin PBXGroup section */ ++ 607A66092B0EFA380010BFC8 = { ++ isa = PBXGroup; ++ children = ( ++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */, ++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */, ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */, ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, ++ 607A66132B0EFA380010BFC8 /* Products */, ++ 607A664F2B0EFFE00010BFC8 /* Frameworks */, ++ ); ++ sourceTree = ""; ++ }; ++ 607A66132B0EFA380010BFC8 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */, ++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */ = { ++ isa = PBXGroup; ++ children = ( ++ 608619552CB7819B00F46182 /* app */, ++ 608619532CB77BA900F46182 /* app_packages */, ++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */, ++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */, ++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */, ++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */, ++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */, ++ 607A66272B0EFA390010BFC8 /* main.m */, ++ ); ++ path = iOSTestbed; ++ sourceTree = ""; ++ }; ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */, ++ ); ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = { ++ isa = PBXGroup; ++ children = ( ++ ); ++ name = Frameworks; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ + -+ # 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 -+ ) ++/* Begin PBXNativeTarget section */ ++ 607A66112B0EFA380010BFC8 /* iOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */; ++ buildPhases = ( ++ 607A660E2B0EFA380010BFC8 /* Sources */, ++ 607A660F2B0EFA380010BFC8 /* Frameworks */, ++ 607A66102B0EFA380010BFC8 /* Resources */, ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */, ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ name = iOSTestbed; ++ productName = iOSTestbed; ++ productReference = 607A66122B0EFA380010BFC8 /* iOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */; ++ buildPhases = ( ++ 607A66292B0EFA3A0010BFC8 /* Sources */, ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, ++ 607A662B2B0EFA3A0010BFC8 /* Resources */, ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, ++ ); ++ name = iOSTestbedTests; ++ productName = iOSTestbedTests; ++ productReference = 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */; ++ productType = "com.apple.product-type.bundle.unit-test"; ++ }; ++/* End PBXNativeTarget section */ + -+ def test_open(self): -+ self._test('open') ++/* Begin PBXProject section */ ++ 607A660A2B0EFA380010BFC8 /* Project object */ = { ++ isa = PBXProject; ++ attributes = { ++ BuildIndependentTargetsInParallel = 1; ++ LastUpgradeCheck = 1500; ++ TargetAttributes = { ++ 607A66112B0EFA380010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; ++ }; ++ 607A662C2B0EFA3A0010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; ++ TestTargetID = 607A66112B0EFA380010BFC8; ++ }; ++ }; ++ }; ++ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */; ++ compatibilityVersion = "Xcode 14.0"; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = 607A66092B0EFA380010BFC8; ++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ 607A66112B0EFA380010BFC8 /* iOSTestbed */, ++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ + -+ def test_open_with_autoraise_false(self): -+ self._test('open', autoraise=False) ++/* Begin PBXResourcesBuildPhase section */ ++ 607A66102B0EFA380010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */, ++ 608619562CB7819B00F46182 /* app in Resources */, ++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, ++ 608619542CB77BA900F46182 /* app_packages in Resources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A662B2B0EFA3A0010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ + -+ def test_open_new(self): -+ self._test('open_new') ++/* Begin PBXShellScriptBuildPhase section */ ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Process Python libraries"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\ninstall_python Python.xcframework app app_packages\n"; ++ showEnvVarsInLog = 0; ++ }; ++/* End PBXShellScriptBuildPhase section */ + -+ def test_open_new_tab(self): -+ self._test('open_new_tab') ++/* Begin PBXSourcesBuildPhase section */ ++ 607A660E2B0EFA380010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, ++ 607A66282B0EFA390010BFC8 /* main.m in Sources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66292B0EFA3A0010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ + ++/* Begin PBXTargetDependency section */ ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; ++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ + - class BrowserRegistrationTest(unittest.TestCase): - - def setUp(self): -@@ -302,6 +373,10 @@ - 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: -@@ -313,6 +388,10 @@ - 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 5d72524c087..48976e4e11f 100755 ---- a/Lib/webbrowser.py -+++ b/Lib/webbrowser.py -@@ -534,6 +534,9 @@ - # 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) ++/* Begin PBXVariantGroup section */ ++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = { ++ isa = PBXVariantGroup; ++ children = ( ++ 607A66242B0EFA390010BFC8 /* Base */, ++ ); ++ name = LaunchScreen.storyboard; ++ sourceTree = ""; ++ }; ++/* End PBXVariantGroup section */ + - if sys.platform == "serenityos": - # SerenityOS webbrowser, simply called "Browser". - register("Browser", None, BackgroundBrowser("Browser")) -@@ -707,6 +710,70 @@ - rc = osapipe.close() - return not rc - -+# -+# Platform support for iOS -+# -+if sys.platform == "ios": -+ from _ios_support import objc -+ if objc: -+ # If objc exists, we know ctypes is also importable. -+ from ctypes import c_void_p, c_char_p, c_ulong ++/* Begin XCBuildConfiguration section */ ++ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = iphoneos; ++ }; ++ name = Debug; ++ }; ++ 607A66402B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = iphoneos; ++ VALIDATE_PRODUCT = YES; ++ }; ++ name = Release; ++ }; ++ 607A66422B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ }; ++ name = Debug; ++ }; ++ 607A66432B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ }; ++ name = Release; ++ }; ++ 607A66452B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; ++ }; ++ name = Debug; ++ }; ++ 607A66462B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ + -+ 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 -+ -+ # 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") -+ # 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 -+ ) ++/* Begin XCConfigurationList section */ ++ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A663F2B0EFA3A0010BFC8 /* Debug */, ++ 607A66402B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66422B0EFA3A0010BFC8 /* Debug */, ++ 607A66432B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66452B0EFA3A0010BFC8 /* Debug */, ++ 607A66462B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; ++} +--- /dev/null ++++ b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme +@@ -0,0 +1,97 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +--- /dev/null ++++ b/Apple/testbed/iOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", ++ "name" : "Test Scheme Action", ++ "options" : { + -+ # 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) ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:iOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "iOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:iOSTestbed.xcodeproj", ++ "identifier" : "607A662C2B0EFA3A0010BFC8", ++ "name" : "iOSTestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// iOSTestbed ++// + -+ # 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) ++#import + -+ # 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 -+ ] -+ # Method returns void -+ objc.objc_msgSend.restype = None -+ objc.objc_msgSend(shared_app, openURL_, ns_url, None, None) ++@interface AppDelegate : UIResponder + -+ return True + - - def main(): - import getopt ++@end --- /dev/null -+++ b/Mac/Resources/app-store-compliance.patch -@@ -0,0 +1 @@ -+# No compliance patching required. -diff --git a/Makefile.pre.in b/Makefile.pre.in -index 81d4d50f82f..d71b63436f9 100644 ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -175,18 +175,29 @@ - EXE= @EXEEXT@ - BUILDEXE= @BUILDEXEEXT@ - -+# Name of the patch file to apply for app store compliance -+APP_STORE_COMPLIANCE_PATCH=@APP_STORE_COMPLIANCE_PATCH@ ++++ b/Apple/testbed/iOSTestbed/AppDelegate.m +@@ -0,0 +1,19 @@ ++// ++// AppDelegate.m ++// iOSTestbed ++// + - # Short name and location for Mac OS X Python framework - UNIVERSALSDK=@UNIVERSALSDK@ - PYTHONFRAMEWORK= @PYTHONFRAMEWORK@ - PYTHONFRAMEWORKDIR= @PYTHONFRAMEWORKDIR@ - PYTHONFRAMEWORKPREFIX= @PYTHONFRAMEWORKPREFIX@ - PYTHONFRAMEWORKINSTALLDIR= @PYTHONFRAMEWORKINSTALLDIR@ --# Deployment target selected during configure, to be checked -+PYTHONFRAMEWORKINSTALLNAMEPREFIX= @PYTHONFRAMEWORKINSTALLNAMEPREFIX@ -+RESSRCDIR= @RESSRCDIR@ -+# 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. 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@ ++#import "AppDelegate.h" + - # Option to install to strip binaries - STRIPFLAG=-s - -@@ -344,6 +355,8 @@ - ########################################################################## - - LIBFFI_INCLUDEDIR= @LIBFFI_INCLUDEDIR@ -+LIBFFI_LIBDIR= @LIBFFI_LIBDIR@ -+LIBFFI_LIB= @LIBFFI_LIB@ - - ########################################################################## - # Parser -@@ -585,7 +598,7 @@ - - # Default target - all: @DEF_MAKE_ALL_RULE@ --build_all: check-clean-src $(BUILDPYTHON) platform oldsharedmods sharedmods \ -+build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform oldsharedmods sharedmods \ - gdbhooks Programs/_testembed python-config - build_wasm: check-clean-src $(BUILDPYTHON) platform oldsharedmods python-config - -@@ -604,6 +617,16 @@ - exit 1; \ - fi - -+# Check that the app store compliance patch can be applied (if configured). -+# This is checked as a dry-run against the original library sources; -+# the patch will be actually applied during the install phase. -+.PHONY: check-app-store-compliance -+check-app-store-compliance: -+ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \ -+ patch --dry-run --quiet --force --strip 1 --directory "$(abs_srcdir)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)"; \ -+ echo "App store compliance patch can be applied."; \ -+ fi ++@interface AppDelegate () + - # Profile generation build must start from a clean tree. - profile-clean-stamp: - $(MAKE) clean -@@ -765,7 +788,7 @@ - $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ - - libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) -- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ -+ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ - - - libpython$(VERSION).sl: $(LIBRARY_OBJS) -@@ -789,14 +812,13 @@ - # This rule is here for OPENSTEP/Rhapsody/MacOSX. It builds a temporary - # minimal framework (not including the Lib directory and such) in the current - # directory. --RESSRCDIR=Mac/Resources/framework - $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \ - $(LIBRARY) \ - $(RESSRCDIR)/Info.plist - $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION) - $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ -- -all_load $(LIBRARY) -Wl,-single_module \ -- -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK) \ -+ -all_load $(LIBRARY) \ -+ -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ - -compatibility_version $(VERSION) \ - -current_version $(VERSION) \ - -framework CoreFoundation $(LIBS); -@@ -808,6 +830,21 @@ - $(LN) -fsn Versions/Current/$(PYTHONFRAMEWORK) $(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK) - $(LN) -fsn Versions/Current/Resources $(PYTHONFRAMEWORKDIR)/Resources - -+# This rule is for iOS, which requires an annoyingly just slighly different -+# format for frameworks to macOS. It *doesn't* use a versioned framework, and -+# the Info.plist must be in the root of the framework. -+$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK): \ -+ $(LIBRARY) \ -+ $(RESSRCDIR)/Info.plist -+ $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR) -+ $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ -+ -all_load $(LIBRARY) \ -+ -install_name $(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ -+ -compatibility_version $(VERSION) \ -+ -current_version $(VERSION) \ -+ -framework CoreFoundation $(LIBS); -+ $(INSTALL_DATA) $(RESSRCDIR)/Info.plist $(PYTHONFRAMEWORKDIR)/Info.plist ++@end + - # This rule builds the Cygwin Python DLL and import library if configured - # for a shared core library; otherwise, this rule is a noop. - $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS) -@@ -1711,6 +1748,36 @@ - $(RUNSHARED) /usr/libexec/oah/translate \ - ./$(BUILDPYTHON) -E -m test -j 0 -u all $(TESTOPTS) - -+# Run the test suite on the iOS simulator. Must be run on a macOS machine with -+# a full Xcode install that has an iPhone SE (3rd edition) simulator available. -+# This must be run *after* a `make install` has completed the build. The -+# `--with-framework-name` argument *cannot* be used when configuring the build. -+XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s) -+.PHONY: testios -+testios: -+ @if test "$(MACHDEP)" != "ios"; then \ -+ echo "Cannot run the iOS testbed for a non-iOS build."; \ -+ exit 1;\ -+ fi -+ @if test "$(findstring -iphonesimulator,$(MULTIARCH))" != "-iphonesimulator"; then \ -+ echo "Cannot run the iOS testbed for non-simulator builds."; \ -+ exit 1;\ -+ fi -+ @if test $(PYTHONFRAMEWORK) != "Python"; then \ -+ echo "Cannot run the iOS testbed with a non-default framework name."; \ -+ exit 1;\ -+ fi -+ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \ -+ echo "Cannot find a finalized iOS Python.framework. Have you run 'make install' to finalize the framework build?"; \ -+ exit 1;\ -+ fi ++@implementation AppDelegate + -+ # Clone the testbed project into the XCFOLDER -+ $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" + -+ # Run the testbed project -+ $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W ++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ++ return YES; ++} + - # Like testall, but with only one pass and without multiple processes. - # Run an optional script to include information about the build environment. - buildbottest: all -@@ -1747,7 +1814,7 @@ - # prevent race conditions with PGO builds. PGO builds use recursive make, - # which can lead to two parallel `./python setup.py build` processes that - # step on each others toes. --install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKINSTALLLAST@ -+install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@ - if test "x$(ENSUREPIP)" != "xno" ; then \ - case $(ENSUREPIP) in \ - upgrade) ensurepip="--upgrade" ;; \ -@@ -2144,7 +2211,16 @@ - $(INSTALL_DATA) $(srcdir)/Modules/xxmodule.c \ - $(DESTDIR)$(LIBDEST)/distutils/tests ; \ - fi -- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \ -+ @ # If app store compliance has been configured, apply the patch to the -+ @ # installed library code. The patch has been previously validated against -+ @ # the original source tree, so we can ignore any errors that are raised -+ @ # due to files that are missing because of --disable-test-modules etc. -+ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \ -+ echo "Applying app store compliance patch"; \ -+ patch --force --reject-file "$(abs_builddir)/app-store-compliance.rej" --strip 2 --directory "$(DESTDIR)$(LIBDEST)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)" || true ; \ -+ fi -+ @ # Build PYC files for the 3 optimization levels (0, 1, 2) -+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \ - $(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \ - -j0 -d $(LIBDEST) -f \ - -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \ -@@ -2316,9 +2392,11 @@ - # automatically set prefix to the location deep down in the framework, so we - # only have to cater for the structural bits of the framework. - --frameworkinstallframework: frameworkinstallstructure install frameworkinstallmaclib -+frameworkinstallframework: @FRAMEWORKINSTALLFIRST@ install frameworkinstallmaclib - --frameworkinstallstructure: $(LDLIBRARY) -+# macOS uses a versioned frameworks structure that includes a full install -+.PHONY: frameworkinstallversionedstructure -+frameworkinstallversionedstructure: $(LDLIBRARY) - @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ - echo Not configured with --enable-framework; \ - exit 1; \ -@@ -2339,6 +2417,27 @@ - $(LN) -fsn Versions/Current/Resources $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Resources - $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) - -+# iOS/tvOS/watchOS uses a non-versioned framework with Info.plist in the -+# framework root, no .lproj data, and only stub compilation assistance binaries -+.PHONY: frameworkinstallunversionedstructure -+frameworkinstallunversionedstructure: $(LDLIBRARY) -+ @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ -+ echo Not configured with --enable-framework; \ -+ exit 1; \ -+ else true; \ -+ fi -+ if test -d $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; then \ -+ echo "Clearing stale header symlink directory"; \ -+ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; \ -+ fi -+ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) -+ sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist -+ $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) -+ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) -+ for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ -+ $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ -+ done ++@end +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +@@ -0,0 +1,11 @@ ++{ ++ "colors" : [ ++ { ++ "idiom" : "universal" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +@@ -0,0 +1,13 @@ ++{ ++ "images" : [ ++ { ++ "idiom" : "universal", ++ "platform" : "ios", ++ "size" : "1024x1024" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json +@@ -0,0 +1,6 @@ ++{ ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,9 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. + - # This installs Mac/Lib into the framework - # Install a number of symlinks to keep software that expects a normal unix - # install (which includes python-config) happy. -@@ -2373,6 +2472,19 @@ - frameworkinstallextras: - cd Mac && $(MAKE) installextras DESTDIR="$(DESTDIR)" - -+# On iOS, bin/lib can't live inside the framework; include needs to be called -+# "Headers", but *must* be in the framework, and *not* include the `python3.X` -+# subdirectory. The install has put these folders in the same folder as -+# Python.framework; Move the headers to their final framework-compatible home. -+.PHONY: frameworkinstallmobileheaders -+frameworkinstallmobileheaders: frameworkinstallunversionedstructure inclinstall -+ if test -d $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; then \ -+ echo "Removing old framework headers"; \ -+ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \ -+ fi -+ mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers" -+ $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" ++During the build, any binary modules found in this folder will be processed into ++Framework form. + - # Build the toplevel Makefile - Makefile.pre: $(srcdir)/Makefile.pre.in config.status - CONFIG_FILES=Makefile.pre CONFIG_HEADERS= $(SHELL) config.status -@@ -2477,6 +2589,10 @@ - -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' - -rm -f Include/pydtrace_probes.h - -rm -f profile-gen-stamp -+ -rm -rf iOS/testbed/Python.xcframework/ios-*/bin -+ -rm -rf iOS/testbed/Python.xcframework/ios-*/lib -+ -rm -rf iOS/testbed/Python.xcframework/ios-*/include -+ -rm -rf iOS/testbed/Python.xcframework/ios-*/Python.framework - - profile-removal: - find . -name '*.gc??' -exec rm -f {} ';' -@@ -2498,6 +2614,8 @@ - config.cache config.log pyconfig.h Modules/config.c - -rm -rf build platform - -rm -rf $(PYTHONFRAMEWORKDIR) -+ -rm -rf iOS/Frameworks -+ -rm -rf iOSTestbed.* - -rm -f python-config.py python-config - - # Make things extra clean, before making a distribution: -@@ -2578,7 +2696,7 @@ - .PHONY: all build_all build_wasm sharedmods check-clean-src oldsharedmods test quicktest - .PHONY: install altinstall oldsharedinstall bininstall altbininstall - .PHONY: maninstall libinstall inclinstall libainstall sharedinstall --.PHONY: frameworkinstall frameworkinstallframework frameworkinstallstructure -+.PHONY: frameworkinstall frameworkinstallframework - .PHONY: frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools - .PHONY: frameworkaltinstallunixtools recheck clean clobber distclean - .PHONY: smelly funny patchcheck touch altmaninstall commoninstall -diff --git a/Modules/getpath.c b/Modules/getpath.c -index 61d654065fd..8bd844d6459 100644 ---- a/Modules/getpath.c -+++ b/Modules/getpath.c -@@ -15,6 +15,7 @@ - #endif - - #ifdef __APPLE__ -+# include "TargetConditionals.h" - # include - #endif - -@@ -767,7 +768,7 @@ - if (PyWin_DLLhModule) { - return winmodule_to_dict(dict, key, PyWin_DLLhModule); - } --#elif defined(WITH_NEXT_FRAMEWORK) -+#elif defined(WITH_NEXT_FRAMEWORK) && !defined(TARGET_OS_IPHONE) - static char modPath[MAXPATHLEN + 1]; - static int modPathInitialized = -1; - if (modPathInitialized < 0) { -@@ -961,4 +962,3 @@ - - return _PyStatus_OK(); - } -- -diff --git a/Python/marshal.c b/Python/marshal.c -index 29f3bab60a5..7d32bb3661d 100644 ---- a/Python/marshal.c -+++ b/Python/marshal.c -@@ -14,6 +14,10 @@ - #include "pycore_hashtable.h" // _Py_hashtable_t - #include "marshal.h" // Py_MARSHAL_VERSION - -+#ifdef __APPLE__ -+# include "TargetConditionals.h" -+#endif /* __APPLE__ */ ++When the test suite runs, this folder will be on the PYTHONPATH, and will be the ++working directory for the test suite. +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/app_packages/README +@@ -0,0 +1,7 @@ ++This folder can be a target for installing any Python dependencies needed by the ++test suite. + - /*[clinic input] - module marshal - [clinic start generated code]*/ -@@ -33,11 +37,14 @@ - * #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' -diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c -index 9248e971d9c..9bac4fed20b 100644 ---- a/Python/pylifecycle.c -+++ b/Python/pylifecycle.c -@@ -35,7 +35,21 @@ - #include // getenv() - - #if defined(__APPLE__) --#include -+# include -+# include -+# include -+// The os_log unified logging APIs were introduced in macOS 10.12, iOS 10.0, -+// tvOS 10.0, and watchOS 3.0; we enable the use of the system logger -+// automatically on non-macOS platforms. -+# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE -+# define USE_APPLE_SYSTEM_LOG 1 -+# else -+# define USE_APPLE_SYSTEM_LOG 0 -+# endif ++During the build, any binary modules found in this folder will be processed into ++Framework form. + -+# if USE_APPLE_SYSTEM_LOG -+# include -+# endif - #endif - - #ifdef HAVE_SIGNAL_H -@@ -72,6 +86,9 @@ - static PyStatus init_import_site(void); - static PyStatus init_set_builtins_open(void); - static PyStatus init_sys_streams(PyThreadState *tstate); -+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG -+static PyStatus init_apple_streams(PyThreadState *tstate); -+#endif - static void wait_for_thread_shutdown(PyThreadState *tstate); - static void call_ll_exitfuncs(_PyRuntimeState *runtime); - -@@ -1161,6 +1178,17 @@ - return status; - } - -+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG -+ status = init_apple_streams(tstate); -+ if (_PyStatus_EXCEPTION(status)) { -+ return status; -+ } -+#endif ++When the test suite runs, this folder will be on the PYTHONPATH. +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist +@@ -0,0 +1,52 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.iOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ LSRequiresIPhoneOS ++ ++ UIRequiresFullScreen ++ ++ UILaunchStoryboardName ++ Launch Screen ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ ++ ++ +--- /dev/null ++++ b/Apple/testbed/iOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// iOSTestbed ++// + -+#ifdef Py_DEBUG -+ run_presite(tstate); -+#endif ++#import ++#import "AppDelegate.h" + - status = add_main_module(interp); - if (_PyStatus_EXCEPTION(status)) { - return status; -@@ -2481,6 +2509,69 @@ - return res; - } - -+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); + -+static PyObject * -+apple_log_write_impl(PyObject *self, PyObject *args) -+{ -+ int logtype = 0; -+ const char *text = NULL; -+ if (!PyArg_ParseTuple(args, "iy", &logtype, &text)) { -+ return NULL; ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); + } -+ -+ // Pass the user-provided text through explicit %s formatting -+ // to avoid % literals being interpreted as a formatting directive. -+ os_log_with_type(OS_LOG_DEFAULT, logtype, "%s", text); -+ Py_RETURN_NONE; +} +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,506 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 77; ++ objects = { + ++/* Begin PBXBuildFile section */ ++ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; ++ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; ++ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++/* End PBXBuildFile section */ + -+static PyMethodDef apple_log_write_method = { -+ "apple_log_write", apple_log_write_impl, METH_VARARGS -+}; ++/* Begin PBXContainerItemProxy section */ ++ EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = EE989E462DCD6E780036B268 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = EE989E4D2DCD6E780036B268; ++ remoteInfo = tvOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ + ++/* Begin PBXCopyFilesBuildPhase section */ ++ EE7C8A212DCD70CD003206DB /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ + -+static PyStatus -+init_apple_streams(PyThreadState *tstate) -+{ -+ PyStatus status = _PyStatus_OK(); -+ PyObject *_apple_support = NULL; -+ PyObject *apple_log_write = NULL; -+ PyObject *result = NULL; ++/* Begin PBXFileReference section */ ++ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = tvOSTestbed.xctestplan; sourceTree = ""; }; ++ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++/* End PBXFileReference section */ + -+ _apple_support = PyImport_ImportModule("_apple_support"); -+ if (_apple_support == NULL) { -+ goto error; -+ } ++/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */ = { ++ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; ++ membershipExceptions = ( ++ "tvOSTestbed-Info.plist", ++ ); ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ }; ++/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + -+ apple_log_write = PyCFunction_New(&apple_log_write_method, NULL); -+ if (apple_log_write == NULL) { -+ goto error; -+ } ++/* Begin PBXFileSystemSynchronizedRootGroup section */ ++ EE989E502DCD6E780036B268 /* tvOSTestbed */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ exceptions = ( ++ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */, ++ ); ++ explicitFolders = ( ++ app, ++ app_packages, ++ ); ++ path = tvOSTestbed; ++ sourceTree = ""; ++ }; ++ EE989E682DCD6E7A0036B268 /* TestbedTests */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++/* End PBXFileSystemSynchronizedRootGroup section */ + -+ // Initialize the logging streams, sending stdout -> Default; stderr -> Error -+ result = PyObject_CallMethod( -+ _apple_support, "init_streams", "Oii", -+ apple_log_write, OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_ERROR); -+ if (result == NULL) { -+ goto error; -+ } -+ goto done; ++/* Begin PBXFrameworksBuildPhase section */ ++ EE989E4B2DCD6E780036B268 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E622DCD6E7A0036B268 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ + -+error: -+ _PyErr_Print(tstate); -+ status = _PyStatus_ERR("failed to initialize Apple log streams"); ++/* Begin PBXGroup section */ ++ EE989E452DCD6E780036B268 = { ++ isa = PBXGroup; ++ children = ( ++ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */, ++ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */, ++ EE989E502DCD6E780036B268 /* tvOSTestbed */, ++ EE989E682DCD6E7A0036B268 /* TestbedTests */, ++ EE989E4F2DCD6E780036B268 /* Products */, ++ ); ++ sourceTree = ""; ++ }; ++ EE989E4F2DCD6E780036B268 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */, ++ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ + -+done: -+ Py_XDECREF(result); -+ Py_XDECREF(apple_log_write); -+ Py_XDECREF(_apple_support); -+ return status; -+} ++/* Begin PBXNativeTarget section */ ++ EE989E4D2DCD6E780036B268 /* tvOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */; ++ buildPhases = ( ++ EE989E4A2DCD6E780036B268 /* Sources */, ++ EE989E4B2DCD6E780036B268 /* Frameworks */, ++ EE989E4C2DCD6E780036B268 /* Resources */, ++ EE7C8A222DCD70F4003206DB /* Process Python libraries */, ++ EE7C8A212DCD70CD003206DB /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ fileSystemSynchronizedGroups = ( ++ EE989E502DCD6E780036B268 /* tvOSTestbed */, ++ ); ++ name = tvOSTestbed; ++ packageProductDependencies = ( ++ ); ++ productName = tvOSTestbed; ++ productReference = EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ EE989E642DCD6E7A0036B268 /* TestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildPhases = ( ++ EE989E612DCD6E7A0036B268 /* Sources */, ++ EE989E622DCD6E7A0036B268 /* Frameworks */, ++ EE989E632DCD6E7A0036B268 /* Resources */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */, ++ ); ++ fileSystemSynchronizedGroups = ( ++ EE989E682DCD6E7A0036B268 /* TestbedTests */, ++ ); ++ name = TestbedTests; ++ packageProductDependencies = ( ++ ); ++ productName = TestbedTests; ++ productReference = EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */; ++ productType = "com.apple.product-type.bundle.unit-test"; ++ }; ++/* End PBXNativeTarget section */ + -+#endif // __APPLE__ && USE_APPLE_SYSTEM_LOG ++/* Begin PBXProject section */ ++ EE989E462DCD6E780036B268 /* Project object */ = { ++ isa = PBXProject; ++ attributes = { ++ BuildIndependentTargetsInParallel = 1; ++ LastUpgradeCheck = 1620; ++ TargetAttributes = { ++ EE989E4D2DCD6E780036B268 = { ++ CreatedOnToolsVersion = 16.2; ++ }; ++ EE989E642DCD6E7A0036B268 = { ++ CreatedOnToolsVersion = 16.2; ++ TestTargetID = EE989E4D2DCD6E780036B268; ++ }; ++ }; ++ }; ++ buildConfigurationList = EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = EE989E452DCD6E780036B268; ++ minimizedProjectReferenceProxies = 1; ++ preferredProjectObjectVersion = 77; ++ productRefGroup = EE989E4F2DCD6E780036B268 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ EE989E4D2DCD6E780036B268 /* tvOSTestbed */, ++ EE989E642DCD6E7A0036B268 /* TestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ + - - static void - _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp, -diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h -index 553585a76a3..991e5255405 100644 ---- a/Python/stdlib_module_names.h -+++ b/Python/stdlib_module_names.h -@@ -5,6 +5,7 @@ - "__future__", - "_abc", - "_aix_support", -+"_apple_support", - "_ast", - "_asyncio", - "_bisect", -@@ -40,6 +41,7 @@ - "_heapq", - "_imp", - "_io", -+"_ios_support", - "_json", - "_locale", - "_lsprof", -diff --git a/config.sub b/config.sub -index d74fb6deac9..1bb6a05dc11 100755 ---- a/config.sub -+++ b/config.sub -@@ -1,14 +1,15 @@ - #! /bin/sh - # Configuration validation subroutine script. --# Copyright 1992-2021 Free Software Foundation, Inc. -+# Copyright 1992-2024 Free Software Foundation, Inc. - - # shellcheck disable=SC2006,SC2268 # see below for rationale - --timestamp='2021-08-14' -+# Patched 2024-02-03 to include support for arm64_32 and iOS/tvOS/watchOS simulators -+timestamp='2024-01-01' - - # This file is free software; you can redistribute it and/or modify it - # under the terms of the GNU General Public License as published by --# the Free Software Foundation; either version 3 of the License, or -+# the Free Software Foundation, either version 3 of the License, or - # (at your option) any later version. - # - # This program is distributed in the hope that it will be useful, but -@@ -76,13 +77,13 @@ - version="\ - GNU config.sub ($timestamp) - --Copyright 1992-2021 Free Software Foundation, Inc. -+Copyright 1992-2024 Free Software Foundation, Inc. - - This is free software; see the source for copying conditions. There is NO - warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." - - help=" --Try \`$me --help' for more information." -+Try '$me --help' for more information." - - # Parse command line - while test $# -gt 0 ; do -@@ -130,7 +131,7 @@ - # Separate into logical components for further validation - case $1 in - *-*-*-*-*) -- echo Invalid configuration \`"$1"\': more than four components >&2 -+ echo "Invalid configuration '$1': more than four components" >&2 - exit 1 - ;; - *-*-*-*) -@@ -145,7 +146,8 @@ - nto-qnx* | linux-* | uclinux-uclibc* \ - | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ - | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ -- | storm-chaos* | os2-emx* | rtmk-nova*) -+ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \ -+ | windows-* ) - basic_machine=$field1 - basic_os=$maybe_os - ;; -@@ -943,7 +945,7 @@ - EOF - IFS=$saved_IFS - ;; -- # We use `pc' rather than `unknown' -+ # We use 'pc' rather than 'unknown' - # because (1) that's what they normally are, and - # (2) the word "unknown" tends to confuse beginning users. - i*86 | x86_64) -@@ -1020,6 +1022,11 @@ - ;; - - # Here we normalize CPU types with a missing or matching vendor -+ armh-unknown | armh-alt) -+ cpu=armv7l -+ vendor=alt -+ basic_os=${basic_os:-linux-gnueabihf} -+ ;; - dpx20-unknown | dpx20-bull) - cpu=rs6000 - vendor=bull -@@ -1070,7 +1077,7 @@ - pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) - cpu=i586 - ;; -- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*) -+ pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*) - cpu=i686 - ;; - pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) -@@ -1121,7 +1128,7 @@ - xscale-* | xscalee[bl]-*) - cpu=`echo "$cpu" | sed 's/^xscale/arm/'` - ;; -- arm64-*) -+ arm64-* | aarch64le-* | arm64_32-*) - cpu=aarch64 - ;; - -@@ -1175,7 +1182,7 @@ - case $cpu in - 1750a | 580 \ - | a29k \ -- | aarch64 | aarch64_be \ -+ | aarch64 | aarch64_be | aarch64c | arm64ec \ - | abacus \ - | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \ - | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \ -@@ -1194,50 +1201,29 @@ - | d10v | d30v | dlx | dsp16xx \ - | e2k | elxsi | epiphany \ - | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \ -+ | javascript \ - | h8300 | h8500 \ - | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ - | hexagon \ - | i370 | i*86 | i860 | i960 | ia16 | ia64 \ - | ip2k | iq2000 \ - | k1om \ -+ | kvx \ - | le32 | le64 \ - | lm32 \ -- | loongarch32 | loongarch64 | loongarchx32 \ -+ | loongarch32 | loongarch64 \ - | m32c | m32r | m32rle \ - | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \ - | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \ - | m88110 | m88k | maxq | mb | mcore | mep | metag \ - | microblaze | microblazeel \ -- | mips | mipsbe | mipseb | mipsel | mipsle \ -- | mips16 \ -- | mips64 | mips64eb | mips64el \ -- | mips64octeon | mips64octeonel \ -- | mips64orion | mips64orionel \ -- | mips64r5900 | mips64r5900el \ -- | mips64vr | mips64vrel \ -- | mips64vr4100 | mips64vr4100el \ -- | mips64vr4300 | mips64vr4300el \ -- | mips64vr5000 | mips64vr5000el \ -- | mips64vr5900 | mips64vr5900el \ -- | mipsisa32 | mipsisa32el \ -- | mipsisa32r2 | mipsisa32r2el \ -- | mipsisa32r3 | mipsisa32r3el \ -- | mipsisa32r5 | mipsisa32r5el \ -- | mipsisa32r6 | mipsisa32r6el \ -- | mipsisa64 | mipsisa64el \ -- | mipsisa64r2 | mipsisa64r2el \ -- | mipsisa64r3 | mipsisa64r3el \ -- | mipsisa64r5 | mipsisa64r5el \ -- | mipsisa64r6 | mipsisa64r6el \ -- | mipsisa64sb1 | mipsisa64sb1el \ -- | mipsisa64sr71k | mipsisa64sr71kel \ -- | mipsr5900 | mipsr5900el \ -- | mipstx39 | mipstx39el \ -+ | mips* \ - | mmix \ - | mn10200 | mn10300 \ - | moxie \ - | mt \ - | msp430 \ -+ | nanomips* \ - | nds32 | nds32le | nds32be \ - | nfp \ - | nios | nios2 | nios2eb | nios2el \ -@@ -1269,6 +1255,7 @@ - | ubicom32 \ - | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \ - | vax \ -+ | vc4 \ - | visium \ - | w65 \ - | wasm32 | wasm64 \ -@@ -1280,7 +1267,7 @@ - ;; - - *) -- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2 -+ echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2 - exit 1 - ;; - esac -@@ -1301,11 +1288,12 @@ - - # Decode manufacturer-specific aliases for certain operating systems. - --if test x$basic_os != x -+if test x"$basic_os" != x - then - --# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just -+# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just - # set os. -+obj= - case $basic_os in - gnu/linux*) - kernel=linux -@@ -1336,6 +1324,10 @@ - kernel=linux - os=`echo "$basic_os" | sed -e 's|linux|gnu|'` - ;; -+ managarm*) -+ kernel=managarm -+ os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'` -+ ;; - *) - kernel= - os=$basic_os -@@ -1501,10 +1493,16 @@ - os=eabi - ;; - *) -- os=elf -+ os= -+ obj=elf - ;; - esac - ;; -+ aout* | coff* | elf* | pe*) -+ # These are machine code file formats, not OSes -+ obj=$os -+ os= -+ ;; - *) - # No normalization, but not necessarily accepted, that comes below. - ;; -@@ -1523,12 +1521,15 @@ - # system, and we'll never get to this point. - - kernel= -+obj= - case $cpu-$vendor in - score-*) -- os=elf -+ os= -+ obj=elf - ;; - spu-*) -- os=elf -+ os= -+ obj=elf - ;; - *-acorn) - os=riscix1.2 -@@ -1538,28 +1539,35 @@ - os=gnu - ;; - arm*-semi) -- os=aout -+ os= -+ obj=aout - ;; - c4x-* | tic4x-*) -- os=coff -+ os= -+ obj=coff - ;; - c8051-*) -- os=elf -+ os= -+ obj=elf - ;; - clipper-intergraph) - os=clix - ;; - hexagon-*) -- os=elf -+ os= -+ obj=elf - ;; - tic54x-*) -- os=coff -+ os= -+ obj=coff - ;; - tic55x-*) -- os=coff -+ os= -+ obj=coff - ;; - tic6x-*) -- os=coff -+ os= -+ obj=coff - ;; - # This must come before the *-dec entry. - pdp10-*) -@@ -1581,19 +1589,24 @@ - os=sunos3 - ;; - m68*-cisco) -- os=aout -+ os= -+ obj=aout - ;; - mep-*) -- os=elf -+ os= -+ obj=elf - ;; - mips*-cisco) -- os=elf -+ os= -+ obj=elf - ;; -- mips*-*) -- os=elf -+ mips*-*|nanomips*-*) -+ os= -+ obj=elf - ;; - or32-*) -- os=coff -+ os= -+ obj=coff - ;; - *-tti) # must be before sparc entry or we get the wrong os. - os=sysv3 -@@ -1602,7 +1615,8 @@ - os=sunos4.1.1 - ;; - pru-*) -- os=elf -+ os= -+ obj=elf - ;; - *-be) - os=beos -@@ -1683,10 +1697,12 @@ - os=uxpv - ;; - *-rom68k) -- os=coff -+ os= -+ obj=coff - ;; - *-*bug) -- os=coff -+ os= -+ obj=coff - ;; - *-apple) - os=macos -@@ -1704,10 +1720,11 @@ - - fi - --# Now, validate our (potentially fixed-up) OS. -+# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ). ++/* Begin PBXResourcesBuildPhase section */ ++ EE989E4C2DCD6E780036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E632DCD6E7A0036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ ++ ++/* Begin PBXShellScriptBuildPhase section */ ++ EE7C8A222DCD70F4003206DB /* Process Python libraries */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Process Python libraries"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ showEnvVarsInLog = 0; ++ }; ++/* End PBXShellScriptBuildPhase section */ ++ ++/* Begin PBXSourcesBuildPhase section */ ++ EE989E4A2DCD6E780036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E612DCD6E7A0036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ ++ ++/* Begin PBXTargetDependency section */ ++ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ targetProxy = EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ ++ ++/* Begin XCBuildConfiguration section */ ++ EE989E772DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = appletvos; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Debug; ++ }; ++ EE989E782DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = appletvos; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ VALIDATE_PRODUCT = YES; ++ }; ++ name = Release; ++ }; ++ EE989E7A2DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 3; ++ }; ++ name = Debug; ++ }; ++ EE989E7B2DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 3; ++ }; ++ name = Release; ++ }; ++ EE989E7D2DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 3; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Debug; ++ }; ++ EE989E7E2DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 3; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ ++ ++/* Begin XCConfigurationList section */ ++ EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E772DCD6E7A0036B268 /* Debug */, ++ EE989E782DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7A2DCD6E7A0036B268 /* Debug */, ++ EE989E7B2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7D2DCD6E7A0036B268 /* Debug */, ++ EE989E7E2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = EE989E462DCD6E780036B268 /* Project object */; ++} +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme +@@ -0,0 +1,97 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", ++ "name" : "Test Scheme Action", ++ "options" : { ++ ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "tvOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "EE989E642DCD6E7A0036B268", ++ "name" : "TestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +\ No newline at end of file +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// tvOSTestbed ++// ++ ++#import ++ ++@interface AppDelegate : UIResponder ++ ++ ++@end +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed/AppDelegate.m +@@ -0,0 +1,19 @@ ++// ++// AppDelegate.m ++// tvOSTestbed ++// ++ ++#import "AppDelegate.h" ++ ++@interface AppDelegate () ++ ++@end ++ ++@implementation AppDelegate ++ ++ ++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ++ return YES; ++} ++ ++@end +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,24 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. ++ ++During the build, any binary modules found in this folder will be processed into ++Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH, and will be the ++working directory for the test suite. +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed/app_packages/README +@@ -0,0 +1,7 @@ ++This folder can be a target for installing any Python dependencies needed by the ++test suite. ++ ++During the build, any binary modules found in this folder will be processed into ++Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH. +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// tvOSTestbed ++// ++ ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +--- /dev/null ++++ b/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +@@ -0,0 +1,52 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.tvOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ LSRequiresIPhoneOS ++ ++ UIRequiresFullScreen ++ ++ UILaunchStoryboardName ++ Launch Screen ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ ++ ++ +--- /dev/null ++++ b/Apple/tvOS/README.rst +@@ -0,0 +1,108 @@ ++===================== ++Python on tvOS README ++===================== ++ ++:Authors: ++ Russell Keith-Magee (2023-11) ++ ++This document provides a quick overview of some tvOS specific features in the ++Python distribution. ++ ++Compilers for building on tvOS ++============================== ++ ++Building for tvOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode, on the ++most recently released macOS. ++ ++tvOS specific arguments to configure ++=================================== ++ ++* ``--enable-framework[=DIR]`` ++ ++ This argument specifies the location where the Python.framework will ++ be installed. ++ ++* ``--with-framework-name=NAME`` ++ ++ Specify the name for the python framework, defaults to ``Python``. ++ ++ ++Building and using Python on tvOS ++================================= ++ ++ABIs and Architectures ++---------------------- ++ ++tvOS apps can be deployed on physical devices, and on the tvOS simulator. ++Although the API used on these devices is identical, the ABI is different - you ++need to link against different libraries for an tvOS device build ++(``appletvos``) or an tvOS simulator build (``appletvsimulator``). Apple uses ++the XCframework format to allow specifying a single dependency that supports ++multiple ABIs. An XCframework is a wrapper around multiple ABI-specific ++frameworks. ++ ++tvOS can also support different CPU architectures within each ABI. At present, ++there is only a single support ed architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines.) ++ ++To support multiple CPU architectures on a single platform, Apple uses a "fat ++binary" format - a single physical file that contains support for multiple ++architectures. ++ ++How do I build Python for tvOS? ++------------------------------- + - case $os in - # Sometimes we do "kernel-libc", so those need to count as OSes. -- musl* | newlib* | relibc* | uclibc*) -+ llvm* | musl* | newlib* | relibc* | uclibc*) - ;; - # Likewise for "kernel-abi" - eabi* | gnueabi*) -@@ -1715,6 +1732,9 @@ - # VxWorks passes extra cpu info in the 4th filed. - simlinux | simwindows | spe) - ;; -+ # See `case $cpu-$os` validation below -+ ghcjs) -+ ;; - # Now accept the basic system types. - # The portable systems comes first. - # Each alternative MUST end in a * to match a version number. -@@ -1723,7 +1743,7 @@ - | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ - | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \ - | hiux* | abug | nacl* | netware* | windows* \ -- | os9* | macos* | osx* | ios* \ -+ | os9* | macos* | osx* | ios* | tvos* | watchos* \ - | mpw* | magic* | mmixware* | mon960* | lnews* \ - | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ - | aos* | aros* | cloudabi* | sortix* | twizzler* \ -@@ -1732,11 +1752,11 @@ - | mirbsd* | netbsd* | dicos* | openedition* | ose* \ - | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \ - | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \ -- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ -- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ -+ | bosx* | nextstep* | cxux* | oabi* \ -+ | ptx* | ecoff* | winnt* | domain* | vsta* \ - | udi* | lites* | ieee* | go32* | aux* | hcos* \ - | chorusrdb* | cegcc* | glidix* | serenity* \ -- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \ -+ | cygwin* | msys* | moss* | proelf* | rtems* \ - | midipix* | mingw32* | mingw64* | mint* \ - | uxpv* | beos* | mpeix* | udk* | moxiebox* \ - | interix* | uwin* | mks* | rhapsody* | darwin* \ -@@ -1748,49 +1768,119 @@ - | skyos* | haiku* | rdos* | toppers* | drops* | es* \ - | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ - | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ -- | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr*) -+ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ -+ | fiwix* | mlibc* | cos* | mbr* | ironclad* ) - ;; - # This one is extra strict with allowed versions - sco3.2v2 | sco3.2v[4-9]* | sco5v6*) - # Don't forget version if it is 3.2v4 or newer. - ;; -+ # This refers to builds using the UEFI calling convention -+ # (which depends on the architecture) and PE file format. -+ # Note that this is both a different calling convention and -+ # different file format than that of GNU-EFI -+ # (x86_64-w64-mingw32). -+ uefi) -+ ;; - none) - ;; -+ kernel* | msvc* ) -+ # Restricted further below -+ ;; -+ '') -+ if test x"$obj" = x -+ then -+ echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2 -+ fi -+ ;; - *) -- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2 -+ echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2 -+ exit 1 -+ ;; -+esac ++The Python build system will build a ``Python.framework`` that supports a ++*single* ABI with a *single* architecture. If you want to use Python in an tvOS ++project, you need to: ++ ++1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; ++2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; ++3. Merge the "fat" frameworks for each ABI into a single XCframework. ++ ++tvOS builds of Python *must* be constructed as framework builds. To support this, ++you must provide the ``--enable-framework`` flag when configuring the build. ++ ++The build also requires the use of cross-compilation. The commands for building ++Python for tvOS will look somethign like:: ++ ++ $ ./configure \ ++ --enable-framework=/path/to/install \ ++ --host=aarch64-apple-tvos \ ++ --build=aarch64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++ $ make ++ $ make install ++ ++In this invocation: ++ ++* ``/path/to/install`` is the location where the final Python.framework will be ++ output. ++ ++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: ++ ++ - ``aarch64-apple-tvos`` for ARM64 tvOS devices. ++ - ``aarch64-apple-tvos-simulator`` for the tvOS simulator running on Apple ++ Silicon devices. ++ - ``x86_64-apple-tvos-simulator`` for the tvOS simulator running on Intel ++ devices. ++ ++* ``--build`` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: ++ ++ - ``aarch64-apple-darwin`` for Apple Silicon devices. ++ - ``x86_64-apple-darwin`` for Intel devices. ++ ++* ``/path/to/python.exe`` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for tvOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the version as the Python that is being compiled. ++ ++Using a framework-based Python on tvOS ++====================================== +--- /dev/null ++++ b/Apple/tvOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ @TVOS_DEPLOYMENT_TARGET@ ++ ++ +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} -E "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch arm64 "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${TVOS_SDK_VERSION} strip -arch arm64 "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch x86_64 "$@" +--- /dev/null ++++ b/Apple/tvOS/Resources/pyconfig.h +@@ -0,0 +1,7 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif ++ ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +--- /dev/null ++++ b/Apple/watchOS/README.rst +@@ -0,0 +1,108 @@ ++======================== ++Python on watchOS README ++======================== ++ ++:Authors: ++ Russell Keith-Magee (2023-11) ++ ++This document provides a quick overview of some watchOS specific features in the ++Python distribution. ++ ++Compilers for building on watchOS ++================================= ++ ++Building for watchOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode, on the ++most recently released macOS. ++ ++watchOS specific arguments to configure ++======================================= ++ ++* ``--enable-framework[=DIR]`` ++ ++ This argument specifies the location where the Python.framework will ++ be installed. ++ ++* ``--with-framework-name=NAME`` ++ ++ Specify the name for the python framework, defaults to ``Python``. ++ ++ ++Building and using Python on watchOS ++==================================== + -+case $obj in -+ aout* | coff* | elf* | pe*) -+ ;; -+ '') -+ # empty is fine -+ ;; -+ *) -+ echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2 -+ exit 1 -+ ;; -+esac ++ABIs and Architectures ++---------------------- + -+# Here we handle the constraint that a (synthetic) cpu and os are -+# valid only in combination with each other and nowhere else. -+case $cpu-$os in -+ # The "javascript-unknown-ghcjs" triple is used by GHC; we -+ # accept it here in order to tolerate that, but reject any -+ # variations. -+ javascript-ghcjs) -+ ;; -+ javascript-* | *-ghcjs) -+ echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2 - exit 1 - ;; - esac - - # As a final step for OS-related things, validate the OS-kernel combination - # (given a valid OS), if there is a kernel. --case $kernel-$os in -- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ -- | linux-musl* | linux-relibc* | linux-uclibc* ) -+case $kernel-$os-$obj in -+ linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \ -+ | linux-mlibc*- | linux-musl*- | linux-newlib*- \ -+ | linux-relibc*- | linux-uclibc*- ) -+ ;; -+ uclinux-uclibc*- ) - ;; -- uclinux-uclibc* ) -+ managarm-mlibc*- | managarm-kernel*- ) - ;; -- -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* ) -+ windows*-msvc*-) -+ ;; -+ -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \ -+ | -uclibc*- ) - # These are just libc implementations, not actual OSes, and thus - # require a kernel. -- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 -+ echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2 - exit 1 - ;; -- kfreebsd*-gnu* | kopensolaris*-gnu*) -+ -kernel*- ) -+ echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2 -+ exit 1 - ;; -- vxworks-simlinux | vxworks-simwindows | vxworks-spe) -+ *-kernel*- ) -+ echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2 -+ exit 1 - ;; -- nto-qnx*) -+ *-msvc*- ) -+ echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2 -+ exit 1 - ;; -- os2-emx) -+ kfreebsd*-gnu*- | kopensolaris*-gnu*-) - ;; -- *-eabi* | *-gnueabi*) -+ vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-) - ;; -- -*) -+ nto-qnx*-) -+ ;; -+ os2-emx-) -+ ;; -+ *-eabi*- | *-gnueabi*-) -+ ;; -+ ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) -+ ;; -+ none--*) -+ # None (no kernel, i.e. freestanding / bare metal), -+ # can be paired with an machine code file format -+ ;; -+ -*-) - # Blank kernel with real OS is always fine. - ;; -- *-*) -- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 -+ --*) -+ # Blank kernel and OS with real machine code file format is always fine. -+ ;; -+ *-*-*) -+ echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 - exit 1 - ;; - esac -@@ -1873,7 +1963,7 @@ - ;; - esac ++watchOS apps can be deployed on physical devices, and on the watchOS simulator. ++Although the API used on these devices is identical, the ABI is different - you ++need to link against different libraries for an watchOS device build ++(``watchos``) or an watchOS simulator build (``watchsimulator``). Apple uses the ++XCframework format to allow specifying a single dependency that supports ++multiple ABIs. An XCframework is a wrapper around multiple ABI-specific ++frameworks. ++ ++watchOS can also support different CPU architectures within each ABI. At present, ++there is only a single support ed architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines.) ++ ++To support multiple CPU architectures on a single platform, Apple uses a "fat ++binary" format - a single physical file that contains support for multiple ++architectures. ++ ++How do I build Python for watchOS? ++------------------------------- ++ ++The Python build system will build a ``Python.framework`` that supports a ++*single* ABI with a *single* architecture. If you want to use Python in an watchOS ++project, you need to: ++ ++1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; ++2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; ++3. Merge the "fat" frameworks for each ABI into a single XCframework. ++ ++watchOS builds of Python *must* be constructed as framework builds. To support this, ++you must provide the ``--enable-framework`` flag when configuring the build. ++ ++The build also requires the use of cross-compilation. The commands for building ++Python for watchOS will look somethign like:: ++ ++ $ ./configure \ ++ --enable-framework=/path/to/install \ ++ --host=aarch64-apple-watchos \ ++ --build=aarch64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++ $ make ++ $ make install ++ ++In this invocation: ++ ++* ``/path/to/install`` is the location where the final Python.framework will be ++ output. ++ ++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: ++ ++ - ``arm64_32-apple-watchos`` for ARM64-32 watchOS devices. ++ - ``aarch64-apple-watchos-simulator`` for the watchOS simulator running on Apple ++ Silicon devices. ++ - ``x86_64-apple-watchos-simulator`` for the watchOS simulator running on Intel ++ devices. ++ ++* ``--build`` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: ++ ++ - ``aarch64-apple-darwin`` for Apple Silicon devices. ++ - ``x86_64-apple-darwin`` for Intel devices. ++ ++* ``/path/to/python.exe`` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for watchOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the version as the Python that is being compiled. ++ ++Using a framework-based Python on watchOS ++====================================== +--- /dev/null ++++ b/Apple/watchOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2023 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ %VERSION% ++ CFBundleSupportedPlatforms ++ ++ watchOS ++ ++ MinimumOSVersion ++ @WATCHOS_DEPLOYMENT_TARGET@ ++ ++ +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} -E "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch x86_64 "$@" +--- /dev/null ++++ b/Apple/watchOS/Resources/pyconfig.h +@@ -0,0 +1,11 @@ ++#ifdef __arm64__ ++# ifdef __LP64__ ++#include "pyconfig-arm64.h" ++# else ++#include "pyconfig-arm64_32.h" ++# endif ++#endif ++ ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +--- /dev/null ++++ b/Doc/includes/wasm-ios-notavail.rst +@@ -0,0 +1,8 @@ ++.. include for modules that don't work on WASM or iOS ++ ++.. availability:: not WASI, not iOS. ++ ++ This module does not work or is not available on WebAssembly platforms, or ++ on iOS. See :ref:`wasm-availability` for more information on WASM ++ availability; see :ref:`iOS-availability` for more information on iOS ++ availability. +diff --git a/Doc/includes/wasm-notavail.rst b/Doc/includes/wasm-notavail.rst +index e680e1f9b43..c1b79d2a4a0 100644 +--- a/Doc/includes/wasm-notavail.rst ++++ b/Doc/includes/wasm-notavail.rst +@@ -1,7 +1,6 @@ + .. include for modules that don't work on WASM --echo "$cpu-$vendor-${kernel:+$kernel-}$os" -+echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" - exit +-.. availability:: not Emscripten, not WASI. ++.. availability:: not WASI. - # Local variables: -diff --git a/configure b/configure -index a1ad0ae2510..66fe8cc92c8 100755 ---- a/configure -+++ b/configure -@@ -838,6 +838,8 @@ - LIBMPDEC_INTERNAL - LIBMPDEC_LDFLAGS - LIBMPDEC_CFLAGS -+LIBFFI_LIB -+LIBFFI_LIBDIR - LIBFFI_INCLUDEDIR - LIBEXPAT_INTERNAL - LIBEXPAT_LDFLAGS -@@ -925,10 +927,14 @@ - LDFLAGS - CFLAGS - CC -+WATCHOS_DEPLOYMENT_TARGET -+TVOS_DEPLOYMENT_TARGET -+IPHONEOS_DEPLOYMENT_TARGET - EXPORT_MACOSX_DEPLOYMENT_TARGET - CONFIGURE_MACOSX_DEPLOYMENT_TARGET - _PYTHON_HOST_PLATFORM --MACHDEP -+APP_STORE_COMPLIANCE_PATCH -+INSTALLTARGETS - FRAMEWORKINSTALLAPPSPREFIX - FRAMEWORKUNIXTOOLSPREFIX - FRAMEWORKPYTHONW -@@ -936,6 +942,8 @@ - FRAMEWORKALTINSTALLFIRST - FRAMEWORKINSTALLLAST - FRAMEWORKINSTALLFIRST -+RESSRCDIR -+PYTHONFRAMEWORKINSTALLNAMEPREFIX - PYTHONFRAMEWORKINSTALLDIR - PYTHONFRAMEWORKPREFIX - PYTHONFRAMEWORKDIR -@@ -945,6 +953,7 @@ - LIPO_32BIT_FLAGS - ARCH_RUN_32BIT - UNIVERSALSDK -+MACHDEP - PKG_CONFIG_LIBDIR - PKG_CONFIG_PATH - PKG_CONFIG -@@ -991,7 +1000,6 @@ - docdir - oldincludedir - includedir --runstatedir - localstatedir - sharedstatedir - sysconfdir -@@ -1020,6 +1028,7 @@ - with_universal_archs - with_framework_name - enable_framework -+with_app_store_compliance - with_cxx_main - with_emscripten_target - enable_wasm_dynamic_linking -@@ -1142,7 +1151,6 @@ - sysconfdir='${prefix}/etc' - sharedstatedir='${prefix}/com' - localstatedir='${prefix}/var' --runstatedir='${localstatedir}/run' - includedir='${prefix}/include' - oldincludedir='/usr/include' - docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' -@@ -1395,15 +1403,6 @@ - | -silent | --silent | --silen | --sile | --sil) - silent=yes ;; +- This module does not work or is not available on WebAssembly platforms +- ``wasm32-emscripten`` and ``wasm32-wasi``. See ++ This module does not work or is not available on WebAssembly. See + :ref:`wasm-availability` for more information. +diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst +index 0d743efd28e..dfd71112231 100644 +--- a/Doc/library/curses.rst ++++ b/Doc/library/curses.rst +@@ -21,6 +21,8 @@ + designed to match the API of ncurses, an open-source curses library hosted on + Linux and the BSD variants of Unix. -- -runstatedir | --runstatedir | --runstatedi | --runstated \ -- | --runstate | --runstat | --runsta | --runst | --runs \ -- | --run | --ru | --r) -- ac_prev=runstatedir ;; -- -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ -- | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ -- | --run=* | --ru=* | --r=*) -- runstatedir=$ac_optarg ;; ++.. include:: ../includes/wasm-ios-notavail.rst ++ + .. note:: + + Whenever the documentation mentions a *character* it can be specified +diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst +index 74f96b6c433..30b7738a08e 100644 +--- a/Doc/library/dbm.rst ++++ b/Doc/library/dbm.rst +@@ -14,6 +14,7 @@ + is a `third party interface `_ to + the Oracle Berkeley DB. + ++.. include:: ../includes/wasm-ios-notavail.rst + + .. exception:: error + +@@ -398,4 +399,3 @@ + .. method:: dumbdbm.close() + + Close the database. - - -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) - ac_prev=sbindir ;; - -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ -@@ -1541,7 +1540,7 @@ - for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ - datadir sysconfdir sharedstatedir localstatedir includedir \ - oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ -- libdir localedir mandir runstatedir -+ libdir localedir mandir - do - eval ac_val=\$$ac_var - # Remove trailing slashes. -@@ -1694,7 +1693,6 @@ - --sysconfdir=DIR read-only single-machine data [PREFIX/etc] - --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] - --localstatedir=DIR modifiable single-machine data [PREFIX/var] -- --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] - --libdir=DIR object code libraries [EPREFIX/lib] - --includedir=DIR C header files [PREFIX/include] - --oldincludedir=DIR C header files for non-gcc [/usr/include] -@@ -1779,6 +1777,10 @@ - specify the name for the python framework on macOS - only valid when --enable-framework is set. see - Mac/README.rst (default is 'Python') -+ --with-app-store-compliance=[PATCH-FILE] -+ Enable any patches required for compiliance with app -+ stores. Optional PATCH-FILE specifies the custom -+ patch to apply. - --with-cxx-main[=COMPILER] - compile main() and link Python executable with C++ - compiler specified in COMPILER (default is $CXX) -@@ -3566,6 +3568,166 @@ - as_fn_error $? "pkg-config is required" "$LINENO" 5] - fi +diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst +index de3b93f5e61..168e45cfd6f 100644 +--- a/Doc/library/ensurepip.rst ++++ b/Doc/library/ensurepip.rst +@@ -38,7 +38,7 @@ + :pep:`453`: Explicit bootstrapping of pip in Python installations + The original rationale and specification for this module. -+# Set name for machine-dependent library files +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst + + Command line interface + ---------------------- +diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst +index 1836f60ca5c..a36974b1594 100644 +--- a/Doc/library/fcntl.rst ++++ b/Doc/library/fcntl.rst +@@ -18,7 +18,7 @@ + See the :manpage:`fcntl(2)` and :manpage:`ioctl(2)` Unix manual pages + for full details. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI. + + All functions in this module take a file descriptor *fd* as their first + argument. This can be an integer file descriptor, such as returned by +diff --git a/Doc/library/grp.rst b/Doc/library/grp.rst +index ee55b12ea86..80260fec8d3 100644 +--- a/Doc/library/grp.rst ++++ b/Doc/library/grp.rst +@@ -10,7 +10,7 @@ + This module provides access to the Unix group database. It is available on all + Unix versions. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI, not iOS. + + Group database entries are reported as a tuple-like object, whose attributes + correspond to the members of the ``group`` structure (Attribute field below, see +diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst +index e9200dd1e2d..5f57362e557 100644 +--- a/Doc/library/importlib.rst ++++ b/Doc/library/importlib.rst +@@ -1202,6 +1202,69 @@ + and how the module's :attr:`__file__` is populated. + + ++.. class:: AppleFrameworkLoader(name, path) ++ ++ A specialization of :class:`importlib.machinery.ExtensionFileLoader` that ++ is able to load extension modules in Framework format. ++ ++ For compatibility with the iOS App Store, *all* binary modules in an iOS app ++ must be dynamic libraries, contained in a framework with appropriate ++ metadata, stored in the ``Frameworks`` folder of the packaged app. There can ++ be only a single binary per framework, and there can be no executable binary ++ material outside the Frameworks folder. ++ ++ To accomodate this requirement, when running on iOS, extension module ++ binaries are *not* packaged as ``.so`` files on ``sys.path``, but as ++ individual standalone frameworks. To discover those frameworks, this loader ++ is be registered against the ``.fwork`` file extension, with a ``.fwork`` ++ file acting as a placeholder in the original location of the binary on ++ ``sys.path``. The ``.fwork`` file contains the path of the actual binary in ++ the ``Frameworks`` folder, relative to the app bundle. To allow for ++ resolving a framework-packaged binary back to the original location, the ++ framework is expected to contain a ``.origin`` file that contains the ++ location of the ``.fwork`` file, relative to the app bundle. ++ ++ For example, consider the case of an import ``from foo.bar import _whiz``, ++ where ``_whiz`` is implemented with the binary module ++ ``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location ++ registered on ``sys.path``, relative to the application bundle. This module ++ *must* be distributed as ++ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` (creating the framework ++ name from the full import path of the module), with an ``Info.plist`` file ++ in the ``.framework`` directory identifying the binary as a framework. The ++ ``foo.bar._whiz`` module would be represented in the original location with ++ a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing the path ++ ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also contain ++ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing the ++ path to the ``.fwork`` file. + -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 -+$as_echo_n "checking MACHDEP... " >&6; } -+if test -z "$MACHDEP" -+then -+ # avoid using uname for cross builds -+ if test "$cross_compiling" = yes; then -+ # ac_sys_system and ac_sys_release are used for setting -+ # a lot of different things including 'define_xopen_source' -+ # in the case statement below. -+ case "$host" in -+ *-*-linux-android*) -+ ac_sys_system=Linux-android -+ ;; -+ *-*-linux*) -+ ac_sys_system=Linux -+ ;; -+ *-*-cygwin*) -+ ac_sys_system=Cygwin -+ ;; -+ *-apple-ios*) -+ ac_sys_system=iOS -+ ;; -+ *-apple-tvos*) -+ ac_sys_system=tvOS -+ ;; -+ *-apple-watchos*) -+ ac_sys_system=watchOS -+ ;; -+ *-*-vxworks*) -+ ac_sys_system=VxWorks -+ ;; -+ *-*-emscripten) -+ ac_sys_system=Emscripten -+ ;; -+ *-*-wasi) -+ ac_sys_system=WASI -+ ;; -+ *) -+ # for now, limit cross builds to known configurations -+ MACHDEP="unknown" -+ as_fn_error $? "cross build not supported for $host" "$LINENO" 5 -+ esac -+ ac_sys_release= -+ else -+ ac_sys_system=`uname -s` -+ if test "$ac_sys_system" = "AIX" \ -+ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then -+ ac_sys_release=`uname -v` -+ else -+ ac_sys_release=`uname -r` -+ fi -+ fi -+ ac_md_system=`echo $ac_sys_system | -+ tr -d '/ ' | tr '[A-Z]' '[a-z]'` -+ ac_md_release=`echo $ac_sys_release | -+ tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` -+ MACHDEP="$ac_md_system$ac_md_release" ++ When a module is loaded with this loader, the ``__file__`` for the module ++ will report as the location of the ``.fwork`` file. This allows code to use ++ the ``__file__`` of a module as an anchor for file system traveral. ++ However, the spec origin will reference the location of the *actual* binary ++ in the ``.framework`` folder. + -+ case $MACHDEP in -+ aix*) MACHDEP="aix";; -+ linux*) MACHDEP="linux";; -+ cygwin*) MACHDEP="cygwin";; -+ darwin*) MACHDEP="darwin";; -+ '') MACHDEP="unknown";; -+ esac ++ The Xcode project building the app is responsible for converting any ``.so`` ++ files from wherever they exist in the ``PYTHONPATH`` into frameworks in the ++ ``Frameworks`` folder (including stripping extensions from the module file, ++ the addition of framework metadata, and signing the resulting framework), ++ and creating the ``.fwork`` and ``.origin`` files. This will usually be done ++ with a build step in the Xcode project; see the iOS documentation for ++ details on how to construct this build step. + -+ if test "$ac_sys_system" = "SunOS"; then -+ # For Solaris, there isn't an OS version specific macro defined -+ # in most compilers, so we define one here. -+ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` ++ .. versionadded:: 3.13 + -+cat >>confdefs.h <<_ACEOF -+#define Py_SUNOS_VERSION $SUNOS_VERSION -+_ACEOF ++ .. availability:: iOS. + -+ fi -+fi -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 -+$as_echo "\"$MACHDEP\"" >&6; } ++ .. attribute:: name + -+# On cross-compile builds, configure will look for a host-specific compiler by -+# prepending the user-provided host triple to the required binary name. -+# -+# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", -+# which isn't a binary that exists, and isn't very convenient, as it contains the -+# iOS version. As the default cross-compiler name won't exist, configure falls -+# back to gcc, which *definitely* won't work. We're providing wrapper scripts for -+# these tools; the binary names of these scripts are better defaults than "gcc". -+# This only requires that the user put the platform scripts folder (e.g., -+# "iOS/Resources/bin") in their path, rather than defining platform-specific -+# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to -+# either put the platform scripts folder in the path, or specify CC etc, -+# configure will fail. -+if test -z "$AR"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; -+ aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; -+ x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; ++ Name of the module the loader supports. + -+ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;; -+ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;; -+ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;; ++ .. attribute:: path + -+ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;; -+ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; -+ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; -+ *) -+ esac -+fi -+if test -z "$CC"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; -+ aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; -+ x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++ Path to the ``.fwork`` file for the extension module. + -+ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;; -+ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;; -+ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;; + -+ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;; -+ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;; -+ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;; -+ *) -+ esac -+fi -+if test -z "$CPP"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; -+ aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; -+ x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; + :mod:`importlib.util` -- Utility code for importers + --------------------------------------------------- + +diff --git a/Doc/library/intro.rst b/Doc/library/intro.rst +index 5a4c9b8b16a..ffc8939d211 100644 +--- a/Doc/library/intro.rst ++++ b/Doc/library/intro.rst +@@ -58,7 +58,7 @@ + operating system. + + * If not separately noted, all functions that claim "Availability: Unix" are +- supported on macOS, which builds on a Unix core. ++ supported on macOS and iOS, both of which build on a Unix core. + + * If an availability note contains both a minimum Kernel version and a minimum + libc version, then both conditions must hold. For example a feature with note +@@ -119,3 +119,44 @@ + .. _wasmtime: https://wasmtime.dev/ + .. _Pyodide: https://pyodide.org/ + .. _PyScript: https://pyscript.net/ + -+ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;; -+ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;; -+ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;; ++.. _iOS-availability: + -+ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;; -+ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;; -+ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;; -+ *) -+ esac -+fi -+if test -z "$CXX"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; -+ aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; -+ x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; ++iOS ++--- ++ ++iOS is, in most respects, a POSIX operating system. File I/O, socket handling, ++and threading all behave as they would on any POSIX operating system. However, ++there are several major differences between iOS and other POSIX systems. ++ ++* iOS can only use Python in "embedded" mode. There is no Python REPL, and no ++ ability to execute binaries that are part of the normal Python developer ++ experience, such as :program:`pip`. To add Python code to your iOS app, you must use ++ the :ref:`Python embedding API ` to add a Python interpreter to an ++ iOS app created with Xcode. See the :ref:`iOS usage guide ` for ++ more details. ++ ++* An iOS app cannot use any form of subprocessing, background processing, or ++ inter-process communication. If an iOS app attempts to create a subprocess, ++ the process creating the subprocess will either lock up, or crash. An iOS app ++ has no visibility of other applications that are running, nor any ability to ++ communicate with other running applications, outside of the iOS-specific APIs ++ that exist for this purpose. ++ ++* iOS apps have limited access to modify system resources (such as the system ++ clock). These resources will often be *readable*, but attempts to modify ++ those resources will usually fail. ++ ++* iOS apps have a limited concept of console input and output. ``stdout`` and ++ ``stderr`` *exist*, and content written to ``stdout`` and ``stderr`` will be ++ visible in logs when running in Xcode, but this content *won't* be recorded ++ in the system log. If a user who has installed your app provides their app ++ logs as a diagnostic aid, they will not include any detail written to ++ ``stdout`` or ``stderr``. ++ ++ iOS apps have no concept of ``stdin`` at all. While iOS apps can have a ++ keyboard, this is a software feature, not something that is attached to ++ ``stdin``. ++ ++ As a result, Python library that involve console manipulation (such as ++ :mod:`curses` and :mod:`readline`) are not available on iOS. +diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst +index ffd690148f1..4173d76d4fb 100644 +--- a/Doc/library/multiprocessing.rst ++++ b/Doc/library/multiprocessing.rst +@@ -8,7 +8,7 @@ + + -------------- + +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst + + Introduction + ------------ +diff --git a/Doc/library/os.rst b/Doc/library/os.rst +index 1af9e79db57..6cd6fb018ff 100644 +--- a/Doc/library/os.rst ++++ b/Doc/library/os.rst +@@ -34,12 +34,13 @@ + + * On VxWorks, os.popen, os.fork, os.execv and os.spawn*p* are not supported. + +-* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, large +- parts of the :mod:`os` module are not available or behave differently. API +- related to processes (e.g. :func:`~os.fork`, :func:`~os.execve`), signals +- (e.g. :func:`~os.kill`, :func:`~os.wait`), and resources +- (e.g. :func:`~os.nice`) are not available. Others like :func:`~os.getuid` +- and :func:`~os.getpid` are emulated or stubs. ++* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, and on ++ iOS, large parts of the :mod:`os` module are not available or behave ++ differently. API related to processes (e.g. :func:`~os.fork`, ++ :func:`~os.execve`) and resources (e.g. :func:`~os.nice`) are not available. ++ Others like :func:`~os.getuid` and :func:`~os.getpid` are emulated or stubs. ++ WebAssembly platforms also lack support for signals (e.g. :func:`~os.kill`, ++ :func:`~os.wait`). + + + .. note:: +@@ -735,6 +736,11 @@ + :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). :func:`platform.uname()` ++ can be used to get the user-facing operating system name and version on iOS and ++ Android. + -+ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;; -+ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;; -+ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;; + .. availability:: Unix. + + .. versionchanged:: 3.3 +@@ -3768,7 +3774,7 @@ + + .. audit-event:: os.exec path,args,env os.execl + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionchanged:: 3.3 + Added support for specifying *path* as an open file descriptor +@@ -3948,7 +3954,16 @@ + + See :mod:`ssl` for applications that use the SSL module with fork(). + +- .. availability:: Unix, not Emscripten, not WASI. ++ Users of macOS or users of libc or malloc implementations other ++ than those typically found in glibc to date are among those ++ already more likely to experience deadlocks running such code. + -+ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;; -+ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;; -+ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;; -+ *) -+ esac -+fi ++ See `this discussion on fork being incompatible with threads ++ `_ ++ for technical details of why we're surfacing this longstanding ++ platform compatibility problem to developers. + - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --enable-universalsdk" >&5 - $as_echo_n "checking for --enable-universalsdk... " >&6; } - # Check whether --enable-universalsdk was given. -@@ -3677,111 +3839,195 @@ - enableval=$enable_framework; - case $enableval in - yes) -- enableval=/Library/Frameworks -+ case $ac_sys_system in -+ Darwin) enableval=/Library/Frameworks ;; -+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; -+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;; -+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;; -+ *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 -+ esac - esac ++ .. availability:: POSIX, not Emscripten, not WASI, not iOS. + + + .. function:: forkpty() +@@ -3970,7 +3985,12 @@ + Calling ``forkpty()`` in a subinterpreter is no longer supported + (:exc:`RuntimeError` is raised). + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. versionchanged:: 3.12 ++ If Python is able to detect that your process has multiple ++ threads, this now raises a :exc:`DeprecationWarning`. See the ++ longer explanation on :func:`os.fork`. + - case $enableval in - no) -- PYTHONFRAMEWORK= -- PYTHONFRAMEWORKDIR=no-framework -- PYTHONFRAMEWORKPREFIX= -- PYTHONFRAMEWORKINSTALLDIR= -- FRAMEWORKINSTALLFIRST= -- FRAMEWORKINSTALLLAST= -- FRAMEWORKALTINSTALLFIRST= -- FRAMEWORKALTINSTALLLAST= -- FRAMEWORKPYTHONW= -- if test "x${prefix}" = "xNONE"; then -- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -- else -- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -- fi -- enable_framework= -+ case $ac_sys_system in -+ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; -+ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; -+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; -+ *) -+ PYTHONFRAMEWORK= -+ PYTHONFRAMEWORKDIR=no-framework -+ PYTHONFRAMEWORKPREFIX= -+ PYTHONFRAMEWORKINSTALLDIR= -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX= -+ RESSRCDIR= -+ FRAMEWORKINSTALLFIRST= -+ FRAMEWORKINSTALLLAST= -+ FRAMEWORKALTINSTALLFIRST= -+ FRAMEWORKALTINSTALLLAST= -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="commoninstall bininstall maninstall" ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: kill(pid, sig, /) +@@ -3994,7 +4014,7 @@ + + .. audit-event:: os.kill pid,sig os.kill + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionchanged:: 3.2 + Added Windows support. +@@ -4010,7 +4030,7 @@ + + .. audit-event:: os.killpg pgid,sig os.killpg + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: nice(increment, /) +@@ -4038,7 +4058,7 @@ + Lock program segments into memory. The value of *op* (defined in + ````) determines which segments are locked. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: popen(cmd, mode='r', buffering=-1) +@@ -4070,7 +4090,7 @@ + documentation for more powerful ways to manage and communicate with + subprocesses. + +- .. availability:: not Emscripten, not WASI. ++ .. availability:: not Emscripten, not WASI, not iOS. + + .. note:: + The :ref:`Python UTF-8 Mode ` affects encodings used +@@ -4165,7 +4185,7 @@ + + .. versionadded:: 3.8 + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ + setpgroup=None, resetids=False, setsid=False, setsigmask=(), \ +@@ -4181,7 +4201,7 @@ + + .. versionadded:: 3.8 + +- .. availability:: POSIX, not Emscripten, not WASI. ++ .. availability:: POSIX, not Emscripten, not WASI, not iOS. + + See :func:`posix_spawn` documentation. + +@@ -4214,7 +4234,7 @@ + + There is no way to unregister a function. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.7 + +@@ -4283,7 +4303,7 @@ + + .. audit-event:: os.spawn mode,path,args,env os.spawnl + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + :func:`spawnlp`, :func:`spawnlpe`, :func:`spawnvp` + and :func:`spawnvpe` are not available on Windows. :func:`spawnle` and +@@ -4407,7 +4427,7 @@ + + .. audit-event:: os.system command os.system + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + + .. function:: times() +@@ -4451,7 +4471,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exit code. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. seealso:: + +@@ -4485,7 +4505,10 @@ + Otherwise, if there are no matching children + that could be waited for, :exc:`ChildProcessError` is raised. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + -+ if test "x${prefix}" = "xNONE"; then -+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -+ else -+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -+ fi -+ enable_framework= -+ esac - ;; - *) - PYTHONFRAMEWORKPREFIX="${enableval}" - PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR -- FRAMEWORKINSTALLFIRST="frameworkinstallstructure" -- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " -- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" -- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" -- FRAMEWORKPYTHONW="frameworkpythonw" -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- -- if test "x${prefix}" = "xNONE" ; then -- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -- -- else -- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -- fi ++ .. note:: ++ This function is not available on macOS. + + .. note:: + This function is not available on macOS. +@@ -4526,7 +4549,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exit code. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionchanged:: 3.5 + If the system call is interrupted and the signal handler does not raise an +@@ -4546,7 +4569,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exitcode. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: wait4(pid, options) +@@ -4560,7 +4583,7 @@ + :func:`waitstatus_to_exitcode` can be used to convert the exit status into an + exitcode. -- case "${enableval}" in -- /System*) -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- if test "${prefix}" = "NONE" ; then -- # See below -- FRAMEWORKUNIXTOOLSPREFIX="/usr" -- fi -- ;; -+ case $ac_sys_system in #( -+ Darwin) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" -+ FRAMEWORKPYTHONW="frameworkpythonw" -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ INSTALLTARGETS="commoninstall bininstall maninstall" -+ -+ if test "x${prefix}" = "xNONE" ; then -+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -+ -+ else -+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -+ fi +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. -- /Library*) -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- ;; -+ case "${enableval}" in -+ /System*) -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ if test "${prefix}" = "NONE" ; then -+ # See below -+ FRAMEWORKUNIXTOOLSPREFIX="/usr" -+ fi -+ ;; -+ -+ /Library*) -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ ;; -+ -+ */Library/Frameworks) -+ MDIR="`dirname "${enableval}"`" -+ MDIR="`dirname "${MDIR}"`" -+ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" -+ -+ if test "${prefix}" = "NONE"; then -+ # User hasn't specified the -+ # --prefix option, but wants to install -+ # the framework in a non-default location, -+ # ensure that the compatibility links get -+ # installed relative to that prefix as well -+ # instead of in /usr/local. -+ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" -+ fi -+ ;; -- */Library/Frameworks) -- MDIR="`dirname "${enableval}"`" -- MDIR="`dirname "${MDIR}"`" -- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" + .. data:: P_PID +@@ -4577,7 +4600,7 @@ + * :data:`!P_PIDFD` - wait for the child identified by the file descriptor + *id* (a process file descriptor created with :func:`pidfd_open`). + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. note:: :data:`!P_PIDFD` is only available on Linux >= 5.4. + +@@ -4592,7 +4615,7 @@ + :func:`waitid` causes child processes to be reported if they have been + continued from a job control stop since they were last reported. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: WEXITED +@@ -4603,7 +4626,7 @@ + The other ``wait*`` functions always report children that have terminated, + so this option is not available for them. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.3 + +@@ -4615,7 +4638,7 @@ + + This option is not available for the other ``wait*`` functions. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.3 + +@@ -4628,7 +4651,7 @@ + + This option is not available for :func:`waitid`. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: WNOHANG +@@ -4637,7 +4660,7 @@ + :func:`waitid` to return right away if no child process status is available + immediately. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: WNOWAIT +@@ -4647,7 +4670,7 @@ + + This option is not available for the other ``wait*`` functions. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. data:: CLD_EXITED +@@ -4660,7 +4683,7 @@ + These are the possible values for :attr:`!si_code` in the result returned by + :func:`waitid`. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.3 + +@@ -4695,7 +4718,7 @@ + :func:`WIFEXITED`, :func:`WEXITSTATUS`, :func:`WIFSIGNALED`, + :func:`WTERMSIG`, :func:`WIFSTOPPED`, :func:`WSTOPSIG` functions. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS. + + .. versionadded:: 3.9 + +@@ -4711,7 +4734,7 @@ + + This function should be employed only if :func:`WIFSIGNALED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WIFCONTINUED(status) +@@ -4722,7 +4745,7 @@ + + See :data:`WCONTINUED` option. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WIFSTOPPED(status) +@@ -4734,14 +4757,14 @@ + done using :data:`WUNTRACED` option or when the process is being traced (see + :manpage:`ptrace(2)`). + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + .. function:: WIFSIGNALED(status) + + Return ``True`` if the process was terminated by a signal, otherwise return + ``False``. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WIFEXITED(status) +@@ -4750,7 +4773,7 @@ + by calling ``exit()`` or ``_exit()``, or by returning from ``main()``; + otherwise return ``False``. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WEXITSTATUS(status) +@@ -4759,7 +4782,7 @@ + + This function should be employed only if :func:`WIFEXITED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WSTOPSIG(status) +@@ -4768,7 +4791,7 @@ + + This function should be employed only if :func:`WIFSTOPPED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + .. function:: WTERMSIG(status) +@@ -4777,7 +4800,7 @@ + + This function should be employed only if :func:`WIFSIGNALED` is true. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not Emscripten, not WASI, not iOS. + + + Interface to the scheduler +diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst +index 60cd6b88084..316b693eece 100644 +--- a/Doc/library/platform.rst ++++ b/Doc/library/platform.rst +@@ -148,6 +148,9 @@ + 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 ++ ``'Linux'``), use :func:`os.uname()`. + + .. function:: system_alias(system, release, version) + +@@ -161,6 +164,8 @@ + 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 ++ Darwin or Linux kernel version, use :func:`os.uname()`. + + .. function:: uname() + +@@ -230,7 +235,6 @@ + macOS Platform + -------------- + - -- if test "${prefix}" = "NONE"; then -- # User hasn't specified the -- # --prefix option, but wants to install -- # the framework in a non-default location, -- # ensure that the compatibility links get -- # installed relative to that prefix as well -- # instead of in /usr/local. -- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" -- fi -- ;; -+ *) -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ ;; -+ esac + .. function:: mac_ver(release='', versioninfo=('','',''), machine='') -- *) -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- ;; -+ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} -+ RESSRCDIR=Mac/Resources/framework -+ -+ # Add files for Mac specific code to the list of output -+ # files: -+ ac_config_files="$ac_config_files Mac/Makefile" -+ -+ ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" -+ -+ ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" -+ -+ ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" -+ -+ ;; -+ iOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" -+ -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=iOS/Resources + Get macOS version information and return it as tuple ``(release, versioninfo, +@@ -240,6 +244,24 @@ + Entries which cannot be determined are set to ``''``. All tuple entries are + strings. + ++iOS Platform ++------------ + -+ ac_config_files="$ac_config_files iOS/Resources/Info.plist" ++.. function:: ios_ver(system='', release='', model='', is_simulator=False) + -+ ;; -+ tvOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ Get iOS version information and return it as a ++ :func:`~collections.namedtuple` with the following attributes: + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=tvOS/Resources ++ * ``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. ++ * ``is_simulator`` is a boolean describing if the app is running on a ++ simulator or a physical device. + -+ ac_config_files="$ac_config_files tvOS/Resources/Info.plist" ++ Entries which cannot be determined are set to the defaults given as ++ parameters. + -+ ;; -+ watchOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" + + Unix Platforms + -------------- +diff --git a/Doc/library/pwd.rst b/Doc/library/pwd.rst +index 755f0d29ac7..b6d212e93b5 100644 +--- a/Doc/library/pwd.rst ++++ b/Doc/library/pwd.rst +@@ -10,7 +10,7 @@ + This module provides access to the Unix user account and password database. It + is available on all Unix versions. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI, not iOS. + + Password database entries are reported as a tuple-like object, whose attributes + correspond to the members of the ``passwd`` structure (Attribute field below, +diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst +index 3fb5ceef086..758b89b4612 100644 +--- a/Doc/library/readline.rst ++++ b/Doc/library/readline.rst +@@ -24,6 +24,8 @@ + allowable constructs of that file, and the capabilities of the + Readline library in general. + ++.. include:: ../includes/wasm-ios-notavail.rst + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=watchOS/Resources + .. note:: + + The underlying Readline library API may be implemented by +diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst +index 389a63f089d..4fea8d5cb71 100644 +--- a/Doc/library/resource.rst ++++ b/Doc/library/resource.rst +@@ -13,7 +13,7 @@ + This module provides basic mechanisms for measuring and controlling system + resources utilized by a program. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI. + + Symbolic constants are used to specify particular system resources and to + request usage information about either the current process or its children. +diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst +index 85a073aad23..05ef45c123b 100644 +--- a/Doc/library/signal.rst ++++ b/Doc/library/signal.rst +@@ -26,9 +26,9 @@ + underlying implementation), with the exception of the handler for + :const:`SIGCHLD`, which follows the underlying implementation. + +-On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, signals +-are emulated and therefore behave differently. Several functions and signals +-are not available on these platforms. ++On WebAssembly platforms, signals are emulated and therefore behave ++differently. Several functions and signals are not available on these ++platforms. + + Execution of Python signal handlers + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst +index 3a5368717ac..de47e1f6e5f 100644 +--- a/Doc/library/socket.rst ++++ b/Doc/library/socket.rst +@@ -1109,7 +1109,7 @@ + buffer. Raises :exc:`OverflowError` if *length* is outside the + permissible range of values. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not WASI. + + Most Unix platforms. + +@@ -1132,7 +1132,7 @@ + amount of ancillary data that can be received, since additional + data may be able to fit into the padding area. + +- .. availability:: Unix, not Emscripten, not WASI. ++ .. availability:: Unix, not WASI. + + most Unix platforms. + +@@ -1172,7 +1172,7 @@ + (index int, name string) tuples. + :exc:`OSError` if the system call fails. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + .. versionadded:: 3.3 + +@@ -1199,7 +1199,7 @@ + interface name. + :exc:`OSError` if no interface with the given name exists. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + .. versionadded:: 3.3 + +@@ -1216,7 +1216,7 @@ + interface index number. + :exc:`OSError` if no interface with the given index exists. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + .. versionadded:: 3.3 + +@@ -1233,7 +1233,7 @@ + The *fds* parameter is a sequence of file descriptors. + Consult :meth:`~socket.sendmsg` for the documentation of these parameters. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + Unix platforms supporting :meth:`~socket.sendmsg` + and :const:`SCM_RIGHTS` mechanism. +@@ -1247,7 +1247,7 @@ + Return ``(msg, list(fds), flags, addr)``. + Consult :meth:`~socket.recvmsg` for the documentation of these parameters. + +- .. availability:: Unix, Windows, not Emscripten, not WASI. ++ .. availability:: Unix, Windows, not WASI. + + Unix platforms supporting :meth:`~socket.sendmsg` + and :const:`SCM_RIGHTS` mechanism. +diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst +index c0ae9e5d7aa..7009a7e8314 100644 +--- a/Doc/library/subprocess.rst ++++ b/Doc/library/subprocess.rst +@@ -25,7 +25,7 @@ + + :pep:`324` -- PEP proposing the subprocess module + +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst + + Using the :mod:`subprocess` Module + ---------------------------------- +diff --git a/Doc/library/syslog.rst b/Doc/library/syslog.rst +index 889bbb39d58..6c7c0907143 100644 +--- a/Doc/library/syslog.rst ++++ b/Doc/library/syslog.rst +@@ -11,7 +11,7 @@ + Refer to the Unix manual pages for a detailed description of the ``syslog`` + facility. + +-.. availability:: Unix, not Emscripten, not WASI. ++.. availability:: Unix, not WASI, not iOS. + + This module wraps the system ``syslog`` family of routines. A pure Python + library that can speak to a syslog server is available in the +diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst +index 9f90418bc96..c198c5798e9 100644 +--- a/Doc/library/urllib.parse.rst ++++ b/Doc/library/urllib.parse.rst +@@ -22,11 +22,19 @@ + + The module has been designed to match the internet RFC on Relative Uniform + Resource Locators. It supports the following URL schemes: ``file``, ``ftp``, +-``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``mailto``, ``mms``, ++``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``itms-services``, ``mailto``, ``mms``, + ``news``, ``nntp``, ``prospero``, ``rsync``, ``rtsp``, ``rtsps``, ``rtspu``, + ``sftp``, ``shttp``, ``sip``, ``sips``, ``snews``, ``svn``, ``svn+ssh``, + ``telnet``, ``wais``, ``ws``, ``wss``. + ++.. impl-detail:: + -+ ac_config_files="$ac_config_files watchOS/Resources/Info.plist" ++ The inclusion of the ``itms-services`` URL scheme can prevent an app from ++ passing Apple's App Store review process for the macOS and iOS App Stores. ++ Handling for the ``itms-services`` scheme is always removed on iOS; on ++ macOS, it *may* be removed if CPython has been built with the ++ :option:`--with-app-store-compliance` option. + -+ ;; -+ *) -+ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 -+ ;; -+ esac - esac - -- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION -- -- # Add files for Mac specific code to the list of output -- # files: -- ac_config_files="$ac_config_files Mac/Makefile" -- -- ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" -- -- ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" -- -- ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" -+else + The :mod:`urllib.parse` module defines functions that fall into two broad + categories: URL parsing and URL quoting. These are covered in detail in + the following sections. +diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst +index ce6d093b741..822cead8050 100644 +--- a/Doc/library/venv.rst ++++ b/Doc/library/venv.rst +@@ -56,7 +56,7 @@ + `Python Packaging User Guide: Creating and using virtual environments + `__ -+ case $ac_sys_system in -+ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; -+ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; -+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; -+ *) -+ PYTHONFRAMEWORK= -+ PYTHONFRAMEWORKDIR=no-framework -+ PYTHONFRAMEWORKPREFIX= -+ PYTHONFRAMEWORKINSTALLDIR= -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX= -+ RESSRCDIR= -+ FRAMEWORKINSTALLFIRST= -+ FRAMEWORKINSTALLLAST= -+ FRAMEWORKALTINSTALLFIRST= -+ FRAMEWORKALTINSTALLLAST= -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="commoninstall bininstall maninstall" -+ if test "x${prefix}" = "xNONE" ; then -+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -+ else -+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -+ fi -+ enable_framework= - esac +-.. include:: ../includes/wasm-notavail.rst ++.. include:: ../includes/wasm-ios-notavail.rst --else -+fi + Creating virtual environments + ----------------------------- +diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst +index 734b6321e5a..b709e511afa 100644 +--- a/Doc/library/webbrowser.rst ++++ b/Doc/library/webbrowser.rst +@@ -33,6 +33,13 @@ + browsers are not available on Unix, the controlling process will launch a new + browser and wait. -- PYTHONFRAMEWORK= -- PYTHONFRAMEWORKDIR=no-framework -- PYTHONFRAMEWORKPREFIX= -- PYTHONFRAMEWORKINSTALLDIR= -- FRAMEWORKINSTALLFIRST= -- FRAMEWORKINSTALLLAST= -- FRAMEWORKALTINSTALLFIRST= -- FRAMEWORKALTINSTALLLAST= -- FRAMEWORKPYTHONW= -- if test "x${prefix}" = "xNONE" ; then -- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -- else -- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -- fi -- enable_framework= ++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; +@@ -157,6 +164,8 @@ + +------------------------+-----------------------------------------+-------+ + | ``'chromium-browser'`` | :class:`Chromium('chromium-browser')` | | + +------------------------+-----------------------------------------+-------+ ++| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) | +++------------------------+-----------------------------------------+-------+ + Notes: --fi +@@ -171,7 +180,11 @@ + Only on Windows platforms. + (3) +- Only on macOS platform. ++ Only on macOS. ++ ++(4) ++ Only on iOS. ++ + .. versionadded:: 3.3 + Support for Chrome/Chromium has been added. +@@ -179,6 +192,9 @@ + .. deprecated-removed:: 3.11 3.13 + :class:`MacOSX` is deprecated, use :class:`MacOSXOSAScript` instead. -@@ -3802,78 +4048,51 @@ - _ACEOF ++.. versionchanged:: 3.13 ++ Support for iOS has been added. ++ + Here are some simple examples:: + url = 'https://docs.python.org/' +diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py +index c71ad641d7c..638bee626ae 100644 +--- a/Doc/tools/extensions/pyspecific.py ++++ b/Doc/tools/extensions/pyspecific.py +@@ -139,7 +139,7 @@ + known_platforms = frozenset({ + "AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD", + "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", "Unix", "VxWorks", +- "WASI", "Windows", "macOS", ++ "WASI", "Windows", "macOS", "iOS", + # libc + "BSD libc", "glibc", "musl", + # POSIX platforms with pthreads +@@ -170,7 +170,7 @@ --# Set name for machine-dependent library files -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-app-store-compliance" >&5 -+$as_echo_n "checking for --with-app-store-compliance... " >&6; } + Example:: --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 --$as_echo_n "checking MACHDEP... " >&6; } --if test -z "$MACHDEP" --then -- # avoid using uname for cross builds -- if test "$cross_compiling" = yes; then -- # ac_sys_system and ac_sys_release are used for setting -- # a lot of different things including 'define_xopen_source' -- # in the case statement below. -- case "$host" in -- *-*-linux-android*) -- ac_sys_system=Linux-android -- ;; -- *-*-linux*) -- ac_sys_system=Linux -- ;; -- *-*-cygwin*) -- ac_sys_system=Cygwin -- ;; -- *-*-vxworks*) -- ac_sys_system=VxWorks -- ;; -- *-*-emscripten) -- ac_sys_system=Emscripten -- ;; -- *-*-wasi) -- ac_sys_system=WASI -- ;; -- *) -- # for now, limit cross builds to known configurations -- MACHDEP="unknown" -- as_fn_error $? "cross build not supported for $host" "$LINENO" 5 -- esac -- ac_sys_release= -- else -- ac_sys_system=`uname -s` -- if test "$ac_sys_system" = "AIX" \ -- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then -- ac_sys_release=`uname -v` -- else -- ac_sys_release=`uname -r` -- fi -- fi -- ac_md_system=`echo $ac_sys_system | -- tr -d '/ ' | tr '[A-Z]' '[a-z]'` -- ac_md_release=`echo $ac_sys_release | -- tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` -- MACHDEP="$ac_md_system$ac_md_release" -- -- case $MACHDEP in -- aix*) MACHDEP="aix";; -- linux*) MACHDEP="linux";; -- cygwin*) MACHDEP="cygwin";; -- darwin*) MACHDEP="darwin";; -- '') MACHDEP="unknown";; -+# Check whether --with-app_store_compliance was given. -+if test "${with_app_store_compliance+set}" = set; then : -+ withval=$with_app_store_compliance; -+ case "$withval" in -+ yes) -+ case $ac_sys_system in -+ Darwin|iOS|tvOS|watchOS) -+ # iOS/tvOS/watchOS is able to share the macOS patch -+ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" -+ ;; -+ *) as_fn_error $? "no default app store compliance patch available for $ac_sys_system" "$LINENO" 5 ;; -+ esac -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 -+$as_echo "applying default app store compliance patch" >&6; } -+ ;; -+ *) -+ APP_STORE_COMPLIANCE_PATCH="${withval}" -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying custom app store compliance patch" >&5 -+$as_echo "applying custom app store compliance patch" >&6; } -+ ;; - esac +- .. availability:: Windows, Linux >= 4.2, not Emscripten, not WASI ++ .. availability:: Windows, Linux >= 4.2, not WASI -- if test "$ac_sys_system" = "SunOS"; then -- # For Solaris, there isn't an OS version specific macro defined -- # in most compilers, so we define one here. -- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` -+else + Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not + parsed into separate tokens. +diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst +index e45a5a4791e..26ac341bc98 100644 +--- a/Doc/using/configure.rst ++++ b/Doc/using/configure.rst +@@ -527,7 +527,7 @@ + macOS Options + ------------- --cat >>confdefs.h <<_ACEOF --#define Py_SUNOS_VERSION $SUNOS_VERSION --_ACEOF -+ case $ac_sys_system in -+ iOS|tvOS|watchOS) -+ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch -+ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 -+$as_echo "applying default app store compliance patch" >&6; } -+ ;; -+ *) -+ # No default app compliance patching on any other platform -+ APP_STORE_COMPLIANCE_PATCH= -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not patching for app store compliance" >&5 -+$as_echo "not patching for app store compliance" >&6; } -+ ;; -+ esac +-See ``Mac/README.rst``. ++See :source:`Mac/README.rst`. -- fi - fi --{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 --$as_echo "\"$MACHDEP\"" >&6; } + .. option:: --enable-universalsdk + .. option:: --enable-universalsdk=SDKDIR +@@ -562,6 +562,31 @@ + Specify the name for the python framework on macOS only valid when + :option:`--enable-framework` is set (default: ``Python``). + ++.. option:: --with-app-store-compliance ++.. option:: --with-app-store-compliance=PATCH-FILE ++ ++ The Python standard library contains strings that are known to trigger ++ automated inspection tool errors when submitted for distribution by ++ the macOS and iOS App Stores. If enabled, this option will apply the list of ++ patches that are known to correct app store compliance. A custom patch ++ file can also be specified. This option is disabled by default. ++ ++ .. versionadded:: 3.13 ++ ++iOS Options ++----------- ++ ++See :source:`iOS/README.rst`. ++ ++.. option:: --enable-framework=INSTALLDIR ++ ++ Create a Python.framework. Unlike macOS, the *INSTALLDIR* argument ++ specifying the installation path is mandatory. + ++.. option:: --with-framework-name=FRAMEWORK ++ ++ Specify the name for the framework (default: ``Python``). + - - if test "$cross_compiling" = yes; then -@@ -3881,27 +4100,93 @@ - *-*-linux*) - case "$host_cpu" in - arm*) -- _host_cpu=arm -+ _host_ident=arm - ;; - *) -- _host_cpu=$host_cpu -+ _host_ident=$host_cpu - esac - ;; - *-*-cygwin*) -- _host_cpu= -+ _host_ident= -+ ;; -+ *-apple-ios*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} + Cross Compiling Options + ----------------------- +diff --git a/Doc/using/index.rst b/Doc/using/index.rst +index e1a3111f36a..f55a12f1ab8 100644 +--- a/Doc/using/index.rst ++++ b/Doc/using/index.rst +@@ -18,4 +18,5 @@ + configure.rst + windows.rst + mac.rst ++ ios.rst + editors.rst +--- /dev/null ++++ b/Doc/using/ios.rst +@@ -0,0 +1,359 @@ ++.. _using-ios: + -+ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking iOS deployment target" >&5 -+$as_echo_n "checking iOS deployment target... " >&6; } -+ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} -+ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0} -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $IPHONEOS_DEPLOYMENT_TARGET" >&5 -+$as_echo "$IPHONEOS_DEPLOYMENT_TARGET" >&6; } ++=================== ++Using Python on iOS ++=================== + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} -+ ;; -+ *) -+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} -+ ;; -+ esac -+ ;; -+ *-apple-tvos*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++:Authors: ++ Russell Keith-Magee (2024-03) + -+ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking tvOS deployment target" >&5 -+$as_echo_n "checking tvOS deployment target... " >&6; } -+ TVOS_DEPLOYMENT_TARGET=${_host_os:4} -+ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0} -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TVOS_DEPLOYMENT_TARGET" >&5 -+$as_echo "$TVOS_DEPLOYMENT_TARGET" >&6; } ++Python on iOS is unlike Python on desktop platforms. On a desktop platform, ++Python is generally installed as a system resource that can be used by any user ++of that computer. Users then interact with Python by running a :program:`python` ++executable and entering commands at an interactive prompt, or by running a ++Python script. + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device} -+ ;; -+ *) -+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device} -+ ;; -+ esac -+ ;; -+ *-apple-watchos*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++On iOS, there is no concept of installing as a system resource. The only unit ++of software distribution is an "app". There is also no console where you could ++run a :program:`python` executable, or interact with a Python REPL. + -+ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking watchOS deployment target" >&5 -+$as_echo_n "checking watchOS deployment target... " >&6; } -+ WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} -+ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5 -+$as_echo "$WATCHOS_DEPLOYMENT_TARGET" >&6; } ++As a result, the only way you can use Python on iOS is in embedded mode - that ++is, by writing a native iOS application, and embedding a Python interpreter ++using ``libPython``, and invoking Python code using the :ref:`Python embedding ++API `. The full Python interpreter, the standard library, and all ++your Python code is then packaged as a standalone bundle that can be ++distributed via the iOS App Store. + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device} -+ ;; -+ *) -+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device} -+ ;; -+ esac - ;; - *-*-vxworks*) -- _host_cpu=$host_cpu -+ _host_ident=$host_cpu - ;; - wasm32-*-* | wasm64-*-*) -- _host_cpu=$host_cpu -+ _host_ident=$host_cpu - ;; - *) - # for now, limit cross builds to known configurations - MACHDEP="unknown" - as_fn_error $? "cross build not supported for $host" "$LINENO" 5 - esac -- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" -+ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" - fi - - # Some systems cannot stand _XOPEN_SOURCE being defined at all; they -@@ -3968,6 +4253,13 @@ - define_xopen_source=no;; - Darwin/[12][0-9].*) - define_xopen_source=no;; -+ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. -+ iOS/*) -+ define_xopen_source=no;; -+ tvOS/*) -+ define_xopen_source=no;; -+ watchOS/*) -+ define_xopen_source=no;; - # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from - # defining NI_NUMERICHOST. - QNX/6.3.2) -@@ -4030,6 +4322,12 @@ - CONFIGURE_MACOSX_DEPLOYMENT_TARGET= - EXPORT_MACOSX_DEPLOYMENT_TARGET='#' - -+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / -+# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. ++If you're looking to experiment for the first time with writing an iOS app in ++Python, projects such as `BeeWare `__ and `Kivy ++`__ will provide a much more approachable user experience. ++These projects manage the complexities associated with getting an iOS project ++running, so you only need to deal with the Python code itself. + ++Python at runtime on iOS ++======================== + ++iOS version compatibility ++------------------------- + ++The minimum supported iOS version is specified at compile time, using the ++:option:`--host` option to ``configure``. By default, when compiled for iOS, ++Python will be compiled with a minimum supported iOS version of 13.0. To use a ++different miniumum iOS version, provide the version number as part of the ++:option:`!--host` argument - for example, ++``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 simulator build ++with a deployment target of 15.4. + - # checks for alternative programs - - # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just -@@ -4062,6 +4360,26 @@ - ;; - esac - -+case $ac_sys_system in #( -+ iOS) : ++Platform identification ++----------------------- + -+ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" -+ as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" -+ ;; #( -+ tvOS) : ++When executing on iOS, ``sys.platform`` will report as ``ios``. This value will ++be returned on an iPhone or iPad, regardless of whether the app is running on ++the simulator or a physical device. + -+ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" -+ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" -+ ;; #( -+ watchOS) : ++Information about the specific runtime environment, including the iOS version, ++device model, and whether the device is a simulator, can be obtained using ++:func:`platform.ios_ver`. :func:`platform.system` will report ``iOS`` or ++``iPadOS``, depending on the device. ++ ++:func:`os.uname` reports kernel-level details; it will report a name of ++``Darwin``. ++ ++Standard library availability ++----------------------------- ++ ++The Python standard library has some notable omissions and restrictions on ++iOS. See the :ref:`API availability guide for iOS ` for ++details. ++ ++Binary extension modules ++------------------------ ++ ++One notable difference about iOS as a platform is that App Store distribution ++imposes hard requirements on the packaging of an application. One of these ++requirements governs how binary extension modules are distributed. ++ ++The iOS App Store requires that *all* binary modules in an iOS app must be ++dynamic libraries, contained in a framework with appropriate metadata, stored ++in the ``Frameworks`` folder of the packaged app. There can be only a single ++binary per framework, and there can be no executable binary material outside ++the ``Frameworks`` folder. ++ ++This conflicts with the usual Python approach for distributing binaries, which ++allows a binary extension module to be loaded from any location on ++``sys.path``. To ensure compliance with App Store policies, an iOS project must ++post-process any Python packages, converting ``.so`` binary modules into ++individual standalone frameworks with appropriate metadata and signing. For ++details on how to perform this post-processing, see the guide for :ref:`adding ++Python to your project `. ++ ++To help Python discover binaries in their new location, the original ``.so`` ++file on ``sys.path`` is replaced with a ``.fwork`` file. This file is a text ++file containing the location of the framework binary, relative to the app ++bundle. To allow the framework to resolve back to the original location, the ++framework must contain a ``.origin`` file that contains the location of the ++``.fwork`` file, relative to the app bundle. ++ ++For example, consider the case of an import ``from foo.bar import _whiz``, ++where ``_whiz`` is implemented with the binary module ++``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location ++registered on ``sys.path``, relative to the application bundle. This module ++*must* be distributed as ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` ++(creating the framework name from the full import path of the module), with an ++``Info.plist`` file in the ``.framework`` directory identifying the binary as a ++framework. The ``foo.bar._whiz`` module would be represented in the original ++location with a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing ++the path ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also ++contain ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing ++the path to the ``.fwork`` file. ++ ++When running on iOS, the Python interpreter will install an ++:class:`~importlib.machinery.AppleFrameworkLoader` that is able to read and ++import ``.fwork`` files. Once imported, the ``__file__`` attribute of the ++binary module will report as the location of the ``.fwork`` file. However, the ++:class:`~importlib.machinery.ModuleSpec` for the loaded module will report the ++``origin`` as the location of the binary in the framework folder. ++ ++Compiler stub binaries ++---------------------- ++ ++Xcode doesn't expose explicit compilers for iOS; instead, it uses an ``xcrun`` ++script that resolves to a full compiler path (e.g., ``xcrun --sdk iphoneos ++clang`` to get the ``clang`` for an iPhone device). However, using this script ++poses two problems: + -+ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" -+ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" -+ ;; #( -+ *) : -+ ;; -+esac ++* The output of ``xcrun`` includes paths that are machine specific, resulting ++ in a sysconfig module that cannot be shared between users; and + - if test "$ac_sys_system" = "Darwin" - then - # Compiler selection on MacOSX is more complicated than -@@ -6170,7 +6488,42 @@ - #elif defined(__gnu_hurd__) - i386-gnu - #elif defined(__APPLE__) -+# include "TargetConditionals.h" -+# if TARGET_OS_IOS -+# if TARGET_OS_SIMULATOR -+# if __x86_64__ -+ x86_64-iphonesimulator -+# else -+ arm64-iphonesimulator -+# endif -+# else -+ arm64-iphoneos -+# endif -+# elif TARGET_OS_TV -+# if TARGET_OS_SIMULATOR -+# if __x86_64__ -+ x86_64-appletvsimulator -+# else -+ arm64-appletvsimulator -+# endif -+# else -+ arm64-appletvos -+# endif -+# elif TARGET_OS_WATCH -+# if TARGET_OS_SIMULATOR -+# if __x86_64__ -+ x86_64-watchsimulator -+# else -+ arm64-watchsimulator -+# endif -+# else -+ arm64_32-watchos -+# endif -+# elif TARGET_OS_OSX - darwin -+# else -+# error unknown Apple platform -+# endif - #elif defined(__VXWORKS__) - vxworks - #elif defined(__wasm32__) -@@ -6215,6 +6568,12 @@ - case $ac_sys_system in #( - Darwin*) : - MULTIARCH="" ;; #( -+ iOS) : -+ MULTIARCH="" ;; #( -+ tvOS) : -+ MULTIARCH="" ;; #( -+ watchOS) : -+ MULTIARCH="" ;; #( - FreeBSD*) : - MULTIARCH="" ;; #( - *) : -@@ -6222,8 +6581,6 @@ - ;; - esac - --{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 --$as_echo "$MULTIARCH" >&6; } - - if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then - if test x$PLATFORM_TRIPLET != x$MULTIARCH; then -@@ -6233,6 +6590,16 @@ - MULTIARCH=$PLATFORM_TRIPLET - fi - -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 -+$as_echo "$MULTIARCH" >&6; } ++* It results in ``CC``/``CPP``/``LD``/``AR`` definitions that include spaces. ++ There is a lot of C ecosystem tooling that assumes that you can split a ++ command line at the first space to get the path to the compiler executable; ++ this isn't the case when using ``xcrun``. + -+case $ac_sys_system in #( -+ iOS|tvOS|watchOS) : -+ SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( -+ *) : -+ SOABI_PLATFORM=$PLATFORM_TRIPLET -+ ;; -+esac - - if test x$MULTIARCH != x; then - MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" -@@ -6276,6 +6643,18 @@ - PY_SUPPORT_TIER=3 ;; #( - x86_64-*-freebsd*/clang) : - PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-ios*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-ios*/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-tvos*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-tvos*/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-watchos*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ arm64_32-apple-watchos*/clang) : -+ PY_SUPPORT_TIER=3 ;; #( - *) : - PY_SUPPORT_TIER=0 - ;; -@@ -6718,17 +7097,25 @@ - { $as_echo "$as_me:${as_lineno-$LINENO}: checking LDLIBRARY" >&5 - $as_echo_n "checking LDLIBRARY... " >&6; } - --# MacOSX framework builds need more magic. LDLIBRARY is the dynamic -+# Apple framework builds need more magic. LDLIBRARY is the dynamic - # library that we build, but we do not want to link against it (we - # will find it with a -framework option). For this reason there is an - # extra variable BLDLIBRARY against which Python and the extension - # modules are linked, BLDLIBRARY. This is normally the same as --# LDLIBRARY, but empty for MacOSX framework builds. -+# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, -+# but uses a non-versioned framework layout. - if test "$enable_framework" - then -- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' -- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} -+ case $ac_sys_system in -+ Darwin) -+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; -+ iOS|tvOS|watchOS) -+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; -+ *) -+ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; -+ esac - BLDLIBRARY='' -+ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} - else - BLDLIBRARY='$(LDLIBRARY)' - fi -@@ -6741,64 +7128,70 @@ - - case $ac_sys_system in - CYGWIN*) -- LDLIBRARY='libpython$(LDVERSION).dll.a' -- DLLLIBRARY='libpython$(LDVERSION).dll' -- ;; -+ LDLIBRARY='libpython$(LDVERSION).dll.a' -+ DLLLIBRARY='libpython$(LDVERSION).dll' -+ ;; - SunOS*) -- LDLIBRARY='libpython$(LDVERSION).so' -- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' -- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -- INSTSONAME="$LDLIBRARY".$SOVERSION -- if test "$with_pydebug" != yes -- then -- PY3LIBRARY=libpython3.so -- fi -- ;; -+ LDLIBRARY='libpython$(LDVERSION).so' -+ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' -+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -+ INSTSONAME="$LDLIBRARY".$SOVERSION -+ if test "$with_pydebug" != yes -+ then -+ PY3LIBRARY=libpython3.so -+ fi -+ ;; - Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) -- LDLIBRARY='libpython$(LDVERSION).so' -- BLDLIBRARY='-L. -lpython$(LDVERSION)' -- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -- INSTSONAME="$LDLIBRARY".$SOVERSION -- if test "$with_pydebug" != yes -- then -- PY3LIBRARY=libpython3.so -- fi -- ;; -+ LDLIBRARY='libpython$(LDVERSION).so' -+ BLDLIBRARY='-L. -lpython$(LDVERSION)' -+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -+ INSTSONAME="$LDLIBRARY".$SOVERSION -+ if test "$with_pydebug" != yes -+ then -+ PY3LIBRARY=libpython3.so -+ fi -+ ;; - hp*|HP*) -- case `uname -m` in -- ia64) -- LDLIBRARY='libpython$(LDVERSION).so' -- ;; -- *) -- LDLIBRARY='libpython$(LDVERSION).sl' -- ;; -- esac -- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' -- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} -- ;; -+ case `uname -m` in -+ ia64) -+ LDLIBRARY='libpython$(LDVERSION).so' -+ ;; -+ *) -+ LDLIBRARY='libpython$(LDVERSION).sl' -+ ;; -+ esac -+ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' -+ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} -+ ;; - Darwin*) -- LDLIBRARY='libpython$(LDVERSION).dylib' -- BLDLIBRARY='-L. -lpython$(LDVERSION)' -- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} -- ;; -+ LDLIBRARY='libpython$(LDVERSION).dylib' -+ BLDLIBRARY='-L. -lpython$(LDVERSION)' -+ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} -+ ;; -+ iOS|tvOS|watchOS) -+ LDLIBRARY='libpython$(LDVERSION).dylib' -+ ;; - AIX*) -- LDLIBRARY='libpython$(LDVERSION).so' -- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} -- ;; -+ LDLIBRARY='libpython$(LDVERSION).so' -+ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} -+ ;; - - esac - else # shared is disabled - PY_ENABLE_SHARED=0 - case $ac_sys_system in - CYGWIN*) -- BLDLIBRARY='$(LIBRARY)' -- LDLIBRARY='libpython$(LDVERSION).dll.a' -- ;; -+ BLDLIBRARY='$(LIBRARY)' -+ LDLIBRARY='libpython$(LDVERSION).dll.a' -+ ;; - esac - fi - -+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 -+$as_echo "$LDLIBRARY" >&6; } ++To avoid these problems, Python provided stubs for these tools. These stubs are ++shell script wrappers around the underingly ``xcrun`` tools, distributed in a ++``bin`` folder distributed alongside the compiled iOS framework. These scripts ++are relocatable, and will always resolve to the appropriate local system paths. ++By including these scripts in the bin folder that accompanies a framework, the ++contents of the ``sysconfig`` module becomes useful for end-users to compile ++their own modules. When compiling third-party Python modules for iOS, you ++should ensure these stub binaries are on your path. + - if test "$cross_compiling" = yes; then -- RUNSHARED= -+ RUNSHARED= - fi - - -@@ -6977,9 +7370,6 @@ - PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" - fi - --{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 --$as_echo "$LDLIBRARY" >&6; } -- - # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable - case $ac_sys_system/$ac_sys_emscripten_target in #( - Emscripten/browser*) : -@@ -7221,11 +7611,16 @@ - fi - - if test "$cross_compiling" = yes; then -- case "$READELF" in -- readelf|:) -- as_fn_error $? "readelf for the host is required for cross builds" "$LINENO" 5 -- ;; -- esac -+ case "$ac_sys_system" in -+ iOS|tvOS|watchOS) ;; -+ *) -+ case "$READELF" in -+ readelf|:) -+ as_fn_error $? "readelf for the host is required for cross builds" "$LINENO" 5 -+ ;; -+ esac -+ ;; -+ esac - fi - - -@@ -10951,6 +11346,11 @@ - BLDSHARED="$LDSHARED" - fi - ;; -+ iOS/*|tvOS/*|watchOS/*) -+ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' -+ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' -+ BLDSHARED="$LDSHARED" -+ ;; - Emscripten|WASI) - LDSHARED='$(CC) -shared' - LDCXXSHARED='$(CXX) -shared';; -@@ -11078,20 +11478,18 @@ - Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; - Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; - # -u libsys_s pulls in all symbols in libsys -- Darwin/*) -+ Darwin/*|iOS/*|tvOS/*|watchOS/*) - LINKFORSHARED="$extra_undefs -framework CoreFoundation" - - # Issue #18075: the default maximum stack size (8MBytes) is too - # small for the default recursion limit. Increase the stack size - # to ensure that tests don't crash -- stack_size="1000000" # 16 MB -- if test "$with_ubsan" = "yes" -- then -- # Undefined behavior sanitizer requires an even deeper stack -- stack_size="4000000" # 64 MB -- fi -- -- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" -+ stack_size="1000000" # 16 MB -+ if test "$with_ubsan" = "yes" -+ then -+ # Undefined behavior sanitizer requires an even deeper stack -+ stack_size="4000000" # 64 MB -+ fi - - - cat >>confdefs.h <<_ACEOF -@@ -11099,11 +11497,17 @@ - _ACEOF - - -- if test "$enable_framework" -- then -- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' -+ if test $ac_sys_system = "Darwin"; then -+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" ++Installing Python on iOS ++======================== + -+ if test "$enable_framework"; then -+ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' -+ fi -+ LINKFORSHARED="$LINKFORSHARED" -+ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then -+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' - fi -- LINKFORSHARED="$LINKFORSHARED";; -+ ;; - OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; - SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; - ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; -@@ -12179,7 +12583,7 @@ - fi - - --if test "$ac_sys_system" = "Darwin" -+if test "$ac_sys_system" = "Darwin" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" - then - case "$with_system_ffi" in - "") -@@ -12207,10 +12611,12 @@ - if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then - LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`" - else -- LIBFFI_INCLUDEDIR="" -+ LIBFFI_INCLUDEDIR="${LIBFFI_INCLUDEDIR}" - fi - - ++Tools for building iOS apps ++--------------------------- ++ ++Building for iOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode. This will ++require the use of the most (or second-most) recently released macOS version, ++as Apple does not maintain Xcode for older macOS versions. The Xcode Command ++Line Tools are not sufficient for iOS development; you need a *full* Xcode ++install. ++ ++If you want to run your code on the iOS simulator, you'll also need to install ++an iOS Simulator Platform. You should be prompted to select an iOS Simulator ++Platform when you first run Xcode. Alternatively, you can add an iOS Simulator ++Platform by selecting from the Platforms tab of the Xcode Settings panel. ++ ++.. _adding-ios: ++ ++Adding Python to an iOS project ++------------------------------- ++ ++Python can be added to any iOS project, using either Swift or Objective C. The ++following examples will use Objective C; if you are using Swift, you may find a ++library like `PythonKit `__ to be ++helpful. ++ ++To add Python to an iOS Xcode project: ++ ++1. Build or obtain a Python ``XCFramework``. See the instructions in ++ :source:`Apple/iOS/README.md` (in the CPython source distribution) for details on ++ how to build a Python ``XCFramework``. At a minimum, you will need a build ++ that supports ``arm64-apple-ios``, plus one of either ++ ``arm64-apple-ios-simulator`` or ``x86_64-apple-ios-simulator``. ++ ++2. Drag the ``XCframework`` into your iOS project. In the following ++ instructions, we'll assume you've dropped the ``XCframework`` into the root ++ of your project; however, you can use any other location that you want by ++ adjusting paths as needed. ++ ++3. Add your application code as a folder in your Xcode project. In the ++ following instructions, we'll assume that your user code is in a folder ++ named ``app`` in the root of your project; you can use any other location by ++ adjusting paths as needed. Ensure that this folder is associated with your ++ app target. ++ ++4. Select the app target by selecting the root node of your Xcode project, then ++ the target name in the sidebar that appears. ++ ++5. In the "General" settings, under "Frameworks, Libraries and Embedded ++ Content", add ``Python.xcframework``, with "Embed & Sign" selected. ++ ++6. In the "Build Settings" tab, modify the following: ++ ++ - Build Options ++ ++ * User Script Sandboxing: No ++ * Enable Testability: Yes ++ ++ - Search Paths ++ ++ * Framework Search Paths: ``$(PROJECT_DIR)`` ++ * Header Search Paths: ``"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"`` ++ ++ - Apple Clang - Warnings - All languages ++ ++ * Quoted Include In Framework Header: No ++ ++7. Add a build step that processes the Python standard library, and your own ++ Python binary dependencies. In the "Build Phases" tab, add a new "Run ++ Script" build step *before* the "Embed Frameworks" step, but *after* the ++ "Copy Bundle Resources" step. Name the step "Process Python libraries", ++ disable the "Based on dependency analysis" checkbox, and set the script ++ content to: ++ ++ .. code-block:: bash ++ ++ set -e ++ source $PROJECT_DIR/Python.xcframework/build/build_utils.sh ++ install_python Python.xcframework app ++ ++ If you have placed your XCframework somewhere other than the root of your ++ project, modify the path to the first argument. ++ ++8. Add Objective C code to initialize and use a Python interpreter in embedded ++ mode. You should ensure that: ++ ++ * UTF-8 mode (:c:member:`PyPreConfig.utf8_mode`) is *enabled*; ++ * Buffered stdio (:c:member:`PyConfig.buffered_stdio`) is *disabled*; ++ * Writing bytecode (:c:member:`PyConfig.write_bytecode`) is *disabled*; ++ * Signal handlers (:c:member:`PyConfig.install_signal_handlers`) are *enabled*; ++ * :envvar:`PYTHONHOME` for the interpreter is configured to point at the ++ ``python`` subfolder of your app's bundle; and ++ * The :envvar:`PYTHONPATH` for the interpreter includes: ++ ++ - the ``python/lib/python3.X`` subfolder of your app's bundle, ++ - the ``python/lib/python3.X/lib-dynload`` subfolder of your app's bundle, and ++ - the ``app`` subfolder of your app's bundle ++ ++ Your app's bundle location can be determined using ``[[NSBundle mainBundle] ++ resourcePath]``. ++ ++Steps 7 and 8 of these instructions assume that you have a single folder of ++pure Python application code, named ``app``. If you have third-party binary ++modules in your app, some additional steps will be required: ++ ++* You need to ensure that any folders containing third-party binaries are ++ either associated with the app target, or are explicitly copied as part of ++ step 7. Step 7 should also purge any binaries that are not appropriate for ++ the platform a specific build is targetting (i.e., delete any device binaries ++ if you're building app app targeting the simulator). ++ ++* If you're using a separate folder for third-party packages, ensure that ++ folder is added to the end of the call to ``install_python`` in step 7, and ++ as part of the :envvar:`PYTHONPATH` configuration in step 8. ++ ++* If any of the folders that contain third-party packages will contain ``.pth`` ++ files, you should add that folder as a *site directory* (using ++ :meth:`site.addsitedir`), rather than adding to :envvar:`PYTHONPATH` or ++ :attr:`sys.path` directly. ++ ++Testing a Python package ++------------------------ ++ ++The CPython source tree contains :source:`a testbed project ` that ++is used to run the CPython test suite on the iOS simulator. This testbed can also ++be used as a testbed project for running your Python library's test suite on iOS. ++ ++After building or obtaining an iOS XCFramework (see :source:`Apple/iOS/README.md` ++for details), create a clone of the Python iOS testbed project. If you used the ++``Apple`` build script to build the XCframework, you can run: ++ ++.. code-block:: bash ++ ++ $ python cross-build/iOS/testbed clone --app --app app-testbed ++ ++Or, if you've sourced your own XCframework, by running: ++ ++.. code-block:: bash ++ ++ $ python Apple/testbed clone --platform iOS --framework --app --app app-testbed ++ ++Any folders specified with the ``--app`` flag will be copied into the cloned ++testbed project. The resulting testbed will be created in the ``app-testbed`` ++folder. In this example, the ``module1`` and ``module2`` would be importable ++modules at runtime. If your project has additional dependencies, they can be ++installed into the ``app-testbed/Testbed/app_packages`` folder (using ``pip ++install --target app-testbed/Testbed/app_packages`` or similar). ++ ++You can then use the ``app-testbed`` folder to run the test suite for your app, ++For example, if ``module1.tests`` was the entry point to your test suite, you ++could run: ++ ++.. code-block:: bash ++ ++ $ python app-testbed run -- module1.tests ++ ++This is the equivalent of running ``python -m module1.tests`` on a desktop ++Python build. Any arguments after the ``--`` will be passed to the testbed as ++if they were arguments to ``python -m`` on a desktop machine. ++ ++You can also open the testbed project in Xcode by running: ++ ++.. code-block:: bash ++ ++ $ open app-testbed/iOSTestbed.xcodeproj ++ ++This will allow you to use the full Xcode suite of tools for debugging. ++ ++The arguments used to run the test suite are defined as part of the test plan. ++To modify the test plan, select the test plan node of the project tree (it ++should be the first child of the root node), and select the "Configurations" ++tab. Modify the "Arguments Passed On Launch" value to change the testing ++arguments. ++ ++The test plan also disables parallel testing, and specifies the use of the ++``Testbed.lldbinit`` file for providing configuration of the debugger. The ++default debugger configuration disables automatic breakpoints on the ++``SIGINT``, ``SIGUSR1``, ``SIGUSR2``, and ``SIGXFSZ`` signals. ++ ++App Store Compliance ++==================== ++ ++The only mechanism for distributing apps to third-party iOS devices is to ++submit the app to the iOS App Store; apps submitted for distribution must pass ++Apple's app review process. This process includes a set of automated validation ++rules that inspect the submitted application bundle for problematic code. There ++are some steps that must be taken to ensure that your app will be able to pass ++these validation steps. + ++Incompatible code in the standard library ++----------------------------------------- + - # Check for use of the system libmpdec library - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-system-libmpdec" >&5 - $as_echo_n "checking for --with-system-libmpdec... " >&6; } -@@ -15209,27 +15615,27 @@ - # checks for library functions - for ac_func in \ - accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ -- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ -+ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ - faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ -- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ -- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ -- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ -+ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ -+ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \ -+ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ - getpeername getpgid getpid getppid getpriority _getpty \ - getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ - getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ - lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ - mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ -- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ -+ pipe2 plock poll posix_fadvise posix_fallocate \ - pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ - pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ - rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ - sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ - sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ - setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ -- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ -+ setresuid setreuid setsid setuid setvbuf shutdown sigaction \ - sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ - sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ -- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ -+ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ - tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ - wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ - -@@ -15262,8 +15668,46 @@ ++The Python standard library contains some code that is known to violate these ++automated rules. While these violations appear to be false positives, Apple's ++review rules cannot be challenged; so, it is necessary to modify the Python ++standard library for an app to pass App Store review. ++ ++The Python source tree contains ++:source:`a patch file ` that will remove ++all code that is known to cause issues with the App Store review process. This ++patch is applied automatically when building for iOS. ++ ++Privacy manifests ++----------------- ++ ++In April 2025, Apple introduced a requirement for `certain third-party ++libraries to provide a Privacy Manifest ++`__. ++As a result, if you have a binary module that uses one of the affected ++libraries, you must provide an ``.xcprivacy`` file for that library. ++OpenSSL is one library affected by this requirement, but there are others. ++ ++If you produce a binary module named ``mymodule.so``, and use you the Xcode ++build script described in step 7 above, you can place a ``mymodule.xcprivacy`` ++file next to ``mymodule.so``, and the privacy manifest will be installed into ++the required location when the binary module is converted into a framework. +diff --git a/Doc/using/mac.rst b/Doc/using/mac.rst +index 986d693d03f..da45aabca28 100644 +--- a/Doc/using/mac.rst ++++ b/Doc/using/mac.rst +@@ -188,6 +188,28 @@ + * `PyInstaller `__: A cross-platform packaging tool that creates + a single file or folder as a distributable artifact. - fi ++App Store Compliance ++-------------------- ++ ++Apps submitted for distribution through the macOS App Store must pass Apple's ++app review process. This process includes a set of automated validation rules ++that inspect the submitted application bundle for problematic code. ++ ++The Python standard library contains some code that is known to violate these ++automated rules. While these violations appear to be false positives, Apple's ++review rules cannot be challenged. Therefore, it is necessary to modify the ++Python standard library for an app to pass App Store review. ++ ++The Python source tree contains ++:source:`a patch file ` that will remove ++all code that is known to cause issues with the App Store review process. This ++patch is applied automatically when CPython is configured with the ++:option:`--with-app-store-compliance` option. ++ ++This patch is not normally required to use CPython on a Mac; nor is it required ++if you are distributing an app *outside* the macOS App Store. It is *only* ++required if you are using the macOS App Store as a distribution channel. ++ + Other Resources + =============== -+# iOS/tvOS/watchOS define some system methods that can be linked (so they are -+# found by configure), but either raise a compilation error (because the -+# header definition prevents usage - autoconf doesn't use the headers), or -+# raise an error if used at runtime. Force these symbols off. -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ for ac_func in getentropy getgroups system -+do : -+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` -+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" -+if eval test \"x\$"$as_ac_var"\" = x"yes"; then : -+ cat >>confdefs.h <<_ACEOF -+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 -+_ACEOF +--- /dev/null ++++ b/Lib/_apple_support.py +@@ -0,0 +1,66 @@ ++import io ++import sys + -+fi -+done + -+fi ++def init_streams(log_write, stdout_level, stderr_level): ++ # Redirect stdout and stderr to the Apple system log. This method is ++ # invoked by init_apple_streams() (initconfig.c) if config->use_system_logger ++ # is enabled. ++ sys.stdout = SystemLog(log_write, stdout_level, errors=sys.stderr.errors) ++ sys.stderr = SystemLog(log_write, stderr_level, errors=sys.stderr.errors) + -+# tvOS/watchOS have some additional methods that can be found, but not used. -+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ for ac_func in \ -+ execv fork fork1 posix_spawn posix_spawnp \ -+ sigaltstack \ + -+do : -+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` -+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" -+if eval test \"x\$"$as_ac_var"\" = x"yes"; then : -+ cat >>confdefs.h <<_ACEOF -+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 -+_ACEOF ++class SystemLog(io.TextIOWrapper): ++ def __init__(self, log_write, level, **kwargs): ++ kwargs.setdefault("encoding", "UTF-8") ++ kwargs.setdefault("line_buffering", True) ++ super().__init__(LogStream(log_write, level), **kwargs) + -+fi -+done ++ def __repr__(self): ++ return f"" ++ ++ def write(self, s): ++ if not isinstance(s, str): ++ raise TypeError( ++ f"write() argument must be str, not {type(s).__name__}") ++ ++ # In case `s` is a str subclass that writes itself to stdout or stderr ++ # when we call its methods, convert it to an actual str. ++ s = str.__str__(s) ++ ++ # We want to emit one log message per line, so split ++ # the string before sending it to the superclass. ++ for line in s.splitlines(keepends=True): ++ super().write(line) ++ ++ return len(s) ++ ++ ++class LogStream(io.RawIOBase): ++ def __init__(self, log_write, level): ++ self.log_write = log_write ++ self.level = level ++ ++ def __repr__(self): ++ return f"" ++ ++ def writable(self): ++ return True ++ ++ def write(self, b): ++ if type(b) is not bytes: ++ try: ++ b = bytes(memoryview(b)) ++ except TypeError: ++ raise TypeError( ++ f"write() argument must be bytes-like, not {type(b).__name__}" ++ ) from None ++ ++ # Writing an empty string to the stream should have no effect. ++ if b: ++ # Encode null bytes using "modified UTF-8" to avoid truncating the ++ # message. This should not affect the return value, as the caller ++ # may be expecting it to match the length of the input. ++ self.log_write(self.level, b.replace(b"\x00", b"\xc0\x80")) ++ ++ return len(b) +--- /dev/null ++++ b/Lib/_ios_support.py +@@ -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") ++ 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/ctypes/__init__.py b/Lib/ctypes/__init__.py +index 26135ad9629..315c725e547 100644 +--- a/Lib/ctypes/__init__.py ++++ b/Lib/ctypes/__init__.py +@@ -343,6 +343,19 @@ + use_errno=False, + use_last_error=False, + winmode=None): ++ if name: ++ name = _os.fspath(name) + -+fi ++ # If the filename that has been provided is an iOS/tvOS/watchOS ++ # .fwork file, dereference the location to the true origin of the ++ # binary. ++ if name.endswith(".fwork"): ++ with open(name) as f: ++ name = _os.path.join( ++ _os.path.dirname(_sys.executable), ++ f.read().strip() ++ ) + - ac_fn_c_check_decl "$LINENO" "dirfd" "ac_cv_have_decl_dirfd" "#include -- #include -+ #include - " - if test "x$ac_cv_have_decl_dirfd" = xyes; then : - -@@ -17817,8 +18261,9 @@ - + self._name = name + flags = self._func_flags_ + if use_errno: +diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py +index c550883e7c7..12d7428fe9a 100644 +--- a/Lib/ctypes/util.py ++++ b/Lib/ctypes/util.py +@@ -67,7 +67,7 @@ + return fname + return None - # check for openpty, login_tty, and forkpty -- --for ac_func in openpty -+# tvOS/watchOS have functions for tty, but can't use them -+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ for ac_func in openpty - do : - ac_fn_c_check_func "$LINENO" "openpty" "ac_cv_func_openpty" - if test "x$ac_cv_func_openpty" = xyes; then : -@@ -17908,14 +18353,12 @@ - LIBS="$LIBS -lbsd" - fi +-elif os.name == "posix" and sys.platform == "darwin": ++elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}: + from ctypes.macholib.dyld import dyld_find as _dyld_find + def find_library(name): + possible = ['lib%s.dylib' % name, +diff --git a/Lib/distutils/tests/test_cygwinccompiler.py b/Lib/distutils/tests/test_cygwinccompiler.py +index 633d3041a1b..a91ee9e0f37 100644 +--- a/Lib/distutils/tests/test_cygwinccompiler.py ++++ b/Lib/distutils/tests/test_cygwinccompiler.py +@@ -4,6 +4,9 @@ + import os + from io import BytesIO -- - fi ++if sys.platform != 'win32': ++ raise unittest.SkipTest("Cygwin tests only needed on Windows") ++ + from distutils import cygwinccompiler + from distutils.cygwinccompiler import (check_config_h, + CONFIG_H_OK, CONFIG_H_NOTOK, +diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py +index 363834fe8b3..c67b7a130b5 100644 +--- a/Lib/distutils/tests/test_sysconfig.py ++++ b/Lib/distutils/tests/test_sysconfig.py +@@ -10,7 +10,7 @@ + from distutils import sysconfig + from distutils.ccompiler import get_default_compiler + from distutils.tests import support +-from test.support import swap_item, requires_subprocess, is_wasi ++from test.support import swap_item, requires_subprocess, is_apple_mobile, is_wasi + from test.support.os_helper import TESTFN + from test.support.warnings_helper import check_warnings -- - fi - done +@@ -33,6 +33,7 @@ + shutil.rmtree(TESTFN) --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5 - $as_echo_n "checking for library containing login_tty... " >&6; } - if ${ac_cv_search_login_tty+:} false; then : - $as_echo_n "(cached) " >&6 -@@ -17974,7 +18417,7 @@ + @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") ++ @unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile") + def test_get_config_h_filename(self): + config_h = sysconfig.get_config_h_filename() + self.assertTrue(os.path.isfile(config_h), config_h) +@@ -50,6 +51,7 @@ + self.assertTrue(cvars) - fi + @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") ++ @unittest.skipIf(is_apple_mobile, "Header files not distributed with Apple mobile") + def test_srcdir(self): + # See Issues #15322, #15364. + srcdir = sysconfig.get_config_var('srcdir') +diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py +index d00c48981eb..5d12b4779db 100644 +--- a/Lib/distutils/unixccompiler.py ++++ b/Lib/distutils/unixccompiler.py +@@ -270,9 +270,9 @@ + static_f = self.library_filename(lib, lib_type='static') --for ac_func in forkpty -+ for ac_func in forkpty - do : - ac_fn_c_check_func "$LINENO" "forkpty" "ac_cv_func_forkpty" - if test "x$ac_cv_func_forkpty" = xyes; then : -@@ -18064,13 +18507,12 @@ - LIBS="$LIBS -lbsd" - fi + if sys.platform == 'darwin': +- # On OSX users can specify an alternate SDK using +- # '-isysroot', calculate the SDK root if it is specified +- # (and use it further on) ++ # On macOS users can specify an alternate SDK using ++ # '-isysroot ' or --sysroot=, calculate the SDK root ++ # if it is specified (and use it further on) + # + # Note that, as of Xcode 7, Apple SDKs may contain textual stub + # libraries with .tbd extensions rather than the normal .dylib +@@ -291,12 +291,14 @@ + cflags = sysconfig.get_config_var('CFLAGS') + m = re.search(r'-isysroot\s*(\S+)', cflags) + if m is None: +- sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC')) ++ m = re.search(r'--sysroot=(\S+)', cflags) ++ if m is None: ++ sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC')) ++ else: ++ sysroot = m.group(1) + else: + sysroot = m.group(1) - - fi - - - fi - done - -+fi - - # check for long file support functions - for ac_func in fseek64 fseeko fstatvfs ftell64 ftello statvfs -@@ -18568,7 +19010,12 @@ - done - - --for ac_func in clock_settime -+# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by -+# configure), but when used in an unprivileged process, it crashes rather than -+# returning an error. Force the symbol off. -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -+then -+ for ac_func in clock_settime - do : - ac_fn_c_check_func "$LINENO" "clock_settime" "ac_cv_func_clock_settime" - if test "x$ac_cv_func_clock_settime" = xyes; then : -@@ -18578,7 +19025,7 @@ - - else - -- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 - $as_echo_n "checking for clock_settime in -lrt... " >&6; } - if ${ac_cv_lib_rt_clock_settime+:} false; then : - $as_echo_n "(cached) " >&6 -@@ -18616,7 +19063,7 @@ - $as_echo "$ac_cv_lib_rt_clock_settime" >&6; } - if test "x$ac_cv_lib_rt_clock_settime" = xyes; then : - -- $as_echo "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h -+ $as_echo "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h - + for dir in dirs: + shared = os.path.join(dir, shared_f) + dylib = os.path.join(dir, dylib_f) +diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py +index 2ce5c5b64d6..e927f4af938 100644 +--- a/Lib/distutils/util.py ++++ b/Lib/distutils/util.py +@@ -89,10 +89,25 @@ + if m: + release = m.group() + elif osname[:6] == "darwin": +- import _osx_support, distutils.sysconfig +- osname, release, machine = _osx_support.get_platform_osx( +- distutils.sysconfig.get_config_vars(), +- osname, release, machine) ++ import distutils.sysconfig ++ config_vars = distutils.sysconfig.get_config_vars() ++ if sys.platform == "ios": ++ release = config_vars.get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "tvos": ++ release = config_vars.get("TVOS_DEPLOYMENT_TARGET", "9.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "watchos": ++ release = config_vars.get("WATCHOS_DEPLOYMENT_TARGET", "4.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ else: ++ import _osx_support ++ osname, release, machine = _osx_support.get_platform_osx( ++ config_vars, ++ osname, release, machine) - fi -@@ -18625,6 +19072,7 @@ - fi - done + return "%s-%s-%s" % (osname, release, machine) -+fi +@@ -170,7 +185,7 @@ + if _environ_checked: + return - for ac_func in clock_nanosleep - do : -@@ -18838,7 +19286,9 @@ - else - if test "$cross_compiling" = yes; then : +- if os.name == 'posix' and 'HOME' not in os.environ: ++ if os.name == 'posix' and 'HOME' not in os.environ and sys.platform not in {"ios", "tvos", "watchos"}: + try: + import pwd + os.environ['HOME'] = pwd.getpwuid(os.getuid())[5] +diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py +index e53f6acf38f..64aa8444410 100644 +--- a/Lib/importlib/_bootstrap_external.py ++++ b/Lib/importlib/_bootstrap_external.py +@@ -52,7 +52,7 @@ --if test "${enable_ipv6+set}" = set; then -+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then -+ ac_cv_buggy_getaddrinfo="no" -+elif test "${enable_ipv6+set}" = set; then - ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" - else - ac_cv_buggy_getaddrinfo=yes -@@ -20762,7 +21212,7 @@ - $as_echo "$ABIFLAGS" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: checking SOABI" >&5 - $as_echo_n "checking SOABI... " >&6; } --SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} -+SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SOABI" >&5 - $as_echo "$SOABI" >&6; } + # Bootstrap-related code ###################################################### + _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', +-_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin' ++_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos' + _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY + + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) -@@ -20770,7 +21220,7 @@ - if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then - # Similar to SOABI but remove "d" flag from ABIFLAGS +@@ -1708,6 +1708,46 @@ + return 'FileFinder({!r})'.format(self.path) -- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} -+ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} - cat >>confdefs.h <<_ACEOF - #define ALT_SOABI "${ALT_SOABI}" -@@ -22152,24 +22602,28 @@ - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 - $as_echo "$as_me: checking for device files" >&6;} ++class AppleFrameworkLoader(ExtensionFileLoader): ++ """A loader for modules that have been packaged as frameworks for ++ compatibility with Apple's iOS App Store policies. ++ """ ++ def create_module(self, spec): ++ # If the ModuleSpec has been created by the FileFinder, it will have ++ # been created with an origin pointing to the .fwork file. We need to ++ # redirect this to the location in the Frameworks folder, using the ++ # content of the .fwork file. ++ if spec.origin.endswith(".fwork"): ++ with _io.FileIO(spec.origin, 'r') as file: ++ framework_binary = file.read().decode().strip() ++ bundle_path = _path_split(sys.executable)[0] ++ spec.origin = _path_join(bundle_path, framework_binary) ++ ++ # If the loader is created based on the spec for a loaded module, the ++ # path will be pointing at the Framework location. If this occurs, ++ # get the original .fwork location to use as the module's __file__. ++ if self.path.endswith(".fwork"): ++ path = self.path ++ else: ++ with _io.FileIO(self.path + ".origin", 'r') as file: ++ origin = file.read().decode().strip() ++ bundle_path = _path_split(sys.executable)[0] ++ path = _path_join(bundle_path, origin) ++ ++ module = _bootstrap._call_with_frames_removed(_imp.create_dynamic, spec) ++ ++ _bootstrap._verbose_message( ++ "Apple framework extension module {!r} loaded from {!r} (path {!r})", ++ spec.name, ++ spec.origin, ++ path, ++ ) ++ ++ # Ensure that the __file__ points at the .fwork location ++ module.__file__ = path ++ ++ return module ++ + # Import setup ############################################################### --if test "x$cross_compiling" = xyes; then -- if test "${ac_cv_file__dev_ptmx+set}" != set; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 -+if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then -+ ac_cv_file__dev_ptmx=no -+ ac_cv_file__dev_ptc=no -+else -+ if test "x$cross_compiling" = xyes; then -+ if test "${ac_cv_file__dev_ptmx+set}" != set; then -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 - $as_echo_n "checking for /dev/ptmx... " >&6; } -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 - $as_echo "not set" >&6; } -- as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 -- fi -- if test "${ac_cv_file__dev_ptc+set}" != set; then -- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 -+ as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 -+ fi -+ if test "${ac_cv_file__dev_ptc+set}" != set; then -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 - $as_echo_n "checking for /dev/ptc... " >&6; } -- { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 - $as_echo "not set" >&6; } -- as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 -+ as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 -+ fi - fi --fi + def _fix_up_module(ns, name, pathname, cpathname=None): +@@ -1738,10 +1778,17 @@ --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 - $as_echo_n "checking for /dev/ptmx... " >&6; } - if ${ac_cv_file__dev_ptmx+:} false; then : - $as_echo_n "(cached) " >&6 -@@ -22188,12 +22642,12 @@ + Each item is a tuple (loader, suffixes). + """ +- extensions = ExtensionFileLoader, _imp.extension_suffixes() ++ if sys.platform in {"ios", "tvos", "watchos"}: ++ extension_loaders = [(AppleFrameworkLoader, [ ++ suffix.replace(".so", ".fwork") ++ for suffix in _imp.extension_suffixes() ++ ])] ++ else: ++ extension_loaders = [] ++ extension_loaders.append((ExtensionFileLoader, _imp.extension_suffixes())) + source = SourceFileLoader, SOURCE_SUFFIXES + bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES +- return [extensions, source, bytecode] ++ return extension_loaders + [source, bytecode] - fi --if test "x$ac_cv_file__dev_ptmx" = xyes; then -+ if test "x$ac_cv_file__dev_ptmx" = xyes; then + def _set_bootstrap_module(_bootstrap_module): +diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py +index 3fa151f390b..47a6a5ed5a6 100644 +--- a/Lib/importlib/abc.py ++++ b/Lib/importlib/abc.py +@@ -261,7 +261,11 @@ + else: + return self.source_to_code(source, path) - $as_echo "#define HAVE_DEV_PTMX 1" >>confdefs.h +-_register(ExecutionLoader, machinery.ExtensionFileLoader) ++_register( ++ ExecutionLoader, ++ machinery.ExtensionFileLoader, ++ machinery.AppleFrameworkLoader, ++) --fi --{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 -+ fi -+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 - $as_echo_n "checking for /dev/ptc... " >&6; } - if ${ac_cv_file__dev_ptc+:} false; then : - $as_echo_n "(cached) " >&6 -@@ -22212,10 +22666,11 @@ - fi + class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader): +diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py +index d9a19a13f7b..fbd30b159fb 100644 +--- a/Lib/importlib/machinery.py ++++ b/Lib/importlib/machinery.py +@@ -12,6 +12,7 @@ + from ._bootstrap_external import SourceFileLoader + from ._bootstrap_external import SourcelessFileLoader + from ._bootstrap_external import ExtensionFileLoader ++from ._bootstrap_external import AppleFrameworkLoader + from ._bootstrap_external import NamespaceLoader --if test "x$ac_cv_file__dev_ptc" = xyes; then -+ if test "x$ac_cv_file__dev_ptc" = xyes; then - $as_echo "#define HAVE_DEV_PTC 1" >>confdefs.h +diff --git a/Lib/inspect.py b/Lib/inspect.py +index 09e6a23652f..0dbe90f470a 100644 +--- a/Lib/inspect.py ++++ b/Lib/inspect.py +@@ -972,6 +972,7 @@ + return object + if hasattr(object, '__module__'): + return sys.modules.get(object.__module__) ++ + # Try the filename to modulename cache + if _filename is not None and _filename in modulesbyfile: + return sys.modules.get(modulesbyfile[_filename]) +@@ -1065,7 +1066,7 @@ + # Allow filenames in form of "" to pass through. + # `doctest` monkeypatches `linecache` module to enable + # inspection, so let `linecache.getlines` to be called. +- if not (file.startswith('<') and file.endswith('>')): ++ if (not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'): + raise OSError('source code not available') -+ fi - fi + module = getmodule(object, file) +diff --git a/Lib/lib2to3/tests/test_parser.py b/Lib/lib2to3/tests/test_parser.py +index 8e7773bcae1..7df3e354f6d 100644 +--- a/Lib/lib2to3/tests/test_parser.py ++++ b/Lib/lib2to3/tests/test_parser.py +@@ -62,9 +62,7 @@ + shutil.rmtree(tmpdir) - if test $ac_sys_system = Darwin -@@ -22701,6 +23156,8 @@ - with_ensurepip=no ;; #( - WASI) : - with_ensurepip=no ;; #( -+ iOS|tvOS|watchOS) : -+ with_ensurepip=no ;; #( - *) : - with_ensurepip=upgrade - ;; -@@ -23604,6 +24061,27 @@ - py_cv_module_ossaudiodev=n/a - py_cv_module_spwd=n/a - ;; #( -+ iOS|tvOS|watchOS) : -+ -+ -+ -+ py_cv_module__curses=n/a -+ py_cv_module__curses_panel=n/a -+ py_cv_module__gdbm=n/a -+ py_cv_module__multiprocessing=n/a -+ py_cv_module__posixshmem=n/a -+ py_cv_module__posixsubprocess=n/a -+ py_cv_module__scproxy=n/a -+ py_cv_module__tkinter=n/a -+ py_cv_module_grp=n/a -+ py_cv_module_nis=n/a -+ py_cv_module_readline=n/a -+ py_cv_module_pwd=n/a -+ py_cv_module_spwd=n/a -+ py_cv_module_syslog=n/a -+ py_cv_module_=n/a -+ -+ ;; #( - CYGWIN*) : + @unittest.skipIf(sys.executable is None, 'sys.executable required') +- @unittest.skipIf( +- sys.platform in {'emscripten', 'wasi'}, 'requires working subprocess' +- ) ++ @test.support.requires_subprocess() + def test_load_grammar_from_subprocess(self): + tmpdir = tempfile.mkdtemp() + tmpsubdir = os.path.join(tmpdir, 'subdir') +diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py +index a0a020f9eeb..ac478ee7f51 100644 +--- a/Lib/modulefinder.py ++++ b/Lib/modulefinder.py +@@ -72,7 +72,12 @@ + if isinstance(spec.loader, importlib.machinery.SourceFileLoader): + kind = _PY_SOURCE +- elif isinstance(spec.loader, importlib.machinery.ExtensionFileLoader): ++ elif isinstance( ++ spec.loader, ( ++ importlib.machinery.ExtensionFileLoader, ++ importlib.machinery.AppleFrameworkLoader, ++ ) ++ ): + kind = _C_EXTENSION -@@ -26886,6 +27364,9 @@ - "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;; - "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; - "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; -+ "iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES iOS/Resources/Info.plist" ;; -+ "tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES tvOS/Resources/Info.plist" ;; -+ "watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES watchOS/Resources/Info.plist" ;; - "Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;; - "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; - "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; -diff --git a/configure.ac b/configure.ac -index 7b4000fa9c3..08c9405a465 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -303,6 +303,161 @@ - AC_MSG_ERROR([pkg-config is required])] - fi + elif isinstance(spec.loader, importlib.machinery.SourcelessFileLoader): +diff --git a/Lib/platform.py b/Lib/platform.py +index 9b9d88bf584..1bb5fcb96d6 100755 +--- a/Lib/platform.py ++++ b/Lib/platform.py +@@ -452,6 +452,78 @@ + # If that also doesn't work return the default values + return release, versioninfo, machine -+# Set name for machine-dependent library files -+AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) -+AC_MSG_CHECKING([MACHDEP]) -+if test -z "$MACHDEP" -+then -+ # avoid using uname for cross builds -+ if test "$cross_compiling" = yes; then -+ # ac_sys_system and ac_sys_release are used for setting -+ # a lot of different things including 'define_xopen_source' -+ # in the case statement below. -+ case "$host" in -+ *-*-linux-android*) -+ ac_sys_system=Linux-android -+ ;; -+ *-*-linux*) -+ ac_sys_system=Linux -+ ;; -+ *-*-cygwin*) -+ ac_sys_system=Cygwin -+ ;; -+ *-apple-ios*) -+ ac_sys_system=iOS -+ ;; -+ *-apple-tvos*) -+ ac_sys_system=tvOS -+ ;; -+ *-apple-watchos*) -+ ac_sys_system=watchOS -+ ;; -+ *-*-vxworks*) -+ ac_sys_system=VxWorks -+ ;; -+ *-*-emscripten) -+ ac_sys_system=Emscripten -+ ;; -+ *-*-wasi) -+ ac_sys_system=WASI -+ ;; -+ *) -+ # for now, limit cross builds to known configurations -+ MACHDEP="unknown" -+ AC_MSG_ERROR([cross build not supported for $host]) -+ esac -+ ac_sys_release= -+ else -+ ac_sys_system=`uname -s` -+ if test "$ac_sys_system" = "AIX" \ -+ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then -+ ac_sys_release=`uname -v` -+ else -+ ac_sys_release=`uname -r` -+ fi -+ fi -+ ac_md_system=`echo $ac_sys_system | -+ tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` -+ ac_md_release=`echo $ac_sys_release | -+ tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` -+ MACHDEP="$ac_md_system$ac_md_release" + -+ case $MACHDEP in -+ aix*) MACHDEP="aix";; -+ linux*) MACHDEP="linux";; -+ cygwin*) MACHDEP="cygwin";; -+ darwin*) MACHDEP="darwin";; -+ '') MACHDEP="unknown";; -+ esac ++# A namedtuple for iOS version information. ++IOSVersionInfo = collections.namedtuple( ++ "IOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) + -+ if test "$ac_sys_system" = "SunOS"; then -+ # For Solaris, there isn't an OS version specific macro defined -+ # in most compilers, so we define one here. -+ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` -+ AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], -+ [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) -+ fi -+fi -+AC_MSG_RESULT(["$MACHDEP"]) + -+# On cross-compile builds, configure will look for a host-specific compiler by -+# prepending the user-provided host triple to the required binary name. -+# -+# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", -+# which isn't a binary that exists, and isn't very convenient, as it contains the -+# iOS version. As the default cross-compiler name won't exist, configure falls -+# back to gcc, which *definitely* won't work. We're providing wrapper scripts for -+# these tools; the binary names of these scripts are better defaults than "gcc". -+# This only requires that the user put the platform scripts folder (e.g., -+# "iOS/Resources/bin") in their path, rather than defining platform-specific -+# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to -+# either put the platform scripts folder in the path, or specify CC etc, -+# configure will fail. -+if test -z "$AR"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; -+ aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; -+ x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; ++def ios_ver(system="", release="", model="", is_simulator=False): ++ """Get iOS version information, and return it as a namedtuple: ++ (system, release, model, is_simulator). + -+ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;; -+ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;; -+ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;; ++ 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) + -+ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;; -+ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; -+ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; -+ *) -+ esac -+fi -+if test -z "$CC"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; -+ aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; -+ x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++ return IOSVersionInfo(system, release, model, is_simulator) + -+ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;; -+ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;; -+ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;; + -+ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;; -+ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;; -+ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;; -+ *) -+ esac -+fi -+if test -z "$CPP"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; -+ aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; -+ x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++# A namedtuple for tvOS version information. ++TVOSVersionInfo = collections.namedtuple( ++ "TVOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) + -+ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;; -+ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;; -+ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;; + -+ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;; -+ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;; -+ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;; -+ *) -+ esac -+fi -+if test -z "$CXX"; then -+ case "$host" in -+ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; -+ aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; -+ x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; ++def tvos_ver(system="", release="", model="", is_simulator=False): ++ """Get tvOS version information, and return it as a namedtuple: ++ (system, release, model, is_simulator). + -+ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;; -+ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;; -+ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;; ++ If values can't be determined, they are set to values provided as ++ parameters. ++ """ ++ if sys.platform == "tvos": ++ # TODO: Can the iOS implementation be used here? ++ import _ios_support ++ result = _ios_support.get_platform_ios() ++ if result is not None: ++ return TVOSVersionInfo(*result) + -+ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;; -+ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;; -+ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;; -+ *) -+ esac -+fi ++ return TVOSVersionInfo(system, release, model, is_simulator) + - AC_MSG_CHECKING([for --enable-universalsdk]) - AC_ARG_ENABLE(universalsdk, - AS_HELP_STRING([--enable-universalsdk@<:@=SDKDIR@:>@], -@@ -412,214 +567,328 @@ - [ - case $enableval in - yes) -- enableval=/Library/Frameworks -+ case $ac_sys_system in -+ Darwin) enableval=/Library/Frameworks ;; -+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; -+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;; -+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;; -+ *) AC_MSG_ERROR([Unknown platform for framework build]) -+ esac - esac + - case $enableval in - no) -- PYTHONFRAMEWORK= -- PYTHONFRAMEWORKDIR=no-framework -- PYTHONFRAMEWORKPREFIX= -- PYTHONFRAMEWORKINSTALLDIR= -- FRAMEWORKINSTALLFIRST= -- FRAMEWORKINSTALLLAST= -- FRAMEWORKALTINSTALLFIRST= -- FRAMEWORKALTINSTALLLAST= -- FRAMEWORKPYTHONW= -- if test "x${prefix}" = "xNONE"; then -- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -- else -- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -- fi -- enable_framework= -+ case $ac_sys_system in -+ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; -+ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; -+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; -+ *) -+ PYTHONFRAMEWORK= -+ PYTHONFRAMEWORKDIR=no-framework -+ PYTHONFRAMEWORKPREFIX= -+ PYTHONFRAMEWORKINSTALLDIR= -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX= -+ RESSRCDIR= -+ FRAMEWORKINSTALLFIRST= -+ FRAMEWORKINSTALLLAST= -+ FRAMEWORKALTINSTALLFIRST= -+ FRAMEWORKALTINSTALLLAST= -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="commoninstall bininstall maninstall" ++# A namedtuple for watchOS version information. ++WatchOSVersionInfo = collections.namedtuple( ++ "WatchOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) ++ ++ ++def watchos_ver(system="", release="", model="", is_simulator=False): ++ """Get watchOS 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 == "watchos": ++ # TODO: Can the iOS implementation be used here? ++ import _ios_support ++ result = _ios_support.get_platform_ios() ++ if result is not None: ++ return WatchOSVersionInfo(*result) ++ ++ return WatchOSVersionInfo(system, release, model, is_simulator) ++ ++ + def _java_getprop(name, default): + + from java.lang import System +@@ -567,7 +639,7 @@ + if cleaned == platform: + break + platform = cleaned +- while platform[-1] == '-': ++ while platform and platform[-1] == '-': + platform = platform[:-1] + + return platform +@@ -608,7 +680,7 @@ + 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 + +@@ -750,6 +822,25 @@ + csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) + return 'Alpha' if cpu_number >= 128 else 'VAX' + ++ # On the iOS/tvOS/watchOS 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 devices, so we know the right ++ # answer. ++ def get_ios(): ++ if sys.implementation._multiarch.endswith("simulator"): ++ return os.uname().machine ++ return 'arm64' ++ ++ def get_tvos(): ++ if sys.implementation._multiarch.endswith("simulator"): ++ return os.uname().machine ++ return 'arm64' ++ ++ def get_watchos(): ++ if sys.implementation._multiarch.endswith("simulator"): ++ return os.uname().machine ++ return 'arm64_32' ++ + def from_subprocess(): + """ + Fall back to `uname -p` +@@ -904,6 +995,14 @@ + system = 'Windows' + release = 'Vista' + ++ # Normalize responses on Apple mobile platforms ++ if sys.platform == 'ios': ++ system, release, _, _ = ios_ver() ++ if sys.platform == 'tvos': ++ system, release, _, _ = tvos_ver() ++ if sys.platform == 'watchos': ++ system, release, _, _ = watchos_ver() + -+ if test "x${prefix}" = "xNONE"; then -+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -+ else -+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -+ fi -+ enable_framework= -+ esac - ;; - *) - PYTHONFRAMEWORKPREFIX="${enableval}" - PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR -- FRAMEWORKINSTALLFIRST="frameworkinstallstructure" -- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " -- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" -- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" -- FRAMEWORKPYTHONW="frameworkpythonw" -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- -- if test "x${prefix}" = "xNONE" ; then -- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -- -- else -- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -- fi + vals = system, node, release, version, machine + # Replace 'unknown' values with the more portable '' + _uname_cache = uname_result(*map(_unknown_as_blank, vals)) +@@ -1216,11 +1315,18 @@ + system, release, version = system_alias(system, release, version) -- case "${enableval}" in -- /System*) -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- if test "${prefix}" = "NONE" ; then -- # See below -- FRAMEWORKUNIXTOOLSPREFIX="/usr" -- fi -- ;; -+ case $ac_sys_system in #( -+ Darwin) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" -+ FRAMEWORKPYTHONW="frameworkpythonw" -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ INSTALLTARGETS="commoninstall bininstall maninstall" + 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() ++ elif sys.platform == "tvos": ++ system, release, _, _ = tvos_ver() ++ elif sys.platform == "watchos": ++ system, release, _, _ = watchos_ver() ++ else: ++ macos_release = mac_ver()[0] ++ if macos_release: ++ system = 'macOS' ++ release = macos_release + + if system == 'Windows': + # MS platforms +diff --git a/Lib/site.py b/Lib/site.py +index 2904e44cffd..371a89ff717 100644 +--- a/Lib/site.py ++++ b/Lib/site.py +@@ -276,8 +276,8 @@ + 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): +diff --git a/Lib/subprocess.py b/Lib/subprocess.py +index 1d17ae3608a..34dfa0019a5 100644 +--- a/Lib/subprocess.py ++++ b/Lib/subprocess.py +@@ -74,8 +74,8 @@ + else: + _mswindows = True + +-# wasm32-emscripten and wasm32-wasi do not support processes +-_can_fork_exec = sys.platform not in {"emscripten", "wasi"} ++# some platforms do not support subprocesses ++_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"} + + if _mswindows: + import _winapi +@@ -103,18 +103,22 @@ + if _can_fork_exec: + from _posixsubprocess import fork_exec as _fork_exec + # used in methods that are called by __del__ +- _waitpid = os.waitpid +- _waitstatus_to_exitcode = os.waitstatus_to_exitcode +- _WIFSTOPPED = os.WIFSTOPPED +- _WSTOPSIG = os.WSTOPSIG +- _WNOHANG = os.WNOHANG ++ class _del_safe: ++ waitpid = os.waitpid ++ waitstatus_to_exitcode = os.waitstatus_to_exitcode ++ WIFSTOPPED = os.WIFSTOPPED ++ WSTOPSIG = os.WSTOPSIG ++ WNOHANG = os.WNOHANG ++ ECHILD = errno.ECHILD + else: +- _fork_exec = None +- _waitpid = None +- _waitstatus_to_exitcode = None +- _WIFSTOPPED = None +- _WSTOPSIG = None +- _WNOHANG = None ++ class _del_safe: ++ waitpid = None ++ waitstatus_to_exitcode = None ++ WIFSTOPPED = None ++ WSTOPSIG = None ++ WNOHANG = None ++ ECHILD = errno.ECHILD + -+ if test "x${prefix}" = "xNONE" ; then -+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + import select + import selectors + +@@ -1958,20 +1962,16 @@ + raise child_exception_type(err_msg) + + +- def _handle_exitstatus(self, sts, +- _waitstatus_to_exitcode=_waitstatus_to_exitcode, +- _WIFSTOPPED=_WIFSTOPPED, +- _WSTOPSIG=_WSTOPSIG): ++ def _handle_exitstatus(self, sts, _del_safe=_del_safe): + """All callers to this function MUST hold self._waitpid_lock.""" + # This method is called (indirectly) by __del__, so it cannot + # refer to anything outside of its local scope. +- if _WIFSTOPPED(sts): +- self.returncode = -_WSTOPSIG(sts) ++ if _del_safe.WIFSTOPPED(sts): ++ self.returncode = -_del_safe.WSTOPSIG(sts) + else: +- self.returncode = _waitstatus_to_exitcode(sts) ++ self.returncode = _del_safe.waitstatus_to_exitcode(sts) + +- def _internal_poll(self, _deadstate=None, _waitpid=_waitpid, +- _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD): ++ def _internal_poll(self, _deadstate=None, _del_safe=_del_safe): + """Check if child process has terminated. Returns returncode + attribute. + +@@ -1987,13 +1987,13 @@ + try: + if self.returncode is not None: + return self.returncode # Another thread waited. +- pid, sts = _waitpid(self.pid, _WNOHANG) ++ pid, sts = _del_safe.waitpid(self.pid, _del_safe.WNOHANG) + if pid == self.pid: + self._handle_exitstatus(sts) + except OSError as e: + if _deadstate is not None: + self.returncode = _deadstate +- elif e.errno == _ECHILD: ++ elif e.errno == _del_safe.ECHILD: + # This happens if SIGCLD is set to be ignored or + # waiting for child processes has otherwise been + # disabled for our process. This child is dead, we +diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py +index ebe37118274..edf12d57068 100644 +--- a/Lib/sysconfig.py ++++ b/Lib/sysconfig.py +@@ -20,6 +20,7 @@ + + # Keys for get_config_var() that are never converted to Python integers. + _ALWAYS_STR = { ++ 'IPHONEOS_DEPLOYMENT_TARGET', + 'MACOSX_DEPLOYMENT_TARGET', + } + +@@ -56,6 +57,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). +@@ -111,8 +113,8 @@ + 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): +@@ -289,6 +291,7 @@ + 'home': 'posix_home', + 'user': 'osx_framework_user', + } + -+ else -+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -+ fi + return { + 'prefix': 'posix_prefix', + 'home': 'posix_home', +@@ -788,10 +791,23 @@ + 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", "13.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "tvos": ++ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "9.0") ++ osname = sys.platform ++ machine = sys.implementation._multiarch ++ elif sys.platform == "watchos": ++ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "4.0") ++ 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) -- /Library*) -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- ;; -+ case "${enableval}" in -+ /System*) -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ if test "${prefix}" = "NONE" ; then -+ # See below -+ FRAMEWORKUNIXTOOLSPREFIX="/usr" -+ fi -+ ;; + return f"{osname}-{release}-{machine}" + +diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py +index 74ebb5e5b8a..d3e5fddf69b 100644 +--- a/Lib/test/pythoninfo.py ++++ b/Lib/test/pythoninfo.py +@@ -287,6 +287,7 @@ + "HOMEDRIVE", + "HOMEPATH", + "IDLESTARTUP", ++ "IPHONEOS_DEPLOYMENT_TARGET", + "LANG", + "LDFLAGS", + "LDSHARED", +diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py +index 79e3d1c6422..9461443efd5 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -45,7 +45,7 @@ + "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", + # sys + "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", +- "check_impl_detail", "unix_shell", "setswitchinterval", ++ "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval", + # network + "open_urlresource", + # processes +@@ -57,6 +57,7 @@ + "ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST", + "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT", + "skip_on_s390x", ++ "on_github_actions" + ] + + +@@ -527,7 +528,7 @@ + + is_android = hasattr(sys, 'getandroidapilevel') + +-if sys.platform not in ('win32', 'vxworks'): ++if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: + unix_shell = '/system/bin/sh' if is_android else '/bin/sh' + else: + unix_shell = None +@@ -537,19 +538,35 @@ + is_emscripten = sys.platform == "emscripten" + is_wasi = sys.platform == "wasi" + +-has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi ++# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not ++# have subprocess or fork support. ++is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} ++is_apple = is_apple_mobile or sys.platform == "darwin" + -+ /Library*) -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ ;; ++has_fork_support = hasattr(os, "fork") and not ( ++ is_emscripten ++ or is_wasi ++ or is_apple_mobile ++) + + def requires_fork(): + return unittest.skipUnless(has_fork_support, "requires working os.fork()") + +-has_subprocess_support = not is_emscripten and not is_wasi ++has_subprocess_support = not ( ++ is_emscripten ++ or is_wasi ++ or is_apple_mobile ++) + + def requires_subprocess(): + """Used for subprocess, os.spawn calls, fd inheritance""" + return unittest.skipUnless(has_subprocess_support, "requires subprocess support") + + # Emscripten's socket emulation and WASI sockets have limitations. +-has_socket_support = not is_emscripten and not is_wasi ++has_socket_support = not ( ++ is_emscripten ++ or is_wasi ++) + + def requires_working_socket(*, module=False): + """Skip tests or modules that require working sockets +@@ -1139,6 +1156,8 @@ + return no_tracing(cpython_only(test)) + + ++on_github_actions = "GITHUB_ACTIONS" in os.environ + -+ */Library/Frameworks) -+ MDIR="`dirname "${enableval}"`" -+ MDIR="`dirname "${MDIR}"`" -+ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" + #======================================================================= + # Check for the presence of docstrings. + +diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py +index c1b2995ef37..353a3f589ac 100644 +--- a/Lib/test/support/os_helper.py ++++ b/Lib/test/support/os_helper.py +@@ -9,6 +9,8 @@ + import unittest + import warnings + ++from test import support + -+ if test "${prefix}" = "NONE"; then -+ # User hasn't specified the -+ # --prefix option, but wants to install -+ # the framework in a non-default location, -+ # ensure that the compatibility links get -+ # installed relative to that prefix as well -+ # instead of in /usr/local. -+ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" -+ fi -+ ;; -- */Library/Frameworks) -- MDIR="`dirname "${enableval}"`" -- MDIR="`dirname "${MDIR}"`" -- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" -- -- if test "${prefix}" = "NONE"; then -- # User hasn't specified the -- # --prefix option, but wants to install -- # the framework in a non-default location, -- # ensure that the compatibility links get -- # installed relative to that prefix as well -- # instead of in /usr/local. -- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" -- fi -- ;; -+ *) -+ FRAMEWORKINSTALLAPPSPREFIX="/Applications" -+ ;; -+ esac + # Filename used for testing + if os.name == 'java': +@@ -23,8 +25,8 @@ -- *) -- FRAMEWORKINSTALLAPPSPREFIX="/Applications" -- ;; -+ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} -+ RESSRCDIR=Mac/Resources/framework + # TESTFN_UNICODE is a non-ascii filename + TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f" +-if sys.platform == 'darwin': +- # In Mac OS X's VFS API file names are, by definition, canonically ++if support.is_apple: ++ # On Apple's VFS API file names are, by definition, canonically + # decomposed Unicode, encoded using UTF-8. See QA1173: + # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html + import unicodedata +@@ -49,8 +51,8 @@ + 'encoding (%s). Unicode filename tests may not be effective' + % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) + TESTFN_UNENCODABLE = None +-# macOS and Emscripten deny unencodable filenames (invalid utf-8) +-elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: ++# Apple and Emscripten deny unencodable filenames (invalid utf-8) ++elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}: + try: + # ascii and utf-8 cannot encode the byte 0xff + b'\xff'.decode(sys.getfilesystemencoding()) +@@ -611,7 +613,8 @@ + if hasattr(os, 'sysconf'): + try: + MAXFD = os.sysconf("SC_OPEN_MAX") +- except OSError: ++ except (OSError, ValueError): ++ # gh-118201: ValueError is raised intermittently on iOS + pass + + old_modes = None +diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py +index ec6d1dee4e9..48900f50508 100644 +--- a/Lib/test/support/socket_helper.py ++++ b/Lib/test/support/socket_helper.py +@@ -1,7 +1,9 @@ + import contextlib + import errno ++import os.path + import socket + import subprocess ++import tempfile + import sys + import unittest + +@@ -273,6 +275,16 @@ + socket.setdefaulttimeout(old_timeout) + + ++def create_unix_domain_name(): ++ """ ++ Create a UNIX domain name: socket.bind() argument of a AF_UNIX socket. + -+ # Add files for Mac specific code to the list of output -+ # files: -+ AC_CONFIG_FILES([Mac/Makefile]) -+ AC_CONFIG_FILES([Mac/PythonLauncher/Makefile]) -+ AC_CONFIG_FILES([Mac/Resources/framework/Info.plist]) -+ AC_CONFIG_FILES([Mac/Resources/app/Info.plist]) -+ ;; -+ iOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ Return a path relative to the current directory to get a short path ++ (around 27 ASCII characters). ++ """ ++ return tempfile.mktemp(prefix="test_python_", suffix='.sock', ++ dir=os.path.curdir) + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=iOS/Resources + # consider that sysctl values should not change while tests are running + _sysctl_cache = {} + +--- /dev/null ++++ b/Lib/test/test_apple.py +@@ -0,0 +1,155 @@ ++import unittest ++from _apple_support import SystemLog ++from test.support import is_apple_mobile ++from unittest.mock import Mock, call + -+ AC_CONFIG_FILES([iOS/Resources/Info.plist]) -+ ;; -+ tvOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++if not is_apple_mobile: ++ raise unittest.SkipTest("iOS-specific") + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=tvOS/Resources + -+ AC_CONFIG_FILES([tvOS/Resources/Info.plist]) -+ ;; -+ watchOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++# Test redirection of stdout and stderr to the Apple system log. ++class TestAppleSystemLogOutput(unittest.TestCase): ++ maxDiff = None + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=watchOS/Resources ++ def assert_writes(self, output): ++ self.assertEqual( ++ self.log_write.mock_calls, ++ [ ++ call(self.log_level, line) ++ for line in output ++ ] ++ ) + -+ AC_CONFIG_FILES([watchOS/Resources/Info.plist]) -+ ;; -+ *) -+ AC_MSG_ERROR([Unknown platform for framework build]) -+ ;; -+ esac - esac -- -- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION -- -- # Add files for Mac specific code to the list of output -- # files: -- AC_CONFIG_FILES(Mac/Makefile) -- AC_CONFIG_FILES(Mac/PythonLauncher/Makefile) -- AC_CONFIG_FILES(Mac/Resources/framework/Info.plist) -- AC_CONFIG_FILES(Mac/Resources/app/Info.plist) -- esac - ],[ -- PYTHONFRAMEWORK= -- PYTHONFRAMEWORKDIR=no-framework -- PYTHONFRAMEWORKPREFIX= -- PYTHONFRAMEWORKINSTALLDIR= -- FRAMEWORKINSTALLFIRST= -- FRAMEWORKINSTALLLAST= -- FRAMEWORKALTINSTALLFIRST= -- FRAMEWORKALTINSTALLLAST= -- FRAMEWORKPYTHONW= -- if test "x${prefix}" = "xNONE" ; then -- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -- else -- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -- fi -- enable_framework= -- --]) --AC_SUBST(PYTHONFRAMEWORK) --AC_SUBST(PYTHONFRAMEWORKIDENTIFIER) --AC_SUBST(PYTHONFRAMEWORKDIR) --AC_SUBST(PYTHONFRAMEWORKPREFIX) --AC_SUBST(PYTHONFRAMEWORKINSTALLDIR) --AC_SUBST(FRAMEWORKINSTALLFIRST) --AC_SUBST(FRAMEWORKINSTALLLAST) --AC_SUBST(FRAMEWORKALTINSTALLFIRST) --AC_SUBST(FRAMEWORKALTINSTALLLAST) --AC_SUBST(FRAMEWORKPYTHONW) --AC_SUBST(FRAMEWORKUNIXTOOLSPREFIX) --AC_SUBST(FRAMEWORKINSTALLAPPSPREFIX) -+ case $ac_sys_system in -+ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; -+ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; -+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; -+ *) -+ PYTHONFRAMEWORK= -+ PYTHONFRAMEWORKDIR=no-framework -+ PYTHONFRAMEWORKPREFIX= -+ PYTHONFRAMEWORKINSTALLDIR= -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX= -+ RESSRCDIR= -+ FRAMEWORKINSTALLFIRST= -+ FRAMEWORKINSTALLLAST= -+ FRAMEWORKALTINSTALLFIRST= -+ FRAMEWORKALTINSTALLLAST= -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="commoninstall bininstall maninstall" -+ if test "x${prefix}" = "xNONE" ; then -+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" -+ else -+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" -+ fi -+ enable_framework= -+ esac -+]) -+AC_SUBST([PYTHONFRAMEWORK]) -+AC_SUBST([PYTHONFRAMEWORKIDENTIFIER]) -+AC_SUBST([PYTHONFRAMEWORKDIR]) -+AC_SUBST([PYTHONFRAMEWORKPREFIX]) -+AC_SUBST([PYTHONFRAMEWORKINSTALLDIR]) -+AC_SUBST([PYTHONFRAMEWORKINSTALLNAMEPREFIX]) -+AC_SUBST([RESSRCDIR]) -+AC_SUBST([FRAMEWORKINSTALLFIRST]) -+AC_SUBST([FRAMEWORKINSTALLLAST]) -+AC_SUBST([FRAMEWORKALTINSTALLFIRST]) -+AC_SUBST([FRAMEWORKALTINSTALLLAST]) -+AC_SUBST([FRAMEWORKPYTHONW]) -+AC_SUBST([FRAMEWORKUNIXTOOLSPREFIX]) -+AC_SUBST([FRAMEWORKINSTALLAPPSPREFIX]) -+AC_SUBST([INSTALLTARGETS]) - - AC_DEFINE_UNQUOTED(_PYTHONFRAMEWORK, "${PYTHONFRAMEWORK}", [framework name]) - --# Set name for machine-dependent library files --AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) --AC_MSG_CHECKING(MACHDEP) --if test -z "$MACHDEP" --then -- # avoid using uname for cross builds -- if test "$cross_compiling" = yes; then -- # ac_sys_system and ac_sys_release are used for setting -- # a lot of different things including 'define_xopen_source' -- # in the case statement below. -- case "$host" in -- *-*-linux-android*) -- ac_sys_system=Linux-android -- ;; -- *-*-linux*) -- ac_sys_system=Linux -- ;; -- *-*-cygwin*) -- ac_sys_system=Cygwin -- ;; -- *-*-vxworks*) -- ac_sys_system=VxWorks -- ;; -- *-*-emscripten) -- ac_sys_system=Emscripten -- ;; -- *-*-wasi) -- ac_sys_system=WASI -- ;; -- *) -- # for now, limit cross builds to known configurations -- MACHDEP="unknown" -- AC_MSG_ERROR([cross build not supported for $host]) -- esac -- ac_sys_release= -- else -- ac_sys_system=`uname -s` -- if test "$ac_sys_system" = "AIX" \ -- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then -- ac_sys_release=`uname -v` -- else -- ac_sys_release=`uname -r` -- fi -- fi -- ac_md_system=`echo $ac_sys_system | -- tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` -- ac_md_release=`echo $ac_sys_release | -- tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` -- MACHDEP="$ac_md_system$ac_md_release" -- -- case $MACHDEP in -- aix*) MACHDEP="aix";; -- linux*) MACHDEP="linux";; -- cygwin*) MACHDEP="cygwin";; -- darwin*) MACHDEP="darwin";; -- '') MACHDEP="unknown";; -+dnl quadrigraphs "@<:@" and "@:>@" produce "[" and "]" in the output -+AC_MSG_CHECKING([for --with-app-store-compliance]) -+AC_ARG_WITH( -+ [app_store_compliance], -+ [AS_HELP_STRING( -+ [--with-app-store-compliance=@<:@PATCH-FILE@:>@], -+ [Enable any patches required for compiliance with app stores. -+ Optional PATCH-FILE specifies the custom patch to apply.] -+ )],[ -+ case "$withval" in -+ yes) -+ case $ac_sys_system in -+ Darwin|iOS|tvOS|watchOS) -+ # iOS/tvOS/watchOS is able to share the macOS patch -+ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" -+ ;; -+ *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;; -+ esac -+ AC_MSG_RESULT([applying default app store compliance patch]) -+ ;; -+ *) -+ APP_STORE_COMPLIANCE_PATCH="${withval}" -+ AC_MSG_RESULT([applying custom app store compliance patch]) -+ ;; - esac -+ ],[ -+ case $ac_sys_system in -+ iOS|tvOS|watchOS) -+ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch -+ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" -+ AC_MSG_RESULT([applying default app store compliance patch]) -+ ;; -+ *) -+ # No default app compliance patching on any other platform -+ APP_STORE_COMPLIANCE_PATCH= -+ AC_MSG_RESULT([not patching for app store compliance]) -+ ;; -+ esac -+]) -+AC_SUBST([APP_STORE_COMPLIANCE_PATCH]) - -- if test "$ac_sys_system" = "SunOS"; then -- # For Solaris, there isn't an OS version specific macro defined -- # in most compilers, so we define one here. -- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` -- AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], -- [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) -- fi --fi --AC_MSG_RESULT("$MACHDEP") -- --AC_SUBST(_PYTHON_HOST_PLATFORM) -+AC_SUBST([_PYTHON_HOST_PLATFORM]) - if test "$cross_compiling" = yes; then - case "$host" in - *-*-linux*) - case "$host_cpu" in - arm*) -- _host_cpu=arm -+ _host_ident=arm - ;; - *) -- _host_cpu=$host_cpu -+ _host_ident=$host_cpu - esac - ;; - *-*-cygwin*) -- _host_cpu= -+ _host_ident= -+ ;; -+ *-apple-ios*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++ self.log_write.reset_mock() + -+ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version -+ AC_MSG_CHECKING([iOS deployment target]) -+ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} -+ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0} -+ AC_MSG_RESULT([$IPHONEOS_DEPLOYMENT_TARGET]) ++ def setUp(self): ++ self.log_write = Mock() ++ self.log_level = 42 ++ self.log = SystemLog(self.log_write, self.log_level, errors="replace") + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} -+ ;; -+ *) -+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} -+ ;; -+ esac -+ ;; -+ *-apple-tvos*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++ def test_repr(self): ++ self.assertEqual(repr(self.log), "") ++ self.assertEqual(repr(self.log.buffer), "") + -+ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version -+ AC_MSG_CHECKING([tvOS deployment target]) -+ TVOS_DEPLOYMENT_TARGET=${_host_os:4} -+ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0} -+ AC_MSG_RESULT([$TVOS_DEPLOYMENT_TARGET]) ++ def test_log_config(self): ++ self.assertIs(self.log.writable(), True) ++ self.assertIs(self.log.readable(), False) + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device} -+ ;; -+ *) -+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device} -+ ;; -+ esac -+ ;; -+ *-apple-watchos*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++ self.assertEqual("UTF-8", self.log.encoding) ++ self.assertEqual("replace", self.log.errors) + -+ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version -+ AC_MSG_CHECKING([watchOS deployment target]) -+ WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} -+ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} -+ AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET]) ++ self.assertIs(self.log.line_buffering, True) ++ self.assertIs(self.log.write_through, False) + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device} -+ ;; -+ *) -+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device} -+ ;; -+ esac - ;; - *-*-vxworks*) -- _host_cpu=$host_cpu -+ _host_ident=$host_cpu - ;; - wasm32-*-* | wasm64-*-*) -- _host_cpu=$host_cpu -+ _host_ident=$host_cpu - ;; - *) - # for now, limit cross builds to known configurations - MACHDEP="unknown" - AC_MSG_ERROR([cross build not supported for $host]) - esac -- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" -+ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" - fi ++ def test_empty_str(self): ++ self.log.write("") ++ self.log.flush() ++ ++ self.assert_writes([]) ++ ++ def test_simple_str(self): ++ self.log.write("hello world\n") ++ ++ self.assert_writes([b"hello world\n"]) ++ ++ def test_buffered_str(self): ++ self.log.write("h") ++ self.log.write("ello") ++ self.log.write(" ") ++ self.log.write("world\n") ++ self.log.write("goodbye.") ++ self.log.flush() ++ ++ self.assert_writes([b"hello world\n", b"goodbye."]) ++ ++ def test_manual_flush(self): ++ self.log.write("Hello") ++ ++ self.assert_writes([]) ++ ++ self.log.write(" world\nHere for a while...\nGoodbye") ++ self.assert_writes([b"Hello world\n", b"Here for a while...\n"]) ++ ++ self.log.write(" world\nHello again") ++ self.assert_writes([b"Goodbye world\n"]) ++ ++ self.log.flush() ++ self.assert_writes([b"Hello again"]) ++ ++ def test_non_ascii(self): ++ # Spanish ++ self.log.write("ol\u00e9\n") ++ self.assert_writes([b"ol\xc3\xa9\n"]) ++ ++ # Chinese ++ self.log.write("\u4e2d\u6587\n") ++ self.assert_writes([b"\xe4\xb8\xad\xe6\x96\x87\n"]) ++ ++ # Printing Non-BMP emoji ++ self.log.write("\U0001f600\n") ++ self.assert_writes([b"\xf0\x9f\x98\x80\n"]) ++ ++ # Non-encodable surrogates are replaced ++ self.log.write("\ud800\udc00\n") ++ self.assert_writes([b"??\n"]) ++ ++ def test_modified_null(self): ++ # Null characters are logged using "modified UTF-8". ++ self.log.write("\u0000\n") ++ self.assert_writes([b"\xc0\x80\n"]) ++ self.log.write("a\u0000\n") ++ self.assert_writes([b"a\xc0\x80\n"]) ++ self.log.write("\u0000b\n") ++ self.assert_writes([b"\xc0\x80b\n"]) ++ self.log.write("a\u0000b\n") ++ self.assert_writes([b"a\xc0\x80b\n"]) ++ ++ def test_nonstandard_str(self): ++ # String subclasses are accepted, but they should be converted ++ # to a standard str without calling any of their methods. ++ class CustomStr(str): ++ def splitlines(self, *args, **kwargs): ++ raise AssertionError() ++ ++ def __len__(self): ++ raise AssertionError() ++ ++ def __str__(self): ++ raise AssertionError() ++ ++ self.log.write(CustomStr("custom\n")) ++ self.assert_writes([b"custom\n"]) ++ ++ def test_non_str(self): ++ # Non-string classes are not accepted. ++ for obj in [b"", b"hello", None, 42]: ++ with self.subTest(obj=obj): ++ with self.assertRaisesRegex( ++ TypeError, ++ fr"write\(\) argument must be str, not " ++ fr"{type(obj).__name__}" ++ ): ++ self.log.write(obj) ++ ++ def test_byteslike_in_buffer(self): ++ # The underlying buffer *can* accept bytes-like objects ++ self.log.buffer.write(bytearray(b"hello")) ++ self.log.flush() ++ ++ self.log.buffer.write(b"") ++ self.log.flush() ++ ++ self.log.buffer.write(b"goodbye") ++ self.log.flush() ++ ++ self.assert_writes([b"hello", b"goodbye"]) ++ ++ def test_non_byteslike_in_buffer(self): ++ for obj in ["hello", None, 42]: ++ with self.subTest(obj=obj): ++ with self.assertRaisesRegex( ++ TypeError, ++ fr"write\(\) argument must be bytes-like, not " ++ fr"{type(obj).__name__}" ++ ): ++ self.log.buffer.write(obj) +diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py +index 8e3c68db550..0e4028a31ae 100644 +--- a/Lib/test/test_asyncio/test_events.py ++++ b/Lib/test/test_asyncio/test_events.py +@@ -1867,6 +1867,7 @@ + else: + self.assertEqual(-signal.SIGKILL, returncode) + ++ @support.requires_subprocess() + def test_subprocess_exec(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1888,6 +1889,7 @@ + self.check_killed(proto.returncode) + self.assertEqual(b'Python The Winner', proto.data[1]) + ++ @support.requires_subprocess() + def test_subprocess_interactive(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1915,6 +1917,7 @@ + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) + ++ @support.requires_subprocess() + def test_subprocess_shell(self): + connect = self.loop.subprocess_shell( + functools.partial(MySubprocessProtocol, self.loop), +@@ -1931,6 +1934,7 @@ + self.assertEqual(proto.data[2], b'') + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_exitcode(self): + connect = self.loop.subprocess_shell( + functools.partial(MySubprocessProtocol, self.loop), +@@ -1942,6 +1946,7 @@ + self.assertEqual(7, proto.returncode) + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_close_after_finish(self): + connect = self.loop.subprocess_shell( + functools.partial(MySubprocessProtocol, self.loop), +@@ -1956,6 +1961,7 @@ + self.assertEqual(7, proto.returncode) + self.assertIsNone(transp.close()) + ++ @support.requires_subprocess() + def test_subprocess_kill(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1972,6 +1978,7 @@ + self.check_killed(proto.returncode) + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_terminate(self): + prog = os.path.join(os.path.dirname(__file__), 'echo.py') + +@@ -1989,6 +1996,7 @@ + transp.close() + + @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP") ++ @support.requires_subprocess() + def test_subprocess_send_signal(self): + # bpo-31034: Make sure that we get the default signal handler (killing + # the process). The parent process may have decided to ignore SIGHUP, +@@ -2013,6 +2021,7 @@ + finally: + signal.signal(signal.SIGHUP, old_handler) - # Some systems cannot stand _XOPEN_SOURCE being defined at all; they -@@ -683,6 +952,13 @@ - define_xopen_source=no;; - Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) - define_xopen_source=no;; -+ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. -+ iOS/*) -+ define_xopen_source=no;; -+ tvOS/*) -+ define_xopen_source=no;; -+ watchOS/*) -+ define_xopen_source=no;; - # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from - # defining NI_NUMERICHOST. - QNX/6.3.2) -@@ -739,6 +1015,12 @@ - CONFIGURE_MACOSX_DEPLOYMENT_TARGET= - EXPORT_MACOSX_DEPLOYMENT_TARGET='#' ++ @support.requires_subprocess() + def test_subprocess_stderr(self): + prog = os.path.join(os.path.dirname(__file__), 'echo2.py') -+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / -+# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. -+AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET]) -+AC_SUBST([TVOS_DEPLOYMENT_TARGET]) -+AC_SUBST([WATCHOS_DEPLOYMENT_TARGET]) -+ - # checks for alternative programs +@@ -2034,6 +2043,7 @@ + self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2]) + self.assertEqual(0, proto.returncode) - # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just -@@ -771,6 +1053,20 @@ - ], - ) ++ @support.requires_subprocess() + def test_subprocess_stderr_redirect_to_stdout(self): + prog = os.path.join(os.path.dirname(__file__), 'echo2.py') -+dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS version. -+AS_CASE([$ac_sys_system], -+ [iOS], [ -+ AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) -+ AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) -+ ],[tvOS], [ -+ AS_VAR_APPEND([CFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"]) -+ AS_VAR_APPEND([LDFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"]) -+ ],[watchOS], [ -+ AS_VAR_APPEND([CFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"]) -+ AS_VAR_APPEND([LDFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"]) -+ ], -+) +@@ -2059,6 +2069,7 @@ + transp.close() + self.assertEqual(0, proto.returncode) + ++ @support.requires_subprocess() + def test_subprocess_close_client_stream(self): + prog = os.path.join(os.path.dirname(__file__), 'echo3.py') + +@@ -2093,6 +2104,7 @@ + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) + ++ @support.requires_subprocess() + def test_subprocess_wait_no_same_group(self): + # start the new process in a new session + connect = self.loop.subprocess_shell( +@@ -2105,6 +2117,7 @@ + self.assertEqual(7, proto.returncode) + transp.close() + ++ @support.requires_subprocess() + def test_subprocess_exec_invalid_args(self): + async def connect(**kwds): + await self.loop.subprocess_exec( +@@ -2118,6 +2131,7 @@ + with self.assertRaises(ValueError): + self.loop.run_until_complete(connect(shell=True)) + ++ @support.requires_subprocess() + def test_subprocess_shell_invalid_args(self): + + async def connect(cmd=None, **kwds): +diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py +index 59ff89e7734..320affdfdec 100644 +--- a/Lib/test/test_asyncio/test_streams.py ++++ b/Lib/test/test_asyncio/test_streams.py +@@ -9,7 +9,7 @@ + import threading + import unittest + from unittest import mock +-from test.support import socket_helper ++from test.support import requires_subprocess, socket_helper + try: + import ssl + except ImportError: +@@ -769,6 +769,7 @@ + self.assertEqual(msg2, b"hello world 2!\n") + + @unittest.skipIf(sys.platform == 'win32', "Don't have pipes") ++ @requires_subprocess() + def test_read_all_from_pipe_reader(self): + # See asyncio issue 168. This test is derived from the example + # subprocess_attach_read_pipe.py, but we configure the +diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py +index f0d1d95d2c1..b07afb97adb 100644 +--- a/Lib/test/test_asyncio/test_subprocess.py ++++ b/Lib/test/test_asyncio/test_subprocess.py +@@ -48,6 +48,7 @@ + self._proc.pid = -1 + + ++@support.requires_subprocess() + class SubprocessTransportTests(test_utils.TestCase): + def setUp(self): + super().setUp() +@@ -111,6 +112,7 @@ + transport.close() + + ++@support.requires_subprocess() + class SubprocessMixin: + + def test_stdin_stdout(self): +diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py +index 01c1214c7f7..7e16e2a369d 100644 +--- a/Lib/test/test_asyncio/test_unix_events.py ++++ b/Lib/test/test_asyncio/test_unix_events.py +@@ -315,11 +315,15 @@ + self.loop.run_until_complete(coro) + + def test_create_unix_server_existing_path_nonsock(self): +- with tempfile.NamedTemporaryFile() as file: +- coro = self.loop.create_unix_server(lambda: None, file.name) +- with self.assertRaisesRegex(OSError, +- 'Address.*is already in use'): +- self.loop.run_until_complete(coro) ++ path = test_utils.gen_unix_socket_path() ++ self.addCleanup(os_helper.unlink, path) ++ # create the file ++ open(path, "wb").close() + - if test "$ac_sys_system" = "Darwin" - then - # Compiler selection on MacOSX is more complicated than -@@ -1062,7 +1358,42 @@ - #elif defined(__gnu_hurd__) - i386-gnu - #elif defined(__APPLE__) -+# include "TargetConditionals.h" -+# if TARGET_OS_IOS -+# if TARGET_OS_SIMULATOR -+# if __x86_64__ -+ x86_64-iphonesimulator -+# else -+ arm64-iphonesimulator -+# endif -+# else -+ arm64-iphoneos -+# endif -+# elif TARGET_OS_TV -+# if TARGET_OS_SIMULATOR -+# if __x86_64__ -+ x86_64-appletvsimulator -+# else -+ arm64-appletvsimulator -+# endif -+# else -+ arm64-appletvos -+# endif -+# elif TARGET_OS_WATCH -+# if TARGET_OS_SIMULATOR -+# if __x86_64__ -+ x86_64-watchsimulator -+# else -+ arm64-watchsimulator -+# endif -+# else -+ arm64_32-watchos -+# endif -+# elif TARGET_OS_OSX - darwin -+# else -+# error unknown Apple platform -+# endif - #elif defined(__VXWORKS__) - vxworks - #elif defined(__wasm32__) -@@ -1100,14 +1431,24 @@ - fi - rm -f conftest.c conftest.out ++ coro = self.loop.create_unix_server(lambda: None, path) ++ with self.assertRaisesRegex(OSError, ++ 'Address.*is already in use'): ++ self.loop.run_until_complete(coro) -+dnl On some platforms, using a true "triplet" for MULTIARCH would be redundant. -+dnl For example, `arm64-apple-darwin` is redundant, because there isn't a -+dnl non-Apple Darwin. Including the CPU architecture can also be potentially -+dnl redundant - on macOS, for example, it's possible to do a single compile -+dnl pass that includes multiple architectures, so it would be misleading for -+dnl MULTIARCH (and thus the sysconfigdata module name) to include a single CPU -+dnl architecture. PLATFORM_TRIPLET will be a pair or single value for these -+dnl platforms. - AC_MSG_CHECKING([for multiarch]) - AS_CASE([$ac_sys_system], - [Darwin*], [MULTIARCH=""], -+ [iOS], [MULTIARCH=""], -+ [tvOS], [MULTIARCH=""], -+ [watchOS], [MULTIARCH=""], - [FreeBSD*], [MULTIARCH=""], - [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] - ) - AC_SUBST([MULTIARCH]) --AC_MSG_RESULT([$MULTIARCH]) + def test_create_unix_server_ssl_bool(self): + coro = self.loop.create_unix_server(lambda: None, path='spam', +diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py +index 045e385511b..c39fb20ed7c 100644 +--- a/Lib/test/test_asyncio/utils.py ++++ b/Lib/test/test_asyncio/utils.py +@@ -11,7 +11,6 @@ + import socket + import socketserver + import sys +-import tempfile + import threading + import time + import unittest +@@ -34,7 +33,7 @@ + from asyncio import tasks + from asyncio.log import logger + from test import support +-from test.support import threading_helper ++from test.support import threading_helper, socket_helper + + + # Use the maximum known clock resolution (gh-75191, gh-110088): Windows +@@ -256,8 +255,7 @@ + + + def gen_unix_socket_path(): +- with tempfile.NamedTemporaryFile() as file: +- return file.name ++ return socket_helper.create_unix_domain_name() + + + @contextlib.contextmanager +diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py +index 98ccd3a9304..d7860853eba 100644 +--- a/Lib/test/test_asyncore.py ++++ b/Lib/test/test_asyncore.py +@@ -9,6 +9,7 @@ + import threading - if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then - if test x$PLATFORM_TRIPLET != x$MULTIARCH; then -@@ -1116,7 +1457,18 @@ - elif test x$PLATFORM_TRIPLET != x && test x$MULTIARCH = x; then - MULTIARCH=$PLATFORM_TRIPLET - fi --AC_SUBST(PLATFORM_TRIPLET) -+AC_SUBST([PLATFORM_TRIPLET]) -+AC_MSG_RESULT([$MULTIARCH]) -+ -+dnl Even if we *do* include the CPU architecture in the MULTIARCH value, some -+dnl platforms don't need the CPU architecture in the SOABI tag. These platforms -+dnl will have multiple sysconfig modules (one for each CPU architecture), but -+dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of -+dnl the PLATFORM_TRIPLET that will be used in binary module extensions. -+AS_CASE([$ac_sys_system], -+ [iOS|tvOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], -+ [SOABI_PLATFORM=$PLATFORM_TRIPLET] -+) + from test import support ++from test.support import is_apple_mobile + from test.support import os_helper + from test.support import socket_helper + from test.support import threading_helper +@@ -657,6 +658,7 @@ - if test x$MULTIARCH != x; then - MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" -@@ -1147,6 +1499,12 @@ - [wasm32-unknown-emscripten/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly Emscripten - [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly System Interface - [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 -+ [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64 -+ [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64 -+ [aarch64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl tvOS Simulator on arm64 -+ [aarch64-apple-tvos*/clang], [PY_SUPPORT_TIER=3], dnl tvOS on ARM64 -+ [aarch64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl watchOS Simulator on arm64 -+ [arm64_32-apple-watchos*/clang], [PY_SUPPORT_TIER=3], dnl watchOS on ARM64 - [PY_SUPPORT_TIER=0] - ) + @unittest.skipIf(sys.platform.startswith("sunos"), + "OOB support is broken on Solaris") ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case in removed test module") + def test_handle_expt(self): + # Make sure handle_expt is called on OOB data received. + # Note: this might fail on some platforms as OOB data is +diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py +index cf28cf35159..54600eafefc 100644 +--- a/Lib/test/test_capi/test_misc.py ++++ b/Lib/test/test_capi/test_misc.py +@@ -1099,6 +1099,13 @@ + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) -@@ -1459,17 +1817,25 @@ ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if support.is_apple_mobile: ++ loader = "AppleFrameworkLoader" ++ else: ++ loader = "ExtensionFileLoader" ++ + script = textwrap.dedent(f""" + import importlib.machinery + import importlib.util +@@ -1106,7 +1113,7 @@ - AC_MSG_CHECKING(LDLIBRARY) + fullname = '_test_module_state_shared' + origin = importlib.util.find_spec('_testmultiphase').origin +- loader = importlib.machinery.ExtensionFileLoader(fullname, origin) ++ loader = importlib.machinery.{loader}(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + attr_id = str(id(module.Error)).encode() +@@ -1311,7 +1318,12 @@ + def setUp(self): + fullname = '_testmultiphase_meth_state_access' # XXX + origin = importlib.util.find_spec('_testmultiphase').origin +- loader = importlib.machinery.ExtensionFileLoader(fullname, origin) ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if support.is_apple_mobile: ++ loader = importlib.machinery.AppleFrameworkLoader(fullname, origin) ++ else: ++ loader = importlib.machinery.ExtensionFileLoader(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) +diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py +index 7fcd563be27..2945b3f56b5 100644 +--- a/Lib/test/test_cmd_line_script.py ++++ b/Lib/test/test_cmd_line_script.py +@@ -14,8 +14,7 @@ --# MacOSX framework builds need more magic. LDLIBRARY is the dynamic -+# Apple framework builds need more magic. LDLIBRARY is the dynamic - # library that we build, but we do not want to link against it (we - # will find it with a -framework option). For this reason there is an - # extra variable BLDLIBRARY against which Python and the extension - # modules are linked, BLDLIBRARY. This is normally the same as --# LDLIBRARY, but empty for MacOSX framework builds. -+# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, -+# but uses a non-versioned framework layout. - if test "$enable_framework" - then -- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' -- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} -+ case $ac_sys_system in -+ Darwin) -+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; -+ iOS|tvOS|watchOS) -+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; -+ *) -+ AC_MSG_ERROR([Unknown platform for framework build]);; -+ esac - BLDLIBRARY='' -+ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} - else - BLDLIBRARY='$(LDLIBRARY)' - fi -@@ -1480,64 +1846,69 @@ - AC_DEFINE(Py_ENABLE_SHARED, 1, [Defined if Python is built as a shared library.]) - case $ac_sys_system in - CYGWIN*) -- LDLIBRARY='libpython$(LDVERSION).dll.a' -- DLLLIBRARY='libpython$(LDVERSION).dll' -- ;; -+ LDLIBRARY='libpython$(LDVERSION).dll.a' -+ DLLLIBRARY='libpython$(LDVERSION).dll' -+ ;; - SunOS*) -- LDLIBRARY='libpython$(LDVERSION).so' -- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' -- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -- INSTSONAME="$LDLIBRARY".$SOVERSION -- if test "$with_pydebug" != yes -- then -- PY3LIBRARY=libpython3.so -- fi -- ;; -+ LDLIBRARY='libpython$(LDVERSION).so' -+ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' -+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -+ INSTSONAME="$LDLIBRARY".$SOVERSION -+ if test "$with_pydebug" != yes -+ then -+ PY3LIBRARY=libpython3.so -+ fi -+ ;; - Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) -- LDLIBRARY='libpython$(LDVERSION).so' -- BLDLIBRARY='-L. -lpython$(LDVERSION)' -- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -- INSTSONAME="$LDLIBRARY".$SOVERSION -- if test "$with_pydebug" != yes -- then -- PY3LIBRARY=libpython3.so -- fi -- ;; -+ LDLIBRARY='libpython$(LDVERSION).so' -+ BLDLIBRARY='-L. -lpython$(LDVERSION)' -+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} -+ INSTSONAME="$LDLIBRARY".$SOVERSION -+ if test "$with_pydebug" != yes -+ then -+ PY3LIBRARY=libpython3.so -+ fi -+ ;; - hp*|HP*) -- case `uname -m` in -- ia64) -- LDLIBRARY='libpython$(LDVERSION).so' -- ;; -- *) -- LDLIBRARY='libpython$(LDVERSION).sl' -- ;; -- esac -- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' -- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} -- ;; -+ case `uname -m` in -+ ia64) -+ LDLIBRARY='libpython$(LDVERSION).so' -+ ;; -+ *) -+ LDLIBRARY='libpython$(LDVERSION).sl' -+ ;; -+ esac -+ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' -+ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} -+ ;; - Darwin*) -- LDLIBRARY='libpython$(LDVERSION).dylib' -- BLDLIBRARY='-L. -lpython$(LDVERSION)' -- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} -- ;; -+ LDLIBRARY='libpython$(LDVERSION).dylib' -+ BLDLIBRARY='-L. -lpython$(LDVERSION)' -+ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} -+ ;; -+ iOS|tvOS|watchOS) -+ LDLIBRARY='libpython$(LDVERSION).dylib' -+ ;; - AIX*) -- LDLIBRARY='libpython$(LDVERSION).so' -- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} -- ;; -+ LDLIBRARY='libpython$(LDVERSION).so' -+ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} -+ ;; + import textwrap + from test import support +-from test.support import import_helper +-from test.support import os_helper ++from test.support import import_helper, is_apple, os_helper + from test.support.script_helper import ( + make_pkg, make_script, make_zip_pkg, make_zip_script, + assert_python_ok, assert_python_failure, spawn_python, kill_python) +@@ -555,12 +554,17 @@ + self.assertTrue(text[3].startswith('NameError')) - esac - else # shared is disabled - PY_ENABLE_SHARED=0 - case $ac_sys_system in - CYGWIN*) -- BLDLIBRARY='$(LIBRARY)' -- LDLIBRARY='libpython$(LDVERSION).dll.a' -- ;; -+ BLDLIBRARY='$(LIBRARY)' -+ LDLIBRARY='libpython$(LDVERSION).dll.a' -+ ;; - esac - fi + def test_non_ascii(self): +- # Mac OS X denies the creation of a file with an invalid UTF-8 name. ++ # Apple platforms deny the creation of a file with an invalid UTF-8 name. + # Windows allows creating a name with an arbitrary bytes name, but + # Python cannot a undecodable bytes argument to a subprocess. +- # WASI does not permit invalid UTF-8 names. +- if (os_helper.TESTFN_UNDECODABLE +- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): ++ # Emscripten/WASI does not permit invalid UTF-8 names. ++ if ( ++ os_helper.TESTFN_UNDECODABLE ++ and sys.platform not in { ++ "win32", "emscripten", "wasi" ++ } ++ and not is_apple ++ ): + name = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + elif os_helper.TESTFN_NONASCII: + name = os_helper.TESTFN_NONASCII +diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py +index 812f989d8f3..dfcf9e16e40 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 @@ + self.assertEqual(len(executor._threads), 1) + executor.shutdown(wait=True) -+AC_MSG_RESULT($LDLIBRARY) -+ - if test "$cross_compiling" = yes; then -- RUNSHARED= -+ RUNSHARED= - fi ++ @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): +diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py +index 8e98256a62c..7681e829d8b 100644 +--- a/Lib/test/test_fcntl.py ++++ b/Lib/test/test_fcntl.py +@@ -6,7 +6,7 @@ + import sys + import unittest + from multiprocessing import Process +-from test.support import verbose, cpython_only ++from test.support import cpython_only, is_apple, requires_subprocess, verbose + from test.support.import_helper import import_module + from test.support.os_helper import TESTFN, unlink - AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform]) -@@ -1593,8 +1964,6 @@ - PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" - fi +@@ -25,7 +25,7 @@ + start_len = "qq" --AC_MSG_RESULT($LDLIBRARY) -- - # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable - AS_CASE([$ac_sys_system/$ac_sys_emscripten_target], - [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js'], -@@ -1635,11 +2004,16 @@ + if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd')) +- or sys.platform == 'darwin'): ++ or is_apple): + if struct.calcsize('l') == 8: + off_t = 'l' + pid_t = 'i' +@@ -156,6 +156,7 @@ + self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH) - AC_CHECK_TOOLS([READELF], [readelf], [:]) - if test "$cross_compiling" = yes; then -- case "$READELF" in -- readelf|:) -- AC_MSG_ERROR([readelf for the host is required for cross builds]) -- ;; -- esac -+ case "$ac_sys_system" in -+ iOS|tvOS|watchOS) ;; -+ *) -+ case "$READELF" in -+ readelf|:) -+ AC_MSG_ERROR([readelf for the host is required for cross builds]) -+ ;; -+ esac -+ ;; -+ esac - fi - AC_SUBST(READELF) + @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") ++ @requires_subprocess() + def test_lockf_exclusive(self): + self.f = open(TESTFN, 'wb+') + cmd = fcntl.LOCK_EX | fcntl.LOCK_NB +@@ -167,6 +168,7 @@ + self.assertEqual(p.exitcode, 0) -@@ -3179,6 +3553,11 @@ - BLDSHARED="$LDSHARED" - fi - ;; -+ iOS/*|tvOS/*|watchOS/*) -+ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' -+ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' -+ BLDSHARED="$LDSHARED" -+ ;; - Emscripten|WASI) - LDSHARED='$(CC) -shared' - LDCXXSHARED='$(CXX) -shared';; -@@ -3299,30 +3678,34 @@ - Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; - Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; - # -u libsys_s pulls in all symbols in libsys -- Darwin/*) -+ Darwin/*|iOS/*|tvOS/*|watchOS/*) - LINKFORSHARED="$extra_undefs -framework CoreFoundation" + @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") ++ @requires_subprocess() + def test_lockf_share(self): + self.f = open(TESTFN, 'wb+') + cmd = fcntl.LOCK_SH | fcntl.LOCK_NB +diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py +index d813ecdcd6f..38e405933b2 100644 +--- a/Lib/test/test_ftplib.py ++++ b/Lib/test/test_ftplib.py +@@ -18,6 +18,7 @@ - # Issue #18075: the default maximum stack size (8MBytes) is too - # small for the default recursion limit. Increase the stack size - # to ensure that tests don't crash -- stack_size="1000000" # 16 MB -- if test "$with_ubsan" = "yes" -- then -- # Undefined behavior sanitizer requires an even deeper stack -- stack_size="4000000" # 64 MB -- fi -+ stack_size="1000000" # 16 MB -+ if test "$with_ubsan" = "yes" -+ then -+ # Undefined behavior sanitizer requires an even deeper stack -+ stack_size="4000000" # 64 MB -+ fi + from unittest import TestCase, skipUnless + from test import support ++from test.support import requires_subprocess + from test.support import threading_helper + from test.support import socket_helper + from test.support import warnings_helper +@@ -902,6 +903,7 @@ -- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" -+ AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE], -+ [0x$stack_size], -+ [Custom thread stack size depending on chosen sanitizer runtimes.]) -- AC_DEFINE_UNQUOTED(THREAD_STACK_SIZE, -- 0x$stack_size, -- [Custom thread stack size depending on chosen sanitizer runtimes.]) -+ if test $ac_sys_system = "Darwin"; then -+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + @skipUnless(ssl, "SSL not available") ++@requires_subprocess() + class TestTLS_FTPClassMixin(TestFTPClass): + """Repeat TestFTPClass tests starting the TLS layer for both control + and data connections first. +@@ -918,6 +920,7 @@ -- if test "$enable_framework" -- then -- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' -+ if test "$enable_framework"; then -+ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' -+ fi -+ LINKFORSHARED="$LINKFORSHARED" -+ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then -+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' - fi -- LINKFORSHARED="$LINKFORSHARED";; -+ ;; - OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; - SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; - ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; -@@ -3634,7 +4017,7 @@ - AC_ARG_WITH(system_ffi, - AS_HELP_STRING([--with-system-ffi], [build _ctypes module using an installed ffi library, see Doc/library/ctypes.rst (default is system-dependent)]),,,) --if test "$ac_sys_system" = "Darwin" -+if test "$ac_sys_system" = "Darwin" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" - then - case "$with_system_ffi" in - "") -@@ -3659,9 +4042,11 @@ - if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then - LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`" - else -- LIBFFI_INCLUDEDIR="" -+ LIBFFI_INCLUDEDIR="${LIBFFI_INCLUDEDIR}" - fi - AC_SUBST(LIBFFI_INCLUDEDIR) -+AC_SUBST(LIBFFI_LIBDIR) -+AC_SUBST(LIBFFI_LIB) + @skipUnless(ssl, "SSL not available") ++@requires_subprocess() + class TestTLS_FTPClass(TestCase): + """Specific TLS_FTP class tests.""" - # Check for use of the system libmpdec library - AC_MSG_CHECKING(for --with-system-libmpdec) -@@ -4594,27 +4979,27 @@ - # checks for library functions - AC_CHECK_FUNCS([ \ - accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ -- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ -+ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ - faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ -- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ -- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ -- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ -+ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ -+ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \ -+ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ - getpeername getpgid getpid getppid getpriority _getpty \ - getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ - getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ - lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ - mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ -- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ -+ pipe2 plock poll posix_fadvise posix_fallocate \ - pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ - pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ - rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ - sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ - sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ - setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ -- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ -+ setresuid setreuid setsid setuid setvbuf shutdown sigaction \ - sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ - sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ -- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ -+ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ - tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ - wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ - ]) -@@ -4626,11 +5011,28 @@ - AC_CHECK_FUNCS(lchmod) - fi +diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py +index 01578539eb3..90669f5ff45 100644 +--- a/Lib/test/test_gc.py ++++ b/Lib/test/test_gc.py +@@ -1188,6 +1188,7 @@ + self.assertEqual(len(gc.garbage), 0) --AC_CHECK_DECL(dirfd, -- AC_DEFINE(HAVE_DIRFD, 1, -- Define if you have the 'dirfd' function or macro.), , -- [#include -- #include ]) -+# iOS/tvOS/watchOS define some system methods that can be linked (so they are -+# found by configure), but either raise a compilation error (because the -+# header definition prevents usage - autoconf doesn't use the headers), or -+# raise an error if used at runtime. Force these symbols off. -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ AC_CHECK_FUNCS([ getentropy getgroups system ]) -+fi -+ -+# tvOS/watchOS have some additional methods that can be found, but not used. -+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ AC_CHECK_FUNCS([ \ -+ execv fork fork1 posix_spawn posix_spawnp \ -+ sigaltstack \ -+ ]) -+fi -+ -+AC_CHECK_DECL([dirfd], -+ [AC_DEFINE([HAVE_DIRFD], [1], -+ [Define if you have the 'dirfd' function or macro.])], -+ [], -+ [@%:@include -+ @%:@include ]) - dnl PY_CHECK_FUNC(FUNCTION, [INCLUDES], [AC_DEFINE-VAR]) - AC_DEFUN([PY_CHECK_FUNC], -@@ -4883,22 +5285,22 @@ - ]) ++ @requires_subprocess() + @unittest.skipIf(BUILD_WITH_NDEBUG, + 'built with -NDEBUG') + def test_refcount_errors(self): +diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py +index 4f311c2d498..b77cd4c67d6 100644 +--- a/Lib/test/test_genericpath.py ++++ b/Lib/test/test_genericpath.py +@@ -7,9 +7,9 @@ + import sys + import unittest + import warnings +-from test.support import is_emscripten +-from test.support import os_helper +-from test.support import warnings_helper ++from test.support import ( ++ is_apple, is_emscripten, os_helper, warnings_helper ++) + from test.support.script_helper import assert_python_ok + from test.support.os_helper import FakePath + +@@ -483,12 +483,16 @@ + self.assertIsInstance(abspath(path), str) - # check for openpty, login_tty, and forkpty -- --AC_CHECK_FUNCS(openpty,, -- AC_CHECK_LIB(util,openpty, -- [AC_DEFINE(HAVE_OPENPTY) LIBS="$LIBS -lutil"], -- AC_CHECK_LIB(bsd,openpty, [AC_DEFINE(HAVE_OPENPTY) LIBS="$LIBS -lbsd"]) -- ) --) --AC_SEARCH_LIBS([login_tty], [util], -- [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])] --) --AC_CHECK_FUNCS(forkpty,, -- AC_CHECK_LIB(util,forkpty, -- [AC_DEFINE(HAVE_FORKPTY) LIBS="$LIBS -lutil"], -- AC_CHECK_LIB(bsd,forkpty, [AC_DEFINE(HAVE_FORKPTY) LIBS="$LIBS -lbsd"]) -- ) --) -+# tvOS/watchOS have functions for tty, but can't use them -+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ AC_CHECK_FUNCS([openpty], [], -+ [AC_CHECK_LIB([util], [openpty], -+ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"], -+ [AC_CHECK_LIB([bsd], [openpty], -+ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])]) -+ AC_SEARCH_LIBS([login_tty], [util], -+ [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])] -+ ) -+ AC_CHECK_FUNCS([forkpty], [], -+ [AC_CHECK_LIB([util], [forkpty], -+ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"], -+ [AC_CHECK_LIB([bsd], [forkpty], -+ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])]) -+fi + def test_nonascii_abspath(self): +- if (os_helper.TESTFN_UNDECODABLE +- # macOS and Emscripten deny the creation of a directory with an +- # invalid UTF-8 name. Windows allows creating a directory with an +- # arbitrary bytes name, but fails to enter this directory +- # (when the bytes name is used). +- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): ++ if ( ++ os_helper.TESTFN_UNDECODABLE ++ # Apple platforms and Emscripten/WASI deny the creation of a ++ # directory with an invalid UTF-8 name. Windows allows creating a ++ # directory with an arbitrary bytes name, but fails to enter this ++ # directory (when the bytes name is used). ++ and sys.platform not in { ++ "win32", "emscripten", "wasi" ++ } and not is_apple ++ ): + name = os_helper.TESTFN_UNDECODABLE + elif os_helper.TESTFN_NONASCII: + name = os_helper.TESTFN_NONASCII +diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py +index cfd8a101dcc..b8ac2544be5 100644 +--- a/Lib/test/test_httpservers.py ++++ b/Lib/test/test_httpservers.py +@@ -30,8 +30,9 @@ - # check for long file support functions - AC_CHECK_FUNCS(fseek64 fseeko fstatvfs ftell64 ftello statvfs) -@@ -4972,11 +5374,17 @@ - ]) - ]) + import unittest + from test import support +-from test.support import os_helper +-from test.support import threading_helper ++from test.support import ( ++ is_apple, os_helper, requires_subprocess, threading_helper ++) --AC_CHECK_FUNCS(clock_settime, [], [ -- AC_CHECK_LIB(rt, clock_settime, [ -- AC_DEFINE(HAVE_CLOCK_SETTIME, 1) -- ]) --]) -+# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by -+# configure), but when used in an unprivileged process, it crashes rather than -+# returning an error. Force the symbol off. -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -+then -+ AC_CHECK_FUNCS([clock_settime], [], [ -+ AC_CHECK_LIB([rt], [clock_settime], [ -+ AC_DEFINE([HAVE_CLOCK_SETTIME], [1]) -+ ]) -+ ]) -+fi + support.requires_working_socket(module=True) - AC_CHECK_FUNCS(clock_nanosleep, [], [ - AC_CHECK_LIB(rt, clock_nanosleep, [ -@@ -5122,7 +5530,9 @@ - [ac_cv_buggy_getaddrinfo=no], - [ac_cv_buggy_getaddrinfo=yes], - [ --if test "${enable_ipv6+set}" = set; then -+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then -+ ac_cv_buggy_getaddrinfo="no" -+elif test "${enable_ipv6+set}" = set; then - ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" - else - ac_cv_buggy_getaddrinfo=yes -@@ -5692,19 +6102,19 @@ - # - # In Python 3.2 and older, --with-wide-unicode added a 'u' flag. - # In Python 3.7 and older, --with-pymalloc added a 'm' flag. --AC_SUBST(SOABI) --AC_MSG_CHECKING(ABIFLAGS) --AC_MSG_RESULT($ABIFLAGS) --AC_MSG_CHECKING(SOABI) --SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} --AC_MSG_RESULT($SOABI) -+AC_SUBST([SOABI]) -+AC_MSG_CHECKING([ABIFLAGS]) -+AC_MSG_RESULT([$ABIFLAGS]) -+AC_MSG_CHECKING([SOABI]) -+SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} -+AC_MSG_RESULT([$SOABI]) +@@ -410,8 +411,8 @@ + reader.close() + return body - # Release and debug (Py_DEBUG) ABI are compatible, but not Py_TRACE_REFS ABI - if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then - # Similar to SOABI but remove "d" flag from ABIFLAGS -- AC_SUBST(ALT_SOABI) -- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} -- AC_DEFINE_UNQUOTED(ALT_SOABI, "${ALT_SOABI}", -+ AC_SUBST([ALT_SOABI]) -+ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} -+ AC_DEFINE_UNQUOTED([ALT_SOABI], ["${ALT_SOABI}"], - [Alternative SOABI used in debug build to load C extensions built in release mode]) - fi +- @unittest.skipIf(sys.platform == 'darwin', +- 'undecodable name cannot always be decoded on macOS') ++ @unittest.skipIf(is_apple, ++ 'undecodable name cannot always be decoded on Apple platforms') + @unittest.skipIf(sys.platform == 'win32', + 'undecodable name cannot be decoded on win32') + @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, +@@ -422,11 +423,11 @@ + with open(os.path.join(self.tempdir, filename), 'wb') as f: + f.write(os_helper.TESTFN_UNDECODABLE) + response = self.request(self.base_url + '/') +- if sys.platform == 'darwin': +- # On Mac OS the HFS+ filesystem replaces bytes that aren't valid +- # UTF-8 into a percent-encoded value. ++ if is_apple: ++ # On Apple platforms the HFS+ filesystem replaces bytes that ++ # aren't valid UTF-8 into a percent-encoded value. + for name in os.listdir(self.tempdir): +- if name != 'test': # Ignore a filename created in setUp(). ++ if name != 'test': # Ignore a filename created in setUp(). + filename = name + break + body = self.check_status_and_reason(response, HTTPStatus.OK) +@@ -697,6 +698,7 @@ -@@ -6193,28 +6603,35 @@ - AC_MSG_NOTICE([checking for device files]) + @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, + "This test can't be run reliably as root (issue #13308).") ++@requires_subprocess() + class CGIHTTPServerTestCase(BaseTestCase): + class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler): + pass +diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py +index aa67cc3514e..7c684c5799f 100644 +--- a/Lib/test/test_imp.py ++++ b/Lib/test/test_imp.py +@@ -10,6 +10,7 @@ + from test.support import os_helper + from test.support import script_helper + from test.support import warnings_helper ++from test.support import is_apple_mobile + from test.support import is_wasi + import unittest + import warnings +@@ -234,6 +235,7 @@ + self.assertIsNot(orig_getenv, new_os.getenv) - dnl NOTE: Inform user how to proceed with files when cross compiling. --if test "x$cross_compiling" = xyes; then -- if test "${ac_cv_file__dev_ptmx+set}" != set; then -- AC_MSG_CHECKING([for /dev/ptmx]) -- AC_MSG_RESULT([not set]) -- AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) -- fi -- if test "${ac_cv_file__dev_ptc+set}" != set; then -- AC_MSG_CHECKING([for /dev/ptc]) -- AC_MSG_RESULT([not set]) -- AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) -+dnl iOS cross-compile builds are predictable; they won't ever -+dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. -+if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then -+ ac_cv_file__dev_ptmx=no -+ ac_cv_file__dev_ptc=no -+else -+ if test "x$cross_compiling" = xyes; then -+ if test "${ac_cv_file__dev_ptmx+set}" != set; then -+ AC_MSG_CHECKING([for /dev/ptmx]) -+ AC_MSG_RESULT([not set]) -+ AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) -+ fi -+ if test "${ac_cv_file__dev_ptc+set}" != set; then -+ AC_MSG_CHECKING([for /dev/ptc]) -+ AC_MSG_RESULT([not set]) -+ AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) -+ fi - fi --fi + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_issue15828_load_extensions(self): + # Issue 15828 picked up that the adapter between the old imp API + # and importlib couldn't handle C extensions +@@ -246,6 +248,7 @@ + self.assertEqual(mod.__name__, example) --AC_CHECK_FILE(/dev/ptmx, [], []) --if test "x$ac_cv_file__dev_ptmx" = xyes; then -- AC_DEFINE(HAVE_DEV_PTMX, 1, -- [Define to 1 if you have the /dev/ptmx device file.]) --fi --AC_CHECK_FILE(/dev/ptc, [], []) --if test "x$ac_cv_file__dev_ptc" = xyes; then -- AC_DEFINE(HAVE_DEV_PTC, 1, -- [Define to 1 if you have the /dev/ptc device file.]) -+ AC_CHECK_FILE([/dev/ptmx], [], []) -+ if test "x$ac_cv_file__dev_ptmx" = xyes; then -+ AC_DEFINE([HAVE_DEV_PTMX], [1], -+ [Define to 1 if you have the /dev/ptmx device file.]) -+ fi -+ AC_CHECK_FILE([/dev/ptc], [], []) -+ if test "x$ac_cv_file__dev_ptc" = xyes; then -+ AC_DEFINE([HAVE_DEV_PTC], [1], -+ [Define to 1 if you have the /dev/ptc device file.]) -+ fi - fi + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_issue16421_multiple_modules_in_one_dll(self): + # Issue 16421: loading several modules from the same compiled file fails + m = '_testimportmultiple' +@@ -273,6 +276,7 @@ + self.assertEqual(name, err.exception.name) - if test $ac_sys_system = Darwin -@@ -6523,6 +6940,7 @@ - AS_CASE([$ac_sys_system], - [Emscripten], [with_ensurepip=no], - [WASI], [with_ensurepip=no], -+ [iOS|tvOS|watchOS], [with_ensurepip=no], - [with_ensurepip=upgrade] - ) - ]) -@@ -6856,6 +7274,28 @@ - [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], - [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])], - [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])], -+ [iOS|tvOS|watchOS], [ -+ dnl subprocess and multiprocessing are not supported (no fork syscall). -+ dnl curses and tkinter user interface are not available. -+ dnl gdbm and nis aren't available -+ dnl Stub implementations are provided for pwd, grp etc APIs -+ PY_STDLIB_MOD_SET_NA( -+ [_curses], -+ [_curses_panel], -+ [_gdbm], -+ [_multiprocessing], -+ [_posixshmem], -+ [_posixsubprocess], -+ [_scproxy], -+ [_tkinter], -+ [grp], -+ [nis], -+ [readline], -+ [pwd], -+ [spwd], -+ [syslog], -+ ) -+ ], - [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], - [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], - [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], ---- /dev/null -+++ b/iOS/README.rst -@@ -0,0 +1,375 @@ -+==================== -+Python on iOS README -+==================== -+ -+:Authors: -+ Russell Keith-Magee (2023-11) -+ -+This document provides a quick overview of some iOS specific features in the -+Python distribution. -+ -+These instructions are only needed if you're planning to compile Python for iOS -+yourself. Most users should *not* need to do this. If you're looking to -+experiment with writing an iOS app in Python, tools such as `BeeWare's Briefcase -+`__ and `Kivy's Buildozer -+`__ will provide a much more approachable -+user experience. -+ -+Compilers for building on iOS -+============================= -+ -+Building for iOS requires the use of Apple's Xcode tooling. It is strongly -+recommended that you use the most recent stable release of Xcode. This will -+require the use of the most (or second-most) recently released macOS version, -+as Apple does not maintain Xcode for older macOS versions. The Xcode Command -+Line Tools are not sufficient for iOS development; you need a *full* Xcode -+install. -+ -+If you want to run your code on the iOS simulator, you'll also need to install -+an iOS Simulator Platform. You should be prompted to select an iOS Simulator -+Platform when you first run Xcode. Alternatively, you can add an iOS Simulator -+Platform by selecting an open the Platforms tab of the Xcode Settings panel. -+ -+iOS specific arguments to configure -+=================================== -+ -+* ``--enable-framework[=DIR]`` -+ -+ This argument specifies the location where the Python.framework will be -+ installed. If ``DIR`` is not specified, the framework will be installed into -+ a subdirectory of the ``iOS/Frameworks`` folder. -+ -+ This argument *must* be provided when configuring iOS builds. iOS does not -+ support non-framework builds. -+ -+* ``--with-framework-name=NAME`` -+ -+ Specify the name for the Python framework; defaults to ``Python``. -+ -+ .. admonition:: Use this option with care! -+ -+ Unless you know what you're doing, changing the name of the Python -+ framework on iOS is not advised. If you use this option, you won't be able -+ to run the ``make testios`` target without making signficant manual -+ alterations, and you won't be able to use any binary packages unless you -+ compile them yourself using your own framework name. -+ -+Building Python on iOS -+====================== -+ -+ABIs and Architectures -+---------------------- -+ -+iOS apps can be deployed on physical devices, and on the iOS simulator. Although -+the API used on these devices is identical, the ABI is different - you need to -+link against different libraries for an iOS device build (``iphoneos``) or an -+iOS simulator build (``iphonesimulator``). -+ -+Apple uses the ``XCframework`` format to allow specifying a single dependency -+that supports multiple ABIs. An ``XCframework`` is a wrapper around multiple -+ABI-specific frameworks that share a common API. -+ -+iOS can also support different CPU architectures within each ABI. At present, -+there is only a single supported architecture on physical devices - ARM64. -+However, the *simulator* supports 2 architectures - ARM64 (for running on Apple -+Silicon machines), and x86_64 (for running on older Intel-based machines). -+ -+To support multiple CPU architectures on a single platform, Apple uses a "fat -+binary" format - a single physical file that contains support for multiple -+architectures. It is possible to compile and use a "thin" single architecture -+version of a binary for testing purposes; however, the "thin" binary will not be -+portable to machines using other architectures. -+ -+Building a single-architecture framework -+---------------------------------------- -+ -+The Python build system will create a ``Python.framework`` that supports a -+*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a -+framework to contain non-library content, so the iOS build will produce a -+``bin`` and ``lib`` folder in the same output folder as ``Python.framework``. -+The ``lib`` folder will be needed at runtime to support the Python library. -+ -+If you want to use Python in a real iOS project, you need to produce multiple -+``Python.framework`` builds, one for each ABI and architecture. iOS builds of -+Python *must* be constructed as framework builds. To support this, you must -+provide the ``--enable-framework`` flag when configuring the build. The build -+also requires the use of cross-compilation. The minimal commands for building -+Python for the ARM64 iOS simulator will look something like:: -+ -+ $ export PATH="$(pwd)/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" -+ $ ./configure \ -+ --enable-framework \ -+ --host=arm64-apple-ios-simulator \ -+ --build=arm64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install -+ -+In this invocation: -+ -+* ``iOS/Resources/bin`` has been added to the path, providing some shims for the -+ compilers and linkers needed by the build. Xcode requires the use of ``xcrun`` -+ to invoke compiler tooling. However, if ``xcrun`` is pre-evaluated and the -+ result passed to ``configure``, these results can embed user- and -+ version-specific paths into the sysconfig data, which limits the portability -+ of the compiled Python. Alternatively, if ``xcrun`` is used *as* the compiler, -+ it requires that compiler variables like ``CC`` include spaces, which can -+ cause significant problems with many C configuration systems which assume that -+ ``CC`` will be a single executable. -+ -+ To work around this problem, the ``iOS/Resources/bin`` folder contains some -+ wrapper scripts that present as simple compilers and linkers, but wrap -+ underlying calls to ``xcrun``. This allows configure to use a ``CC`` -+ definition without spaces, and without user- or version-specific paths, while -+ retaining the ability to adapt to the local Xcode install. These scripts are -+ included in the ``bin`` directory of an iOS install. -+ -+ These scripts will, by default, use the currently active Xcode installation. -+ If you want to use a different Xcode installation, you can use -+ ``xcode-select`` to set a new default Xcode globally, or you can use the -+ ``DEVELOPER_DIR`` environment variable to specify an Xcode install. The -+ scripts will use the default ``iphoneos``/``iphonesimulator`` SDK version for -+ the select Xcode install; if you want to use a different SDK, you can set the -+ ``IOS_SDK_VERSION`` environment variable. (e.g, setting -+ ``IOS_SDK_VERSION=17.1`` would cause the scripts to use the ``iphoneos17.1`` -+ and ``iphonesimulator17.1`` SDKs, regardless of the Xcode default.) -+ -+ The path has also been cleared of any user customizations. A common source of -+ bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS -+ build. Resetting the path to a known "bare bones" value is the easiest way to -+ avoid these problems. -+ -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: -+ -+ - ``arm64-apple-ios`` for ARM64 iOS devices. -+ - ``arm64-apple-ios-simulator`` for the iOS simulator running on Apple -+ Silicon devices. -+ - ``x86_64-apple-ios-simulator`` for the iOS simulator running on Intel -+ devices. -+ -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: -+ -+ - ``arm64-apple-darwin`` for Apple Silicon devices. -+ - ``x86_64-apple-darwin`` for Intel devices. -+ -+* ``/path/to/python.exe`` is the path to a Python binary on the machine that -+ will be running the compiler. This is needed because the Python compilation -+ process involves running some Python code. On a normal desktop build of -+ Python, you can compile a python interpreter and then use that interpreter to -+ run Python code. However, the binaries produced for iOS won't run on macOS, so -+ you need to provide an external Python interpreter. This interpreter must be -+ the same version as the Python that is being compiled. To be completely safe, -+ this should be the *exact* same commit hash. However, the longer a Python -+ release has been stable, the more likely it is that this constraint can be -+ relaxed - the same micro version will often be sufficient. -+ -+* The ``install`` target for iOS builds is slightly different to other -+ platforms. On most platforms, ``make install`` will install the build into -+ the final runtime location. This won't be the case for iOS, as the final -+ runtime location will be on a physical device. -+ -+ However, you still need to run the ``install`` target for iOS builds, as it -+ performs some final framework assembly steps. The location specified with -+ ``--enable-framework`` will be the location where ``make install`` will -+ assemble the complete iOS framework. This completed framework can then -+ be copied and relocated as required. -+ -+For a full CPython build, you also need to specify the paths to iOS builds of -+the binary libraries that CPython depends on (XZ, BZip2, LibFFI and OpenSSL). -+This can be done by defining the ``LIBLZMA_CFLAGS``, ``LIBLZMA_LIBS``, -+``BZIP2_CFLAGS``, ``BZIP2_LIBS``, ``LIBFFI_CFLAGS``, and ``LIBFFI_LIBS`` -+environment variables, and the ``--with-openssl`` configure option. Versions of -+these libraries pre-compiled for iOS can be found in `this repository -+`__. LibFFI is -+especially important, as many parts of the standard library (including the -+``platform``, ``sysconfig`` and ``webbrowser`` modules) require the use of the -+``ctypes`` module at runtime. -+ -+By default, Python will be compiled with an iOS deployment target (i.e., the -+minimum supported iOS version) of 13.0. To specify a different deployment -+target, provide the version number as part of the ``--host`` argument - for -+example, ``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 -+simulator build with a deployment target of 15.4. -+ -+Merge thin frameworks into fat frameworks -+----------------------------------------- -+ -+Once you've built a ``Python.framework`` for each ABI and and architecture, you -+must produce a "fat" framework for each ABI that contains all the architectures -+for that ABI. -+ -+The ``iphoneos`` build only needs to support a single architecture, so it can be -+used without modification. -+ -+If you only want to support a single simulator architecture, (e.g., only support -+ARM64 simulators), you can use a single architecture ``Python.framework`` build. -+However, if you want to create ``Python.xcframework`` that supports *all* -+architectures, you'll need to merge the ``iphonesimulator`` builds for ARM64 and -+x86_64 into a single "fat" framework. -+ -+The "fat" framework can be constructed by performing a directory merge of the -+content of the two "thin" ``Python.framework`` directories, plus the ``bin`` and -+``lib`` folders for each thin framework. When performing this merge: -+ -+* The pure Python standard library content is identical for each architecture, -+ except for a handful of platform-specific files (such as the ``sysconfig`` -+ module). Ensure that the "fat" framework has the union of all standard library -+ files. -+ -+* Any binary files in the standard library, plus the main -+ ``libPython3.X.dylib``, can be merged using the ``lipo`` tool, provide by -+ Xcode:: -+ -+ $ lipo -create -output module.dylib path/to/x86_64/module.dylib path/to/arm64/module.dylib -+ -+* The header files will be indentical on both architectures, except for -+ ``pyconfig.h``. Copy all the headers from one platform (say, arm64), rename -+ ``pyconfig.h`` to ``pyconfig-arm64.h``, and copy the ``pyconfig.h`` for the -+ other architecture into the merged header folder as ``pyconfig-x86_64.h``. -+ Then copy the ``iOS/Resources/pyconfig.h`` file from the CPython sources into -+ the merged headers folder. This will allow the two Python architectures to -+ share a common ``pyconfig.h`` header file. -+ -+At this point, you should have 2 Python.framework folders - one for ``iphoneos``, -+and one for ``iphonesimulator`` that is a merge of x86+64 and ARM64 content. -+ -+Merge frameworks into an XCframework -+------------------------------------ -+ -+Now that we have 2 (potentially fat) ABI-specific frameworks, we can merge those -+frameworks into a single ``XCframework``. -+ -+The initial skeleton of an ``XCframework`` is built using:: -+ -+ xcodebuild -create-xcframework -output Python.xcframework -framework path/to/iphoneos/Python.framework -framework path/to/iphonesimulator/Python.framework -+ -+Then, copy the ``bin`` and ``lib`` folders into the architecture-specific slices of -+the XCframework:: -+ -+ cp path/to/iphoneos/bin Python.xcframework/ios-arm64 -+ cp path/to/iphoneos/lib Python.xcframework/ios-arm64 -+ -+ cp path/to/iphonesimulator/bin Python.xcframework/ios-arm64_x86_64-simulator -+ cp path/to/iphonesimulator/lib Python.xcframework/ios-arm64_x86_64-simulator -+ -+Note that the name of the architecture-specific slice for the simulator will -+depend on the CPU architecture(s) that you build. -+ -+You now have a Python.xcframework that can be used in a project. -+ -+Testing Python on iOS -+===================== -+ -+The ``iOS/testbed`` folder that contains an Xcode project that is able to run -+the iOS test suite. This project converts the Python test suite into a single -+test case in Xcode's XCTest framework. The single XCTest passes if the test -+suite passes. -+ -+To run the test suite, configure a Python build for an iOS simulator (i.e., -+``--host=arm64-apple-ios-simulator`` or ``--host=x86_64-apple-ios-simulator`` -+), specifying a framework build (i.e. ``--enable-framework``). Ensure that your -+``PATH`` has been configured to include the ``iOS/Resources/bin`` folder and -+exclude any non-iOS tools, then run:: -+ -+ $ make all -+ $ make install -+ $ make testios -+ -+This will: -+ -+* Build an iOS framework for your chosen architecture; -+* Finalize the single-platform framework; -+* Make a clean copy of the testbed project; -+* Install the Python iOS framework into the copy of the testbed project; and -+* Run the test suite on an "iPhone SE (3rd generation)" simulator. -+ -+On success, the test suite will exit and report successful completion of the -+test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15 -+minutes to run; a couple of extra minutes is required to compile the testbed -+project, and then boot and prepare the iOS simulator. -+ -+Debugging test failures -+----------------------- -+ -+Running ``make test`` generates a standalone version of the ``iOS/testbed`` -+project, and runs the full test suite. It does this using ``iOS/testbed`` -+itself - the folder is an executable module that can be used to create and run -+a clone of the testbed project. -+ -+You can generate your own standalone testbed instance by running:: -+ -+ $ python iOS/testbed clone --framework iOS/Frameworks/arm64-iphonesimulator my-testbed -+ -+This invocation assumes that ``iOS/Frameworks/arm64-iphonesimulator`` is the -+path to the iOS simulator framework for your platform (ARM64 in this case); -+``my-testbed`` is the name of the folder for the new testbed clone. -+ -+You can then use the ``my-testbed`` folder to run the Python test suite, -+passing in any command line arguments you may require. For example, if you're -+trying to diagnose a failure in the ``os`` module, you might run:: -+ -+ $ python my-testbed run -- test -W test_os -+ -+This is the equivalent of running ``python -m test -W test_os`` on a desktop -+Python build. Any arguments after the ``--`` will be passed to testbed as if -+they were arguments to ``python -m`` on a desktop machine. -+ -+You can also open the testbed project in Xcode by running:: -+ -+ $ open my-testbed/iOSTestbed.xcodeproj -+ -+This will allow you to use the full Xcode suite of tools for debugging. -+ -+Testing on an iOS device -+^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+To test on an iOS device, the app needs to be signed with known developer -+credentials. To obtain these credentials, you must have an iOS Developer -+account, and your Xcode install will need to be logged into your account (see -+the Accounts tab of the Preferences dialog). -+ -+Once the project is open, and you're signed into your Apple Developer account, -+select the root node of the project tree (labeled "iOSTestbed"), then the -+"Signing & Capabilities" tab in the details page. Select a development team -+(this will likely be your own name), and plug in a physical device to your -+macOS machine with a USB cable. You should then be able to select your physical -+device from the list of targets in the pulldown in the Xcode titlebar. + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_load_module_extension_file_is_None(self): + # When loading an extension module and the file is None, open one + # on the behalf of imp.load_dynamic(). +@@ -286,6 +290,7 @@ + imp.load_module(name, None, *found[1:]) + + @requires_load_dynamic ++ @unittest.skipIf(is_apple_mobile, "FIXME: edge case of module loader") + def test_issue24748_load_module_skips_sys_modules_check(self): + name = 'test.imp_dummy' + try: +diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py +index f991f323573..190dbaa9d2e 100644 +--- a/Lib/test/test_import/__init__.py ++++ b/Lib/test/test_import/__init__.py +@@ -20,7 +20,7 @@ + + from test.support import os_helper + from test.support import ( +- STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only, is_emscripten, ++ STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only, is_apple_mobile, is_emscripten, + is_wasi) + from test.support.import_helper import ( + forget, make_legacy_pyc, unlink, unload, ready_to_import, +@@ -83,10 +83,14 @@ + from _testcapi import i_dont_exist + self.assertEqual(cm.exception.name, '_testcapi') + if hasattr(_testcapi, "__file__"): +- self.assertEqual(cm.exception.path, _testcapi.__file__) ++ # The path on the exception is strictly the spec origin, not the ++ # module's __file__. For most cases, these are the same; but on ++ # iOS, the Framework relocation process results in the exception ++ # being raised from the spec location. ++ self.assertEqual(cm.exception.path, _testcapi.__spec__.origin) + self.assertRegex( + str(cm.exception), +- r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|pyd)\)" ++ r"cannot import name 'i_dont_exist' from '_testcapi' \(.*(\.(so|pyd))?\)" + ) + else: + self.assertEqual( +diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py +index 3de120958fd..cdc8884d668 100644 +--- a/Lib/test/test_importlib/extension/test_finder.py ++++ b/Lib/test/test_importlib/extension/test_finder.py +@@ -1,3 +1,4 @@ ++from test.support import is_apple_mobile + from test.test_importlib import abc, util + + machinery = util.import_importlib('importlib.machinery') +@@ -19,9 +20,27 @@ + ) + + def find_spec(self, fullname): +- importer = self.machinery.FileFinder(util.EXTENSIONS.path, +- (self.machinery.ExtensionFileLoader, +- self.machinery.EXTENSION_SUFFIXES)) ++ if is_apple_mobile: ++ # Apple mobile platforms require a specialist loader that uses ++ # .fwork files as placeholders for the true `.so` files. ++ loaders = [ ++ ( ++ self.machinery.AppleFrameworkLoader, ++ [ ++ ext.replace(".so", ".fwork") ++ for ext in self.machinery.EXTENSION_SUFFIXES ++ ] ++ ) ++ ] ++ else: ++ loaders = [ ++ ( ++ self.machinery.ExtensionFileLoader, ++ self.machinery.EXTENSION_SUFFIXES ++ ) ++ ] + -+Running specific tests -+^^^^^^^^^^^^^^^^^^^^^^ -+ -+As the test suite is being executed on an iOS simulator, it is not possible to -+pass in command line arguments to configure test suite operation. To work -+around this limitation, the arguments that would normally be passed as command -+line arguments are configured as part of the ``iOSTestbed-Info.plist`` file -+that is used to configure the iOS testbed app. In this file, the ``TestArgs`` -+key is an array containing the arguments that would be passed to ``python -m`` -+on the command line (including ``test`` in position 0, the name of the test -+module to be executed). -+ -+Disabling automated breakpoints -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+By default, Xcode will inserts an automatic breakpoint whenever a signal is -+raised. The Python test suite raises many of these signals as part of normal -+operation; unless you are trying to diagnose an issue with signals, the -+automatic breakpoints can be inconvenient. However, they can be disabled by -+creating a symbolic breakpoint that is triggered at the start of the test run. -+ -+Select "Debug > Breakpoints > Create Symbolic Breakpoint" from the Xcode menu, and -+populate the new brewpoint with the following details: -+ -+* **Name**: IgnoreSignals -+* **Symbol**: UIApplicationMain -+* **Action**: Add debugger commands for: -+ - ``process handle SIGINT -n true -p true -s false`` -+ - ``process handle SIGUSR1 -n true -p true -s false`` -+ - ``process handle SIGUSR2 -n true -p true -s false`` -+ - ``process handle SIGXFSZ -n true -p true -s false`` -+* Check the "Automatically continue after evaluating" box. -+ -+All other details can be left blank. When the process executes the -+``UIApplicationMain`` entry point, the breakpoint will trigger, run the debugger -+commands to disable the automatic breakpoints, and automatically resume. ---- /dev/null -+++ b/iOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ Python -+ CFBundleGetInfoString -+ Python Runtime and Library -+ CFBundleIdentifier -+ @PYTHONFRAMEWORKIDENTIFIER@ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleName -+ Python -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ %VERSION% -+ CFBundleLongVersionString -+ %VERSION%, (c) 2001-2024 Python Software Foundation. -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ %VERSION% -+ CFBundleSupportedPlatforms -+ -+ iPhoneOS -+ -+ MinimumOSVersion -+ @IPHONEOS_DEPLOYMENT_TARGET@ -+ -+ ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-ar -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-clang -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios "$@" ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios "$@" ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-cpp -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E "$@" ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator "$@" ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios-simulator "$@" ---- /dev/null -+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator -E "$@" ---- /dev/null -+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator "$@" ---- /dev/null -+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios-simulator "$@" ---- /dev/null -+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator -E "$@" ---- /dev/null -+++ b/iOS/Resources/dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ iPhoneOS -+ -+ MinimumOSVersion -+ 12.0 -+ CFBundleVersion -+ 1 -+ -+ ---- /dev/null -+++ b/iOS/Resources/pyconfig.h -@@ -0,0 +1,7 @@ -+#ifdef __arm64__ -+#include "pyconfig-arm64.h" -+#endif ++ importer = self.machinery.FileFinder(util.EXTENSIONS.path, *loaders) + + return importer.find_spec(fullname) + +diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py +index 2519149c071..828b6f0dfd3 100644 +--- a/Lib/test/test_importlib/extension/test_loader.py ++++ b/Lib/test/test_importlib/extension/test_loader.py +@@ -1,3 +1,4 @@ ++from test.support import is_apple_mobile + from warnings import catch_warnings + from test.test_importlib import abc, util + +@@ -25,8 +26,15 @@ + raise unittest.SkipTest( + f"{util.EXTENSIONS.name} is a builtin module" + ) +- self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name, +- util.EXTENSIONS.file_path) + -+#ifdef __x86_64__ -+#include "pyconfig-x86_64.h" -+#endif ---- /dev/null -+++ b/iOS/testbed/Python.xcframework/Info.plist -@@ -0,0 +1,44 @@ -+ -+ -+ -+ -+ AvailableLibraries -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ ios-arm64 -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ -+ SupportedPlatform -+ ios -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ ios-arm64_x86_64-simulator -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ x86_64 -+ -+ SupportedPlatform -+ ios -+ SupportedPlatformVariant -+ simulator -+ -+ -+ CFBundlePackageType -+ XFWK -+ XCFrameworkFormatVersion -+ 1.0 -+ -+ ---- /dev/null -+++ b/iOS/testbed/Python.xcframework/ios-arm64/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if is_apple_mobile: ++ self.LoaderClass = self.machinery.AppleFrameworkLoader ++ else: ++ self.LoaderClass = self.machinery.ExtensionFileLoader + -+It should be used as a target for `--enable-framework` when compiling an iOS on-device -+build for testing purposes. ---- /dev/null -+++ b/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++ self.loader = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path) + + def load_module(self, fullname): + with warnings.catch_warnings(): +@@ -43,13 +51,11 @@ + self.load_module('XXX') + + def test_equality(self): +- other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name, +- util.EXTENSIONS.file_path) ++ other = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path) + self.assertEqual(self.loader, other) + + def test_inequality(self): +- other = self.machinery.ExtensionFileLoader('_' + util.EXTENSIONS.name, +- util.EXTENSIONS.file_path) ++ other = self.LoaderClass('_' + util.EXTENSIONS.name, util.EXTENSIONS.file_path) + self.assertNotEqual(self.loader, other) + + def test_module(self): +@@ -60,8 +66,7 @@ + ('__package__', '')]: + self.assertEqual(getattr(module, attr), value) + self.assertIn(util.EXTENSIONS.name, sys.modules) +- self.assertIsInstance(module.__loader__, +- self.machinery.ExtensionFileLoader) ++ self.assertIsInstance(module.__loader__, self.LoaderClass) + + # No extension module as __init__ available for testing. + test_package = None +@@ -88,7 +93,7 @@ + self.assertFalse(self.loader.is_package(util.EXTENSIONS.name)) + for suffix in self.machinery.EXTENSION_SUFFIXES: + path = os.path.join('some', 'path', 'pkg', '__init__' + suffix) +- loader = self.machinery.ExtensionFileLoader('pkg', path) ++ loader = self.LoaderClass('pkg', path) + self.assertTrue(loader.is_package('pkg')) + + (Frozen_LoaderTests, +@@ -101,6 +106,14 @@ + def setUp(self): + if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS: + raise unittest.SkipTest("Requires dynamic loading support.") + -+It should be used as a target for `--enable-framework` when compiling an iOS simulator -+build for testing purposes (either x86_64 or ARM64). ---- /dev/null -+++ b/iOS/testbed/__main__.py -@@ -0,0 +1,468 @@ -+import argparse -+import asyncio -+import json -+import plistlib -+import re -+import shutil -+import subprocess -+import sys -+from contextlib import asynccontextmanager -+from datetime import datetime -+from itertools import chain -+from pathlib import Path ++ # Apple extensions must be distributed as frameworks. This requires ++ # a specialist loader. ++ if is_apple_mobile: ++ self.LoaderClass = self.machinery.AppleFrameworkLoader ++ else: ++ self.LoaderClass = self.machinery.ExtensionFileLoader + + self.name = '_testmultiphase' + if self.name in sys.builtin_module_names: + raise unittest.SkipTest( +@@ -109,8 +122,7 @@ + finder = self.machinery.FileFinder(None) + self.spec = importlib.util.find_spec(self.name) + assert self.spec +- self.loader = self.machinery.ExtensionFileLoader( +- self.name, self.spec.origin) ++ self.loader = self.LoaderClass(self.name, self.spec.origin) + + def load_module(self): + # Load the module from the test extension. +@@ -121,7 +133,7 @@ + def load_module_by_name(self, fullname): + # Load a module from the test extension by name. + origin = self.spec.origin +- loader = self.machinery.ExtensionFileLoader(fullname, origin) ++ loader = self.LoaderClass(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) +@@ -147,8 +159,7 @@ + with self.assertRaises(AttributeError): + module.__path__ + self.assertIs(module, sys.modules[self.name]) +- self.assertIsInstance(module.__loader__, +- self.machinery.ExtensionFileLoader) ++ self.assertIsInstance(module.__loader__, self.LoaderClass) + + def test_functionality(self): + # Test basic functionality of stuff defined in an extension module. +diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py +index 4f1b190f4cc..12eb544912f 100644 +--- a/Lib/test/test_importlib/util.py ++++ b/Lib/test/test_importlib/util.py +@@ -8,6 +8,7 @@ + import os.path + from test import support + from test.support import import_helper ++from test.support import is_apple_mobile + from test.support import os_helper + import unittest + import sys +@@ -43,6 +44,11 @@ + global EXTENSIONS + for path in sys.path: + for ext in machinery.EXTENSION_SUFFIXES: ++ # Apple mobile platforms mechanically load .so files, ++ # but the findable files are labelled .fwork ++ if is_apple_mobile: ++ ext = ext.replace(".so", ".fwork") + -+DECODE_ARGS = ("UTF-8", "backslashreplace") + filename = EXTENSIONS.name + ext + file_path = os.path.join(path, filename) + if os.path.exists(file_path): +diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py +index 5abb66c7ae4..f69f9bd38b8 100644 +--- a/Lib/test/test_inspect/test_inspect.py ++++ b/Lib/test/test_inspect/test_inspect.py +@@ -25,7 +25,7 @@ + except ImportError: + ThreadPoolExecutor = None + +-from test.support import cpython_only ++from test.support import cpython_only, is_apple_mobile + from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ + from test.support.import_helper import DirsOnSysPath, ready_to_import + from test.support.os_helper import TESTFN +@@ -783,6 +783,7 @@ + @unittest.skipIf(not hasattr(unicodedata, '__file__') or + unicodedata.__file__.endswith('.py'), + "unicodedata is not an external binary module") ++ @unittest.skipIf(is_apple_mobile, "FIXME: Edge case of module loader") + def test_findsource_binary(self): + self.assertRaises(OSError, inspect.getsource, unicodedata) + self.assertRaises(OSError, inspect.findsource, unicodedata) +diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py +index d528b4aa287..2d2351a0319 100644 +--- a/Lib/test/test_io.py ++++ b/Lib/test/test_io.py +@@ -39,11 +39,9 @@ + from test import support + from test.support.script_helper import ( + assert_python_ok, assert_python_failure, run_python_until_end) +-from test.support import import_helper +-from test.support import os_helper +-from test.support import threading_helper +-from test.support import warnings_helper +-from test.support import skip_if_sanitizer ++from test.support import ( ++ import_helper, is_apple, os_helper, skip_if_sanitizer, threading_helper, warnings_helper ++) + from test.support.os_helper import FakePath + + import codecs +@@ -631,10 +629,10 @@ + self.read_ops(f, True) + + def test_large_file_ops(self): +- # On Windows and Mac OSX this test consumes large resources; It takes +- # a long time to build the >2 GiB file and takes >2 GiB of disk space +- # therefore the resource must be enabled to run this test. +- if sys.platform[:3] == 'win' or sys.platform == 'darwin': ++ # On Windows and Apple platforms this test consumes large resources; It ++ # takes a long time to build the >2 GiB file and takes >2 GiB of disk ++ # space therefore the resource must be enabled to run this test. ++ if sys.platform[:3] == 'win' or is_apple: + support.requires( + 'largefile', + 'test requires %s bytes and a long time to run' % self.LARGE) +diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py +index fbaafde881c..7117b160949 100644 +--- a/Lib/test/test_logging.py ++++ b/Lib/test/test_logging.py +@@ -43,6 +43,7 @@ + import tempfile + from test.support.script_helper import assert_python_ok, assert_python_failure + from test import support ++from test.support import is_apple_mobile + from test.support import os_helper + from test.support import socket_helper + from test.support import threading_helper +@@ -2003,6 +2004,7 @@ + self.handled.wait(support.LONG_TIMEOUT) + self.assertEqual(self.log_output, b'<11>h\xc3\xa4m-sp\xc3\xa4m') + ++ @unittest.skipIf(is_apple_mobile, "FIXME: Edge case of logging setup") + def test_udp_reconnection(self): + logger = logging.getLogger("slh") + self.sl_hdlr.close() +diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py +index a1a91f661ba..dde44aeffb6 100644 +--- a/Lib/test/test_marshal.py ++++ b/Lib/test/test_marshal.py +@@ -1,5 +1,5 @@ + from test import support +-from test.support import os_helper, requires_debug_ranges ++from test.support import is_apple_mobile, os_helper, requires_debug_ranges + from test.support.script_helper import assert_python_ok + import array + import io +@@ -259,7 +259,7 @@ + #if os.name == 'nt' and hasattr(sys, 'gettotalrefcount'): + if os.name == 'nt': + MAX_MARSHAL_STACK_DEPTH = 1000 +- elif sys.platform == 'wasi': ++ elif sys.platform == 'wasi' or is_apple_mobile: + MAX_MARSHAL_STACK_DEPTH = 1500 + else: + MAX_MARSHAL_STACK_DEPTH = 2000 +diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py +index 1867e8c957f..f75e40940e4 100644 +--- a/Lib/test/test_mmap.py ++++ b/Lib/test/test_mmap.py +@@ -1,5 +1,5 @@ + from test.support import ( +- requires, _2G, _4G, gc_collect, cpython_only, is_emscripten ++ requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple, + ) + from test.support.import_helper import import_module + from test.support.os_helper import TESTFN, unlink +@@ -1009,7 +1009,7 @@ + unlink(TESTFN) + + def _make_test_file(self, num_zeroes, tail): +- if sys.platform[:3] == 'win' or sys.platform == 'darwin': ++ if sys.platform[:3] == 'win' or is_apple: + requires('largefile', + 'test requires %s bytes and a long time to run' % str(0x180000000)) + f = open(TESTFN, 'w+b') +diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py +index 2d5b76ec826..7b6497a2619 100644 +--- a/Lib/test/test_os.py ++++ b/Lib/test/test_os.py +@@ -2298,6 +2298,7 @@ + support.is_emscripten or support.is_wasi, + "musl libc issue on Emscripten/WASI, bpo-46390" + ) ++ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS") + def test_fpathconf(self): + self.check(os.pathconf, "PC_NAME_MAX") + self.check(os.fpathconf, "PC_NAME_MAX") +@@ -3762,6 +3763,7 @@ + self.assertGreaterEqual(size.columns, 0) + self.assertGreaterEqual(size.lines, 0) + ++ @support.requires_subprocess() + def test_stty_match(self): + """Check if stty returns the same results + +diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py +index 4569e30ce86..fceeb4cbfa3 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 + -+# The system log prefixes each line: -+# 2025-01-17 16:14:29.090 Df iOSTestbed[23987:1fd393b4] (Python) ... -+# 2025-01-17 16:14:29.090 E iOSTestbed[23987:1fd393b4] (Python) ... + FEDORA_OS_RELEASE = """\ + NAME=Fedora + VERSION="32 (Thirty Two)" +@@ -229,6 +237,29 @@ + self.assertEqual(res[-1], res.processor) + self.assertEqual(len(res), 6) + ++ if os.name == "posix": ++ uname = os.uname() ++ self.assertEqual(res.node, uname.nodename) ++ self.assertEqual(res.version, uname.version) ++ self.assertEqual(res.machine, uname.machine) + -+LOG_PREFIX_REGEX = re.compile( -+ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD -+ r"\s+\d+:\d{2}:\d{2}\.\d+" # HH:MM:SS.sss -+ r"\s+\w+" # Df/E -+ r"\s+iOSTestbed\[\d+:\w+\]" # Process/thread ID -+ r"\s+\(Python\)\s" # Logger name -+) ++ if sys.platform == "android": ++ self.assertEqual(res.system, "Android") ++ self.assertEqual(res.release, platform.android_ver().release) ++ elif sys.platform == "ios": ++ # 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) + + def test_uname_cast_to_tuple(self): + res = platform.uname() + expected = ( +@@ -400,6 +431,56 @@ + # parent + support.wait_process(pid, exitcode=0) + ++ def test_ios_ver(self): ++ result = platform.ios_ver() + -+# Work around a bug involving sys.exit and TaskGroups -+# (https://github.com/python/cpython/issues/101515). -+def exit(*args): -+ raise MySystemExit(*args) ++ # 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) ++ 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. + -+class MySystemExit(Exception): -+ pass ++ # System is either iOS or iPadOS, depending on the test device ++ self.assertIn(system, {"iOS", "iPadOS"}) + ++ # 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)) + -+# All subprocesses are executed through this context manager so that no matter -+# what happens, they can always be cancelled from another task, and they will -+# always be cleaned up on exit. -+@asynccontextmanager -+async def async_process(*args, **kwargs): -+ process = await asyncio.create_subprocess_exec(*args, **kwargs) -+ try: -+ yield process -+ finally: -+ if process.returncode is None: -+ # Allow a reasonably long time for Xcode to clean itself up, -+ # because we don't want stale emulators left behind. -+ timeout = 10 -+ process.terminate() -+ try: -+ await asyncio.wait_for(process.wait(), timeout) -+ except TimeoutError: -+ print( -+ f"Command {args} did not terminate after {timeout} seconds " -+ f" - sending SIGKILL" ++ # 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 + ) -+ process.kill() -+ -+ # Even after killing the process we must still wait for it, -+ # otherwise we'll get the warning "Exception ignored in __del__". -+ await asyncio.wait_for(process.wait(), timeout=1) -+ -+ -+async def async_check_output(*args, **kwargs): -+ async with async_process( -+ *args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs -+ ) as process: -+ stdout, stderr = await process.communicate() -+ if process.returncode == 0: -+ return stdout.decode(*DECODE_ARGS) -+ else: -+ raise subprocess.CalledProcessError( -+ process.returncode, -+ args, -+ stdout.decode(*DECODE_ARGS), -+ stderr.decode(*DECODE_ARGS), -+ ) -+ + -+# Return a list of UDIDs associated with booted simulators -+async def list_devices(): -+ try: -+ # List the testing simulators, in JSON format -+ raw_json = await async_check_output( -+ "xcrun", "simctl", "--set", "testing", "list", "-j" -+ ) -+ json_data = json.loads(raw_json) -+ -+ # Filter out the booted iOS simulators -+ return [ -+ simulator["udid"] -+ for runtime, simulators in json_data["devices"].items() -+ for simulator in simulators -+ if runtime.split(".")[-1].startswith("iOS") and simulator["state"] == "Booted" -+ ] -+ except subprocess.CalledProcessError as e: -+ # If there's no ~/Library/Developer/XCTestDevices folder (which is the -+ # case on fresh installs, and in some CI environments), `simctl list` -+ # returns error code 1, rather than an empty list. Handle that case, -+ # but raise all other errors. -+ if e.returncode == 1: -+ return [] -+ else: -+ raise -+ -+ -+async def find_device(initial_devices): -+ while True: -+ new_devices = set(await list_devices()).difference(initial_devices) -+ if len(new_devices) == 0: -+ await asyncio.sleep(1) -+ elif len(new_devices) == 1: -+ udid = new_devices.pop() -+ print(f"{datetime.now():%Y-%m-%d %H:%M:%S}: New test simulator detected") -+ print(f"UDID: {udid}") -+ return udid ++ self.assertEqual(type(is_simulator), bool) + else: -+ exit(f"Found more than one new device: {new_devices}") -+ -+ -+async def log_stream_task(initial_devices): -+ # Wait up to 5 minutes for the build to complete and the simulator to boot. -+ udid = await asyncio.wait_for(find_device(initial_devices), 5 * 60) -+ -+ # Stream the iOS device's logs, filtering out messages that come from the -+ # XCTest test suite (catching NSLog messages from the test method), or -+ # Python itself (catching stdout/stderr content routed to the system log -+ # with config->use_system_logger). -+ args = [ -+ "xcrun", -+ "simctl", -+ "--set", -+ "testing", -+ "spawn", -+ udid, -+ "log", -+ "stream", -+ "--style", -+ "compact", -+ "--predicate", -+ ( -+ 'senderImagePath ENDSWITH "/iOSTestbedTests.xctest/iOSTestbedTests"' -+ ' OR senderImagePath ENDSWITH "/Python.framework/Python"' -+ ), -+ ] -+ -+ async with async_process( -+ *args, -+ stdout=subprocess.PIPE, -+ stderr=subprocess.STDOUT, -+ ) as process: -+ suppress_dupes = False -+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS): -+ # Strip the prefix from each log line -+ line = LOG_PREFIX_REGEX.sub("", line) -+ # The iOS log streamer can sometimes lag; when it does, it outputs -+ # a warning about messages being dropped... often multiple times. -+ # Only print the first of these duplicated warnings. -+ if line.startswith("=== Messages dropped "): -+ if not suppress_dupes: -+ suppress_dupes = True -+ sys.stdout.write(line) -+ else: -+ suppress_dupes = False -+ sys.stdout.write(line) -+ sys.stdout.flush() -+ -+ -+async def xcode_test(location, simulator, verbose): -+ # Run the test suite on the named simulator -+ print("Starting xcodebuild...") -+ args = [ -+ "xcodebuild", -+ "test", -+ "-project", -+ str(location / "iOSTestbed.xcodeproj"), -+ "-scheme", -+ "iOSTestbed", -+ "-destination", -+ f"platform=iOS Simulator,name={simulator}", -+ "-resultBundlePath", -+ str(location / f"{datetime.now():%Y%m%d-%H%M%S}.xcresult"), -+ "-derivedDataPath", -+ str(location / "DerivedData"), -+ ] -+ if not verbose: -+ args += ["-quiet"] -+ -+ async with async_process( -+ *args, -+ stdout=subprocess.PIPE, -+ stderr=subprocess.STDOUT, -+ ) as process: -+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS): -+ sys.stdout.write(line) -+ sys.stdout.flush() -+ -+ status = await asyncio.wait_for(process.wait(), timeout=1) -+ exit(status) -+ -+ -+# A backport of Path.relative_to(*, walk_up=True) -+def relative_to(target, other): -+ for step, path in enumerate(chain([other], other.parents)): -+ if path == target or path in target.parents: -+ break -+ else: -+ raise ValueError(f"{str(target)!r} and {str(other)!r} have different anchors") -+ parts = ['..'] * step + list(target.parts[len(path.parts):]) -+ return Path("/".join(parts)) -+ -+ -+def clone_testbed( -+ source: Path, -+ target: Path, -+ framework: Path, -+ apps: list[Path], -+) -> None: -+ if target.exists(): -+ print(f"{target} already exists; aborting without creating project.") -+ sys.exit(10) ++ # On non-iOS platforms, calling ios_ver doesn't fail; you get ++ # default values ++ self.assertEqual(result.system, "") ++ self.assertEqual(result.release, "") ++ self.assertEqual(result.model, "") ++ self.assertFalse(result.is_simulator) + -+ if framework is None: -+ if not ( -+ source / "Python.xcframework/ios-arm64_x86_64-simulator/bin" -+ ).is_dir(): -+ print( -+ f"The testbed being cloned ({source}) does not contain " -+ f"a simulator framework. Re-run with --framework" -+ ) -+ sys.exit(11) -+ else: -+ if not framework.is_dir(): -+ print(f"{framework} does not exist.") -+ sys.exit(12) -+ elif not ( -+ framework.suffix == ".xcframework" -+ or (framework / "Python.framework").is_dir() -+ ): -+ print( -+ f"{framework} is not an XCframework, " -+ f"or a simulator slice of a framework build." -+ ) -+ sys.exit(13) ++ # 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) + -+ print("Cloning testbed project:") -+ print(f" Cloning {source}...", end="", flush=True) -+ shutil.copytree(source, target, symlinks=True) -+ print(" done") + @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 +@@ -495,7 +576,8 @@ + 'root:xnu-4570.71.2~1/RELEASE_X86_64'), + 'x86_64', 'i386') + arch = ('64bit', '') +- with mock.patch.object(platform, 'uname', return_value=uname), \ ++ with mock.patch.object(sys, "platform", "darwin"), \ ++ mock.patch.object(platform, 'uname', return_value=uname), \ + mock.patch.object(platform, 'architecture', return_value=arch): + for mac_ver, expected_terse, expected in [ + # darwin: mac_ver() returns empty strings +diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py +index 221d25a4d62..a4f473b58ec 100644 +--- a/Lib/test/test_posix.py ++++ b/Lib/test/test_posix.py +@@ -1,7 +1,7 @@ + "Test posix functions" + + from test import support +-from test.support import import_helper ++from test.support import is_apple + from test.support import os_helper + from test.support import warnings_helper + from test.support.script_helper import assert_python_ok +@@ -561,6 +561,7 @@ + + @unittest.skipUnless(hasattr(posix, 'confstr'), + 'test needs posix.confstr()') ++ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS") + def test_confstr(self): + self.assertRaises(ValueError, posix.confstr, "CS_garbage") + self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) +@@ -778,9 +779,10 @@ + check_stat(uid, gid) + self.assertRaises(OSError, chown_func, first_param, 0, -1) + check_stat(uid, gid) +- if 0 not in os.getgroups(): +- self.assertRaises(OSError, chown_func, first_param, -1, 0) +- check_stat(uid, gid) ++ if hasattr(os, 'getgroups'): ++ if 0 not in os.getgroups(): ++ self.assertRaises(OSError, chown_func, first_param, -1, 0) ++ check_stat(uid, gid) + # test illegal types + for t in str, float: + self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) +@@ -1249,8 +1251,8 @@ + self.assertIsInstance(lo, int) + self.assertIsInstance(hi, int) + self.assertGreaterEqual(hi, lo) +- # OSX evidently just returns 15 without checking the argument. +- if sys.platform != "darwin": ++ # Apple plaforms return 15 without checking the argument. ++ if not is_apple: + self.assertRaises(OSError, posix.sched_get_priority_min, -23) + self.assertRaises(OSError, posix.sched_get_priority_max, -23) + +@@ -2036,11 +2038,13 @@ + + + @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") ++@support.requires_subprocess() + class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): + spawn_func = getattr(posix, 'posix_spawn', None) + + + @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") ++@support.requires_subprocess() + class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): + spawn_func = getattr(posix, 'posix_spawnp', None) + +diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py +index 51e3a46d0df..3f2bac0155f 100644 +--- a/Lib/test/test_pty.py ++++ b/Lib/test/test_pty.py +@@ -1,12 +1,17 @@ +-from test.support import verbose, reap_children +-from test.support.os_helper import TESTFN, unlink ++import sys ++import unittest ++from test.support import ( ++ is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose ++) + from test.support.import_helper import import_module ++from test.support.os_helper import TESTFN, unlink + +-# Skip these tests if termios or fcntl are not available ++# Skip these tests if termios is not available + import_module('termios') +-# fcntl is a proxy for not being one of the wasm32 platforms even though we +-# don't use this module... a proper check for what crashes those is needed. +-import_module("fcntl") + -+ xc_framework_path = target / "Python.xcframework" -+ sim_framework_path = xc_framework_path / "ios-arm64_x86_64-simulator" -+ if framework is not None: -+ if framework.suffix == ".xcframework": -+ print(" Installing XCFramework...", end="", flush=True) -+ if xc_framework_path.is_dir(): -+ shutil.rmtree(xc_framework_path) -+ else: -+ xc_framework_path.unlink(missing_ok=True) -+ xc_framework_path.symlink_to( -+ relative_to(framework, xc_framework_path.parent) -+ ) -+ print(" done") -+ else: -+ print(" Installing simulator framework...", end="", flush=True) -+ if sim_framework_path.is_dir(): -+ shutil.rmtree(sim_framework_path) -+ else: -+ sim_framework_path.unlink(missing_ok=True) -+ sim_framework_path.symlink_to( -+ relative_to(framework, sim_framework_path.parent) -+ ) -+ print(" done") -+ else: ++# Skip tests on WASM platforms, plus iOS/tvOS/watchOS ++if is_apple_mobile or is_emscripten or is_wasi: ++ raise unittest.SkipTest(f"pty tests not required on {sys.platform}") + + import errno + import os +@@ -17,7 +22,6 @@ + import signal + import socket + import io # readline +-import unittest + import warnings + + TEST_STRING_1 = b"I wish to buy a fish license.\n" +diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py +index 31757205ca3..6b88b121580 100644 +--- a/Lib/test/test_selectors.py ++++ b/Lib/test/test_selectors.py +@@ -6,8 +6,7 @@ + import socket + import sys + from test import support +-from test.support import os_helper +-from test.support import socket_helper ++from test.support import is_apple, os_helper, socket_helper + from time import sleep + import unittest + import unittest.mock +@@ -520,7 +519,7 @@ + try: + fds = s.select() + except OSError as e: +- if e.errno == errno.EINVAL and sys.platform == 'darwin': ++ if e.errno == errno.EINVAL and is_apple: + # unexplainable errors on macOS don't need to fail the test + self.skipTest("Invalid argument error calling poll()") + raise +diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py +index 6728d309589..e85ff0fdcfa 100644 +--- a/Lib/test/test_shutil.py ++++ b/Lib/test/test_shutil.py +@@ -1906,6 +1906,7 @@ + check_chown(dirname, uid, gid) + + ++@support.requires_subprocess() + class TestWhich(BaseTest, unittest.TestCase): + + def setUp(self): +@@ -2801,6 +2802,7 @@ + self.assertGreaterEqual(size.lines, 0) + + @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty") ++ @support.requires_subprocess() + @unittest.skipUnless(hasattr(os, 'get_terminal_size'), + 'need os.get_terminal_size()') + def test_stty_match(self): +diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py +index 3711e7d1c6c..8003f3a226c 100644 +--- a/Lib/test/test_signal.py ++++ b/Lib/test/test_signal.py +@@ -13,9 +13,10 @@ + import time + import unittest + from test import support +-from test.support import os_helper ++from test.support import ( ++ is_apple, is_apple_mobile, os_helper, threading_helper ++) + from test.support.script_helper import assert_python_ok, spawn_python +-from test.support import threading_helper + try: + import _testcapi + except ImportError: +@@ -832,7 +833,7 @@ + self.assertEqual(self.hndl_called, True) + + # Issue 3864, unknown if this affects earlier versions of freebsd also +- @unittest.skipIf(sys.platform in ('netbsd5',), ++ @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile, + 'itimer not reliable (does not mix well with threading) on some BSDs.') + def test_itimer_virtual(self): + self.itimer = signal.ITIMER_VIRTUAL +@@ -1352,7 +1353,7 @@ + # Python handler + self.assertEqual(len(sigs), N, "Some signals were lost") + +- @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)") ++ @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)") + @unittest.skipUnless(hasattr(signal, "SIGUSR1"), + "test needs SIGUSR1") + @threading_helper.requires_working_threading() +diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py +index 5ea8c8e62cd..2b373a4119a 100644 +--- a/Lib/test/test_socket.py ++++ b/Lib/test/test_socket.py +@@ -3,6 +3,7 @@ + from test.support import os_helper + from test.support import socket_helper + from test.support import threading_helper ++from test.support import is_apple + + import errno + import io +@@ -691,7 +692,7 @@ + super().setUp() + + def bindSock(self, sock): +- path = tempfile.mktemp(dir=self.dir_path) ++ path = socket_helper.create_unix_domain_name() + socket_helper.bind_unix_socket(sock, path) + self.addCleanup(os_helper.unlink, path) + +@@ -1168,8 +1169,11 @@ + # Find one service that exists, then check all the related interfaces. + # I've ordered this by protocols that have both a tcp and udp + # protocol, at least for modern Linuxes. +- if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) +- or sys.platform in ('linux', 'darwin')): + if ( -+ xc_framework_path.is_symlink() -+ and not xc_framework_path.readlink().is_absolute() -+ ): -+ # XCFramework is a relative symlink. Rewrite the symlink relative -+ # to the new location. -+ print(" Rewriting symlink to XCframework...", end="", flush=True) -+ orig_xc_framework_path = ( -+ source -+ / xc_framework_path.readlink() -+ ).resolve() -+ xc_framework_path.unlink() -+ xc_framework_path.symlink_to( -+ orig_xc_framework_path.relative_to( -+ xc_framework_path.parent, walk_up=True -+ ) -+ ) -+ print(" done") -+ elif ( -+ sim_framework_path.is_symlink() -+ and not sim_framework_path.readlink().is_absolute() ++ sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) ++ or sys.platform == 'linux' ++ or is_apple + ): -+ print(" Rewriting symlink to simulator framework...", end="", flush=True) -+ # Simulator framework is a relative symlink. Rewrite the symlink -+ # relative to the new location. -+ orig_sim_framework_path = ( -+ source -+ / "Python.XCframework" -+ / sim_framework_path.readlink() -+ ).resolve() -+ sim_framework_path.unlink() -+ sim_framework_path.symlink_to( -+ orig_sim_framework_path.relative_to( -+ sim_framework_path.parent, walk_up=True -+ ) -+ ) -+ print(" done") -+ else: -+ print(" Using pre-existing iOS framework.") -+ -+ for app_src in apps: -+ print(f" Installing app {app_src.name!r}...", end="", flush=True) -+ app_target = target / f"iOSTestbed/app/{app_src.name}" -+ if app_target.is_dir(): -+ shutil.rmtree(app_target) -+ shutil.copytree(app_src, app_target) -+ print(" done") -+ -+ print(f"Successfully cloned testbed: {target.resolve()}") -+ -+ -+def update_plist(testbed_path, args): -+ # Add the test runner arguments to the testbed's Info.plist file. -+ info_plist = testbed_path / "iOSTestbed" / "iOSTestbed-Info.plist" -+ with info_plist.open("rb") as f: -+ info = plistlib.load(f) -+ -+ info["TestArgs"] = args -+ -+ with info_plist.open("wb") as f: -+ plistlib.dump(info, f) -+ -+ -+async def run_testbed(simulator: str, args: list[str], verbose: bool=False): -+ location = Path(__file__).parent -+ print("Updating plist...", end="", flush=True) -+ update_plist(location, args) -+ print(" done.") -+ -+ # Get the list of devices that are booted at the start of the test run. -+ # The simulator started by the test suite will be detected as the new -+ # entry that appears on the device list. -+ initial_devices = await list_devices() -+ -+ try: -+ async with asyncio.TaskGroup() as tg: -+ tg.create_task(log_stream_task(initial_devices)) -+ tg.create_task(xcode_test(location, simulator=simulator, verbose=verbose)) -+ except* MySystemExit as e: -+ raise SystemExit(*e.exceptions[0].args) from None -+ except* subprocess.CalledProcessError as e: -+ # Extract it from the ExceptionGroup so it can be handled by `main`. -+ raise e.exceptions[0] -+ -+ -+def main(): -+ parser = argparse.ArgumentParser( -+ description=( -+ "Manages the process of testing a Python project in the iOS simulator." -+ ), -+ ) -+ -+ subcommands = parser.add_subparsers(dest="subcommand") -+ -+ clone = subcommands.add_parser( -+ "clone", -+ description=( -+ "Clone the testbed project, copying in an iOS Python framework and" -+ "any specified application code." -+ ), -+ help="Clone a testbed project to a new location.", -+ ) -+ clone.add_argument( -+ "--framework", -+ help=( -+ "The location of the XCFramework (or simulator-only slice of an " -+ "XCFramework) to use when running the testbed" -+ ), -+ ) -+ clone.add_argument( -+ "--app", -+ dest="apps", -+ action="append", -+ default=[], -+ help="The location of any code to include in the testbed project", -+ ) -+ clone.add_argument( -+ "location", -+ help="The path where the testbed will be cloned.", -+ ) + # avoid the 'echo' service on this platform, as there is an + # assumption breaking non-standard port/protocol entry + services = ('daytime', 'qotd', 'domain') +@@ -1931,12 +1935,13 @@ + self._test_socket_fileno(s, socket.AF_INET6, socket.SOCK_STREAM) + + if hasattr(socket, "AF_UNIX"): +- tmpdir = tempfile.mkdtemp() +- self.addCleanup(shutil.rmtree, tmpdir) ++ unix_name = socket_helper.create_unix_domain_name() ++ self.addCleanup(os_helper.unlink, unix_name) + -+ run = subcommands.add_parser( -+ "run", -+ usage="%(prog)s [-h] [--simulator SIMULATOR] -- [ ...]", -+ description=( -+ "Run a testbed project. The arguments provided after `--` will be " -+ "passed to the running iOS process as if they were arguments to " -+ "`python -m`." -+ ), -+ help="Run a testbed project", -+ ) -+ run.add_argument( -+ "--simulator", -+ default="iPhone SE (3rd Generation)", -+ help="The name of the simulator to use (default: 'iPhone SE (3rd Generation)')", -+ ) -+ run.add_argument( -+ "-v", "--verbose", -+ action="store_true", -+ help="Enable verbose output", -+ ) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(s.close) + try: +- s.bind(os.path.join(tmpdir, 'socket')) ++ s.bind(unix_name) + except PermissionError: + pass + else: +@@ -3579,7 +3584,7 @@ + def _testFDPassCMSG_LEN(self): + self.createAndSendFDs(1) + +- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") + @unittest.skipIf(AIX, "skipping, see issue #22397") + @requireAttrs(socket, "CMSG_SPACE") + def testFDPassSeparate(self): +@@ -3590,7 +3595,7 @@ + maxcmsgs=2) + + @testFDPassSeparate.client_skip +- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") + @unittest.skipIf(AIX, "skipping, see issue #22397") + def _testFDPassSeparate(self): + fd0, fd1 = self.newFDs(2) +@@ -3603,7 +3608,7 @@ + array.array("i", [fd1]))]), + len(MSG)) + +- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") + @unittest.skipIf(AIX, "skipping, see issue #22397") + @requireAttrs(socket, "CMSG_SPACE") + def testFDPassSeparateMinSpace(self): +@@ -3617,7 +3622,7 @@ + maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC) + + @testFDPassSeparateMinSpace.client_skip +- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") + @unittest.skipIf(AIX, "skipping, see issue #22397") + def _testFDPassSeparateMinSpace(self): + fd0, fd1 = self.newFDs(2) +@@ -3641,7 +3646,7 @@ + nbytes = self.sendmsgToServer([msg]) + self.assertEqual(nbytes, len(msg)) + +- @unittest.skipIf(sys.platform == "darwin", "see issue #24725") ++ @unittest.skipIf(is_apple, "skipping, see issue #12958") + def testFDPassEmpty(self): + # Try to pass an empty FD array. Can receive either no array + # or an empty array. +diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py +index 80e1968c0bf..ac8b35d8201 100644 +--- a/Lib/test/test_socketserver.py ++++ b/Lib/test/test_socketserver.py +@@ -91,8 +91,7 @@ + else: + # XXX: We need a way to tell AF_UNIX to pick its own name + # like AF_INET provides port==0. +- dir = None +- fn = tempfile.mktemp(prefix='unix_socket.', dir=dir) ++ fn = socket_helper.create_unix_domain_name() + self.test_files.append(fn) + return fn + +@@ -228,12 +227,16 @@ + self.dgram_examine) + + @requires_unix_sockets ++ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, ++ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") + def test_UnixDatagramServer(self): + self.run_server(socketserver.UnixDatagramServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) + + @requires_unix_sockets ++ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, ++ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") + def test_ThreadingUnixDatagramServer(self): + self.run_server(socketserver.ThreadingUnixDatagramServer, + socketserver.DatagramRequestHandler, +diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py +index ff86291bc57..59370264ab3 100644 +--- a/Lib/test/test_sqlite3/test_dbapi.py ++++ b/Lib/test/test_sqlite3/test_dbapi.py +@@ -31,7 +31,7 @@ + + from test.support import ( + SHORT_TIMEOUT, bigmemtest, check_disallow_instantiation, requires_subprocess, +- is_emscripten, is_wasi ++ is_apple, is_emscripten, is_wasi + ) + from test.support import threading_helper + from _testcapi import INT_MAX, ULLONG_MAX +@@ -335,7 +335,7 @@ + + # sqlite3_enable_shared_cache() is deprecated on macOS and calling it may raise + # OperationalError on some buildbots. +- @unittest.skipIf(sys.platform == "darwin", "shared cache is deprecated on macOS") ++ @unittest.skipIf(is_apple, "shared cache is deprecated on Apple platforms") + def test_shared_cache_deprecated(self): + for enable in (True, False): + with self.assertWarns(DeprecationWarning) as cm: +@@ -659,7 +659,7 @@ + cx.execute(self._sql) + + @unittest.skipIf(sys.platform == "win32", "skipped on Windows") +- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") ++ @unittest.skipIf(is_apple, "skipped on Apple platforms") + @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") + @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") + def test_open_with_undecodable_path(self): +@@ -705,7 +705,7 @@ + cx.execute(self._sql) + + @unittest.skipIf(sys.platform == "win32", "skipped on Windows") +- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") ++ @unittest.skipIf(is_apple, "skipped on Apple platforms") + @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") + @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") + def test_open_undecodable_uri(self): +diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py +index c77fec3d39d..ca55d429aec 100644 +--- a/Lib/test/test_stat.py ++++ b/Lib/test/test_stat.py +@@ -2,8 +2,7 @@ + import os + import socket + import sys +-from test.support import os_helper +-from test.support import socket_helper ++from test.support import is_apple, os_helper, socket_helper + from test.support.import_helper import import_fresh_module + from test.support.os_helper import TESTFN + +diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py +index b1782947c07..b4449d33a06 100644 +--- a/Lib/test/test_sundry.py ++++ b/Lib/test/test_sundry.py +@@ -1,5 +1,6 @@ + """Do a minimal test of all the modules that aren't otherwise tested.""" + import importlib ++import sys + from test import support + from test.support import import_helper + from test.support import warnings_helper +@@ -47,7 +48,8 @@ + + import distutils.bcppcompiler + import distutils.ccompiler +- import distutils.cygwinccompiler ++ if sys.platform.startswith("win"): ++ import distutils.cygwinccompiler + import distutils.filelist + import distutils.text_file + import distutils.unixccompiler +diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py +index d3bb0d25cac..f487fb5313b 100644 +--- a/Lib/test/test_sysconfig.py ++++ b/Lib/test/test_sysconfig.py +@@ -6,7 +6,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, +@@ -340,6 +344,8 @@ + # 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 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) +@@ -449,6 +455,8 @@ + 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 include config folder at runtime") + def test_srcdir(self): + # See Issues #15322, #15364. + srcdir = sysconfig.get_config_var('srcdir') +@@ -517,6 +525,8 @@ + @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 include config folder at runtime") + def test_get_makefile_filename(self): + makefile = sysconfig.get_makefile_filename() + self.assertTrue(os.path.isfile(makefile), makefile) +diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py +index 47619c8807b..25c16e3a0b7 100644 +--- a/Lib/test/test_unicode_file_functions.py ++++ b/Lib/test/test_unicode_file_functions.py +@@ -5,7 +5,7 @@ + import unittest + import warnings + from unicodedata import normalize +-from test.support import os_helper ++from test.support import is_apple, os_helper + from test import support + + +@@ -23,13 +23,13 @@ + '10_\u1fee\u1ffd', + ] + +-# Mac OS X decomposes Unicode names, using Normal Form D. ++# Apple platforms decompose Unicode names, using Normal Form D. + # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html + # "However, most volume formats do not follow the exact specification for + # these normal forms. For example, HFS Plus uses a variant of Normal Form D + # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through + # U+2FAFF are not decomposed." +-if sys.platform != 'darwin': ++if not is_apple: + filenames.extend([ + # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different + '11_\u0385\u03d3\u03d4', +@@ -119,11 +119,11 @@ + os.stat(name) + self._apply_failure(os.listdir, name, self._listdir_failure) + +- # Skip the test on darwin, because darwin does normalize the filename to ++ # Skip the test on Apple platforms, because they don't normalize the filename to + # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC, + # NFKD in Python is useless, because darwin will normalize it later and so + # open(), os.stat(), etc. don't raise any exception. +- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') ++ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') + @unittest.skipIf( + support.is_emscripten or support.is_wasi, + "test fails on Emscripten/WASI when host platform is macOS." +@@ -142,10 +142,10 @@ + self._apply_failure(os.remove, name) + self._apply_failure(os.listdir, name) + +- # Skip the test on darwin, because darwin uses a normalization different ++ # Skip the test on Apple platforms, because they use a normalization different + # than Python NFD normalization: filenames are different even if we use + # Python NFD normalization. +- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') ++ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') + def test_listdir(self): + sf0 = set(self.files) + with warnings.catch_warnings(): +diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py +index 94cbc8a62f9..06a553f093f 100644 +--- a/Lib/test/test_urllib2.py ++++ b/Lib/test/test_urllib2.py +@@ -1,7 +1,7 @@ + import unittest + from test import support + from test.support import os_helper +-from test.support import socket_helper ++from test.support import requires_subprocess + from test.support import warnings_helper + from test import test_urllib + +@@ -987,6 +987,7 @@ + + file_obj.close() + ++ @requires_subprocess() + def test_http_body_pipe(self): + # A file reading from a pipe. + # A pipe cannot be seek'ed. There is no way to determine the +diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py +index d3abb77f40f..3e4c0d583a9 100644 +--- a/Lib/test/test_venv.py ++++ b/Lib/test/test_venv.py +@@ -20,8 +20,8 @@ + import shlex + from test.support import (captured_stdout, captured_stderr, + skip_if_broken_multiprocessing_synchronize, verbose, +- requires_subprocess, is_emscripten, is_wasi, +- requires_venv_with_pip, TEST_HOME_DIR, ++ requires_subprocess, is_apple_mobile, is_emscripten, ++ is_wasi, requires_venv_with_pip, TEST_HOME_DIR, + requires_resource, copy_python_src_ignore) + from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) + import unittest +@@ -40,8 +40,10 @@ + or sys._base_executable != sys.executable, + 'cannot run venv.create from within a venv on this platform') + +-if is_emscripten or is_wasi: +- raise unittest.SkipTest("venv is not available on Emscripten/WASI.") ++# Skip tests on WASM platforms, plus iOS/tvOS/watchOS ++if is_apple_mobile or is_emscripten or is_wasi: ++ raise unittest.SkipTest(f"venv tests not required on {sys.platform}") + -+ try: -+ pos = sys.argv.index("--") -+ testbed_args = sys.argv[1:pos] -+ test_args = sys.argv[pos + 1 :] -+ except ValueError: -+ testbed_args = sys.argv[1:] -+ test_args = [] + + @requires_subprocess() + def check_output(cmd, encoding=None): +diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py +index 9d608d63a01..24b1103f500 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 = 'http://www.example.com' + CMD_NAME = 'test' +@@ -24,6 +27,7 @@ + return 0 + + ++@requires_subprocess() + class CommandTestMixin: + + def _test(self, meth, *, args=[URL], kw={}, options, arguments): +@@ -219,6 +223,73 @@ + 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) + -+ context = parser.parse_args(testbed_args) ++ @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 ++ # wrap msgSend to return stringified object references ++ self.orig_objc = webbrowser.objc + -+ if context.subcommand == "clone": -+ clone_testbed( -+ source=Path(__file__).parent.resolve(), -+ target=Path(context.location).resolve(), -+ framework=Path(context.framework).resolve() if context.framework else None, -+ apps=[Path(app) for app in context.apps], -+ ) -+ elif context.subcommand == "run": -+ if test_args: -+ if not ( -+ Path(__file__).parent / "Python.xcframework/ios-arm64_x86_64-simulator/bin" -+ ).is_dir(): -+ print( -+ f"Testbed does not contain a compiled iOS framework. Use " -+ f"`python {sys.argv[0]} clone ...` to create a runnable " -+ f"clone of this testbed." -+ ) -+ sys.exit(20) ++ 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 + -+ asyncio.run( -+ run_testbed( -+ simulator=context.simulator, -+ verbose=context.verbose, -+ args=test_args, -+ ) -+ ) -+ else: -+ print(f"Must specify test arguments (e.g., {sys.argv[0]} run -- test)") -+ print() -+ parser.print_help(sys.stderr) -+ sys.exit(21) -+ else: -+ parser.print_help(sys.stderr) -+ sys.exit(1) ++ 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) + -+if __name__ == "__main__": -+ main() ---- /dev/null -+++ b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj -@@ -0,0 +1,580 @@ -+// !$*UTF8*$! -+{ -+ archiveVersion = 1; -+ classes = { -+ }; -+ objectVersion = 56; -+ objects = { ++ # The ObjC String version of the URL is created with UTF-8 encoding ++ url_string_args = [ ++ "C#NSString", ++ "S#stringWithCString:encoding:", ++ b'http://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"] + -+/* Begin PBXBuildFile section */ -+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; }; -+ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; }; -+ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; }; -+ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; }; -+ 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */; }; -+ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; -+ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; -+ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; }; -+ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; }; -+ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; }; -+/* End PBXBuildFile section */ ++ # 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 ++ ) + -+/* Begin PBXContainerItemProxy section */ -+ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { -+ isa = PBXContainerItemProxy; -+ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; -+ proxyType = 1; -+ remoteGlobalIDString = 607A66112B0EFA380010BFC8; -+ remoteInfo = iOSTestbed; -+ }; -+/* End PBXContainerItemProxy section */ ++ def test_open(self): ++ self._test('open') + -+/* Begin PBXCopyFilesBuildPhase section */ -+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = { -+ isa = PBXCopyFilesBuildPhase; -+ buildActionMask = 2147483647; -+ dstPath = ""; -+ dstSubfolderSpec = 10; -+ files = ( -+ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */, -+ ); -+ name = "Embed Frameworks"; -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { -+ isa = PBXCopyFilesBuildPhase; -+ buildActionMask = 2147483647; -+ dstPath = ""; -+ dstSubfolderSpec = 10; -+ files = ( -+ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */, -+ ); -+ name = "Embed Frameworks"; -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXCopyFilesBuildPhase section */ ++ def test_open_with_autoraise_false(self): ++ self._test('open', autoraise=False) + -+/* Begin PBXFileReference section */ -+ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; -+ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; -+ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; -+ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; -+ 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; -+ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; -+ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -+ 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSTestbedTests.m; sourceTree = ""; }; -+ 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; -+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = ""; }; -+ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = ""; }; -+ 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; }; -+ 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; }; -+/* End PBXFileReference section */ ++ def test_open_new(self): ++ self._test('open_new') + -+/* Begin PBXFrameworksBuildPhase section */ -+ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { -+ isa = PBXFrameworksBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { -+ isa = PBXFrameworksBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXFrameworksBuildPhase section */ ++ def test_open_new_tab(self): ++ self._test('open_new_tab') + -+/* Begin PBXGroup section */ -+ 607A66092B0EFA380010BFC8 = { -+ isa = PBXGroup; -+ children = ( -+ 607A664A2B0EFB310010BFC8 /* Python.xcframework */, -+ 607A66142B0EFA380010BFC8 /* iOSTestbed */, -+ 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */, -+ 607A66132B0EFA380010BFC8 /* Products */, -+ 607A664F2B0EFFE00010BFC8 /* Frameworks */, -+ ); -+ sourceTree = ""; -+ }; -+ 607A66132B0EFA380010BFC8 /* Products */ = { -+ isa = PBXGroup; -+ children = ( -+ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */, -+ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */, -+ ); -+ name = Products; -+ sourceTree = ""; -+ }; -+ 607A66142B0EFA380010BFC8 /* iOSTestbed */ = { -+ isa = PBXGroup; -+ children = ( -+ 608619552CB7819B00F46182 /* app */, -+ 608619532CB77BA900F46182 /* app_packages */, -+ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */, -+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */, -+ 607A66152B0EFA380010BFC8 /* AppDelegate.h */, -+ 607A66162B0EFA380010BFC8 /* AppDelegate.m */, -+ 607A66212B0EFA390010BFC8 /* Assets.xcassets */, -+ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */, -+ 607A66272B0EFA390010BFC8 /* main.m */, -+ ); -+ path = iOSTestbed; -+ sourceTree = ""; -+ }; -+ 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */ = { -+ isa = PBXGroup; -+ children = ( -+ 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */, -+ ); -+ path = iOSTestbedTests; -+ sourceTree = ""; -+ }; -+ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = { -+ isa = PBXGroup; -+ children = ( -+ ); -+ name = Frameworks; -+ sourceTree = ""; -+ }; -+/* End PBXGroup section */ + -+/* Begin PBXNativeTarget section */ -+ 607A66112B0EFA380010BFC8 /* iOSTestbed */ = { -+ isa = PBXNativeTarget; -+ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */; -+ buildPhases = ( -+ 607A660E2B0EFA380010BFC8 /* Sources */, -+ 607A660F2B0EFA380010BFC8 /* Frameworks */, -+ 607A66102B0EFA380010BFC8 /* Resources */, -+ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */, -+ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */, -+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, -+ ); -+ buildRules = ( -+ ); -+ dependencies = ( -+ ); -+ name = iOSTestbed; -+ productName = iOSTestbed; -+ productReference = 607A66122B0EFA380010BFC8 /* iOSTestbed.app */; -+ productType = "com.apple.product-type.application"; -+ }; -+ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */ = { -+ isa = PBXNativeTarget; -+ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */; -+ buildPhases = ( -+ 607A66292B0EFA3A0010BFC8 /* Sources */, -+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, -+ 607A662B2B0EFA3A0010BFC8 /* Resources */, -+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, -+ ); -+ buildRules = ( -+ ); -+ dependencies = ( -+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, -+ ); -+ name = iOSTestbedTests; -+ productName = iOSTestbedTests; -+ productReference = 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */; -+ productType = "com.apple.product-type.bundle.unit-test"; -+ }; -+/* End PBXNativeTarget section */ + class BrowserRegistrationTest(unittest.TestCase): + + def setUp(self): +@@ -302,6 +373,10 @@ + 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: +@@ -313,6 +388,10 @@ + 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 5d72524c087..48976e4e11f 100755 +--- a/Lib/webbrowser.py ++++ b/Lib/webbrowser.py +@@ -534,6 +534,9 @@ + # 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) + -+/* Begin PBXProject section */ -+ 607A660A2B0EFA380010BFC8 /* Project object */ = { -+ isa = PBXProject; -+ attributes = { -+ BuildIndependentTargetsInParallel = 1; -+ LastUpgradeCheck = 1500; -+ TargetAttributes = { -+ 607A66112B0EFA380010BFC8 = { -+ CreatedOnToolsVersion = 15.0.1; -+ }; -+ 607A662C2B0EFA3A0010BFC8 = { -+ CreatedOnToolsVersion = 15.0.1; -+ TestTargetID = 607A66112B0EFA380010BFC8; -+ }; -+ }; -+ }; -+ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */; -+ compatibilityVersion = "Xcode 14.0"; -+ developmentRegion = en; -+ hasScannedForEncodings = 0; -+ knownRegions = ( -+ en, -+ Base, -+ ); -+ mainGroup = 607A66092B0EFA380010BFC8; -+ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; -+ projectDirPath = ""; -+ projectRoot = ""; -+ targets = ( -+ 607A66112B0EFA380010BFC8 /* iOSTestbed */, -+ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */, -+ ); -+ }; -+/* End PBXProject section */ + if sys.platform == "serenityos": + # SerenityOS webbrowser, simply called "Browser". + register("Browser", None, BackgroundBrowser("Browser")) +@@ -707,6 +710,70 @@ + rc = osapipe.close() + return not rc + ++# ++# Platform support for iOS ++# ++if sys.platform == "ios": ++ from _ios_support import objc ++ if objc: ++ # If objc exists, we know ctypes is also importable. ++ from ctypes import c_void_p, c_char_p, c_ulong + -+/* Begin PBXResourcesBuildPhase section */ -+ 607A66102B0EFA380010BFC8 /* Resources */ = { -+ isa = PBXResourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */, -+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */, -+ 608619562CB7819B00F46182 /* app in Resources */, -+ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, -+ 608619542CB77BA900F46182 /* app_packages in Resources */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ 607A662B2B0EFA3A0010BFC8 /* Resources */ = { -+ isa = PBXResourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXResourcesBuildPhase section */ ++ 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 + -+/* Begin PBXShellScriptBuildPhase section */ -+ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = { -+ isa = PBXShellScriptBuildPhase; -+ alwaysOutOfDate = 1; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ inputFileListPaths = ( -+ ); -+ inputPaths = ( -+ ); -+ name = "Install Target Specific Python Standard Library"; -+ outputFileListPaths = ( -+ ); -+ outputPaths = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ shellPath = /bin/sh; -+ shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-iphonesimulator\" ]; then\n echo \"Installing Python modules for iOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for iOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n"; -+ showEnvVarsInLog = 0; -+ }; -+ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = { -+ isa = PBXShellScriptBuildPhase; -+ alwaysOutOfDate = 1; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ inputFileListPaths = ( -+ ); -+ inputPaths = ( -+ ); -+ name = "Prepare Python Binary Modules"; -+ outputFileListPaths = ( -+ ); -+ outputPaths = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ shellPath = /bin/sh; -+ shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\necho \"Install app package extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app_packages\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app_packages/ \"$FULL_EXT\"\ndone\necho \"Install app extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n"; -+ showEnvVarsInLog = 0; -+ }; -+/* End PBXShellScriptBuildPhase section */ ++ # All the messages in this call return object references. ++ objc.objc_msgSend.restype = c_void_p + -+/* Begin PBXSourcesBuildPhase section */ -+ 607A660E2B0EFA380010BFC8 /* Sources */ = { -+ isa = PBXSourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, -+ 607A66282B0EFA390010BFC8 /* main.m in Sources */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ 607A66292B0EFA3A0010BFC8 /* Sources */ = { -+ isa = PBXSourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXSourcesBuildPhase section */ ++ # 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 ++ ) + -+/* Begin PBXTargetDependency section */ -+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { -+ isa = PBXTargetDependency; -+ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; -+ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; -+ }; -+/* End PBXTargetDependency section */ ++ # 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) + -+/* Begin PBXVariantGroup section */ -+ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = { -+ isa = PBXVariantGroup; -+ children = ( -+ 607A66242B0EFA390010BFC8 /* Base */, -+ ); -+ name = LaunchScreen.storyboard; -+ sourceTree = ""; -+ }; -+/* End PBXVariantGroup section */ ++ # 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 ++ ] ++ # Method returns void ++ objc.objc_msgSend.restype = None ++ objc.objc_msgSend(shared_app, openURL_, ns_url, None, None) + -+/* Begin XCBuildConfiguration section */ -+ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; -+ CLANG_ANALYZER_NONNULL = YES; -+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; -+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; -+ CLANG_ENABLE_MODULES = YES; -+ CLANG_ENABLE_OBJC_ARC = YES; -+ CLANG_ENABLE_OBJC_WEAK = YES; -+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; -+ CLANG_WARN_BOOL_CONVERSION = YES; -+ CLANG_WARN_COMMA = YES; -+ CLANG_WARN_CONSTANT_CONVERSION = YES; -+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; -+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -+ CLANG_WARN_EMPTY_BODY = YES; -+ CLANG_WARN_ENUM_CONVERSION = YES; -+ CLANG_WARN_INFINITE_RECURSION = YES; -+ CLANG_WARN_INT_CONVERSION = YES; -+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; -+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; -+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; -+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; -+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; -+ CLANG_WARN_STRICT_PROTOTYPES = YES; -+ CLANG_WARN_SUSPICIOUS_MOVE = YES; -+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; -+ CLANG_WARN_UNREACHABLE_CODE = YES; -+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -+ COPY_PHASE_STRIP = NO; -+ DEBUG_INFORMATION_FORMAT = dwarf; -+ ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = YES; -+ GCC_C_LANGUAGE_STANDARD = gnu17; -+ GCC_DYNAMIC_NO_PIC = NO; -+ GCC_NO_COMMON_BLOCKS = YES; -+ GCC_OPTIMIZATION_LEVEL = 0; -+ GCC_PREPROCESSOR_DEFINITIONS = ( -+ "DEBUG=1", -+ "$(inherited)", -+ ); -+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -+ GCC_WARN_UNDECLARED_SELECTOR = YES; -+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -+ GCC_WARN_UNUSED_FUNCTION = YES; -+ GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; -+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; -+ MTL_FAST_MATH = YES; -+ ONLY_ACTIVE_ARCH = YES; -+ SDKROOT = iphoneos; -+ }; -+ name = Debug; -+ }; -+ 607A66402B0EFA3A0010BFC8 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; -+ CLANG_ANALYZER_NONNULL = YES; -+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; -+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; -+ CLANG_ENABLE_MODULES = YES; -+ CLANG_ENABLE_OBJC_ARC = YES; -+ CLANG_ENABLE_OBJC_WEAK = YES; -+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; -+ CLANG_WARN_BOOL_CONVERSION = YES; -+ CLANG_WARN_COMMA = YES; -+ CLANG_WARN_CONSTANT_CONVERSION = YES; -+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; -+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -+ CLANG_WARN_EMPTY_BODY = YES; -+ CLANG_WARN_ENUM_CONVERSION = YES; -+ CLANG_WARN_INFINITE_RECURSION = YES; -+ CLANG_WARN_INT_CONVERSION = YES; -+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; -+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; -+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; -+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; -+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; -+ CLANG_WARN_STRICT_PROTOTYPES = YES; -+ CLANG_WARN_SUSPICIOUS_MOVE = YES; -+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; -+ CLANG_WARN_UNREACHABLE_CODE = YES; -+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -+ COPY_PHASE_STRIP = NO; -+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; -+ ENABLE_NS_ASSERTIONS = NO; -+ ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = YES; -+ GCC_C_LANGUAGE_STANDARD = gnu17; -+ GCC_NO_COMMON_BLOCKS = YES; -+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -+ GCC_WARN_UNDECLARED_SELECTOR = YES; -+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -+ GCC_WARN_UNUSED_FUNCTION = YES; -+ GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; -+ MTL_ENABLE_DEBUG_INFO = NO; -+ MTL_FAST_MATH = YES; -+ SDKROOT = iphoneos; -+ VALIDATE_PRODUCT = YES; -+ }; -+ name = Release; -+ }; -+ 607A66422B0EFA3A0010BFC8 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = ""; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; -+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; -+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; -+ INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ LD_RUNPATH_SEARCH_PATHS = ( -+ "$(inherited)", -+ "@executable_path/Frameworks", -+ ); -+ MARKETING_VERSION = 3.13.0a1; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = "1,2"; -+ }; -+ name = Debug; -+ }; -+ 607A66432B0EFA3A0010BFC8 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = ""; -+ ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; -+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; -+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; -+ INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; -+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ LD_RUNPATH_SEARCH_PATHS = ( -+ "$(inherited)", -+ "@executable_path/Frameworks", -+ ); -+ MARKETING_VERSION = 3.13.0a1; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = "1,2"; -+ }; -+ name = Release; -+ }; -+ 607A66452B0EFA3A0010BFC8 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ BUNDLE_LOADER = "$(TEST_HOST)"; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = 3HEZE76D99; -+ GENERATE_INFOPLIST_FILE = YES; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = "1,2"; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; -+ }; -+ name = Debug; -+ }; -+ 607A66462B0EFA3A0010BFC8 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ BUNDLE_LOADER = "$(TEST_HOST)"; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = 3HEZE76D99; -+ GENERATE_INFOPLIST_FILE = YES; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = "1,2"; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; -+ }; -+ name = Release; -+ }; -+/* End XCBuildConfiguration section */ ++ return True ++ + + def main(): + import getopt +--- /dev/null ++++ b/Mac/Resources/app-store-compliance.patch +@@ -0,0 +1 @@ ++# No compliance patching required. +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 81d4d50f82f..8d75c4749db 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -175,18 +175,29 @@ + EXE= @EXEEXT@ + BUILDEXE= @BUILDEXEEXT@ + ++# Name of the patch file to apply for app store compliance ++APP_STORE_COMPLIANCE_PATCH=@APP_STORE_COMPLIANCE_PATCH@ ++ + # Short name and location for Mac OS X Python framework + UNIVERSALSDK=@UNIVERSALSDK@ + PYTHONFRAMEWORK= @PYTHONFRAMEWORK@ + PYTHONFRAMEWORKDIR= @PYTHONFRAMEWORKDIR@ + PYTHONFRAMEWORKPREFIX= @PYTHONFRAMEWORKPREFIX@ + PYTHONFRAMEWORKINSTALLDIR= @PYTHONFRAMEWORKINSTALLDIR@ +-# Deployment target selected during configure, to be checked ++PYTHONFRAMEWORKINSTALLNAMEPREFIX= @PYTHONFRAMEWORKINSTALLNAMEPREFIX@ ++RESSRCDIR= @RESSRCDIR@ ++# 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. 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 + STRIPFLAG=-s + +@@ -344,6 +355,8 @@ + ########################################################################## + + LIBFFI_INCLUDEDIR= @LIBFFI_INCLUDEDIR@ ++LIBFFI_LIBDIR= @LIBFFI_LIBDIR@ ++LIBFFI_LIB= @LIBFFI_LIB@ + + ########################################################################## + # Parser +@@ -585,7 +598,7 @@ + + # Default target + all: @DEF_MAKE_ALL_RULE@ +-build_all: check-clean-src $(BUILDPYTHON) platform oldsharedmods sharedmods \ ++build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform oldsharedmods sharedmods \ + gdbhooks Programs/_testembed python-config + build_wasm: check-clean-src $(BUILDPYTHON) platform oldsharedmods python-config + +@@ -604,6 +617,16 @@ + exit 1; \ + fi + ++# Check that the app store compliance patch can be applied (if configured). ++# This is checked as a dry-run against the original library sources; ++# the patch will be actually applied during the install phase. ++.PHONY: check-app-store-compliance ++check-app-store-compliance: ++ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \ ++ patch --dry-run --quiet --force --strip 1 --directory "$(abs_srcdir)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)"; \ ++ echo "App store compliance patch can be applied."; \ ++ fi ++ + # Profile generation build must start from a clean tree. + profile-clean-stamp: + $(MAKE) clean +@@ -765,7 +788,7 @@ + $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ + + libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) +- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + + + libpython$(VERSION).sl: $(LIBRARY_OBJS) +@@ -789,14 +812,13 @@ + # This rule is here for OPENSTEP/Rhapsody/MacOSX. It builds a temporary + # minimal framework (not including the Lib directory and such) in the current + # directory. +-RESSRCDIR=Mac/Resources/framework + $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \ + $(LIBRARY) \ + $(RESSRCDIR)/Info.plist + $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION) + $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ +- -all_load $(LIBRARY) -Wl,-single_module \ +- -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK) \ ++ -all_load $(LIBRARY) \ ++ -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ + -compatibility_version $(VERSION) \ + -current_version $(VERSION) \ + -framework CoreFoundation $(LIBS); +@@ -808,6 +830,21 @@ + $(LN) -fsn Versions/Current/$(PYTHONFRAMEWORK) $(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK) + $(LN) -fsn Versions/Current/Resources $(PYTHONFRAMEWORKDIR)/Resources + ++# This rule is for iOS, which requires an annoyingly just slighly different ++# format for frameworks to macOS. It *doesn't* use a versioned framework, and ++# the Info.plist must be in the root of the framework. ++$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK): \ ++ $(LIBRARY) \ ++ $(RESSRCDIR)/Info.plist ++ $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR) ++ $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ ++ -all_load $(LIBRARY) \ ++ -install_name $(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ ++ -compatibility_version $(VERSION) \ ++ -current_version $(VERSION) \ ++ -framework CoreFoundation $(LIBS); ++ $(INSTALL_DATA) $(RESSRCDIR)/Info.plist $(PYTHONFRAMEWORKDIR)/Info.plist ++ + # This rule builds the Cygwin Python DLL and import library if configured + # for a shared core library; otherwise, this rule is a noop. + $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS) +@@ -1711,6 +1748,36 @@ + $(RUNSHARED) /usr/libexec/oah/translate \ + ./$(BUILDPYTHON) -E -m test -j 0 -u all $(TESTOPTS) + ++# Run the test suite on the iOS simulator. Must be run on a macOS machine with ++# a full Xcode install that has an iPhone SE (3rd edition) simulator available. ++# This must be run *after* a `make install` has completed the build. The ++# `--with-framework-name` argument *cannot* be used when configuring the build. ++XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s) ++.PHONY: testios ++testios: ++ @if test "$(MACHDEP)" != "ios"; then \ ++ echo "Cannot run the iOS testbed for a non-iOS build."; \ ++ exit 1;\ ++ fi ++ @if test "$(findstring -iphonesimulator,$(MULTIARCH))" != "-iphonesimulator"; then \ ++ echo "Cannot run the iOS testbed for non-simulator builds."; \ ++ exit 1;\ ++ fi ++ @if test $(PYTHONFRAMEWORK) != "Python"; then \ ++ echo "Cannot run the iOS testbed with a non-default framework name."; \ ++ exit 1;\ ++ fi ++ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \ ++ echo "Cannot find a finalized iOS Python.framework. Have you run 'make install' to finalize the framework build?"; \ ++ exit 1;\ ++ fi ++ ++ # Clone the testbed project into the XCFOLDER ++ $(PYTHON_FOR_BUILD) $(srcdir)/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" ++ ++ # Run the testbed project ++ $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W ++ + # Like testall, but with only one pass and without multiple processes. + # Run an optional script to include information about the build environment. + buildbottest: all +@@ -1747,7 +1814,7 @@ + # prevent race conditions with PGO builds. PGO builds use recursive make, + # which can lead to two parallel `./python setup.py build` processes that + # step on each others toes. +-install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKINSTALLLAST@ ++install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@ + if test "x$(ENSUREPIP)" != "xno" ; then \ + case $(ENSUREPIP) in \ + upgrade) ensurepip="--upgrade" ;; \ +@@ -2144,7 +2211,16 @@ + $(INSTALL_DATA) $(srcdir)/Modules/xxmodule.c \ + $(DESTDIR)$(LIBDEST)/distutils/tests ; \ + fi +- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \ ++ @ # If app store compliance has been configured, apply the patch to the ++ @ # installed library code. The patch has been previously validated against ++ @ # the original source tree, so we can ignore any errors that are raised ++ @ # due to files that are missing because of --disable-test-modules etc. ++ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \ ++ echo "Applying app store compliance patch"; \ ++ patch --force --reject-file "$(abs_builddir)/app-store-compliance.rej" --strip 2 --directory "$(DESTDIR)$(LIBDEST)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)" || true ; \ ++ fi ++ @ # Build PYC files for the 3 optimization levels (0, 1, 2) ++ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \ + $(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \ + -j0 -d $(LIBDEST) -f \ + -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \ +@@ -2316,9 +2392,11 @@ + # automatically set prefix to the location deep down in the framework, so we + # only have to cater for the structural bits of the framework. + +-frameworkinstallframework: frameworkinstallstructure install frameworkinstallmaclib ++frameworkinstallframework: @FRAMEWORKINSTALLFIRST@ install frameworkinstallmaclib + +-frameworkinstallstructure: $(LDLIBRARY) ++# macOS uses a versioned frameworks structure that includes a full install ++.PHONY: frameworkinstallversionedstructure ++frameworkinstallversionedstructure: $(LDLIBRARY) + @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ + echo Not configured with --enable-framework; \ + exit 1; \ +@@ -2339,6 +2417,30 @@ + $(LN) -fsn Versions/Current/Resources $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Resources + $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) + ++# iOS/tvOS/watchOS uses a non-versioned framework with Info.plist in the ++# framework root, no .lproj data, and only stub compilation assistance binaries ++.PHONY: frameworkinstallunversionedstructure ++frameworkinstallunversionedstructure: $(LDLIBRARY) ++ @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ ++ echo Not configured with --enable-framework; \ ++ exit 1; \ ++ else true; \ ++ fi ++ if test -d $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; then \ ++ echo "Clearing stale header symlink directory"; \ ++ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; \ ++ fi ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) ++ sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist ++ $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(LIBDIR) ++ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(LDVERSION).dylib" ++ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(VERSION).dylib" ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) ++ for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ ++ $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ ++ done ++ + # This installs Mac/Lib into the framework + # Install a number of symlinks to keep software that expects a normal unix + # install (which includes python-config) happy. +@@ -2373,6 +2475,19 @@ + frameworkinstallextras: + cd Mac && $(MAKE) installextras DESTDIR="$(DESTDIR)" + ++# On iOS, bin/lib can't live inside the framework; include needs to be called ++# "Headers", but *must* be in the framework, and *not* include the `python3.X` ++# subdirectory. The install has put these folders in the same folder as ++# Python.framework; Move the headers to their final framework-compatible home. ++.PHONY: frameworkinstallmobileheaders ++frameworkinstallmobileheaders: frameworkinstallunversionedstructure inclinstall ++ if test -d $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; then \ ++ echo "Removing old framework headers"; \ ++ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \ ++ fi ++ 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 + CONFIG_FILES=Makefile.pre CONFIG_HEADERS= $(SHELL) config.status +@@ -2477,6 +2592,10 @@ + -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' + -rm -f Include/pydtrace_probes.h + -rm -f profile-gen-stamp ++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/bin ++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/lib ++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/include ++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/Python.framework + + profile-removal: + find . -name '*.gc??' -exec rm -f {} ';' +@@ -2498,6 +2617,8 @@ + config.cache config.log pyconfig.h Modules/config.c + -rm -rf build platform + -rm -rf $(PYTHONFRAMEWORKDIR) ++ -rm -rf Apple/iOS/Frameworks ++ -rm -rf iOSTestbed.* + -rm -f python-config.py python-config + + # Make things extra clean, before making a distribution: +@@ -2578,7 +2699,7 @@ + .PHONY: all build_all build_wasm sharedmods check-clean-src oldsharedmods test quicktest + .PHONY: install altinstall oldsharedinstall bininstall altbininstall + .PHONY: maninstall libinstall inclinstall libainstall sharedinstall +-.PHONY: frameworkinstall frameworkinstallframework frameworkinstallstructure ++.PHONY: frameworkinstall frameworkinstallframework + .PHONY: frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools + .PHONY: frameworkaltinstallunixtools recheck clean clobber distclean + .PHONY: smelly funny patchcheck touch altmaninstall commoninstall +diff --git a/Modules/getpath.c b/Modules/getpath.c +index 61d654065fd..8bd844d6459 100644 +--- a/Modules/getpath.c ++++ b/Modules/getpath.c +@@ -15,6 +15,7 @@ + #endif + + #ifdef __APPLE__ ++# include "TargetConditionals.h" + # include + #endif + +@@ -767,7 +768,7 @@ + if (PyWin_DLLhModule) { + return winmodule_to_dict(dict, key, PyWin_DLLhModule); + } +-#elif defined(WITH_NEXT_FRAMEWORK) ++#elif defined(WITH_NEXT_FRAMEWORK) && !defined(TARGET_OS_IPHONE) + static char modPath[MAXPATHLEN + 1]; + static int modPathInitialized = -1; + if (modPathInitialized < 0) { +@@ -961,4 +962,3 @@ + + return _PyStatus_OK(); + } +- +diff --git a/Python/marshal.c b/Python/marshal.c +index 29f3bab60a5..7d32bb3661d 100644 +--- a/Python/marshal.c ++++ b/Python/marshal.c +@@ -14,6 +14,10 @@ + #include "pycore_hashtable.h" // _Py_hashtable_t + #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 @@ + * #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' +diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c +index 9248e971d9c..9bac4fed20b 100644 +--- a/Python/pylifecycle.c ++++ b/Python/pylifecycle.c +@@ -35,7 +35,21 @@ + #include // getenv() + + #if defined(__APPLE__) +-#include ++# include ++# include ++# include ++// The os_log unified logging APIs were introduced in macOS 10.12, iOS 10.0, ++// tvOS 10.0, and watchOS 3.0; we enable the use of the system logger ++// automatically on non-macOS platforms. ++# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE ++# define USE_APPLE_SYSTEM_LOG 1 ++# else ++# define USE_APPLE_SYSTEM_LOG 0 ++# endif ++ ++# if USE_APPLE_SYSTEM_LOG ++# include ++# endif + #endif + + #ifdef HAVE_SIGNAL_H +@@ -72,6 +86,9 @@ + static PyStatus init_import_site(void); + static PyStatus init_set_builtins_open(void); + static PyStatus init_sys_streams(PyThreadState *tstate); ++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG ++static PyStatus init_apple_streams(PyThreadState *tstate); ++#endif + static void wait_for_thread_shutdown(PyThreadState *tstate); + static void call_ll_exitfuncs(_PyRuntimeState *runtime); + +@@ -1161,6 +1178,17 @@ + return status; + } + ++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG ++ status = init_apple_streams(tstate); ++ if (_PyStatus_EXCEPTION(status)) { ++ return status; ++ } ++#endif ++ ++#ifdef Py_DEBUG ++ run_presite(tstate); ++#endif ++ + status = add_main_module(interp); + if (_PyStatus_EXCEPTION(status)) { + return status; +@@ -2481,6 +2509,69 @@ + return res; + } + ++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG ++ ++static PyObject * ++apple_log_write_impl(PyObject *self, PyObject *args) ++{ ++ int logtype = 0; ++ const char *text = NULL; ++ if (!PyArg_ParseTuple(args, "iy", &logtype, &text)) { ++ return NULL; ++ } ++ ++ // Pass the user-provided text through explicit %s formatting ++ // to avoid % literals being interpreted as a formatting directive. ++ os_log_with_type(OS_LOG_DEFAULT, logtype, "%s", text); ++ Py_RETURN_NONE; ++} ++ ++ ++static PyMethodDef apple_log_write_method = { ++ "apple_log_write", apple_log_write_impl, METH_VARARGS ++}; ++ ++ ++static PyStatus ++init_apple_streams(PyThreadState *tstate) ++{ ++ PyStatus status = _PyStatus_OK(); ++ PyObject *_apple_support = NULL; ++ PyObject *apple_log_write = NULL; ++ PyObject *result = NULL; ++ ++ _apple_support = PyImport_ImportModule("_apple_support"); ++ if (_apple_support == NULL) { ++ goto error; ++ } ++ ++ apple_log_write = PyCFunction_New(&apple_log_write_method, NULL); ++ if (apple_log_write == NULL) { ++ goto error; ++ } ++ ++ // Initialize the logging streams, sending stdout -> Default; stderr -> Error ++ result = PyObject_CallMethod( ++ _apple_support, "init_streams", "Oii", ++ apple_log_write, OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_ERROR); ++ if (result == NULL) { ++ goto error; ++ } ++ goto done; ++ ++error: ++ _PyErr_Print(tstate); ++ status = _PyStatus_ERR("failed to initialize Apple log streams"); ++ ++done: ++ Py_XDECREF(result); ++ Py_XDECREF(apple_log_write); ++ Py_XDECREF(_apple_support); ++ return status; ++} ++ ++#endif // __APPLE__ && USE_APPLE_SYSTEM_LOG ++ + + static void + _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp, +diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h +index 553585a76a3..991e5255405 100644 +--- a/Python/stdlib_module_names.h ++++ b/Python/stdlib_module_names.h +@@ -5,6 +5,7 @@ + "__future__", + "_abc", + "_aix_support", ++"_apple_support", + "_ast", + "_asyncio", + "_bisect", +@@ -40,6 +41,7 @@ + "_heapq", + "_imp", + "_io", ++"_ios_support", + "_json", + "_locale", + "_lsprof", +diff --git a/config.sub b/config.sub +index d74fb6deac9..1bb6a05dc11 100755 +--- a/config.sub ++++ b/config.sub +@@ -1,14 +1,15 @@ + #! /bin/sh + # Configuration validation subroutine script. +-# Copyright 1992-2021 Free Software Foundation, Inc. ++# Copyright 1992-2024 Free Software Foundation, Inc. + + # shellcheck disable=SC2006,SC2268 # see below for rationale + +-timestamp='2021-08-14' ++# Patched 2024-02-03 to include support for arm64_32 and iOS/tvOS/watchOS simulators ++timestamp='2024-01-01' + + # This file is free software; you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by +-# the Free Software Foundation; either version 3 of the License, or ++# the Free Software Foundation, either version 3 of the License, or + # (at your option) any later version. + # + # This program is distributed in the hope that it will be useful, but +@@ -76,13 +77,13 @@ + version="\ + GNU config.sub ($timestamp) + +-Copyright 1992-2021 Free Software Foundation, Inc. ++Copyright 1992-2024 Free Software Foundation, Inc. + + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + + help=" +-Try \`$me --help' for more information." ++Try '$me --help' for more information." + + # Parse command line + while test $# -gt 0 ; do +@@ -130,7 +131,7 @@ + # Separate into logical components for further validation + case $1 in + *-*-*-*-*) +- echo Invalid configuration \`"$1"\': more than four components >&2 ++ echo "Invalid configuration '$1': more than four components" >&2 + exit 1 + ;; + *-*-*-*) +@@ -145,7 +146,8 @@ + nto-qnx* | linux-* | uclinux-uclibc* \ + | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ + | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ +- | storm-chaos* | os2-emx* | rtmk-nova*) ++ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \ ++ | windows-* ) + basic_machine=$field1 + basic_os=$maybe_os + ;; +@@ -943,7 +945,7 @@ + EOF + IFS=$saved_IFS + ;; +- # We use `pc' rather than `unknown' ++ # We use 'pc' rather than 'unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) +@@ -1020,6 +1022,11 @@ + ;; + + # Here we normalize CPU types with a missing or matching vendor ++ armh-unknown | armh-alt) ++ cpu=armv7l ++ vendor=alt ++ basic_os=${basic_os:-linux-gnueabihf} ++ ;; + dpx20-unknown | dpx20-bull) + cpu=rs6000 + vendor=bull +@@ -1070,7 +1077,7 @@ + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + cpu=i586 + ;; +- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*) ++ pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*) + cpu=i686 + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) +@@ -1121,7 +1128,7 @@ + xscale-* | xscalee[bl]-*) + cpu=`echo "$cpu" | sed 's/^xscale/arm/'` + ;; +- arm64-*) ++ arm64-* | aarch64le-* | arm64_32-*) + cpu=aarch64 + ;; + +@@ -1175,7 +1182,7 @@ + case $cpu in + 1750a | 580 \ + | a29k \ +- | aarch64 | aarch64_be \ ++ | aarch64 | aarch64_be | aarch64c | arm64ec \ + | abacus \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \ +@@ -1194,50 +1201,29 @@ + | d10v | d30v | dlx | dsp16xx \ + | e2k | elxsi | epiphany \ + | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \ ++ | javascript \ + | h8300 | h8500 \ + | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i*86 | i860 | i960 | ia16 | ia64 \ + | ip2k | iq2000 \ + | k1om \ ++ | kvx \ + | le32 | le64 \ + | lm32 \ +- | loongarch32 | loongarch64 | loongarchx32 \ ++ | loongarch32 | loongarch64 \ + | m32c | m32r | m32rle \ + | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \ + | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \ + | m88110 | m88k | maxq | mb | mcore | mep | metag \ + | microblaze | microblazeel \ +- | mips | mipsbe | mipseb | mipsel | mipsle \ +- | mips16 \ +- | mips64 | mips64eb | mips64el \ +- | mips64octeon | mips64octeonel \ +- | mips64orion | mips64orionel \ +- | mips64r5900 | mips64r5900el \ +- | mips64vr | mips64vrel \ +- | mips64vr4100 | mips64vr4100el \ +- | mips64vr4300 | mips64vr4300el \ +- | mips64vr5000 | mips64vr5000el \ +- | mips64vr5900 | mips64vr5900el \ +- | mipsisa32 | mipsisa32el \ +- | mipsisa32r2 | mipsisa32r2el \ +- | mipsisa32r3 | mipsisa32r3el \ +- | mipsisa32r5 | mipsisa32r5el \ +- | mipsisa32r6 | mipsisa32r6el \ +- | mipsisa64 | mipsisa64el \ +- | mipsisa64r2 | mipsisa64r2el \ +- | mipsisa64r3 | mipsisa64r3el \ +- | mipsisa64r5 | mipsisa64r5el \ +- | mipsisa64r6 | mipsisa64r6el \ +- | mipsisa64sb1 | mipsisa64sb1el \ +- | mipsisa64sr71k | mipsisa64sr71kel \ +- | mipsr5900 | mipsr5900el \ +- | mipstx39 | mipstx39el \ ++ | mips* \ + | mmix \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ ++ | nanomips* \ + | nds32 | nds32le | nds32be \ + | nfp \ + | nios | nios2 | nios2eb | nios2el \ +@@ -1269,6 +1255,7 @@ + | ubicom32 \ + | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \ + | vax \ ++ | vc4 \ + | visium \ + | w65 \ + | wasm32 | wasm64 \ +@@ -1280,7 +1267,7 @@ + ;; + + *) +- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2 ++ echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2 + exit 1 + ;; + esac +@@ -1301,11 +1288,12 @@ + + # Decode manufacturer-specific aliases for certain operating systems. + +-if test x$basic_os != x ++if test x"$basic_os" != x + then + +-# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just ++# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just + # set os. ++obj= + case $basic_os in + gnu/linux*) + kernel=linux +@@ -1336,6 +1324,10 @@ + kernel=linux + os=`echo "$basic_os" | sed -e 's|linux|gnu|'` + ;; ++ managarm*) ++ kernel=managarm ++ os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'` ++ ;; + *) + kernel= + os=$basic_os +@@ -1501,10 +1493,16 @@ + os=eabi + ;; + *) +- os=elf ++ os= ++ obj=elf + ;; + esac + ;; ++ aout* | coff* | elf* | pe*) ++ # These are machine code file formats, not OSes ++ obj=$os ++ os= ++ ;; + *) + # No normalization, but not necessarily accepted, that comes below. + ;; +@@ -1523,12 +1521,15 @@ + # system, and we'll never get to this point. + + kernel= ++obj= + case $cpu-$vendor in + score-*) +- os=elf ++ os= ++ obj=elf + ;; + spu-*) +- os=elf ++ os= ++ obj=elf + ;; + *-acorn) + os=riscix1.2 +@@ -1538,28 +1539,35 @@ + os=gnu + ;; + arm*-semi) +- os=aout ++ os= ++ obj=aout + ;; + c4x-* | tic4x-*) +- os=coff ++ os= ++ obj=coff + ;; + c8051-*) +- os=elf ++ os= ++ obj=elf + ;; + clipper-intergraph) + os=clix + ;; + hexagon-*) +- os=elf ++ os= ++ obj=elf + ;; + tic54x-*) +- os=coff ++ os= ++ obj=coff + ;; + tic55x-*) +- os=coff ++ os= ++ obj=coff + ;; + tic6x-*) +- os=coff ++ os= ++ obj=coff + ;; + # This must come before the *-dec entry. + pdp10-*) +@@ -1581,19 +1589,24 @@ + os=sunos3 + ;; + m68*-cisco) +- os=aout ++ os= ++ obj=aout + ;; + mep-*) +- os=elf ++ os= ++ obj=elf + ;; + mips*-cisco) +- os=elf ++ os= ++ obj=elf + ;; +- mips*-*) +- os=elf ++ mips*-*|nanomips*-*) ++ os= ++ obj=elf + ;; + or32-*) +- os=coff ++ os= ++ obj=coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=sysv3 +@@ -1602,7 +1615,8 @@ + os=sunos4.1.1 + ;; + pru-*) +- os=elf ++ os= ++ obj=elf + ;; + *-be) + os=beos +@@ -1683,10 +1697,12 @@ + os=uxpv + ;; + *-rom68k) +- os=coff ++ os= ++ obj=coff + ;; + *-*bug) +- os=coff ++ os= ++ obj=coff + ;; + *-apple) + os=macos +@@ -1704,10 +1720,11 @@ + + fi + +-# Now, validate our (potentially fixed-up) OS. ++# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ). ++ + case $os in + # Sometimes we do "kernel-libc", so those need to count as OSes. +- musl* | newlib* | relibc* | uclibc*) ++ llvm* | musl* | newlib* | relibc* | uclibc*) + ;; + # Likewise for "kernel-abi" + eabi* | gnueabi*) +@@ -1715,6 +1732,9 @@ + # VxWorks passes extra cpu info in the 4th filed. + simlinux | simwindows | spe) + ;; ++ # See `case $cpu-$os` validation below ++ ghcjs) ++ ;; + # Now accept the basic system types. + # The portable systems comes first. + # Each alternative MUST end in a * to match a version number. +@@ -1723,7 +1743,7 @@ + | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ + | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \ + | hiux* | abug | nacl* | netware* | windows* \ +- | os9* | macos* | osx* | ios* \ ++ | os9* | macos* | osx* | ios* | tvos* | watchos* \ + | mpw* | magic* | mmixware* | mon960* | lnews* \ + | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ + | aos* | aros* | cloudabi* | sortix* | twizzler* \ +@@ -1732,11 +1752,11 @@ + | mirbsd* | netbsd* | dicos* | openedition* | ose* \ + | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \ + | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \ +- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ +- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ ++ | bosx* | nextstep* | cxux* | oabi* \ ++ | ptx* | ecoff* | winnt* | domain* | vsta* \ + | udi* | lites* | ieee* | go32* | aux* | hcos* \ + | chorusrdb* | cegcc* | glidix* | serenity* \ +- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \ ++ | cygwin* | msys* | moss* | proelf* | rtems* \ + | midipix* | mingw32* | mingw64* | mint* \ + | uxpv* | beos* | mpeix* | udk* | moxiebox* \ + | interix* | uwin* | mks* | rhapsody* | darwin* \ +@@ -1748,49 +1768,119 @@ + | skyos* | haiku* | rdos* | toppers* | drops* | es* \ + | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ + | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ +- | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr*) ++ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ ++ | fiwix* | mlibc* | cos* | mbr* | ironclad* ) + ;; + # This one is extra strict with allowed versions + sco3.2v2 | sco3.2v[4-9]* | sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + ;; ++ # This refers to builds using the UEFI calling convention ++ # (which depends on the architecture) and PE file format. ++ # Note that this is both a different calling convention and ++ # different file format than that of GNU-EFI ++ # (x86_64-w64-mingw32). ++ uefi) ++ ;; + none) + ;; ++ kernel* | msvc* ) ++ # Restricted further below ++ ;; ++ '') ++ if test x"$obj" = x ++ then ++ echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2 ++ fi ++ ;; + *) +- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2 ++ echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2 ++ exit 1 ++ ;; ++esac ++ ++case $obj in ++ aout* | coff* | elf* | pe*) ++ ;; ++ '') ++ # empty is fine ++ ;; ++ *) ++ echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2 ++ exit 1 ++ ;; ++esac ++ ++# Here we handle the constraint that a (synthetic) cpu and os are ++# valid only in combination with each other and nowhere else. ++case $cpu-$os in ++ # The "javascript-unknown-ghcjs" triple is used by GHC; we ++ # accept it here in order to tolerate that, but reject any ++ # variations. ++ javascript-ghcjs) ++ ;; ++ javascript-* | *-ghcjs) ++ echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2 + exit 1 + ;; + esac + + # As a final step for OS-related things, validate the OS-kernel combination + # (given a valid OS), if there is a kernel. +-case $kernel-$os in +- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ +- | linux-musl* | linux-relibc* | linux-uclibc* ) ++case $kernel-$os-$obj in ++ linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \ ++ | linux-mlibc*- | linux-musl*- | linux-newlib*- \ ++ | linux-relibc*- | linux-uclibc*- ) ++ ;; ++ uclinux-uclibc*- ) + ;; +- uclinux-uclibc* ) ++ managarm-mlibc*- | managarm-kernel*- ) + ;; +- -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* ) ++ windows*-msvc*-) ++ ;; ++ -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \ ++ | -uclibc*- ) + # These are just libc implementations, not actual OSes, and thus + # require a kernel. +- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 ++ echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2 + exit 1 + ;; +- kfreebsd*-gnu* | kopensolaris*-gnu*) ++ -kernel*- ) ++ echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2 ++ exit 1 + ;; +- vxworks-simlinux | vxworks-simwindows | vxworks-spe) ++ *-kernel*- ) ++ echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2 ++ exit 1 + ;; +- nto-qnx*) ++ *-msvc*- ) ++ echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2 ++ exit 1 + ;; +- os2-emx) ++ kfreebsd*-gnu*- | kopensolaris*-gnu*-) + ;; +- *-eabi* | *-gnueabi*) ++ vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-) + ;; +- -*) ++ nto-qnx*-) ++ ;; ++ os2-emx-) ++ ;; ++ *-eabi*- | *-gnueabi*-) ++ ;; ++ ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) ++ ;; ++ none--*) ++ # None (no kernel, i.e. freestanding / bare metal), ++ # can be paired with an machine code file format ++ ;; ++ -*-) + # Blank kernel with real OS is always fine. + ;; +- *-*) +- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 ++ --*) ++ # Blank kernel and OS with real machine code file format is always fine. ++ ;; ++ *-*-*) ++ echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 + exit 1 + ;; + esac +@@ -1873,7 +1963,7 @@ + ;; + esac + +-echo "$cpu-$vendor-${kernel:+$kernel-}$os" ++echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" + exit + + # Local variables: +diff --git a/configure b/configure +index a1ad0ae2510..ee391d354f6 100755 +--- a/configure ++++ b/configure +@@ -838,6 +838,8 @@ + LIBMPDEC_INTERNAL + LIBMPDEC_LDFLAGS + LIBMPDEC_CFLAGS ++LIBFFI_LIB ++LIBFFI_LIBDIR + LIBFFI_INCLUDEDIR + LIBEXPAT_INTERNAL + LIBEXPAT_LDFLAGS +@@ -925,10 +927,14 @@ + LDFLAGS + CFLAGS + CC ++WATCHOS_DEPLOYMENT_TARGET ++TVOS_DEPLOYMENT_TARGET ++IPHONEOS_DEPLOYMENT_TARGET + EXPORT_MACOSX_DEPLOYMENT_TARGET + CONFIGURE_MACOSX_DEPLOYMENT_TARGET + _PYTHON_HOST_PLATFORM +-MACHDEP ++APP_STORE_COMPLIANCE_PATCH ++INSTALLTARGETS + FRAMEWORKINSTALLAPPSPREFIX + FRAMEWORKUNIXTOOLSPREFIX + FRAMEWORKPYTHONW +@@ -936,6 +942,8 @@ + FRAMEWORKALTINSTALLFIRST + FRAMEWORKINSTALLLAST + FRAMEWORKINSTALLFIRST ++RESSRCDIR ++PYTHONFRAMEWORKINSTALLNAMEPREFIX + PYTHONFRAMEWORKINSTALLDIR + PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKDIR +@@ -945,6 +953,7 @@ + LIPO_32BIT_FLAGS + ARCH_RUN_32BIT + UNIVERSALSDK ++MACHDEP + PKG_CONFIG_LIBDIR + PKG_CONFIG_PATH + PKG_CONFIG +@@ -991,7 +1000,6 @@ + docdir + oldincludedir + includedir +-runstatedir + localstatedir + sharedstatedir + sysconfdir +@@ -1020,6 +1028,7 @@ + with_universal_archs + with_framework_name + enable_framework ++with_app_store_compliance + with_cxx_main + with_emscripten_target + enable_wasm_dynamic_linking +@@ -1142,7 +1151,6 @@ + sysconfdir='${prefix}/etc' + sharedstatedir='${prefix}/com' + localstatedir='${prefix}/var' +-runstatedir='${localstatedir}/run' + includedir='${prefix}/include' + oldincludedir='/usr/include' + docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +@@ -1395,15 +1403,6 @@ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + +- -runstatedir | --runstatedir | --runstatedi | --runstated \ +- | --runstate | --runstat | --runsta | --runst | --runs \ +- | --run | --ru | --r) +- ac_prev=runstatedir ;; +- -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ +- | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ +- | --run=* | --ru=* | --r=*) +- runstatedir=$ac_optarg ;; +- + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ +@@ -1541,7 +1540,7 @@ + for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ +- libdir localedir mandir runstatedir ++ libdir localedir mandir + do + eval ac_val=\$$ac_var + # Remove trailing slashes. +@@ -1694,7 +1693,6 @@ + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] +- --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] +@@ -1779,6 +1777,10 @@ + specify the name for the python framework on macOS + only valid when --enable-framework is set. see + Mac/README.rst (default is 'Python') ++ --with-app-store-compliance=[PATCH-FILE] ++ Enable any patches required for compiliance with app ++ stores. Optional PATCH-FILE specifies the custom ++ patch to apply. + --with-cxx-main[=COMPILER] + compile main() and link Python executable with C++ + compiler specified in COMPILER (default is $CXX) +@@ -3566,6 +3568,166 @@ + as_fn_error $? "pkg-config is required" "$LINENO" 5] + fi + ++# Set name for machine-dependent library files + -+/* Begin XCConfigurationList section */ -+ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ 607A663F2B0EFA3A0010BFC8 /* Debug */, -+ 607A66402B0EFA3A0010BFC8 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ 607A66422B0EFA3A0010BFC8 /* Debug */, -+ 607A66432B0EFA3A0010BFC8 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ 607A66452B0EFA3A0010BFC8 /* Debug */, -+ 607A66462B0EFA3A0010BFC8 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+/* End XCConfigurationList section */ -+ }; -+ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; -+} ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/AppDelegate.h -@@ -0,0 +1,11 @@ -+// -+// AppDelegate.h -+// iOSTestbed -+// ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 ++$as_echo_n "checking MACHDEP... " >&6; } ++if test -z "$MACHDEP" ++then ++ # avoid using uname for cross builds ++ if test "$cross_compiling" = yes; then ++ # ac_sys_system and ac_sys_release are used for setting ++ # a lot of different things including 'define_xopen_source' ++ # in the case statement below. ++ case "$host" in ++ *-*-linux-android*) ++ ac_sys_system=Linux-android ++ ;; ++ *-*-linux*) ++ ac_sys_system=Linux ++ ;; ++ *-*-cygwin*) ++ ac_sys_system=Cygwin ++ ;; ++ *-apple-ios*) ++ ac_sys_system=iOS ++ ;; ++ *-apple-tvos*) ++ ac_sys_system=tvOS ++ ;; ++ *-apple-watchos*) ++ ac_sys_system=watchOS ++ ;; ++ *-*-vxworks*) ++ ac_sys_system=VxWorks ++ ;; ++ *-*-emscripten) ++ ac_sys_system=Emscripten ++ ;; ++ *-*-wasi) ++ ac_sys_system=WASI ++ ;; ++ *) ++ # for now, limit cross builds to known configurations ++ MACHDEP="unknown" ++ as_fn_error $? "cross build not supported for $host" "$LINENO" 5 ++ esac ++ ac_sys_release= ++ else ++ ac_sys_system=`uname -s` ++ if test "$ac_sys_system" = "AIX" \ ++ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then ++ ac_sys_release=`uname -v` ++ else ++ ac_sys_release=`uname -r` ++ fi ++ fi ++ ac_md_system=`echo $ac_sys_system | ++ tr -d '/ ' | tr '[A-Z]' '[a-z]'` ++ ac_md_release=`echo $ac_sys_release | ++ tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` ++ MACHDEP="$ac_md_system$ac_md_release" + -+#import ++ case $MACHDEP in ++ aix*) MACHDEP="aix";; ++ linux*) MACHDEP="linux";; ++ cygwin*) MACHDEP="cygwin";; ++ darwin*) MACHDEP="darwin";; ++ '') MACHDEP="unknown";; ++ esac + -+@interface AppDelegate : UIResponder ++ if test "$ac_sys_system" = "SunOS"; then ++ # For Solaris, there isn't an OS version specific macro defined ++ # in most compilers, so we define one here. ++ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` + ++cat >>confdefs.h <<_ACEOF ++#define Py_SUNOS_VERSION $SUNOS_VERSION ++_ACEOF + -+@end ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/AppDelegate.m -@@ -0,0 +1,19 @@ -+// -+// AppDelegate.m -+// iOSTestbed -+// ++ fi ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 ++$as_echo "\"$MACHDEP\"" >&6; } + -+#import "AppDelegate.h" ++# On cross-compile builds, configure will look for a host-specific compiler by ++# prepending the user-provided host triple to the required binary name. ++# ++# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", ++# which isn't a binary that exists, and isn't very convenient, as it contains the ++# iOS version. As the default cross-compiler name won't exist, configure falls ++# back to gcc, which *definitely* won't work. We're providing wrapper scripts for ++# these tools; the binary names of these scripts are better defaults than "gcc". ++# This only requires that the user put the platform scripts folder (e.g., ++# "iOS/Resources/bin") in their path, rather than defining platform-specific ++# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to ++# either put the platform scripts folder in the path, or specify CC etc, ++# configure will fail. ++if test -z "$AR"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; ++ aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; ++ x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; + -+@interface AppDelegate () ++ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;; ++ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;; ++ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;; + -+@end ++ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;; ++ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; ++ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; ++ *) ++ esac ++fi ++if test -z "$CC"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; ++ aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; ++ x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; + -+@implementation AppDelegate ++ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;; ++ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;; ++ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;; ++ ++ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;; ++ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;; ++ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;; ++ *) ++ esac ++fi ++if test -z "$CPP"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; ++ aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; ++ x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++ ++ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;; ++ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;; ++ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;; ++ ++ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;; ++ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;; ++ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;; ++ *) ++ esac ++fi ++if test -z "$CXX"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; ++ aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; ++ x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; + ++ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;; ++ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;; ++ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;; + -+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -+ return YES; -+} ++ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;; ++ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;; ++ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;; ++ *) ++ esac ++fi + -+@end ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json -@@ -0,0 +1,11 @@ -+{ -+ "colors" : [ -+ { -+ "idiom" : "universal" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json -@@ -0,0 +1,13 @@ -+{ -+ "images" : [ -+ { -+ "idiom" : "universal", -+ "platform" : "ios", -+ "size" : "1024x1024" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/Assets.xcassets/Contents.json -@@ -0,0 +1,6 @@ -+{ -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard -@@ -0,0 +1,9 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/app/README -@@ -0,0 +1,7 @@ -+This folder can contain any Python application code. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --enable-universalsdk" >&5 + $as_echo_n "checking for --enable-universalsdk... " >&6; } + # Check whether --enable-universalsdk was given. +@@ -3677,111 +3839,195 @@ + enableval=$enable_framework; + case $enableval in + yes) +- enableval=/Library/Frameworks ++ case $ac_sys_system in ++ Darwin) enableval=/Library/Frameworks ;; ++ iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; ++ tvOS) enableval=Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;; ++ watchOS) enableval=Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;; ++ *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 ++ esac + esac + -+During the build, any binary modules found in this folder will be processed into -+iOS Framework form. + case $enableval in + no) +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE"; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= ++ case $ac_sys_system in ++ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; ++ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; ++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" + -+When the test suite runs, this folder will be on the PYTHONPATH, and will be the -+working directory for the test suite. ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/app_packages/README -@@ -0,0 +1,7 @@ -+This folder can be a target for installing any Python dependencies needed by the -+test suite. ++ if test "x${prefix}" = "xNONE"; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= ++ esac + ;; + *) + PYTHONFRAMEWORKPREFIX="${enableval}" + PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR +- FRAMEWORKINSTALLFIRST="frameworkinstallstructure" +- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " +- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" +- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" +- FRAMEWORKPYTHONW="frameworkpythonw" +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi + +- case "${enableval}" in +- /System*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- if test "${prefix}" = "NONE" ; then +- # See below +- FRAMEWORKUNIXTOOLSPREFIX="/usr" +- fi +- ;; ++ case $ac_sys_system in #( ++ Darwin) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" ++ FRAMEWORKPYTHONW="frameworkpythonw" ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ INSTALLTARGETS="commoninstall bininstall maninstall" + -+During the build, any binary modules found in this folder will be processed into -+iOS Framework form. ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + -+When the test suite runs, this folder will be on the PYTHONPATH. ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ iPhoneOS -+ -+ MinimumOSVersion -+ 12.0 -+ CFBundleVersion -+ 1 -+ -+ ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist -@@ -0,0 +1,62 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleDisplayName -+ ${PRODUCT_NAME} -+ CFBundleExecutable -+ ${EXECUTABLE_NAME} -+ CFBundleIdentifier -+ org.python.iOSTestbed -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleName -+ ${PRODUCT_NAME} -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ 1 -+ LSRequiresIPhoneOS -+ -+ UIRequiresFullScreen -+ -+ UILaunchStoryboardName -+ Launch Screen -+ UISupportedInterfaceOrientations -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ -+ UISupportedInterfaceOrientations~ipad -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationPortraitUpsideDown -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ -+ TestArgs -+ -+ test -+ -uall -+ -W -+ -+ -+ UIApplicationSceneManifest -+ -+ UIApplicationSupportsMultipleScenes -+ -+ UISceneConfigurations -+ -+ -+ -+ ---- /dev/null -+++ b/iOS/testbed/iOSTestbed/main.m -@@ -0,0 +1,16 @@ -+// -+// main.m -+// iOSTestbed -+// ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi + +- /Library*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ case "${enableval}" in ++ /System*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ if test "${prefix}" = "NONE" ; then ++ # See below ++ FRAMEWORKUNIXTOOLSPREFIX="/usr" ++ fi ++ ;; + -+#import -+#import "AppDelegate.h" ++ /Library*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; + -+int main(int argc, char * argv[]) { -+ NSString * appDelegateClassName; -+ @autoreleasepool { -+ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ */Library/Frameworks) ++ MDIR="`dirname "${enableval}"`" ++ MDIR="`dirname "${MDIR}"`" ++ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" + -+ return UIApplicationMain(argc, argv, nil, appDelegateClassName); -+ } -+} ---- /dev/null -+++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m -@@ -0,0 +1,160 @@ -+#import -+#import ++ if test "${prefix}" = "NONE"; then ++ # User hasn't specified the ++ # --prefix option, but wants to install ++ # the framework in a non-default location, ++ # ensure that the compatibility links get ++ # installed relative to that prefix as well ++ # instead of in /usr/local. ++ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" ++ fi ++ ;; + +- */Library/Frameworks) +- MDIR="`dirname "${enableval}"`" +- MDIR="`dirname "${MDIR}"`" +- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" +- +- if test "${prefix}" = "NONE"; then +- # User hasn't specified the +- # --prefix option, but wants to install +- # the framework in a non-default location, +- # ensure that the compatibility links get +- # installed relative to that prefix as well +- # instead of in /usr/local. +- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" +- fi +- ;; ++ *) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; ++ esac + +- *) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} ++ RESSRCDIR=Mac/Resources/framework ++ ++ # Add files for Mac specific code to the list of output ++ # files: ++ ac_config_files="$ac_config_files Mac/Makefile" + -+@interface iOSTestbedTests : XCTestCase ++ ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" + -+@end ++ ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" + -+@implementation iOSTestbedTests ++ ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" + ++ ;; ++ iOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" + -+- (void)testPython { -+ const char **argv; -+ int exit_code; -+ int failed; -+ PyStatus status; -+ PyPreConfig preconfig; -+ PyConfig config; -+ PyObject *sys_module; -+ PyObject *sys_path_attr; -+ NSArray *test_args; -+ NSString *python_home; -+ NSString *path; -+ wchar_t *wtmp_str; ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Apple/iOS/Resources + -+ NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; ++ ac_config_files="$ac_config_files Apple/iOS/Resources/Info.plist" + -+ // Set some other common environment indicators to disable color, as the -+ // Xcode log can't display color. Stdout will report that it is *not* a -+ // TTY. -+ setenv("NO_COLOR", "1", true); -+ setenv("PYTHON_COLORS", "0", true); ++ ;; ++ tvOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" + -+ // Arguments to pass into the test suite runner. -+ // argv[0] must identify the process; any subsequent arg -+ // will be handled as if it were an argument to `python -m test` -+ test_args = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"TestArgs"]; -+ if (test_args == NULL) { -+ NSLog(@"Unable to identify test arguments."); -+ } -+ argv = malloc(sizeof(char *) * ([test_args count] + 1)); -+ argv[0] = "iOSTestbed"; -+ for (int i = 1; i < [test_args count]; i++) { -+ argv[i] = [[test_args objectAtIndex:i] UTF8String]; -+ } -+ NSLog(@"Test command: %@", test_args); ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Apple/tvOS/Resources + -+ // Generate an isolated Python configuration. -+ NSLog(@"Configuring isolated Python..."); -+ PyPreConfig_InitIsolatedConfig(&preconfig); -+ PyConfig_InitIsolatedConfig(&config); ++ ac_config_files="$ac_config_files Apple/tvOS/Resources/Info.plist" ++ ++ ;; ++ watchOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Apple/watchOS/Resources ++ ++ ac_config_files="$ac_config_files Apple/watchOS/Resources/Info.plist" ++ ++ ;; ++ *) ++ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 ++ ;; ++ esac + esac + +- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION +- +- # Add files for Mac specific code to the list of output +- # files: +- ac_config_files="$ac_config_files Mac/Makefile" +- +- ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile" +- +- ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist" +- +- ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" ++else + ++ case $ac_sys_system in ++ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; ++ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;; ++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= + esac + +-else ++fi + +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= + + +-fi + + + +@@ -3802,78 +4048,51 @@ + _ACEOF + + +-# Set name for machine-dependent library files ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-app-store-compliance" >&5 ++$as_echo_n "checking for --with-app-store-compliance... " >&6; } + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5 +-$as_echo_n "checking MACHDEP... " >&6; } +-if test -z "$MACHDEP" +-then +- # avoid using uname for cross builds +- if test "$cross_compiling" = yes; then +- # ac_sys_system and ac_sys_release are used for setting +- # a lot of different things including 'define_xopen_source' +- # in the case statement below. +- case "$host" in +- *-*-linux-android*) +- ac_sys_system=Linux-android +- ;; +- *-*-linux*) +- ac_sys_system=Linux +- ;; +- *-*-cygwin*) +- ac_sys_system=Cygwin +- ;; +- *-*-vxworks*) +- ac_sys_system=VxWorks +- ;; +- *-*-emscripten) +- ac_sys_system=Emscripten +- ;; +- *-*-wasi) +- ac_sys_system=WASI +- ;; +- *) +- # for now, limit cross builds to known configurations +- MACHDEP="unknown" +- as_fn_error $? "cross build not supported for $host" "$LINENO" 5 +- esac +- ac_sys_release= +- else +- ac_sys_system=`uname -s` +- if test "$ac_sys_system" = "AIX" \ +- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then +- ac_sys_release=`uname -v` +- else +- ac_sys_release=`uname -r` +- fi +- fi +- ac_md_system=`echo $ac_sys_system | +- tr -d '/ ' | tr '[A-Z]' '[a-z]'` +- ac_md_release=`echo $ac_sys_release | +- tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'` +- MACHDEP="$ac_md_system$ac_md_release" +- +- case $MACHDEP in +- aix*) MACHDEP="aix";; +- linux*) MACHDEP="linux";; +- cygwin*) MACHDEP="cygwin";; +- darwin*) MACHDEP="darwin";; +- '') MACHDEP="unknown";; ++# Check whether --with-app_store_compliance was given. ++if test "${with_app_store_compliance+set}" = set; then : ++ withval=$with_app_store_compliance; ++ case "$withval" in ++ yes) ++ case $ac_sys_system in ++ Darwin|iOS|tvOS|watchOS) ++ # iOS/tvOS/watchOS is able to share the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ ;; ++ *) as_fn_error $? "no default app store compliance patch available for $ac_sys_system" "$LINENO" 5 ;; ++ esac ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 ++$as_echo "applying default app store compliance patch" >&6; } ++ ;; ++ *) ++ APP_STORE_COMPLIANCE_PATCH="${withval}" ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying custom app store compliance patch" >&5 ++$as_echo "applying custom app store compliance patch" >&6; } ++ ;; + esac + +- if test "$ac_sys_system" = "SunOS"; then +- # For Solaris, there isn't an OS version specific macro defined +- # in most compilers, so we define one here. +- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'` ++else + +-cat >>confdefs.h <<_ACEOF +-#define Py_SUNOS_VERSION $SUNOS_VERSION +-_ACEOF ++ case $ac_sys_system in ++ iOS|tvOS|watchOS) ++ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 ++$as_echo "applying default app store compliance patch" >&6; } ++ ;; ++ *) ++ # No default app compliance patching on any other platform ++ APP_STORE_COMPLIANCE_PATCH= ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not patching for app store compliance" >&5 ++$as_echo "not patching for app store compliance" >&6; } ++ ;; ++ esac + +- fi + fi +-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 +-$as_echo "\"$MACHDEP\"" >&6; } + -+ // Configure the Python interpreter: -+ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. -+ // See https://docs.python.org/3/library/os.html#python-utf-8-mode. -+ preconfig.utf8_mode = 1; -+ // Don't buffer stdio. We want output to appears in the log immediately -+ config.buffered_stdio = 0; -+ // Don't write bytecode; we can't modify the app bundle -+ // after it has been signed. -+ config.write_bytecode = 0; -+ // Ensure that signal handlers are installed -+ config.install_signal_handlers = 1; -+ // Run the test module. -+ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:0] UTF8String], NULL); -+ // For debugging - enable verbose mode. -+ // config.verbose = 1; + -+ NSLog(@"Pre-initializing Python runtime..."); -+ status = Py_PreInitialize(&preconfig); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } + + + if test "$cross_compiling" = yes; then +@@ -3881,27 +4100,93 @@ + *-*-linux*) + case "$host_cpu" in + arm*) +- _host_cpu=arm ++ _host_ident=arm + ;; + *) +- _host_cpu=$host_cpu ++ _host_ident=$host_cpu + esac + ;; + *-*-cygwin*) +- _host_cpu= ++ _host_ident= ++ ;; ++ *-apple-ios*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} + -+ // Set the home for the Python interpreter -+ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; -+ NSLog(@"PythonHome: %@", python_home); -+ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); -+ status = PyConfig_SetString(&config, &config.home, wtmp_str); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } -+ PyMem_RawFree(wtmp_str); ++ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking iOS deployment target" >&5 ++$as_echo_n "checking iOS deployment target... " >&6; } ++ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} ++ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0} ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $IPHONEOS_DEPLOYMENT_TARGET" >&5 ++$as_echo "$IPHONEOS_DEPLOYMENT_TARGET" >&6; } + -+ // Read the site config -+ status = PyConfig_Read(&config); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to read site config: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} ++ ;; ++ *) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-tvos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} + -+ NSLog(@"Configure argc/argv..."); -+ status = PyConfig_SetBytesArgv(&config, [test_args count], (char**) argv); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } ++ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking tvOS deployment target" >&5 ++$as_echo_n "checking tvOS deployment target... " >&6; } ++ TVOS_DEPLOYMENT_TARGET=${_host_os:4} ++ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0} ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TVOS_DEPLOYMENT_TARGET" >&5 ++$as_echo "$TVOS_DEPLOYMENT_TARGET" >&6; } + -+ NSLog(@"Initializing Python runtime..."); -+ status = Py_InitializeFromConfig(&config); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device} ++ ;; ++ *) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-watchos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} + -+ sys_module = PyImport_ImportModule("sys"); -+ if (sys_module == NULL) { -+ XCTFail(@"Could not import sys module"); -+ return; -+ } ++ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking watchOS deployment target" >&5 ++$as_echo_n "checking watchOS deployment target... " >&6; } ++ WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5 ++$as_echo "$WATCHOS_DEPLOYMENT_TARGET" >&6; } + -+ sys_path_attr = PyObject_GetAttrString(sys_module, "path"); -+ if (sys_path_attr == NULL) { -+ XCTFail(@"Could not access sys.path"); -+ return; -+ } ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device} ++ ;; ++ *) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device} ++ ;; ++ esac + ;; + *-*-vxworks*) +- _host_cpu=$host_cpu ++ _host_ident=$host_cpu + ;; + wasm32-*-* | wasm64-*-*) +- _host_cpu=$host_cpu ++ _host_ident=$host_cpu + ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" + as_fn_error $? "cross build not supported for $host" "$LINENO" 5 + esac +- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" ++ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" + fi + + # Some systems cannot stand _XOPEN_SOURCE being defined at all; they +@@ -3968,6 +4253,13 @@ + define_xopen_source=no;; + Darwin/[12][0-9].*) + define_xopen_source=no;; ++ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. ++ iOS/*) ++ define_xopen_source=no;; ++ tvOS/*) ++ define_xopen_source=no;; ++ watchOS/*) ++ define_xopen_source=no;; + # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from + # defining NI_NUMERICHOST. + QNX/6.3.2) +@@ -4030,6 +4322,12 @@ + CONFIGURE_MACOSX_DEPLOYMENT_TARGET= + EXPORT_MACOSX_DEPLOYMENT_TARGET='#' + ++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / ++# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. + -+ // Add the app packages path -+ path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil]; -+ NSLog(@"App packages path: %@", path); -+ wtmp_str = Py_DecodeLocale([path UTF8String], NULL); -+ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String])); -+ if (failed) { -+ XCTFail(@"Unable to add app packages to sys.path"); -+ return; -+ } -+ PyMem_RawFree(wtmp_str); + -+ path = [NSString stringWithFormat:@"%@/app", resourcePath, nil]; -+ NSLog(@"App path: %@", path); -+ wtmp_str = Py_DecodeLocale([path UTF8String], NULL); -+ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String])); -+ if (failed) { -+ XCTFail(@"Unable to add app to sys.path"); -+ return; -+ } -+ PyMem_RawFree(wtmp_str); + -+ // Ensure the working directory is the app folder. -+ chdir([path UTF8String]); + -+ // Start the test suite. Print a separator to differentiate Python startup logs from app logs -+ NSLog(@"---------------------------------------------------------------------------"); + # checks for alternative programs + + # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just +@@ -4062,6 +4360,26 @@ + ;; + esac + ++case $ac_sys_system in #( ++ iOS) : + -+ exit_code = Py_RunMain(); -+ XCTAssertEqual(exit_code, 0, @"Test suite did not pass"); ++ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ++ as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ++ ;; #( ++ tvOS) : + -+ NSLog(@"---------------------------------------------------------------------------"); ++ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" ++ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" ++ ;; #( ++ watchOS) : + -+ Py_Finalize(); -+} ++ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" ++ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" ++ ;; #( ++ *) : ++ ;; ++esac ++ + if test "$ac_sys_system" = "Darwin" + then + # Compiler selection on MacOSX is more complicated than +@@ -6170,7 +6488,42 @@ + #elif defined(__gnu_hurd__) + i386-gnu + #elif defined(__APPLE__) ++# include "TargetConditionals.h" ++# if TARGET_OS_IOS ++# if TARGET_OS_SIMULATOR ++# if __x86_64__ ++ x86_64-iphonesimulator ++# else ++ arm64-iphonesimulator ++# endif ++# else ++ arm64-iphoneos ++# endif ++# elif TARGET_OS_TV ++# if TARGET_OS_SIMULATOR ++# if __x86_64__ ++ x86_64-appletvsimulator ++# else ++ arm64-appletvsimulator ++# endif ++# else ++ arm64-appletvos ++# endif ++# elif TARGET_OS_WATCH ++# if TARGET_OS_SIMULATOR ++# if __x86_64__ ++ x86_64-watchsimulator ++# else ++ arm64-watchsimulator ++# endif ++# else ++ arm64_32-watchos ++# endif ++# elif TARGET_OS_OSX + darwin ++# else ++# error unknown Apple platform ++# endif + #elif defined(__VXWORKS__) + vxworks + #elif defined(__wasm32__) +@@ -6215,6 +6568,12 @@ + case $ac_sys_system in #( + Darwin*) : + MULTIARCH="" ;; #( ++ iOS) : ++ MULTIARCH="" ;; #( ++ tvOS) : ++ MULTIARCH="" ;; #( ++ watchOS) : ++ MULTIARCH="" ;; #( + FreeBSD*) : + MULTIARCH="" ;; #( + *) : +@@ -6222,8 +6581,6 @@ + ;; + esac + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 +-$as_echo "$MULTIARCH" >&6; } + + if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then + if test x$PLATFORM_TRIPLET != x$MULTIARCH; then +@@ -6233,6 +6590,16 @@ + MULTIARCH=$PLATFORM_TRIPLET + fi + ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5 ++$as_echo "$MULTIARCH" >&6; } + ++case $ac_sys_system in #( ++ iOS|tvOS|watchOS) : ++ SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( ++ *) : ++ SOABI_PLATFORM=$PLATFORM_TRIPLET ++ ;; ++esac + + if test x$MULTIARCH != x; then + MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" +@@ -6276,6 +6643,18 @@ + PY_SUPPORT_TIER=3 ;; #( + x86_64-*-freebsd*/clang) : + PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-ios*-simulator/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-ios*/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-tvos*-simulator/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-tvos*/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-watchos*-simulator/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ arm64_32-apple-watchos*/clang) : ++ PY_SUPPORT_TIER=3 ;; #( + *) : + PY_SUPPORT_TIER=0 + ;; +@@ -6718,17 +7097,25 @@ + { $as_echo "$as_me:${as_lineno-$LINENO}: checking LDLIBRARY" >&5 + $as_echo_n "checking LDLIBRARY... " >&6; } + +-# MacOSX framework builds need more magic. LDLIBRARY is the dynamic ++# Apple framework builds need more magic. LDLIBRARY is the dynamic + # library that we build, but we do not want to link against it (we + # will find it with a -framework option). For this reason there is an + # extra variable BLDLIBRARY against which Python and the extension + # modules are linked, BLDLIBRARY. This is normally the same as +-# LDLIBRARY, but empty for MacOSX framework builds. ++# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, ++# but uses a non-versioned framework layout. + if test "$enable_framework" + then +- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' +- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} ++ case $ac_sys_system in ++ Darwin) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; ++ *) ++ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; ++ esac + BLDLIBRARY='' ++ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} + else + BLDLIBRARY='$(LDLIBRARY)' + fi +@@ -6741,64 +7128,70 @@ + + case $ac_sys_system in + CYGWIN*) +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- DLLLIBRARY='libpython$(LDVERSION).dll' +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ DLLLIBRARY='libpython$(LDVERSION).dll' ++ ;; + SunOS*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + hp*|HP*) +- case `uname -m` in +- ia64) +- LDLIBRARY='libpython$(LDVERSION).so' +- ;; +- *) +- LDLIBRARY='libpython$(LDVERSION).sl' +- ;; +- esac +- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} +- ;; ++ case `uname -m` in ++ ia64) ++ LDLIBRARY='libpython$(LDVERSION).so' ++ ;; ++ *) ++ LDLIBRARY='libpython$(LDVERSION).sl' ++ ;; ++ esac ++ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} ++ ;; + Darwin*) +- LDLIBRARY='libpython$(LDVERSION).dylib' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ++ ;; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ ;; + AIX*) +- LDLIBRARY='libpython$(LDVERSION).so' +- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} ++ ;; + + esac + else # shared is disabled + PY_ENABLE_SHARED=0 + case $ac_sys_system in + CYGWIN*) +- BLDLIBRARY='$(LIBRARY)' +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- ;; ++ BLDLIBRARY='$(LIBRARY)' ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ ;; + esac + fi + ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 ++$as_echo "$LDLIBRARY" >&6; } + -+@end -diff --git a/setup.py b/setup.py -index ad8fb81b218..266035c472c 100644 ---- a/setup.py -+++ b/setup.py -@@ -82,6 +82,9 @@ - MS_WINDOWS = (HOST_PLATFORM == 'win32') - CYGWIN = (HOST_PLATFORM == 'cygwin') - MACOS = (HOST_PLATFORM == 'darwin') -+IOS = HOST_PLATFORM.startswith('ios-') -+TVOS = HOST_PLATFORM.startswith('tvos-') -+WATCHOS = HOST_PLATFORM.startswith('watchos-') - AIX = (HOST_PLATFORM.startswith('aix')) - VXWORKS = ('vxworks' in HOST_PLATFORM) - EMSCRIPTEN = HOST_PLATFORM == 'emscripten-wasm32' -@@ -166,16 +169,20 @@ - for var_name in make_vars: - var = sysconfig.get_config_var(var_name) - if var is not None: -- m = re.search(r'--sysroot=([^"]\S*|"[^"]+")', var) -- if m is not None: -- sysroot = m.group(1).strip('"') -- for subdir in subdirs: -- if os.path.isabs(subdir): -- subdir = subdir[1:] -- path = os.path.join(sysroot, subdir) -- if os.path.isdir(path): -- dirs.append(path) -- break -+ for pattern in [ -+ r'-isysroot\s*([^"]\S*|"[^"]+")', -+ r'--sysroot=([^"]\S*|"[^"]+")', -+ ]: -+ m = re.search(pattern, var) -+ if m is not None: -+ sysroot = m.group(1).strip('"') -+ for subdir in subdirs: -+ if os.path.isabs(subdir): -+ subdir = subdir[1:] -+ path = os.path.join(sysroot, subdir) -+ if os.path.isdir(path): -+ dirs.append(path) -+ break - return dirs + if test "$cross_compiling" = yes; then +- RUNSHARED= ++ RUNSHARED= + fi -@@ -1400,6 +1407,11 @@ - extra_compile_args.append('-DMACOSX') - include_dirs.append('_ctypes/darwin') +@@ -6977,9 +7370,6 @@ + PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" + fi -+ elif IOS or TVOS or WATCHOS: -+ sources.append('_ctypes/malloc_closure.c') -+ extra_compile_args.append('-DUSING_MALLOC_CLOSURE_DOT_C') -+ include_dirs.append('_ctypes/darwin') -+ - elif HOST_PLATFORM == 'sunos5': - # XXX This shouldn't be necessary; it appears that some - # of the assembler code is non-PIC (i.e. it has relocations -@@ -1422,7 +1434,8 @@ - self.addext(Extension('_ctypes_test', ['_ctypes/_ctypes_test.c'])) +-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5 +-$as_echo "$LDLIBRARY" >&6; } +- + # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable + case $ac_sys_system/$ac_sys_emscripten_target in #( + Emscripten/browser*) : +@@ -7221,11 +7611,16 @@ + fi - ffi_inc = sysconfig.get_config_var("LIBFFI_INCLUDEDIR") -- ffi_lib = None -+ ffi_lib_dir = sysconfig.get_config_var("LIBFFI_LIBDIR") -+ ffi_lib = sysconfig.get_config_var("LIBFFI_LIB") + if test "$cross_compiling" = yes; then +- case "$READELF" in +- readelf|:) +- as_fn_error $? "readelf for the host is required for cross builds" "$LINENO" 5 +- ;; +- esac ++ case "$ac_sys_system" in ++ iOS|tvOS|watchOS) ;; ++ *) ++ case "$READELF" in ++ readelf|:) ++ as_fn_error $? "readelf for the host is required for cross builds" "$LINENO" 5 ++ ;; ++ esac ++ ;; ++ esac + fi - ffi_inc_dirs = self.inc_dirs.copy() - if MACOS: -@@ -1451,6 +1464,7 @@ - for lib_name in ('ffi', 'ffi_pic'): - if (self.compiler.find_library_file(self.lib_dirs, lib_name)): - ffi_lib = lib_name -+ self.use_system_libffi = True - break - if ffi_inc and ffi_lib: -@@ -1464,7 +1478,8 @@ +@@ -10951,6 +11346,11 @@ + BLDSHARED="$LDSHARED" + fi + ;; ++ iOS/*|tvOS/*|watchOS/*) ++ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ BLDSHARED="$LDSHARED" ++ ;; + Emscripten|WASI) + LDSHARED='$(CC) -shared' + LDCXXSHARED='$(CXX) -shared';; +@@ -11078,20 +11478,18 @@ + Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; + Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; + # -u libsys_s pulls in all symbols in libsys +- Darwin/*) ++ Darwin/*|iOS/*|tvOS/*|watchOS/*) + LINKFORSHARED="$extra_undefs -framework CoreFoundation" - ext.include_dirs.append(ffi_inc) - ext.libraries.append(ffi_lib) -- self.use_system_libffi = True -+ if ffi_lib_dir: -+ ext.library_dirs.append(ffi_lib_dir) + # Issue #18075: the default maximum stack size (8MBytes) is too + # small for the default recursion limit. Increase the stack size + # to ensure that tests don't crash +- stack_size="1000000" # 16 MB +- if test "$with_ubsan" = "yes" +- then +- # Undefined behavior sanitizer requires an even deeper stack +- stack_size="4000000" # 64 MB +- fi +- +- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" ++ stack_size="1000000" # 16 MB ++ if test "$with_ubsan" = "yes" ++ then ++ # Undefined behavior sanitizer requires an even deeper stack ++ stack_size="4000000" # 64 MB ++ fi - if sysconfig.get_config_var('HAVE_LIBDL'): - # for dlopen, see bpo-32647 ---- /dev/null -+++ b/tvOS/README.rst -@@ -0,0 +1,108 @@ -+===================== -+Python on tvOS README -+===================== -+ -+:Authors: -+ Russell Keith-Magee (2023-11) -+ -+This document provides a quick overview of some tvOS specific features in the -+Python distribution. -+ -+Compilers for building on tvOS -+============================== -+ -+Building for tvOS requires the use of Apple's Xcode tooling. It is strongly -+recommended that you use the most recent stable release of Xcode, on the -+most recently released macOS. -+ -+tvOS specific arguments to configure -+=================================== -+ -+* ``--enable-framework[=DIR]`` -+ -+ This argument specifies the location where the Python.framework will -+ be installed. -+ -+* ``--with-framework-name=NAME`` -+ -+ Specify the name for the python framework, defaults to ``Python``. -+ -+ -+Building and using Python on tvOS -+================================= -+ -+ABIs and Architectures -+---------------------- -+ -+tvOS apps can be deployed on physical devices, and on the tvOS simulator. -+Although the API used on these devices is identical, the ABI is different - you -+need to link against different libraries for an tvOS device build -+(``appletvos``) or an tvOS simulator build (``appletvsimulator``). Apple uses -+the XCframework format to allow specifying a single dependency that supports -+multiple ABIs. An XCframework is a wrapper around multiple ABI-specific -+frameworks. -+ -+tvOS can also support different CPU architectures within each ABI. At present, -+there is only a single support ed architecture on physical devices - ARM64. -+However, the *simulator* supports 2 architectures - ARM64 (for running on Apple -+Silicon machines), and x86_64 (for running on older Intel-based machines.) -+ -+To support multiple CPU architectures on a single platform, Apple uses a "fat -+binary" format - a single physical file that contains support for multiple -+architectures. + + cat >>confdefs.h <<_ACEOF +@@ -11099,11 +11497,17 @@ + _ACEOF + + +- if test "$enable_framework" +- then +- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ if test $ac_sys_system = "Darwin"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + -+How do I build Python for tvOS? -+------------------------------- ++ if test "$enable_framework"; then ++ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ fi ++ LINKFORSHARED="$LINKFORSHARED" ++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi +- LINKFORSHARED="$LINKFORSHARED";; ++ ;; + OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; + SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; + ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; +@@ -12179,7 +12583,7 @@ + fi + + +-if test "$ac_sys_system" = "Darwin" ++if test "$ac_sys_system" = "Darwin" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" + then + case "$with_system_ffi" in + "") +@@ -12207,10 +12611,12 @@ + if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then + LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`" + else +- LIBFFI_INCLUDEDIR="" ++ LIBFFI_INCLUDEDIR="${LIBFFI_INCLUDEDIR}" + fi + + + -+The Python build system will build a ``Python.framework`` that supports a -+*single* ABI with a *single* architecture. If you want to use Python in an tvOS -+project, you need to: + -+1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; -+2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; -+3. Merge the "fat" frameworks for each ABI into a single XCframework. + # Check for use of the system libmpdec library + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-system-libmpdec" >&5 + $as_echo_n "checking for --with-system-libmpdec... " >&6; } +@@ -15209,27 +15615,27 @@ + # checks for library functions + for ac_func in \ + accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ +- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ ++ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ + faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ +- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ +- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ +- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ ++ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ ++ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \ ++ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ + getpeername getpgid getpid getppid getpriority _getpty \ + getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ + getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ + lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ + mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ +- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ ++ pipe2 plock poll posix_fadvise posix_fallocate \ + pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ + pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ + rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ + sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ + sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ + setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ +- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ ++ setresuid setreuid setsid setuid setvbuf shutdown sigaction \ + sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ + sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ +- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ ++ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ + tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ + wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ + +@@ -15262,8 +15668,46 @@ + + fi + ++# iOS/tvOS/watchOS define some system methods that can be linked (so they are ++# found by configure), but either raise a compilation error (because the ++# header definition prevents usage - autoconf doesn't use the headers), or ++# raise an error if used at runtime. Force these symbols off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ for ac_func in getentropy getgroups system ++do : ++ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ++ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" ++if eval test \"x\$"$as_ac_var"\" = x"yes"; then : ++ cat >>confdefs.h <<_ACEOF ++#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 ++_ACEOF + -+tvOS builds of Python *must* be constructed as framework builds. To support this, -+you must provide the ``--enable-framework`` flag when configuring the build. ++fi ++done + -+The build also requires the use of cross-compilation. The commands for building -+Python for tvOS will look somethign like:: ++fi + -+ $ ./configure \ -+ --enable-framework=/path/to/install \ -+ --host=aarch64-apple-tvos \ -+ --build=aarch64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install ++# tvOS/watchOS have some additional methods that can be found, but not used. ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ for ac_func in \ ++ execv fork fork1 posix_spawn posix_spawnp \ ++ sigaltstack \ + -+In this invocation: ++do : ++ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ++ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" ++if eval test \"x\$"$as_ac_var"\" = x"yes"; then : ++ cat >>confdefs.h <<_ACEOF ++#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 ++_ACEOF + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++fi ++done + -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: ++fi + -+ - ``aarch64-apple-tvos`` for ARM64 tvOS devices. -+ - ``aarch64-apple-tvos-simulator`` for the tvOS simulator running on Apple -+ Silicon devices. -+ - ``x86_64-apple-tvos-simulator`` for the tvOS simulator running on Intel -+ devices. + ac_fn_c_check_decl "$LINENO" "dirfd" "ac_cv_have_decl_dirfd" "#include +- #include ++ #include + " + if test "x$ac_cv_have_decl_dirfd" = xyes; then : + +@@ -17817,8 +18261,9 @@ + + + # check for openpty, login_tty, and forkpty +- +-for ac_func in openpty ++# tvOS/watchOS have functions for tty, but can't use them ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ for ac_func in openpty + do : + ac_fn_c_check_func "$LINENO" "openpty" "ac_cv_func_openpty" + if test "x$ac_cv_func_openpty" = xyes; then : +@@ -17908,14 +18353,12 @@ + LIBS="$LIBS -lbsd" + fi + +- + fi + +- + fi + done + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5 + $as_echo_n "checking for library containing login_tty... " >&6; } + if ${ac_cv_search_login_tty+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -17974,7 +18417,7 @@ + + fi + +-for ac_func in forkpty ++ for ac_func in forkpty + do : + ac_fn_c_check_func "$LINENO" "forkpty" "ac_cv_func_forkpty" + if test "x$ac_cv_func_forkpty" = xyes; then : +@@ -18064,13 +18507,12 @@ + LIBS="$LIBS -lbsd" + fi + +- + fi + +- + fi + done + ++fi + + # check for long file support functions + for ac_func in fseek64 fseeko fstatvfs ftell64 ftello statvfs +@@ -18568,7 +19010,12 @@ + done + + +-for ac_func in clock_settime ++# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by ++# configure), but when used in an unprivileged process, it crashes rather than ++# returning an error. Force the symbol off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ++then ++ for ac_func in clock_settime + do : + ac_fn_c_check_func "$LINENO" "clock_settime" "ac_cv_func_clock_settime" + if test "x$ac_cv_func_clock_settime" = xyes; then : +@@ -18578,7 +19025,7 @@ + + else + +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 + $as_echo_n "checking for clock_settime in -lrt... " >&6; } + if ${ac_cv_lib_rt_clock_settime+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -18616,7 +19063,7 @@ + $as_echo "$ac_cv_lib_rt_clock_settime" >&6; } + if test "x$ac_cv_lib_rt_clock_settime" = xyes; then : + +- $as_echo "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h ++ $as_echo "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h + + + fi +@@ -18625,6 +19072,7 @@ + fi + done + ++fi + + for ac_func in clock_nanosleep + do : +@@ -18838,7 +19286,9 @@ + else + if test "$cross_compiling" = yes; then : + +-if test "${enable_ipv6+set}" = set; then ++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ ac_cv_buggy_getaddrinfo="no" ++elif test "${enable_ipv6+set}" = set; then + ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" + else + ac_cv_buggy_getaddrinfo=yes +@@ -20762,7 +21212,7 @@ + $as_echo "$ABIFLAGS" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking SOABI" >&5 + $as_echo_n "checking SOABI... " >&6; } +-SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} ++SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SOABI" >&5 + $as_echo "$SOABI" >&6; } + +@@ -20770,7 +21220,7 @@ + if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then + # Similar to SOABI but remove "d" flag from ABIFLAGS + +- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} ++ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} + + cat >>confdefs.h <<_ACEOF + #define ALT_SOABI "${ALT_SOABI}" +@@ -22152,24 +22602,28 @@ + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 + $as_echo "$as_me: checking for device files" >&6;} + +-if test "x$cross_compiling" = xyes; then +- if test "${ac_cv_file__dev_ptmx+set}" != set; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then ++ ac_cv_file__dev_ptmx=no ++ ac_cv_file__dev_ptc=no ++else ++ if test "x$cross_compiling" = xyes; then ++ if test "${ac_cv_file__dev_ptmx+set}" != set; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 + $as_echo_n "checking for /dev/ptmx... " >&6; } +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + $as_echo "not set" >&6; } +- as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 +- fi +- if test "${ac_cv_file__dev_ptc+set}" != set; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 ++ as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 ++ fi ++ if test "${ac_cv_file__dev_ptc+set}" != set; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + $as_echo_n "checking for /dev/ptc... " >&6; } +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + $as_echo "not set" >&6; } +- as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 ++ as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 ++ fi + fi +-fi + +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 + $as_echo_n "checking for /dev/ptmx... " >&6; } + if ${ac_cv_file__dev_ptmx+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -22188,12 +22642,12 @@ + + fi + +-if test "x$ac_cv_file__dev_ptmx" = xyes; then ++ if test "x$ac_cv_file__dev_ptmx" = xyes; then + + $as_echo "#define HAVE_DEV_PTMX 1" >>confdefs.h + +-fi +-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 ++ fi ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + $as_echo_n "checking for /dev/ptc... " >&6; } + if ${ac_cv_file__dev_ptc+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -22212,10 +22666,11 @@ + + fi + +-if test "x$ac_cv_file__dev_ptc" = xyes; then ++ if test "x$ac_cv_file__dev_ptc" = xyes; then + + $as_echo "#define HAVE_DEV_PTC 1" >>confdefs.h + ++ fi + fi + + if test $ac_sys_system = Darwin +@@ -22701,6 +23156,8 @@ + with_ensurepip=no ;; #( + WASI) : + with_ensurepip=no ;; #( ++ iOS|tvOS|watchOS) : ++ with_ensurepip=no ;; #( + *) : + with_ensurepip=upgrade + ;; +@@ -23604,6 +24061,27 @@ + py_cv_module_ossaudiodev=n/a + py_cv_module_spwd=n/a + ;; #( ++ iOS|tvOS|watchOS) : + -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: + -+ - ``aarch64-apple-darwin`` for Apple Silicon devices. -+ - ``x86_64-apple-darwin`` for Intel devices. + -+* ``/path/to/python.exe`` is the path to a Python binary on the machine that -+ will be running the compiler. This is needed because the Python compilation -+ process involves running some Python code. On a normal desktop build of -+ Python, you can compile a python interpreter and then use that interpreter to -+ run Python code. However, the binaries produced for tvOS won't run on macOS, so -+ you need to provide an external Python interpreter. This interpreter must be -+ the version as the Python that is being compiled. ++ py_cv_module__curses=n/a ++ py_cv_module__curses_panel=n/a ++ py_cv_module__gdbm=n/a ++ py_cv_module__multiprocessing=n/a ++ py_cv_module__posixshmem=n/a ++ py_cv_module__posixsubprocess=n/a ++ py_cv_module__scproxy=n/a ++ py_cv_module__tkinter=n/a ++ py_cv_module_grp=n/a ++ py_cv_module_nis=n/a ++ py_cv_module_readline=n/a ++ py_cv_module_pwd=n/a ++ py_cv_module_spwd=n/a ++ py_cv_module_syslog=n/a ++ py_cv_module_=n/a + -+Using a framework-based Python on tvOS -+====================================== ---- /dev/null -+++ b/tvOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ Python -+ CFBundleGetInfoString -+ Python Runtime and Library -+ CFBundleIdentifier -+ @PYTHONFRAMEWORKIDENTIFIER@ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleName -+ Python -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ %VERSION% -+ CFBundleLongVersionString -+ %VERSION%, (c) 2001-2024 Python Software Foundation. -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ 1 -+ CFBundleSupportedPlatforms -+ -+ tvOS -+ -+ MinimumOSVersion -+ @TVOS_DEPLOYMENT_TARGET@ -+ -+ ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos -E "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos-simulator "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator -E "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos-simulator "$@" ---- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator -E "$@" ---- /dev/null -+++ b/tvOS/Resources/dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ tvOS -+ -+ MinimumOSVersion -+ 9.0 -+ CFBundleVersion -+ 1 -+ -+ ---- /dev/null -+++ b/tvOS/Resources/pyconfig.h -@@ -0,0 +1,7 @@ -+#ifdef __arm64__ -+#include "pyconfig-arm64.h" -+#endif ++ ;; #( + CYGWIN*) : + + +@@ -26886,6 +27364,9 @@ + "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;; + "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; + "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; ++ "Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/iOS/Resources/Info.plist" ;; ++ "Apple/tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/tvOS/Resources/Info.plist" ;; ++ "Apple/watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/watchOS/Resources/Info.plist" ;; + "Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;; + "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; + "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; +diff --git a/configure.ac b/configure.ac +index 7b4000fa9c3..fff8109b034 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -303,6 +303,161 @@ + AC_MSG_ERROR([pkg-config is required])] + fi + ++# Set name for machine-dependent library files ++AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) ++AC_MSG_CHECKING([MACHDEP]) ++if test -z "$MACHDEP" ++then ++ # avoid using uname for cross builds ++ if test "$cross_compiling" = yes; then ++ # ac_sys_system and ac_sys_release are used for setting ++ # a lot of different things including 'define_xopen_source' ++ # in the case statement below. ++ case "$host" in ++ *-*-linux-android*) ++ ac_sys_system=Linux-android ++ ;; ++ *-*-linux*) ++ ac_sys_system=Linux ++ ;; ++ *-*-cygwin*) ++ ac_sys_system=Cygwin ++ ;; ++ *-apple-ios*) ++ ac_sys_system=iOS ++ ;; ++ *-apple-tvos*) ++ ac_sys_system=tvOS ++ ;; ++ *-apple-watchos*) ++ ac_sys_system=watchOS ++ ;; ++ *-*-vxworks*) ++ ac_sys_system=VxWorks ++ ;; ++ *-*-emscripten) ++ ac_sys_system=Emscripten ++ ;; ++ *-*-wasi) ++ ac_sys_system=WASI ++ ;; ++ *) ++ # for now, limit cross builds to known configurations ++ MACHDEP="unknown" ++ AC_MSG_ERROR([cross build not supported for $host]) ++ esac ++ ac_sys_release= ++ else ++ ac_sys_system=`uname -s` ++ if test "$ac_sys_system" = "AIX" \ ++ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then ++ ac_sys_release=`uname -v` ++ else ++ ac_sys_release=`uname -r` ++ fi ++ fi ++ ac_md_system=`echo $ac_sys_system | ++ tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` ++ ac_md_release=`echo $ac_sys_release | ++ tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` ++ MACHDEP="$ac_md_system$ac_md_release" + -+#ifdef __x86_64__ -+#include "pyconfig-x86_64.h" -+#endif ---- /dev/null -+++ b/watchOS/README.rst -@@ -0,0 +1,108 @@ -+======================== -+Python on watchOS README -+======================== ++ case $MACHDEP in ++ aix*) MACHDEP="aix";; ++ linux*) MACHDEP="linux";; ++ cygwin*) MACHDEP="cygwin";; ++ darwin*) MACHDEP="darwin";; ++ '') MACHDEP="unknown";; ++ esac + -+:Authors: -+ Russell Keith-Magee (2023-11) ++ if test "$ac_sys_system" = "SunOS"; then ++ # For Solaris, there isn't an OS version specific macro defined ++ # in most compilers, so we define one here. ++ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` ++ AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], ++ [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) ++ fi ++fi ++AC_MSG_RESULT(["$MACHDEP"]) + -+This document provides a quick overview of some watchOS specific features in the -+Python distribution. ++# On cross-compile builds, configure will look for a host-specific compiler by ++# prepending the user-provided host triple to the required binary name. ++# ++# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", ++# which isn't a binary that exists, and isn't very convenient, as it contains the ++# iOS version. As the default cross-compiler name won't exist, configure falls ++# back to gcc, which *definitely* won't work. We're providing wrapper scripts for ++# these tools; the binary names of these scripts are better defaults than "gcc". ++# This only requires that the user put the platform scripts folder (e.g., ++# "iOS/Resources/bin") in their path, rather than defining platform-specific ++# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to ++# either put the platform scripts folder in the path, or specify CC etc, ++# configure will fail. ++if test -z "$AR"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; ++ aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; ++ x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; + -+Compilers for building on watchOS -+================================= ++ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;; ++ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;; ++ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;; + -+Building for watchOS requires the use of Apple's Xcode tooling. It is strongly -+recommended that you use the most recent stable release of Xcode, on the -+most recently released macOS. ++ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;; ++ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;; ++ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;; ++ *) ++ esac ++fi ++if test -z "$CC"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; ++ aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; ++ x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; + -+watchOS specific arguments to configure -+======================================= ++ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;; ++ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;; ++ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;; + -+* ``--enable-framework[=DIR]`` ++ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;; ++ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;; ++ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;; ++ *) ++ esac ++fi ++if test -z "$CPP"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; ++ aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; ++ x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; + -+ This argument specifies the location where the Python.framework will -+ be installed. ++ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;; ++ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;; ++ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;; + -+* ``--with-framework-name=NAME`` ++ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;; ++ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;; ++ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;; ++ *) ++ esac ++fi ++if test -z "$CXX"; then ++ case "$host" in ++ aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; ++ aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; ++ x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; + -+ Specify the name for the python framework, defaults to ``Python``. ++ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;; ++ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;; ++ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;; ++ ++ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;; ++ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;; ++ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;; ++ *) ++ esac ++fi ++ + AC_MSG_CHECKING([for --enable-universalsdk]) + AC_ARG_ENABLE(universalsdk, + AS_HELP_STRING([--enable-universalsdk@<:@=SDKDIR@:>@], +@@ -412,214 +567,328 @@ + [ + case $enableval in + yes) +- enableval=/Library/Frameworks ++ case $ac_sys_system in ++ Darwin) enableval=/Library/Frameworks ;; ++ iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; ++ tvOS) enableval=Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;; ++ watchOS) enableval=Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;; ++ *) AC_MSG_ERROR([Unknown platform for framework build]) ++ esac + esac ++ + case $enableval in + no) +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE"; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= ++ case $ac_sys_system in ++ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; ++ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; ++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ ++ if test "x${prefix}" = "xNONE"; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= ++ esac + ;; + *) + PYTHONFRAMEWORKPREFIX="${enableval}" + PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR +- FRAMEWORKINSTALLFIRST="frameworkinstallstructure" +- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " +- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" +- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" +- FRAMEWORKPYTHONW="frameworkpythonw" +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi + +- case "${enableval}" in +- /System*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- if test "${prefix}" = "NONE" ; then +- # See below +- FRAMEWORKUNIXTOOLSPREFIX="/usr" +- fi +- ;; ++ case $ac_sys_system in #( ++ Darwin) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" ++ FRAMEWORKPYTHONW="frameworkpythonw" ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi + +- /Library*) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ case "${enableval}" in ++ /System*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ if test "${prefix}" = "NONE" ; then ++ # See below ++ FRAMEWORKUNIXTOOLSPREFIX="/usr" ++ fi ++ ;; ++ ++ /Library*) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; + ++ */Library/Frameworks) ++ MDIR="`dirname "${enableval}"`" ++ MDIR="`dirname "${MDIR}"`" ++ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" ++ ++ if test "${prefix}" = "NONE"; then ++ # User hasn't specified the ++ # --prefix option, but wants to install ++ # the framework in a non-default location, ++ # ensure that the compatibility links get ++ # installed relative to that prefix as well ++ # instead of in /usr/local. ++ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" ++ fi ++ ;; + +- */Library/Frameworks) +- MDIR="`dirname "${enableval}"`" +- MDIR="`dirname "${MDIR}"`" +- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications" +- +- if test "${prefix}" = "NONE"; then +- # User hasn't specified the +- # --prefix option, but wants to install +- # the framework in a non-default location, +- # ensure that the compatibility links get +- # installed relative to that prefix as well +- # instead of in /usr/local. +- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}" +- fi +- ;; ++ *) ++ FRAMEWORKINSTALLAPPSPREFIX="/Applications" ++ ;; ++ esac + +- *) +- FRAMEWORKINSTALLAPPSPREFIX="/Applications" +- ;; ++ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix} ++ RESSRCDIR=Mac/Resources/framework + -+Building and using Python on watchOS -+==================================== ++ # Add files for Mac specific code to the list of output ++ # files: ++ AC_CONFIG_FILES([Mac/Makefile]) ++ AC_CONFIG_FILES([Mac/PythonLauncher/Makefile]) ++ AC_CONFIG_FILES([Mac/Resources/framework/Info.plist]) ++ AC_CONFIG_FILES([Mac/Resources/app/Info.plist]) ++ ;; ++ iOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" + -+ABIs and Architectures -+---------------------- ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Apple/iOS/Resources + -+watchOS apps can be deployed on physical devices, and on the watchOS simulator. -+Although the API used on these devices is identical, the ABI is different - you -+need to link against different libraries for an watchOS device build -+(``watchos``) or an watchOS simulator build (``watchsimulator``). Apple uses the -+XCframework format to allow specifying a single dependency that supports -+multiple ABIs. An XCframework is a wrapper around multiple ABI-specific -+frameworks. ++ AC_CONFIG_FILES([Apple/iOS/Resources/Info.plist]) ++ ;; ++ tvOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" + -+watchOS can also support different CPU architectures within each ABI. At present, -+there is only a single support ed architecture on physical devices - ARM64. -+However, the *simulator* supports 2 architectures - ARM64 (for running on Apple -+Silicon machines), and x86_64 (for running on older Intel-based machines.) ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Apple/tvOS/Resources + -+To support multiple CPU architectures on a single platform, Apple uses a "fat -+binary" format - a single physical file that contains support for multiple -+architectures. ++ AC_CONFIG_FILES([Apple/tvOS/Resources/Info.plist]) ++ ;; ++ watchOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" + -+How do I build Python for watchOS? -+------------------------------- ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Apple/watchOS/Resources + -+The Python build system will build a ``Python.framework`` that supports a -+*single* ABI with a *single* architecture. If you want to use Python in an watchOS -+project, you need to: ++ AC_CONFIG_FILES([Apple/watchOS/Resources/Info.plist]) ++ ;; ++ *) ++ AC_MSG_ERROR([Unknown platform for framework build]) ++ ;; ++ esac + esac +- +- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION +- +- # Add files for Mac specific code to the list of output +- # files: +- AC_CONFIG_FILES(Mac/Makefile) +- AC_CONFIG_FILES(Mac/PythonLauncher/Makefile) +- AC_CONFIG_FILES(Mac/Resources/framework/Info.plist) +- AC_CONFIG_FILES(Mac/Resources/app/Info.plist) +- esac + ],[ +- PYTHONFRAMEWORK= +- PYTHONFRAMEWORKDIR=no-framework +- PYTHONFRAMEWORKPREFIX= +- PYTHONFRAMEWORKINSTALLDIR= +- FRAMEWORKINSTALLFIRST= +- FRAMEWORKINSTALLLAST= +- FRAMEWORKALTINSTALLFIRST= +- FRAMEWORKALTINSTALLLAST= +- FRAMEWORKPYTHONW= +- if test "x${prefix}" = "xNONE" ; then +- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" +- else +- FRAMEWORKUNIXTOOLSPREFIX="${prefix}" +- fi +- enable_framework= +- +-]) +-AC_SUBST(PYTHONFRAMEWORK) +-AC_SUBST(PYTHONFRAMEWORKIDENTIFIER) +-AC_SUBST(PYTHONFRAMEWORKDIR) +-AC_SUBST(PYTHONFRAMEWORKPREFIX) +-AC_SUBST(PYTHONFRAMEWORKINSTALLDIR) +-AC_SUBST(FRAMEWORKINSTALLFIRST) +-AC_SUBST(FRAMEWORKINSTALLLAST) +-AC_SUBST(FRAMEWORKALTINSTALLFIRST) +-AC_SUBST(FRAMEWORKALTINSTALLLAST) +-AC_SUBST(FRAMEWORKPYTHONW) +-AC_SUBST(FRAMEWORKUNIXTOOLSPREFIX) +-AC_SUBST(FRAMEWORKINSTALLAPPSPREFIX) ++ case $ac_sys_system in ++ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; ++ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; ++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; ++ *) ++ PYTHONFRAMEWORK= ++ PYTHONFRAMEWORKDIR=no-framework ++ PYTHONFRAMEWORKPREFIX= ++ PYTHONFRAMEWORKINSTALLDIR= ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX= ++ RESSRCDIR= ++ FRAMEWORKINSTALLFIRST= ++ FRAMEWORKINSTALLLAST= ++ FRAMEWORKALTINSTALLFIRST= ++ FRAMEWORKALTINSTALLLAST= ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="commoninstall bininstall maninstall" ++ if test "x${prefix}" = "xNONE" ; then ++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" ++ else ++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}" ++ fi ++ enable_framework= ++ esac ++]) ++AC_SUBST([PYTHONFRAMEWORK]) ++AC_SUBST([PYTHONFRAMEWORKIDENTIFIER]) ++AC_SUBST([PYTHONFRAMEWORKDIR]) ++AC_SUBST([PYTHONFRAMEWORKPREFIX]) ++AC_SUBST([PYTHONFRAMEWORKINSTALLDIR]) ++AC_SUBST([PYTHONFRAMEWORKINSTALLNAMEPREFIX]) ++AC_SUBST([RESSRCDIR]) ++AC_SUBST([FRAMEWORKINSTALLFIRST]) ++AC_SUBST([FRAMEWORKINSTALLLAST]) ++AC_SUBST([FRAMEWORKALTINSTALLFIRST]) ++AC_SUBST([FRAMEWORKALTINSTALLLAST]) ++AC_SUBST([FRAMEWORKPYTHONW]) ++AC_SUBST([FRAMEWORKUNIXTOOLSPREFIX]) ++AC_SUBST([FRAMEWORKINSTALLAPPSPREFIX]) ++AC_SUBST([INSTALLTARGETS]) + + AC_DEFINE_UNQUOTED(_PYTHONFRAMEWORK, "${PYTHONFRAMEWORK}", [framework name]) + +-# Set name for machine-dependent library files +-AC_ARG_VAR([MACHDEP], [name for machine-dependent library files]) +-AC_MSG_CHECKING(MACHDEP) +-if test -z "$MACHDEP" +-then +- # avoid using uname for cross builds +- if test "$cross_compiling" = yes; then +- # ac_sys_system and ac_sys_release are used for setting +- # a lot of different things including 'define_xopen_source' +- # in the case statement below. +- case "$host" in +- *-*-linux-android*) +- ac_sys_system=Linux-android +- ;; +- *-*-linux*) +- ac_sys_system=Linux +- ;; +- *-*-cygwin*) +- ac_sys_system=Cygwin +- ;; +- *-*-vxworks*) +- ac_sys_system=VxWorks +- ;; +- *-*-emscripten) +- ac_sys_system=Emscripten +- ;; +- *-*-wasi) +- ac_sys_system=WASI +- ;; +- *) +- # for now, limit cross builds to known configurations +- MACHDEP="unknown" +- AC_MSG_ERROR([cross build not supported for $host]) +- esac +- ac_sys_release= +- else +- ac_sys_system=`uname -s` +- if test "$ac_sys_system" = "AIX" \ +- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then +- ac_sys_release=`uname -v` +- else +- ac_sys_release=`uname -r` +- fi +- fi +- ac_md_system=`echo $ac_sys_system | +- tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'` +- ac_md_release=`echo $ac_sys_release | +- tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'` +- MACHDEP="$ac_md_system$ac_md_release" +- +- case $MACHDEP in +- aix*) MACHDEP="aix";; +- linux*) MACHDEP="linux";; +- cygwin*) MACHDEP="cygwin";; +- darwin*) MACHDEP="darwin";; +- '') MACHDEP="unknown";; ++dnl quadrigraphs "@<:@" and "@:>@" produce "[" and "]" in the output ++AC_MSG_CHECKING([for --with-app-store-compliance]) ++AC_ARG_WITH( ++ [app_store_compliance], ++ [AS_HELP_STRING( ++ [--with-app-store-compliance=@<:@PATCH-FILE@:>@], ++ [Enable any patches required for compiliance with app stores. ++ Optional PATCH-FILE specifies the custom patch to apply.] ++ )],[ ++ case "$withval" in ++ yes) ++ case $ac_sys_system in ++ Darwin|iOS|tvOS|watchOS) ++ # iOS/tvOS/watchOS is able to share the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ ;; ++ *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;; ++ esac ++ AC_MSG_RESULT([applying default app store compliance patch]) ++ ;; ++ *) ++ APP_STORE_COMPLIANCE_PATCH="${withval}" ++ AC_MSG_RESULT([applying custom app store compliance patch]) ++ ;; + esac ++ ],[ ++ case $ac_sys_system in ++ iOS|tvOS|watchOS) ++ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch ++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ++ AC_MSG_RESULT([applying default app store compliance patch]) ++ ;; ++ *) ++ # No default app compliance patching on any other platform ++ APP_STORE_COMPLIANCE_PATCH= ++ AC_MSG_RESULT([not patching for app store compliance]) ++ ;; ++ esac ++]) ++AC_SUBST([APP_STORE_COMPLIANCE_PATCH]) + +- if test "$ac_sys_system" = "SunOS"; then +- # For Solaris, there isn't an OS version specific macro defined +- # in most compilers, so we define one here. +- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'` +- AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION], +- [The version of SunOS/Solaris as reported by `uname -r' without the dot.]) +- fi +-fi +-AC_MSG_RESULT("$MACHDEP") +- +-AC_SUBST(_PYTHON_HOST_PLATFORM) ++AC_SUBST([_PYTHON_HOST_PLATFORM]) + if test "$cross_compiling" = yes; then + case "$host" in + *-*-linux*) + case "$host_cpu" in + arm*) +- _host_cpu=arm ++ _host_ident=arm + ;; + *) +- _host_cpu=$host_cpu ++ _host_ident=$host_cpu + esac + ;; + *-*-cygwin*) +- _host_cpu= ++ _host_ident= ++ ;; ++ *-apple-ios*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} + -+1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture; -+2. Merge the binaries for each architecture on a given ABI into a single "fat" binary; -+3. Merge the "fat" frameworks for each ABI into a single XCframework. ++ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version ++ AC_MSG_CHECKING([iOS deployment target]) ++ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3} ++ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0} ++ AC_MSG_RESULT([$IPHONEOS_DEPLOYMENT_TARGET]) + -+watchOS builds of Python *must* be constructed as framework builds. To support this, -+you must provide the ``--enable-framework`` flag when configuring the build. ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} ++ ;; ++ *) ++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-tvos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} + -+The build also requires the use of cross-compilation. The commands for building -+Python for watchOS will look somethign like:: ++ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version ++ AC_MSG_CHECKING([tvOS deployment target]) ++ TVOS_DEPLOYMENT_TARGET=${_host_os:4} ++ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0} ++ AC_MSG_RESULT([$TVOS_DEPLOYMENT_TARGET]) + -+ $ ./configure \ -+ --enable-framework=/path/to/install \ -+ --host=aarch64-apple-watchos \ -+ --build=aarch64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device} ++ ;; ++ *) ++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-watchos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} + -+In this invocation: ++ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version ++ AC_MSG_CHECKING([watchOS deployment target]) ++ WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET]) + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device} ++ ;; ++ *) ++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device} ++ ;; ++ esac + ;; + *-*-vxworks*) +- _host_cpu=$host_cpu ++ _host_ident=$host_cpu + ;; + wasm32-*-* | wasm64-*-*) +- _host_cpu=$host_cpu ++ _host_ident=$host_cpu + ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" + AC_MSG_ERROR([cross build not supported for $host]) + esac +- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}" ++ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}" + fi + + # Some systems cannot stand _XOPEN_SOURCE being defined at all; they +@@ -683,6 +952,13 @@ + define_xopen_source=no;; + Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) + define_xopen_source=no;; ++ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. ++ iOS/*) ++ define_xopen_source=no;; ++ tvOS/*) ++ define_xopen_source=no;; ++ watchOS/*) ++ define_xopen_source=no;; + # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from + # defining NI_NUMERICHOST. + QNX/6.3.2) +@@ -739,6 +1015,12 @@ + CONFIGURE_MACOSX_DEPLOYMENT_TARGET= + EXPORT_MACOSX_DEPLOYMENT_TARGET='#' + ++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / ++# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. ++AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET]) ++AC_SUBST([TVOS_DEPLOYMENT_TARGET]) ++AC_SUBST([WATCHOS_DEPLOYMENT_TARGET]) + -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: + # checks for alternative programs + + # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just +@@ -771,6 +1053,20 @@ + ], + ) + ++dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS version. ++AS_CASE([$ac_sys_system], ++ [iOS], [ ++ AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) ++ AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"]) ++ ],[tvOS], [ ++ AS_VAR_APPEND([CFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"]) ++ AS_VAR_APPEND([LDFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"]) ++ ],[watchOS], [ ++ AS_VAR_APPEND([CFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"]) ++ AS_VAR_APPEND([LDFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"]) ++ ], ++) + -+ - ``arm64_32-apple-watchos`` for ARM64-32 watchOS devices. -+ - ``aarch64-apple-watchos-simulator`` for the watchOS simulator running on Apple -+ Silicon devices. -+ - ``x86_64-apple-watchos-simulator`` for the watchOS simulator running on Intel -+ devices. + if test "$ac_sys_system" = "Darwin" + then + # Compiler selection on MacOSX is more complicated than +@@ -1062,7 +1358,42 @@ + #elif defined(__gnu_hurd__) + i386-gnu + #elif defined(__APPLE__) ++# include "TargetConditionals.h" ++# if TARGET_OS_IOS ++# if TARGET_OS_SIMULATOR ++# if __x86_64__ ++ x86_64-iphonesimulator ++# else ++ arm64-iphonesimulator ++# endif ++# else ++ arm64-iphoneos ++# endif ++# elif TARGET_OS_TV ++# if TARGET_OS_SIMULATOR ++# if __x86_64__ ++ x86_64-appletvsimulator ++# else ++ arm64-appletvsimulator ++# endif ++# else ++ arm64-appletvos ++# endif ++# elif TARGET_OS_WATCH ++# if TARGET_OS_SIMULATOR ++# if __x86_64__ ++ x86_64-watchsimulator ++# else ++ arm64-watchsimulator ++# endif ++# else ++ arm64_32-watchos ++# endif ++# elif TARGET_OS_OSX + darwin ++# else ++# error unknown Apple platform ++# endif + #elif defined(__VXWORKS__) + vxworks + #elif defined(__wasm32__) +@@ -1100,14 +1431,24 @@ + fi + rm -f conftest.c conftest.out + ++dnl On some platforms, using a true "triplet" for MULTIARCH would be redundant. ++dnl For example, `arm64-apple-darwin` is redundant, because there isn't a ++dnl non-Apple Darwin. Including the CPU architecture can also be potentially ++dnl redundant - on macOS, for example, it's possible to do a single compile ++dnl pass that includes multiple architectures, so it would be misleading for ++dnl MULTIARCH (and thus the sysconfigdata module name) to include a single CPU ++dnl architecture. PLATFORM_TRIPLET will be a pair or single value for these ++dnl platforms. + AC_MSG_CHECKING([for multiarch]) + AS_CASE([$ac_sys_system], + [Darwin*], [MULTIARCH=""], ++ [iOS], [MULTIARCH=""], ++ [tvOS], [MULTIARCH=""], ++ [watchOS], [MULTIARCH=""], + [FreeBSD*], [MULTIARCH=""], + [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] + ) + AC_SUBST([MULTIARCH]) +-AC_MSG_RESULT([$MULTIARCH]) + + if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then + if test x$PLATFORM_TRIPLET != x$MULTIARCH; then +@@ -1116,7 +1457,18 @@ + elif test x$PLATFORM_TRIPLET != x && test x$MULTIARCH = x; then + MULTIARCH=$PLATFORM_TRIPLET + fi +-AC_SUBST(PLATFORM_TRIPLET) ++AC_SUBST([PLATFORM_TRIPLET]) ++AC_MSG_RESULT([$MULTIARCH]) + -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: ++dnl Even if we *do* include the CPU architecture in the MULTIARCH value, some ++dnl platforms don't need the CPU architecture in the SOABI tag. These platforms ++dnl will have multiple sysconfig modules (one for each CPU architecture), but ++dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of ++dnl the PLATFORM_TRIPLET that will be used in binary module extensions. ++AS_CASE([$ac_sys_system], ++ [iOS|tvOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], ++ [SOABI_PLATFORM=$PLATFORM_TRIPLET] ++) + + if test x$MULTIARCH != x; then + MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\"" +@@ -1147,6 +1499,12 @@ + [wasm32-unknown-emscripten/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly Emscripten + [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly System Interface + [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 ++ [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64 ++ [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64 ++ [aarch64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl tvOS Simulator on arm64 ++ [aarch64-apple-tvos*/clang], [PY_SUPPORT_TIER=3], dnl tvOS on ARM64 ++ [aarch64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl watchOS Simulator on arm64 ++ [arm64_32-apple-watchos*/clang], [PY_SUPPORT_TIER=3], dnl watchOS on ARM64 + [PY_SUPPORT_TIER=0] + ) + +@@ -1459,17 +1817,25 @@ + + AC_MSG_CHECKING(LDLIBRARY) + +-# MacOSX framework builds need more magic. LDLIBRARY is the dynamic ++# Apple framework builds need more magic. LDLIBRARY is the dynamic + # library that we build, but we do not want to link against it (we + # will find it with a -framework option). For this reason there is an + # extra variable BLDLIBRARY against which Python and the extension + # modules are linked, BLDLIBRARY. This is normally the same as +-# LDLIBRARY, but empty for MacOSX framework builds. ++# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, ++# but uses a non-versioned framework layout. + if test "$enable_framework" + then +- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' +- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} ++ case $ac_sys_system in ++ Darwin) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; ++ *) ++ AC_MSG_ERROR([Unknown platform for framework build]);; ++ esac + BLDLIBRARY='' ++ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}} + else + BLDLIBRARY='$(LDLIBRARY)' + fi +@@ -1480,64 +1846,69 @@ + AC_DEFINE(Py_ENABLE_SHARED, 1, [Defined if Python is built as a shared library.]) + case $ac_sys_system in + CYGWIN*) +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- DLLLIBRARY='libpython$(LDVERSION).dll' +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ DLLLIBRARY='libpython$(LDVERSION).dll' ++ ;; + SunOS*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*) +- LDLIBRARY='libpython$(LDVERSION).so' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} +- INSTSONAME="$LDLIBRARY".$SOVERSION +- if test "$with_pydebug" != yes +- then +- PY3LIBRARY=libpython3.so +- fi +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ++ INSTSONAME="$LDLIBRARY".$SOVERSION ++ if test "$with_pydebug" != yes ++ then ++ PY3LIBRARY=libpython3.so ++ fi ++ ;; + hp*|HP*) +- case `uname -m` in +- ia64) +- LDLIBRARY='libpython$(LDVERSION).so' +- ;; +- *) +- LDLIBRARY='libpython$(LDVERSION).sl' +- ;; +- esac +- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' +- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} +- ;; ++ case `uname -m` in ++ ia64) ++ LDLIBRARY='libpython$(LDVERSION).so' ++ ;; ++ *) ++ LDLIBRARY='libpython$(LDVERSION).sl' ++ ;; ++ esac ++ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)' ++ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}} ++ ;; + Darwin*) +- LDLIBRARY='libpython$(LDVERSION).dylib' +- BLDLIBRARY='-L. -lpython$(LDVERSION)' +- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ BLDLIBRARY='-L. -lpython$(LDVERSION)' ++ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ++ ;; ++ iOS|tvOS|watchOS) ++ LDLIBRARY='libpython$(LDVERSION).dylib' ++ ;; + AIX*) +- LDLIBRARY='libpython$(LDVERSION).so' +- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} +- ;; ++ LDLIBRARY='libpython$(LDVERSION).so' ++ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} ++ ;; + + esac + else # shared is disabled + PY_ENABLE_SHARED=0 + case $ac_sys_system in + CYGWIN*) +- BLDLIBRARY='$(LIBRARY)' +- LDLIBRARY='libpython$(LDVERSION).dll.a' +- ;; ++ BLDLIBRARY='$(LIBRARY)' ++ LDLIBRARY='libpython$(LDVERSION).dll.a' ++ ;; + esac + fi + ++AC_MSG_RESULT($LDLIBRARY) + -+ - ``aarch64-apple-darwin`` for Apple Silicon devices. -+ - ``x86_64-apple-darwin`` for Intel devices. + if test "$cross_compiling" = yes; then +- RUNSHARED= ++ RUNSHARED= + fi + + AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform]) +@@ -1593,8 +1964,6 @@ + PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD" + fi + +-AC_MSG_RESULT($LDLIBRARY) +- + # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable + AS_CASE([$ac_sys_system/$ac_sys_emscripten_target], + [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js'], +@@ -1635,11 +2004,16 @@ + + AC_CHECK_TOOLS([READELF], [readelf], [:]) + if test "$cross_compiling" = yes; then +- case "$READELF" in +- readelf|:) +- AC_MSG_ERROR([readelf for the host is required for cross builds]) +- ;; +- esac ++ case "$ac_sys_system" in ++ iOS|tvOS|watchOS) ;; ++ *) ++ case "$READELF" in ++ readelf|:) ++ AC_MSG_ERROR([readelf for the host is required for cross builds]) ++ ;; ++ esac ++ ;; ++ esac + fi + AC_SUBST(READELF) + +@@ -3179,6 +3553,11 @@ + BLDSHARED="$LDSHARED" + fi + ;; ++ iOS/*|tvOS/*|watchOS/*) ++ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' ++ BLDSHARED="$LDSHARED" ++ ;; + Emscripten|WASI) + LDSHARED='$(CC) -shared' + LDCXXSHARED='$(CXX) -shared';; +@@ -3299,30 +3678,34 @@ + Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; + Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; + # -u libsys_s pulls in all symbols in libsys +- Darwin/*) ++ Darwin/*|iOS/*|tvOS/*|watchOS/*) + LINKFORSHARED="$extra_undefs -framework CoreFoundation" + + # Issue #18075: the default maximum stack size (8MBytes) is too + # small for the default recursion limit. Increase the stack size + # to ensure that tests don't crash +- stack_size="1000000" # 16 MB +- if test "$with_ubsan" = "yes" +- then +- # Undefined behavior sanitizer requires an even deeper stack +- stack_size="4000000" # 64 MB +- fi ++ stack_size="1000000" # 16 MB ++ if test "$with_ubsan" = "yes" ++ then ++ # Undefined behavior sanitizer requires an even deeper stack ++ stack_size="4000000" # 64 MB ++ fi + +- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" ++ AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE], ++ [0x$stack_size], ++ [Custom thread stack size depending on chosen sanitizer runtimes.]) + +- AC_DEFINE_UNQUOTED(THREAD_STACK_SIZE, +- 0x$stack_size, +- [Custom thread stack size depending on chosen sanitizer runtimes.]) ++ if test $ac_sys_system = "Darwin"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + +- if test "$enable_framework" +- then +- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ if test "$enable_framework"; then ++ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' ++ fi ++ LINKFORSHARED="$LINKFORSHARED" ++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi +- LINKFORSHARED="$LINKFORSHARED";; ++ ;; + OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; + SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; + ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; +@@ -3634,7 +4017,7 @@ + AC_ARG_WITH(system_ffi, + AS_HELP_STRING([--with-system-ffi], [build _ctypes module using an installed ffi library, see Doc/library/ctypes.rst (default is system-dependent)]),,,) + +-if test "$ac_sys_system" = "Darwin" ++if test "$ac_sys_system" = "Darwin" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" + then + case "$with_system_ffi" in + "") +@@ -3659,9 +4042,11 @@ + if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then + LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`" + else +- LIBFFI_INCLUDEDIR="" ++ LIBFFI_INCLUDEDIR="${LIBFFI_INCLUDEDIR}" + fi + AC_SUBST(LIBFFI_INCLUDEDIR) ++AC_SUBST(LIBFFI_LIBDIR) ++AC_SUBST(LIBFFI_LIB) + + # Check for use of the system libmpdec library + AC_MSG_CHECKING(for --with-system-libmpdec) +@@ -4594,27 +4979,27 @@ + # checks for library functions + AC_CHECK_FUNCS([ \ + accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ +- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ ++ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ + faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ +- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ +- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ +- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ ++ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ ++ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \ ++ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ + getpeername getpgid getpid getppid getpriority _getpty \ + getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ + getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ + lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ + mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ +- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ ++ pipe2 plock poll posix_fadvise posix_fallocate \ + pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ + pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ + rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ + sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ + sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ + setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ +- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ ++ setresuid setreuid setsid setuid setvbuf shutdown sigaction \ + sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ + sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ +- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ ++ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ + tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ + wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ + ]) +@@ -4626,11 +5011,28 @@ + AC_CHECK_FUNCS(lchmod) + fi + +-AC_CHECK_DECL(dirfd, +- AC_DEFINE(HAVE_DIRFD, 1, +- Define if you have the 'dirfd' function or macro.), , +- [#include +- #include ]) ++# iOS/tvOS/watchOS define some system methods that can be linked (so they are ++# found by configure), but either raise a compilation error (because the ++# header definition prevents usage - autoconf doesn't use the headers), or ++# raise an error if used at runtime. Force these symbols off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ AC_CHECK_FUNCS([ getentropy getgroups system ]) ++fi + -+* ``/path/to/python.exe`` is the path to a Python binary on the machine that -+ will be running the compiler. This is needed because the Python compilation -+ process involves running some Python code. On a normal desktop build of -+ Python, you can compile a python interpreter and then use that interpreter to -+ run Python code. However, the binaries produced for watchOS won't run on macOS, so -+ you need to provide an external Python interpreter. This interpreter must be -+ the version as the Python that is being compiled. ++# tvOS/watchOS have some additional methods that can be found, but not used. ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ AC_CHECK_FUNCS([ \ ++ execv fork fork1 posix_spawn posix_spawnp \ ++ sigaltstack \ ++ ]) ++fi + -+Using a framework-based Python on watchOS -+====================================== ++AC_CHECK_DECL([dirfd], ++ [AC_DEFINE([HAVE_DIRFD], [1], ++ [Define if you have the 'dirfd' function or macro.])], ++ [], ++ [@%:@include ++ @%:@include ]) + + dnl PY_CHECK_FUNC(FUNCTION, [INCLUDES], [AC_DEFINE-VAR]) + AC_DEFUN([PY_CHECK_FUNC], +@@ -4883,22 +5285,22 @@ + ]) + + # check for openpty, login_tty, and forkpty +- +-AC_CHECK_FUNCS(openpty,, +- AC_CHECK_LIB(util,openpty, +- [AC_DEFINE(HAVE_OPENPTY) LIBS="$LIBS -lutil"], +- AC_CHECK_LIB(bsd,openpty, [AC_DEFINE(HAVE_OPENPTY) LIBS="$LIBS -lbsd"]) +- ) +-) +-AC_SEARCH_LIBS([login_tty], [util], +- [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])] +-) +-AC_CHECK_FUNCS(forkpty,, +- AC_CHECK_LIB(util,forkpty, +- [AC_DEFINE(HAVE_FORKPTY) LIBS="$LIBS -lutil"], +- AC_CHECK_LIB(bsd,forkpty, [AC_DEFINE(HAVE_FORKPTY) LIBS="$LIBS -lbsd"]) +- ) +-) ++# tvOS/watchOS have functions for tty, but can't use them ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ AC_CHECK_FUNCS([openpty], [], ++ [AC_CHECK_LIB([util], [openpty], ++ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"], ++ [AC_CHECK_LIB([bsd], [openpty], ++ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])]) ++ AC_SEARCH_LIBS([login_tty], [util], ++ [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])] ++ ) ++ AC_CHECK_FUNCS([forkpty], [], ++ [AC_CHECK_LIB([util], [forkpty], ++ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"], ++ [AC_CHECK_LIB([bsd], [forkpty], ++ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])]) ++fi + + # check for long file support functions + AC_CHECK_FUNCS(fseek64 fseeko fstatvfs ftell64 ftello statvfs) +@@ -4972,11 +5374,17 @@ + ]) + ]) + +-AC_CHECK_FUNCS(clock_settime, [], [ +- AC_CHECK_LIB(rt, clock_settime, [ +- AC_DEFINE(HAVE_CLOCK_SETTIME, 1) +- ]) +-]) ++# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by ++# configure), but when used in an unprivileged process, it crashes rather than ++# returning an error. Force the symbol off. ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ++then ++ AC_CHECK_FUNCS([clock_settime], [], [ ++ AC_CHECK_LIB([rt], [clock_settime], [ ++ AC_DEFINE([HAVE_CLOCK_SETTIME], [1]) ++ ]) ++ ]) ++fi + + AC_CHECK_FUNCS(clock_nanosleep, [], [ + AC_CHECK_LIB(rt, clock_nanosleep, [ +@@ -5122,7 +5530,9 @@ + [ac_cv_buggy_getaddrinfo=no], + [ac_cv_buggy_getaddrinfo=yes], + [ +-if test "${enable_ipv6+set}" = set; then ++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then ++ ac_cv_buggy_getaddrinfo="no" ++elif test "${enable_ipv6+set}" = set; then + ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" + else + ac_cv_buggy_getaddrinfo=yes +@@ -5692,19 +6102,19 @@ + # + # In Python 3.2 and older, --with-wide-unicode added a 'u' flag. + # In Python 3.7 and older, --with-pymalloc added a 'm' flag. +-AC_SUBST(SOABI) +-AC_MSG_CHECKING(ABIFLAGS) +-AC_MSG_RESULT($ABIFLAGS) +-AC_MSG_CHECKING(SOABI) +-SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} +-AC_MSG_RESULT($SOABI) ++AC_SUBST([SOABI]) ++AC_MSG_CHECKING([ABIFLAGS]) ++AC_MSG_RESULT([$ABIFLAGS]) ++AC_MSG_CHECKING([SOABI]) ++SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM} ++AC_MSG_RESULT([$SOABI]) + + # Release and debug (Py_DEBUG) ABI are compatible, but not Py_TRACE_REFS ABI + if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then + # Similar to SOABI but remove "d" flag from ABIFLAGS +- AC_SUBST(ALT_SOABI) +- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET} +- AC_DEFINE_UNQUOTED(ALT_SOABI, "${ALT_SOABI}", ++ AC_SUBST([ALT_SOABI]) ++ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM} ++ AC_DEFINE_UNQUOTED([ALT_SOABI], ["${ALT_SOABI}"], + [Alternative SOABI used in debug build to load C extensions built in release mode]) + fi + +@@ -6193,28 +6603,35 @@ + AC_MSG_NOTICE([checking for device files]) + + dnl NOTE: Inform user how to proceed with files when cross compiling. +-if test "x$cross_compiling" = xyes; then +- if test "${ac_cv_file__dev_ptmx+set}" != set; then +- AC_MSG_CHECKING([for /dev/ptmx]) +- AC_MSG_RESULT([not set]) +- AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) +- fi +- if test "${ac_cv_file__dev_ptc+set}" != set; then +- AC_MSG_CHECKING([for /dev/ptc]) +- AC_MSG_RESULT([not set]) +- AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) ++dnl iOS cross-compile builds are predictable; they won't ever ++dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then ++ ac_cv_file__dev_ptmx=no ++ ac_cv_file__dev_ptc=no ++else ++ if test "x$cross_compiling" = xyes; then ++ if test "${ac_cv_file__dev_ptmx+set}" != set; then ++ AC_MSG_CHECKING([for /dev/ptmx]) ++ AC_MSG_RESULT([not set]) ++ AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) ++ fi ++ if test "${ac_cv_file__dev_ptc+set}" != set; then ++ AC_MSG_CHECKING([for /dev/ptc]) ++ AC_MSG_RESULT([not set]) ++ AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) ++ fi + fi +-fi + +-AC_CHECK_FILE(/dev/ptmx, [], []) +-if test "x$ac_cv_file__dev_ptmx" = xyes; then +- AC_DEFINE(HAVE_DEV_PTMX, 1, +- [Define to 1 if you have the /dev/ptmx device file.]) +-fi +-AC_CHECK_FILE(/dev/ptc, [], []) +-if test "x$ac_cv_file__dev_ptc" = xyes; then +- AC_DEFINE(HAVE_DEV_PTC, 1, +- [Define to 1 if you have the /dev/ptc device file.]) ++ AC_CHECK_FILE([/dev/ptmx], [], []) ++ if test "x$ac_cv_file__dev_ptmx" = xyes; then ++ AC_DEFINE([HAVE_DEV_PTMX], [1], ++ [Define to 1 if you have the /dev/ptmx device file.]) ++ fi ++ AC_CHECK_FILE([/dev/ptc], [], []) ++ if test "x$ac_cv_file__dev_ptc" = xyes; then ++ AC_DEFINE([HAVE_DEV_PTC], [1], ++ [Define to 1 if you have the /dev/ptc device file.]) ++ fi + fi + + if test $ac_sys_system = Darwin +@@ -6523,6 +6940,7 @@ + AS_CASE([$ac_sys_system], + [Emscripten], [with_ensurepip=no], + [WASI], [with_ensurepip=no], ++ [iOS|tvOS|watchOS], [with_ensurepip=no], + [with_ensurepip=upgrade] + ) + ]) +@@ -6856,6 +7274,28 @@ + [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], + [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])], + [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])], ++ [iOS|tvOS|watchOS], [ ++ dnl subprocess and multiprocessing are not supported (no fork syscall). ++ dnl curses and tkinter user interface are not available. ++ dnl gdbm and nis aren't available ++ dnl Stub implementations are provided for pwd, grp etc APIs ++ PY_STDLIB_MOD_SET_NA( ++ [_curses], ++ [_curses_panel], ++ [_gdbm], ++ [_multiprocessing], ++ [_posixshmem], ++ [_posixsubprocess], ++ [_scproxy], ++ [_tkinter], ++ [grp], ++ [nis], ++ [readline], ++ [pwd], ++ [spwd], ++ [syslog], ++ ) ++ ], + [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], + [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], + [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], --- /dev/null -+++ b/watchOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ Python -+ CFBundleGetInfoString -+ Python Runtime and Library -+ CFBundleIdentifier -+ @PYTHONFRAMEWORKIDENTIFIER@ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleName -+ Python -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ %VERSION% -+ CFBundleLongVersionString -+ %VERSION%, (c) 2001-2023 Python Software Foundation. -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ %VERSION% -+ CFBundleSupportedPlatforms -+ -+ watchOS -+ -+ MinimumOSVersion -+ @WATCHOS_DEPLOYMENT_TARGET@ -+ -+ ++++ b/iOS/Resources/bin/arm64-apple-ios-ar +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar ++++ b/iOS/Resources/bin/arm64-apple-ios-clang @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang ++++ b/iOS/Resources/bin/arm64-apple-ios-clang++ @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ ++++ b/iOS/Resources/bin/arm64-apple-ios-cpp @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator clang -target arm64-apple-watchos-simulator -E "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-ar ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" --- /dev/null -+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-cpp ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-strip @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos -E "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@" --- /dev/null -+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar ++++ b/iOS/Resources/bin/arm64-apple-ios-strip @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@" --- /dev/null -+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" --- /dev/null -+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" --- /dev/null -+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator -E "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" --- /dev/null -+++ b/watchOS/Resources/dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ watchOS -+ -+ MinimumOSVersion -+ 4.0 -+ CFBundleVersion -+ 1 -+ -+ ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" --- /dev/null -+++ b/watchOS/Resources/pyconfig.h -@@ -0,0 +1,11 @@ -+#ifdef __arm64__ -+# ifdef __LP64__ -+#include "pyconfig-arm64.h" -+# else -+#include "pyconfig-arm64_32.h" -+# endif -+#endif ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@" +diff --git a/setup.py b/setup.py +index ad8fb81b218..266035c472c 100644 +--- a/setup.py ++++ b/setup.py +@@ -82,6 +82,9 @@ + MS_WINDOWS = (HOST_PLATFORM == 'win32') + CYGWIN = (HOST_PLATFORM == 'cygwin') + MACOS = (HOST_PLATFORM == 'darwin') ++IOS = HOST_PLATFORM.startswith('ios-') ++TVOS = HOST_PLATFORM.startswith('tvos-') ++WATCHOS = HOST_PLATFORM.startswith('watchos-') + AIX = (HOST_PLATFORM.startswith('aix')) + VXWORKS = ('vxworks' in HOST_PLATFORM) + EMSCRIPTEN = HOST_PLATFORM == 'emscripten-wasm32' +@@ -166,16 +169,20 @@ + for var_name in make_vars: + var = sysconfig.get_config_var(var_name) + if var is not None: +- m = re.search(r'--sysroot=([^"]\S*|"[^"]+")', var) +- if m is not None: +- sysroot = m.group(1).strip('"') +- for subdir in subdirs: +- if os.path.isabs(subdir): +- subdir = subdir[1:] +- path = os.path.join(sysroot, subdir) +- if os.path.isdir(path): +- dirs.append(path) +- break ++ for pattern in [ ++ r'-isysroot\s*([^"]\S*|"[^"]+")', ++ r'--sysroot=([^"]\S*|"[^"]+")', ++ ]: ++ m = re.search(pattern, var) ++ if m is not None: ++ sysroot = m.group(1).strip('"') ++ for subdir in subdirs: ++ if os.path.isabs(subdir): ++ subdir = subdir[1:] ++ path = os.path.join(sysroot, subdir) ++ if os.path.isdir(path): ++ dirs.append(path) ++ break + return dirs + + +@@ -1400,6 +1407,11 @@ + extra_compile_args.append('-DMACOSX') + include_dirs.append('_ctypes/darwin') + ++ elif IOS or TVOS or WATCHOS: ++ sources.append('_ctypes/malloc_closure.c') ++ extra_compile_args.append('-DUSING_MALLOC_CLOSURE_DOT_C') ++ include_dirs.append('_ctypes/darwin') + -+#ifdef __x86_64__ -+#include "pyconfig-x86_64.h" -+#endif + elif HOST_PLATFORM == 'sunos5': + # XXX This shouldn't be necessary; it appears that some + # of the assembler code is non-PIC (i.e. it has relocations +@@ -1422,7 +1434,8 @@ + self.addext(Extension('_ctypes_test', ['_ctypes/_ctypes_test.c'])) + + ffi_inc = sysconfig.get_config_var("LIBFFI_INCLUDEDIR") +- ffi_lib = None ++ ffi_lib_dir = sysconfig.get_config_var("LIBFFI_LIBDIR") ++ ffi_lib = sysconfig.get_config_var("LIBFFI_LIB") + + ffi_inc_dirs = self.inc_dirs.copy() + if MACOS: +@@ -1451,6 +1464,7 @@ + for lib_name in ('ffi', 'ffi_pic'): + if (self.compiler.find_library_file(self.lib_dirs, lib_name)): + ffi_lib = lib_name ++ self.use_system_libffi = True + break + + if ffi_inc and ffi_lib: +@@ -1464,7 +1478,8 @@ + + ext.include_dirs.append(ffi_inc) + ext.libraries.append(ffi_lib) +- self.use_system_libffi = True ++ if ffi_lib_dir: ++ ext.library_dirs.append(ffi_lib_dir) + + if sysconfig.get_config_var('HAVE_LIBDL'): + # for dlopen, see bpo-32647 diff --git a/patch/Python/_cross_target.py.tmpl b/patch/Python/_cross_target.py.tmpl index 5f822e24..6b75958a 100644 --- a/patch/Python/_cross_target.py.tmpl +++ b/patch/Python/_cross_target.py.tmpl @@ -12,6 +12,8 @@ import sysconfig sys.cross_compiling = True sys.platform = "{{platform}}" sys.implementation._multiarch = "{{arch}}-{{sdk}}" +sys.base_prefix = sysconfig.get_config_var("prefix") +sys.base_exec_prefix = sysconfig.get_config_var("prefix") ########################################################################### # subprocess module patches @@ -74,5 +76,10 @@ def cross_get_sysconfigdata_name(): sysconfig.get_platform = cross_get_platform sysconfig._get_sysconfigdata_name = cross_get_sysconfigdata_name +# Ensure module-level values cached at time of import are updated. +sysconfig._BASE_PREFIX = sys.base_prefix +sysconfig._BASE_EXEC_PREFIX = sys.base_exec_prefix + # Force sysconfig data to be loaded (and cached). -sysconfig._init_config_vars() +sysconfig._CONFIG_VARS = None +sysconfig.get_config_vars() diff --git a/tests/test_cross_env.py b/tests/test_cross_env.py new file mode 100644 index 00000000..24b90457 --- /dev/null +++ b/tests/test_cross_env.py @@ -0,0 +1,98 @@ +import os +import platform +import sys +import sysconfig +from pathlib import Path + +import pytest + +# To run these tests, the following three environment variables must be set, +# reflecting the cross-platform environment that is in effect.' +PYTHON_CROSS_PLATFORM = os.getenv("PYTHON_CROSS_PLATFORM", "unknown") +PYTHON_CROSS_SLICE = os.getenv("PYTHON_CROSS_SLICE", "unknown") +PYTHON_CROSS_MULTIARCH = os.getenv("PYTHON_CROSS_MULTIARCH", "unknown") + +# Determine some file system anchor points for the tests +# Assumes that the tests are run in a virtual environment named +# `cross-venv`, +VENV_PREFIX = os.getenv("VIRTUAL_ENV", Path(__file__).parent.parent / "cross-venv") +default_support_base = ( + f"support/{sys.version_info.major}.{sys.version_info.minor}/{PYTHON_CROSS_PLATFORM}" +) +SUPPORT_PREFIX = ( + Path(__file__).parent.parent + / os.getenv("PYTHON_SUPPORT_BASE", default_support_base) + / "Python.xcframework" + / PYTHON_CROSS_SLICE +) + + +########################################################################### +# sys +########################################################################### + + +def test_sys_platform(): + assert sys.platform == PYTHON_CROSS_PLATFORM.lower() + + +def test_sys_cross_compiling(): + assert sys.cross_compiling + + +def test_sys_multiarch(): + assert sys.implementation._multiarch == PYTHON_CROSS_MULTIARCH + + +def test_sys_base_prefix(): + assert Path(sys.base_prefix) == SUPPORT_PREFIX + + +def test_sys_base_exec_prefix(): + assert Path(sys.base_exec_prefix) == SUPPORT_PREFIX + + +########################################################################### +# platform +########################################################################### + + +def test_platform_system(): + assert platform.system() == PYTHON_CROSS_PLATFORM + + +########################################################################### +# sysconfig +########################################################################### + + +def test_sysconfig_get_platform(): + parts = sysconfig.get_platform().split("-", 2) + assert parts[0] == PYTHON_CROSS_PLATFORM.lower() + assert parts[2] == PYTHON_CROSS_MULTIARCH + + +def test_sysconfig_get_sysconfigdata_name(): + parts = sysconfig._get_sysconfigdata_name().split("_", 4) + assert parts[3] == PYTHON_CROSS_PLATFORM.lower() + assert parts[4] == PYTHON_CROSS_MULTIARCH + + +@pytest.mark.parametrize( + "name, prefix", + [ + # Paths that should be relative to the support folder + ("stdlib", SUPPORT_PREFIX), + ("include", SUPPORT_PREFIX), + ("platinclude", SUPPORT_PREFIX), + ("stdlib", SUPPORT_PREFIX), + # paths that should be relative to the venv + ("platstdlib", VENV_PREFIX), + ("purelib", VENV_PREFIX), + ("platlib", VENV_PREFIX), + ("scripts", VENV_PREFIX), + ("data", VENV_PREFIX), + ], +) +def test_sysconfig_get_paths(name, prefix): + assert sysconfig.get_paths()[name].startswith(str(prefix)) From 5d4248d5f442e5709b430d05e0380d3893bfdcb4 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 12 Jun 2026 10:50:26 +0800 Subject: [PATCH 12/13] Reformat YAML. --- .github/workflows/ci.yaml | 390 ++++++++++++++++----------------- .github/workflows/publish.yaml | 72 +++--- .github/workflows/release.yaml | 68 +++--- 3 files changed, 265 insertions(+), 265 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d726b62d..0efa0dbb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -56,136 +56,136 @@ jobs: XZ_VERSION: ${{ steps.extract.outputs.XZ_VERSION }} steps: - - uses: actions/checkout@v6 - - - name: Extract config variables - id: extract - run: | - PYTHON_VER=$(make config | grep "PYTHON_VER=" | cut -d "=" -f 2) - PYTHON_VERSION=$(make config | grep "PYTHON_VERSION=" | cut -d "=" -f 2) - BZIP2_VERSION=$(make config | grep "BZIP2_VERSION=" | cut -d "=" -f 2) - LIBFFI_VERSION=$(make config | grep "LIBFFI_VERSION=" | cut -d "=" -f 2) - MPDECIMAL_VERSION=$(make config | grep "MPDECIMAL_VERSION=" | cut -d "=" -f 2) - OPENSSL_VERSION=$(make config | grep "OPENSSL_VERSION=" | cut -d "=" -f 2) - XZ_VERSION=$(make config | grep "XZ_VERSION=" | cut -d "=" -f 2) - if [ -z "${{ inputs.build-number }}" ]; then - BUILD_NUMBER=custom - else - BUILD_NUMBER=${{ inputs.build-number }} - fi - - echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} - echo "PYTHON_VERSION=${PYTHON_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} - echo "BZIP2_VERSION=${BZIP2_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "LIBFFI_VERSION=${LIBFFI_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "MPDECIMAL_VERSION=${MPDECIMAL_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "OPENSSL_VERSION=${OPENSSL_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "XZ_VERSION=${XZ_VERSION}" | tee -a ${GITHUB_OUTPUT} + - uses: actions/checkout@v6 + + - name: Extract config variables + id: extract + run: | + PYTHON_VER=$(make config | grep "PYTHON_VER=" | cut -d "=" -f 2) + PYTHON_VERSION=$(make config | grep "PYTHON_VERSION=" | cut -d "=" -f 2) + BZIP2_VERSION=$(make config | grep "BZIP2_VERSION=" | cut -d "=" -f 2) + LIBFFI_VERSION=$(make config | grep "LIBFFI_VERSION=" | cut -d "=" -f 2) + MPDECIMAL_VERSION=$(make config | grep "MPDECIMAL_VERSION=" | cut -d "=" -f 2) + OPENSSL_VERSION=$(make config | grep "OPENSSL_VERSION=" | cut -d "=" -f 2) + XZ_VERSION=$(make config | grep "XZ_VERSION=" | cut -d "=" -f 2) + if [ -z "${{ inputs.build-number }}" ]; then + BUILD_NUMBER=custom + else + BUILD_NUMBER=${{ inputs.build-number }} + fi + + echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} + echo "PYTHON_VERSION=${PYTHON_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} + echo "BZIP2_VERSION=${BZIP2_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "LIBFFI_VERSION=${LIBFFI_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "MPDECIMAL_VERSION=${MPDECIMAL_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "OPENSSL_VERSION=${OPENSSL_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "XZ_VERSION=${XZ_VERSION}" | tee -a ${GITHUB_OUTPUT} build: runs-on: macOS-15 - needs: [ config ] + needs: [config] strategy: fail-fast: false matrix: - platform: ['macOS', 'iOS', 'tvOS', 'watchOS'] + platform: ["macOS", "iOS", "tvOS", "watchOS"] steps: - - uses: actions/checkout@v6 - - - name: Set up Xcode - # GitHub recommends explicitly selecting the desired Xcode version: - # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 - # This became a necessity as a result of - # https://github.com/actions/runner-images/issues/12541 and - # https://github.com/actions/runner-images/issues/12751. - run: | - sudo xcode-select --switch /Applications/Xcode_16.4.app - - - name: Set up Python - uses: actions/setup-python@v6.1.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev - # Ensure that we *always* use the latest build, not a cached version. - # It's an edge case, but when a new alpha is released, we need to use it ASAP. - check-latest: true - - - name: Build ${{ matrix.platform }} - run: | - # Do the build for the requested platform. - make ${{ matrix.platform }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} - - - name: Upload build artefacts - uses: actions/upload-artifact@v6.0.0 - with: - name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + - uses: actions/checkout@v6 + + - name: Set up Xcode + # GitHub recommends explicitly selecting the desired Xcode version: + # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 + # This became a necessity as a result of + # https://github.com/actions/runner-images/issues/12541 and + # https://github.com/actions/runner-images/issues/12751. + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app + + - name: Set up Python + uses: actions/setup-python@v6.1.0 + with: + # Appending -dev ensures that we can always build the dev release. + # It's a no-op for versions that have been published. + python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true + + - name: Build ${{ matrix.platform }} + run: | + # Do the build for the requested platform. + make ${{ matrix.platform }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} + + - name: Upload build artefacts + uses: actions/upload-artifact@v6.0.0 + with: + name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz briefcase-testbed: name: Briefcase testbed (${{ matrix.platform }}) runs-on: macOS-15 - needs: [ config, build ] + needs: [config, build] strategy: fail-fast: false matrix: platform: ["macOS", "iOS"] include: - - briefcase-run-args: + - briefcase-run-args: - - platform: iOS - briefcase-run-args: ' -d "iPhone 16e::iOS 18.5"' + - platform: iOS + briefcase-run-args: ' -d "iPhone 16e::iOS 18.5"' steps: - - uses: actions/checkout@v6 - - - name: Set up Xcode - # GitHub recommends explicitly selecting the desired Xcode version: - # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 - # This became a necessity as a result of - # https://github.com/actions/runner-images/issues/12541 and - # https://github.com/actions/runner-images/issues/12751. - run: | - sudo xcode-select --switch /Applications/Xcode_16.4.app - - - name: Get build artifact - uses: actions/download-artifact@v7.0.0 - with: - pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - path: dist - merge-multiple: true - - - name: Set up Python - uses: actions/setup-python@v6.1.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev - # Ensure that we *always* use the latest build, not a cached version. - # It's an edge case, but when a new alpha is released, we need to use it ASAP. - check-latest: true - - - uses: actions/checkout@v6 - with: - repository: beeware/Python-support-testbed - path: Python-support-testbed - - - name: Install dependencies - run: | - # Use the development version of Briefcase - python -m pip install git+https://github.com/beeware/briefcase.git - - - name: Run support testbed check - timeout-minutes: 15 - working-directory: Python-support-testbed - run: briefcase run ${{ matrix.platform }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\' + - uses: actions/checkout@v6 + + - name: Set up Xcode + # GitHub recommends explicitly selecting the desired Xcode version: + # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 + # This became a necessity as a result of + # https://github.com/actions/runner-images/issues/12541 and + # https://github.com/actions/runner-images/issues/12751. + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app + + - name: Get build artifact + uses: actions/download-artifact@v7.0.0 + with: + pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist + merge-multiple: true + + - name: Set up Python + uses: actions/setup-python@v6.1.0 + with: + # Appending -dev ensures that we can always build the dev release. + # It's a no-op for versions that have been published. + python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true + + - uses: actions/checkout@v6 + with: + repository: beeware/Python-support-testbed + path: Python-support-testbed + + - name: Install dependencies + run: | + # Use the development version of Briefcase + python -m pip install git+https://github.com/beeware/briefcase.git + + - name: Run support testbed check + timeout-minutes: 15 + working-directory: Python-support-testbed + run: briefcase run ${{ matrix.platform }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\' cpython-testbed: name: CPython testbed (${{ matrix.platform }}) runs-on: macOS-15 - needs: [ config, build ] + needs: [config, build] strategy: fail-fast: false matrix: @@ -195,56 +195,56 @@ jobs: testbed-args: '--simulator "iPhone 16e,arch=arm64,OS=18.5"' steps: - - uses: actions/checkout@v6 - - - name: Get build artifact - uses: actions/download-artifact@v7.0.0 - with: - pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - path: dist - merge-multiple: true - - - name: Set up Xcode - # GitHub recommends explicitly selecting the desired Xcode version: - # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 - # This became a necessity as a result of - # https://github.com/actions/runner-images/issues/12541 and - # https://github.com/actions/runner-images/issues/12751. - run: | - sudo xcode-select --switch /Applications/Xcode_16.4.app - - - name: Set up Python - uses: actions/setup-python@v6.1.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev - # Ensure that we *always* use the latest build, not a cached version. - # It's an edge case, but when a new alpha is released, we need to use it ASAP. - check-latest: true - - - name: Unpack support package - run: | - mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} - cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} - tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - - - name: Run CPython testbed - timeout-minutes: 15 - working-directory: support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} - run: | - # Run a representative subset of CPython core tests: - # - test_builtin as a test of core language tools - # - test_grammar as a test of core language features - # - test_os as a test of system library calls - # - test_bz2 as a simple test of third party libraries - # - test_ctypes as a test of FFI - python -m testbed run --verbose ${{ matrix.testbed-args }} -- test --rerun -W --timeout=-1 test_builtin test_grammar test_os test_bz2 test_ctypes + - uses: actions/checkout@v6 + + - name: Get build artifact + uses: actions/download-artifact@v7.0.0 + with: + pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist + merge-multiple: true + + - name: Set up Xcode + # GitHub recommends explicitly selecting the desired Xcode version: + # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 + # This became a necessity as a result of + # https://github.com/actions/runner-images/issues/12541 and + # https://github.com/actions/runner-images/issues/12751. + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app + + - name: Set up Python + uses: actions/setup-python@v6.1.0 + with: + # Appending -dev ensures that we can always build the dev release. + # It's a no-op for versions that have been published. + python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true + + - name: Unpack support package + run: | + mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + + - name: Run CPython testbed + timeout-minutes: 15 + working-directory: support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + run: | + # Run a representative subset of CPython core tests: + # - test_builtin as a test of core language tools + # - test_grammar as a test of core language features + # - test_os as a test of system library calls + # - test_bz2 as a simple test of third party libraries + # - test_ctypes as a test of FFI + python -m testbed run --verbose ${{ matrix.testbed-args }} -- test --rerun -W --timeout=-1 test_builtin test_grammar test_os test_bz2 test_ctypes crossenv-test: name: Cross-platform env test (${{ matrix.multiarch }}) runs-on: macOS-latest - needs: [ config, build ] + needs: [config, build] strategy: fail-fast: false matrix: @@ -260,46 +260,46 @@ jobs: multiarch: arm64-iphoneos steps: - - uses: actions/checkout@v6 - - - name: Get build artifact - uses: actions/download-artifact@v7.0.0 - with: - pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - path: dist - merge-multiple: true - - - name: Set up Python - uses: actions/setup-python@v6.1.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev - # Ensure that we *always* use the latest build, not a cached version. - # It's an edge case, but when a new alpha is released, we need to use it ASAP. - check-latest: true - - - name: Unpack support package - run: | - mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} - cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} - tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - - - name: Run cross-platform environment test - env: - PYTHON_CROSS_PLATFORM: ${{ matrix.platform }} - PYTHON_CROSS_SLICE: ${{ matrix.slice }} - PYTHON_CROSS_MULTIARCH: ${{ matrix.multiarch }} - run: | - # Create and activate a native virtual environment - python${{ needs.config.outputs.PYTHON_VER }} -m venv cross-venv - source cross-venv/bin/activate - - # Install pytest - python -m pip install pytest - - # Convert venv into cross-venv - python support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}/Python.xcframework/${{ matrix.slice }}/platform-config/${{ matrix.multiarch }}/make_cross_venv.py cross-venv - - # Run the test suite - python -m pytest tests + - uses: actions/checkout@v6 + + - name: Get build artifact + uses: actions/download-artifact@v7.0.0 + with: + pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist + merge-multiple: true + + - name: Set up Python + uses: actions/setup-python@v6.1.0 + with: + # Appending -dev ensures that we can always build the dev release. + # It's a no-op for versions that have been published. + python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev + # Ensure that we *always* use the latest build, not a cached version. + # It's an edge case, but when a new alpha is released, we need to use it ASAP. + check-latest: true + + - name: Unpack support package + run: | + mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }} + tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + + - name: Run cross-platform environment test + env: + PYTHON_CROSS_PLATFORM: ${{ matrix.platform }} + PYTHON_CROSS_SLICE: ${{ matrix.slice }} + PYTHON_CROSS_MULTIARCH: ${{ matrix.multiarch }} + run: | + # Create and activate a native virtual environment + python${{ needs.config.outputs.PYTHON_VER }} -m venv cross-venv + source cross-venv/bin/activate + + # Install pytest + python -m pip install pytest + + # Convert venv into cross-venv + python support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}/Python.xcframework/${{ matrix.slice }}/platform-config/${{ matrix.multiarch }}/make_cross_venv.py cross-venv + + # Run the test suite + python -m pytest tests diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 8ba1300f..a3b23c53 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -8,43 +8,43 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6 - - name: Set up Python environment - uses: actions/setup-python@v6.1.0 - with: - python-version: "3.X" + - name: Set up Python environment + uses: actions/setup-python@v6.1.0 + with: + python-version: "3.X" - - name: Set build variables - id: build-vars - env: - TAG_NAME: ${{ github.ref }} - run: | - TAG=$(basename $TAG_NAME) - PYTHON_VER="${TAG%-*}" - BUILD_NUMBER="${TAG#*-}" + - name: Set build variables + id: build-vars + env: + TAG_NAME: ${{ github.ref }} + run: | + TAG=$(basename $TAG_NAME) + PYTHON_VER="${TAG%-*}" + BUILD_NUMBER="${TAG#*-}" - echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} - echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} - echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} + echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} + echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} + echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} - - name: Update Release Asset to S3 - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - run: | - python -m pip install -U pip - python -m pip install -U setuptools - python -m pip install awscli - # macOS build - curl -o macOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp macOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/macOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - # iOS build - curl -o iOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp iOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/iOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - # tvOS build - curl -o tvOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp tvOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/tvOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - # watchOS build - curl -o watchOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp watchOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/watchOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + - name: Update Release Asset to S3 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + python -m pip install -U pip + python -m pip install -U setuptools + python -m pip install awscli + # macOS build + curl -o macOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp macOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/macOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + # iOS build + curl -o iOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp iOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/iOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + # tvOS build + curl -o tvOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp tvOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/tvOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + # watchOS build + curl -o watchOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp watchOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/watchOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1b65e9fd..cda51326 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,7 +5,7 @@ name: Build support package on: push: tags: - - '*-b*' + - "*-b*" jobs: config: @@ -16,20 +16,20 @@ jobs: BUILD_NUMBER: ${{ steps.build-vars.outputs.BUILD_NUMBER }} steps: - - name: Set Build Variables - id: build-vars - env: - TAG_NAME: ${{ github.ref }} - run: | - export TAG=$(basename $TAG_NAME) - export BUILD_NUMBER="${TAG#*-}" + - name: Set Build Variables + id: build-vars + env: + TAG_NAME: ${{ github.ref }} + run: | + export TAG=$(basename $TAG_NAME) + export BUILD_NUMBER="${TAG#*-}" - echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} - echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} + echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} + echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} ci: name: CI - needs: [ config ] + needs: [config] uses: ./.github/workflows/ci.yaml with: build-number: ${{ needs.config.outputs.BUILD_NUMBER }} @@ -37,29 +37,29 @@ jobs: make-release: name: Make Release runs-on: ubuntu-latest - needs: [ config, ci ] + needs: [config, ci] steps: - - name: Get build artifacts - uses: actions/download-artifact@v7.0.0 - with: - pattern: Python-* - path: dist - merge-multiple: true + - name: Get build artifacts + uses: actions/download-artifact@v7.0.0 + with: + pattern: Python-* + path: dist + merge-multiple: true - - name: Create Release - uses: ncipollo/release-action@v1.20.0 - with: - name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} - tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} - draft: true - body: | - Build ${{ needs.config.outputs.BUILD_NUMBER }} of the BeeWare support package for Python ${{ needs.ci.outputs.PYTHON_VER }}. + - name: Create Release + uses: ncipollo/release-action@v1.20.0 + with: + name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} + tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} + draft: true + body: | + Build ${{ needs.config.outputs.BUILD_NUMBER }} of the BeeWare support package for Python ${{ needs.ci.outputs.PYTHON_VER }}. - Includes: - * Python ${{ needs.ci.outputs.PYTHON_VERSION }} - * BZip2 ${{ needs.ci.outputs.BZIP2_VERSION }} - * libFFI ${{ needs.ci.outputs.LIBFFI_VERSION }} - * mpdecimal ${{ needs.ci.outputs.MPDECIMAL_VERSION }} - * OpenSSL ${{ needs.ci.outputs.OPENSSL_VERSION }} - * XZ ${{ needs.ci.outputs.XZ_VERSION }} - artifacts: "dist/*" + Includes: + * Python ${{ needs.ci.outputs.PYTHON_VERSION }} + * BZip2 ${{ needs.ci.outputs.BZIP2_VERSION }} + * libFFI ${{ needs.ci.outputs.LIBFFI_VERSION }} + * mpdecimal ${{ needs.ci.outputs.MPDECIMAL_VERSION }} + * OpenSSL ${{ needs.ci.outputs.OPENSSL_VERSION }} + * XZ ${{ needs.ci.outputs.XZ_VERSION }} + artifacts: "dist/*" From 04c548ce982566b89f33ee7641cb3d75d6decee9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 12 Jun 2026 15:02:21 +0800 Subject: [PATCH 13/13] Update patch to Python 3.11.15. (#356) --- Makefile | 2 +- patch/Python/Python.patch | 484 +++++++++++++++++++++++++++++++++++--- 2 files changed, 455 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 4c6e2dfc..6de19e22 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ BUILD_NUMBER=custom # of a release cycle, as official binaries won't be published. # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.11.14 +PYTHON_VERSION=3.11.15 PYTHON_PKG_VERSION=3.11.9 PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+") diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 18f0d95f..ad3e01f0 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -33,6 +33,9 @@ index 04a0d1fe65e..58695d56657 100644 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 +diff --git a/.ruff.toml b/.ruff.toml +new file mode 100644 +index 00000000000..1c015fa8841 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,12 @@ @@ -48,6 +51,9 @@ index 04a0d1fe65e..58695d56657 100644 +# To override this, use ``fix = false`` in a subdirectory's config file +# or ``--no-fix`` on the command line. +fix = true +diff --git a/Apple/.ruff.toml b/Apple/.ruff.toml +new file mode 100644 +index 00000000000..f9098e0f5ce --- /dev/null +++ b/Apple/.ruff.toml @@ -0,0 +1,22 @@ @@ -73,9 +79,12 @@ index 04a0d1fe65e..58695d56657 100644 + "W", # pycodestyle + "YTT", # flake8-2020 +] +diff --git a/Apple/__main__.py b/Apple/__main__.py +new file mode 100644 +index 00000000000..81d3812bd05 --- /dev/null +++ b/Apple/__main__.py -@@ -0,0 +1,1111 @@ +@@ -0,0 +1,1150 @@ +#!/usr/bin/env python3 +########################################################################## +# Apple XCframework build script @@ -290,8 +299,11 @@ index 04a0d1fe65e..58695d56657 100644 + return triples + + -+def clean(context: argparse.Namespace, target: str = "all") -> None: ++def clean(context: argparse.Namespace, target: str | None = None) -> None: + """The implementation of the "clean" command.""" ++ if target is None: ++ target = context.host ++ + # If we're explicitly targeting the build, there's no platform or + # distribution artefacts. If we're cleaning tests, we keep all built + # artefacts. Otherwise, the built artefacts must be dirty, so we remove @@ -433,7 +445,7 @@ index 04a0d1fe65e..58695d56657 100644 + for name_ver in [ + "BZip2-1.0.8-2", + "libFFI-3.4.7-2", -+ "OpenSSL-3.0.18-1", ++ "OpenSSL-3.0.19-1", + "XZ-5.6.4-2", + "mpdecimal-4.0.0-2", + "zstd-1.5.7-1", @@ -494,7 +506,12 @@ index 04a0d1fe65e..58695d56657 100644 + with group(f"Downloading dependencies ({host})"): + if not prefix_dir.exists(): + prefix_dir.mkdir() -+ unpack_deps(context.platform, host, prefix_dir, context.cache_dir) ++ cache_dir = ( ++ Path(context.cache_dir).resolve() ++ if context.cache_dir ++ else CROSS_BUILD_DIR / "downloads" ++ ) ++ unpack_deps(context.platform, host, prefix_dir, cache_dir) + else: + print("Dependencies already installed") + @@ -882,7 +899,7 @@ index 04a0d1fe65e..58695d56657 100644 + ]: + step(context, host=step_host) + -+ if host in {"all", "hosts"}: ++ if host == "all": + package(context) + + @@ -1018,7 +1035,7 @@ index 04a0d1fe65e..58695d56657 100644 + configure_build = subcommands.add_parser( + "configure-build", help="Run `configure` for the build Python" + ) -+ subcommands.add_parser( ++ make_build = subcommands.add_parser( + "make-build", help="Run `make` for the build Python" + ) + configure_host = subcommands.add_parser( @@ -1074,6 +1091,31 @@ index 04a0d1fe65e..58695d56657 100644 + ), + ) + ++ # --cross-build-dir argument ++ for cmd in [ ++ clean, ++ configure_build, ++ make_build, ++ configure_host, ++ make_host, ++ build, ++ package, ++ test, ++ ci, ++ ]: ++ cmd.add_argument( ++ "--cross-build-dir", ++ action="store", ++ default=os.environ.get("CROSS_BUILD_DIR"), ++ dest="cross_build_dir", ++ type=Path, ++ help=( ++ "Path to the cross-build directory " ++ f"(default: {CROSS_BUILD_DIR}). Can also be set " ++ "with the CROSS_BUILD_DIR environment variable." ++ ), ++ ) ++ + # --clean option + for cmd in [configure_build, configure_host, build, package, test, ci]: + cmd.add_argument( @@ -1088,7 +1130,7 @@ index 04a0d1fe65e..58695d56657 100644 + for cmd in [configure_host, build, ci]: + cmd.add_argument( + "--cache-dir", -+ default="./cross-build/downloads", ++ default=os.environ.get("CACHE_DIR"), + help="The directory to store cached downloads.", + ) + @@ -1155,6 +1197,12 @@ index 04a0d1fe65e..58695d56657 100644 + + # Process command line arguments + context = parse_args() ++ ++ # Set the CROSS_BUILD_DIR if an argument was provided ++ if context.cross_build_dir: ++ global CROSS_BUILD_DIR ++ CROSS_BUILD_DIR = context.cross_build_dir.resolve() ++ + dispatch: dict[str, Callable] = { + "clean": clean, + "configure-build": configure_build_python, @@ -1187,6 +1235,9 @@ index 04a0d1fe65e..58695d56657 100644 + stream.reconfigure(line_buffering=True) + + main() +diff --git a/Apple/iOS/README.md b/Apple/iOS/README.md +new file mode 100644 +index 00000000000..7ee257b5d64 --- /dev/null +++ b/Apple/iOS/README.md @@ -0,0 +1,339 @@ @@ -1529,6 +1580,9 @@ index 04a0d1fe65e..58695d56657 100644 +(this will likely be your own name), and plug in a physical device to your +macOS machine with a USB cable. You should then be able to select your physical +device from the list of targets in the pulldown in the Xcode titlebar. +diff --git a/Apple/iOS/Resources/Info.plist.in b/Apple/iOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..26ef7a95de4 --- /dev/null +++ b/Apple/iOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ @@ -1566,81 +1620,129 @@ index 04a0d1fe65e..58695d56657 100644 + @IPHONEOS_DEPLOYMENT_TARGET@ + + +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-ar b/Apple/iOS/Resources/bin/arm64-apple-ios-ar +new file mode 100755 +index 00000000000..3cf3eb21874 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-clang b/Apple/iOS/Resources/bin/arm64-apple-ios-clang +new file mode 100755 +index 00000000000..f50d5b5142f --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ +new file mode 100755 +index 00000000000..0794731d7dc --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-cpp b/Apple/iOS/Resources/bin/arm64-apple-ios-cpp +new file mode 100755 +index 00000000000..24fa1506bab --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar +new file mode 100755 +index 00000000000..b836b6db902 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang +new file mode 100755 +index 00000000000..4891a00876e --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ +new file mode 100755 +index 00000000000..58b2a5f6f18 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +new file mode 100755 +index 00000000000..c9df94e8b7c --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip +new file mode 100755 +index 00000000000..fd59d309b73 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-strip b/Apple/iOS/Resources/bin/arm64-apple-ios-strip +new file mode 100755 +index 00000000000..75e823a3d02 --- /dev/null +++ b/Apple/iOS/Resources/bin/arm64-apple-ios-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar +new file mode 100755 +index 00000000000..b836b6db902 --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang +new file mode 100755 +index 00000000000..f4739a7b945 --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ +new file mode 100755 +index 00000000000..c348ae4c103 --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +new file mode 100755 +index 00000000000..6d7f8084c9f --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip +new file mode 100755 +index 00000000000..c5cfb289291 --- /dev/null +++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@" +diff --git a/Apple/iOS/Resources/pyconfig.h b/Apple/iOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..4acff2c6051 --- /dev/null +++ b/Apple/iOS/Resources/pyconfig.h @@ -0,0 +1,7 @@ @@ -1651,6 +1753,9 @@ index 04a0d1fe65e..58695d56657 100644 +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif +diff --git a/Apple/testbed/Python.xcframework/Info.plist b/Apple/testbed/Python.xcframework/Info.plist +new file mode 100644 +index 00000000000..f90105a6d70 --- /dev/null +++ b/Apple/testbed/Python.xcframework/Info.plist @@ -0,0 +1,106 @@ @@ -1760,6 +1865,9 @@ index 04a0d1fe65e..58695d56657 100644 + 1.0 + + +diff --git a/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..d6caa01c1e4 --- /dev/null +++ b/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist @@ -0,0 +1,26 @@ @@ -1789,6 +1897,9 @@ index 04a0d1fe65e..58695d56657 100644 + 1 + + +diff --git a/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..a20d476fa7b --- /dev/null +++ b/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist @@ -0,0 +1,26 @@ @@ -1818,6 +1929,9 @@ index 04a0d1fe65e..58695d56657 100644 + 1 + + +diff --git a/Apple/testbed/Python.xcframework/build/utils.sh b/Apple/testbed/Python.xcframework/build/utils.sh +new file mode 100755 +index 00000000000..2c3c8008512 --- /dev/null +++ b/Apple/testbed/Python.xcframework/build/utils.sh @@ -0,0 +1,174 @@ @@ -1888,11 +2002,11 @@ index 04a0d1fe65e..58695d56657 100644 + # If the XCframework has a shared lib folder, then it's a full framework. + # Copy both the common and slice-specific part of the lib directory. + # Otherwise, it's a single-arch framework; use the "full" lib folder. ++ # Don't include any libpython symlink; that can't be included at runtime + if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then -+ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" -+ rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/" ++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib' ++ rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib' + else -+ # A single-arch framework will have a libpython symlink; that can't be included at runtime + rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib' + fi +} @@ -1986,7 +2100,7 @@ index 04a0d1fe65e..58695d56657 100644 + shift + + install_stdlib $PYTHON_XCFRAMEWORK_PATH -+ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") ++ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib" | grep -E "^python3\.\d+$") + echo "Install Python $PYTHON_VER standard library extension modules..." + process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload + @@ -1995,6 +2109,9 @@ index 04a0d1fe65e..58695d56657 100644 + process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path + done +} +diff --git a/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..6f8c0bc2095 --- /dev/null +++ b/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist @@ -0,0 +1,26 @@ @@ -2024,6 +2141,9 @@ index 04a0d1fe65e..58695d56657 100644 + 1 + + +diff --git a/Apple/testbed/Python.xcframework/ios-arm64/README b/Apple/testbed/Python.xcframework/ios-arm64/README +new file mode 100644 +index 00000000000..c1b076d12cd --- /dev/null +++ b/Apple/testbed/Python.xcframework/ios-arm64/README @@ -0,0 +1,4 @@ @@ -2031,6 +2151,9 @@ index 04a0d1fe65e..58695d56657 100644 + +It should be used as a target for `--enable-framework` when compiling an iOS on-device +build for testing purposes. +diff --git a/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README b/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..ae334e5d769 --- /dev/null +++ b/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README @@ -0,0 +1,4 @@ @@ -2038,6 +2161,9 @@ index 04a0d1fe65e..58695d56657 100644 + +It should be used as a target for `--enable-framework` when compiling an iOS simulator +build for testing purposes (either x86_64 or ARM64). +diff --git a/Apple/testbed/Python.xcframework/tvos-arm64/README b/Apple/testbed/Python.xcframework/tvos-arm64/README +new file mode 100644 +index 00000000000..ebd648d04bd --- /dev/null +++ b/Apple/testbed/Python.xcframework/tvos-arm64/README @@ -0,0 +1,4 @@ @@ -2045,6 +2171,9 @@ index 04a0d1fe65e..58695d56657 100644 + +It should be used as a target for `--enable-framework` when compiling a tvOS +on-device build for testing purposes. +diff --git a/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README b/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..f8163468d8c --- /dev/null +++ b/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README @@ -0,0 +1,4 @@ @@ -2052,6 +2181,9 @@ index 04a0d1fe65e..58695d56657 100644 + +It should be used as a target for `--enable-framework` when compiling a tvOS +simulator build for testing purposes (either x86_64 or ARM64). +diff --git a/Apple/testbed/Python.xcframework/watchos-arm64_32/README b/Apple/testbed/Python.xcframework/watchos-arm64_32/README +new file mode 100644 +index 00000000000..696af231df3 --- /dev/null +++ b/Apple/testbed/Python.xcframework/watchos-arm64_32/README @@ -0,0 +1,4 @@ @@ -2059,6 +2191,9 @@ index 04a0d1fe65e..58695d56657 100644 + +It should be used as a target for `--enable-framework` when compiling a watchOS on-device +build for testing purposes. +diff --git a/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README b/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..d38e1e98276 --- /dev/null +++ b/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README @@ -0,0 +1,4 @@ @@ -2066,6 +2201,9 @@ index 04a0d1fe65e..58695d56657 100644 + +It should be used as a target for `--enable-framework` when compiling a watchOS +simulator build for testing purposes (either x86_64 or ARM64). +diff --git a/Apple/testbed/Testbed.lldbinit b/Apple/testbed/Testbed.lldbinit +new file mode 100644 +index 00000000000..4cf00dd0f9d --- /dev/null +++ b/Apple/testbed/Testbed.lldbinit @@ -0,0 +1,4 @@ @@ -2073,6 +2211,9 @@ index 04a0d1fe65e..58695d56657 100644 +process handle SIGUSR1 -n true -p true -s false +process handle SIGUSR2 -n true -p true -s false +process handle SIGXFSZ -n true -p true -s false +diff --git a/Apple/testbed/TestbedTests/TestbedTests.m b/Apple/testbed/TestbedTests/TestbedTests.m +new file mode 100644 +index 00000000000..cc0d9224042 --- /dev/null +++ b/Apple/testbed/TestbedTests/TestbedTests.m @@ -0,0 +1,198 @@ @@ -2274,6 +2415,9 @@ index 04a0d1fe65e..58695d56657 100644 + + +@end +diff --git a/Apple/testbed/__main__.py b/Apple/testbed/__main__.py +new file mode 100644 +index 00000000000..7872ecba271 --- /dev/null +++ b/Apple/testbed/__main__.py @@ -0,0 +1,472 @@ @@ -2304,7 +2448,7 @@ index 04a0d1fe65e..58695d56657 100644 +LOG_PREFIX_REGEX = re.compile( + r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD + r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ -+ r"\s+.*Testbed\[\d+:\w+\]" # Process/thread ID ++ r"\s+.*Testbed\[\d+:\w+\] " # Process/thread ID +) + + @@ -2749,6 +2893,9 @@ index 04a0d1fe65e..58695d56657 100644 + for stream in [sys.stdout, sys.stderr]: + stream.reconfigure(line_buffering=True) + main() +diff --git a/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +new file mode 100644 +index 00000000000..f8835a3bc58 --- /dev/null +++ b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj @@ -0,0 +1,557 @@ @@ -3309,6 +3456,9 @@ index 04a0d1fe65e..58695d56657 100644 + }; + rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; +} +diff --git a/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme +new file mode 100644 +index 00000000000..3c330a4152b --- /dev/null +++ b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme @@ -0,0 +1,97 @@ @@ -3409,6 +3559,9 @@ index 04a0d1fe65e..58695d56657 100644 + revealArchiveInOrganizer = "YES"> + + +diff --git a/Apple/testbed/iOSTestbed.xctestplan b/Apple/testbed/iOSTestbed.xctestplan +new file mode 100644 +index 00000000000..0c4ab9eb2ba --- /dev/null +++ b/Apple/testbed/iOSTestbed.xctestplan @@ -0,0 +1,46 @@ @@ -3458,6 +3611,9 @@ index 04a0d1fe65e..58695d56657 100644 + ], + "version" : 1 +} +diff --git a/Apple/testbed/iOSTestbed/AppDelegate.h b/Apple/testbed/iOSTestbed/AppDelegate.h +new file mode 100644 +index 00000000000..f695b3b5efc --- /dev/null +++ b/Apple/testbed/iOSTestbed/AppDelegate.h @@ -0,0 +1,11 @@ @@ -3472,6 +3628,9 @@ index 04a0d1fe65e..58695d56657 100644 + + +@end +diff --git a/Apple/testbed/iOSTestbed/AppDelegate.m b/Apple/testbed/iOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..e5085399d0c --- /dev/null +++ b/Apple/testbed/iOSTestbed/AppDelegate.m @@ -0,0 +1,19 @@ @@ -3494,6 +3653,9 @@ index 04a0d1fe65e..58695d56657 100644 +} + +@end +diff --git a/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +new file mode 100644 +index 00000000000..eb878970081 --- /dev/null +++ b/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ @@ -3508,6 +3670,9 @@ index 04a0d1fe65e..58695d56657 100644 + "version" : 1 + } +} +diff --git a/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +new file mode 100644 +index 00000000000..13613e3ee1a --- /dev/null +++ b/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ @@ -3524,6 +3689,9 @@ index 04a0d1fe65e..58695d56657 100644 + "version" : 1 + } +} +diff --git a/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json b/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json +new file mode 100644 +index 00000000000..73c00596a7f --- /dev/null +++ b/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ @@ -3533,6 +3701,9 @@ index 04a0d1fe65e..58695d56657 100644 + "version" : 1 + } +} +diff --git a/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard b/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard +new file mode 100644 +index 00000000000..5daafe73a86 --- /dev/null +++ b/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,9 @@ @@ -3545,6 +3716,9 @@ index 04a0d1fe65e..58695d56657 100644 + + + +diff --git a/Apple/testbed/iOSTestbed/app/README b/Apple/testbed/iOSTestbed/app/README +new file mode 100644 +index 00000000000..46c0e8e2a29 --- /dev/null +++ b/Apple/testbed/iOSTestbed/app/README @@ -0,0 +1,7 @@ @@ -3555,6 +3729,9 @@ index 04a0d1fe65e..58695d56657 100644 + +When the test suite runs, this folder will be on the PYTHONPATH, and will be the +working directory for the test suite. +diff --git a/Apple/testbed/iOSTestbed/app_packages/README b/Apple/testbed/iOSTestbed/app_packages/README +new file mode 100644 +index 00000000000..02c2beccfbd --- /dev/null +++ b/Apple/testbed/iOSTestbed/app_packages/README @@ -0,0 +1,7 @@ @@ -3565,6 +3742,9 @@ index 04a0d1fe65e..58695d56657 100644 +Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH. +diff --git a/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist b/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist +new file mode 100644 +index 00000000000..fea45e1fad6 --- /dev/null +++ b/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist @@ -0,0 +1,52 @@ @@ -3620,6 +3800,9 @@ index 04a0d1fe65e..58695d56657 100644 + + + +diff --git a/Apple/testbed/iOSTestbed/main.m b/Apple/testbed/iOSTestbed/main.m +new file mode 100644 +index 00000000000..e32bd78c9b4 --- /dev/null +++ b/Apple/testbed/iOSTestbed/main.m @@ -0,0 +1,16 @@ @@ -3639,6 +3822,9 @@ index 04a0d1fe65e..58695d56657 100644 + return UIApplicationMain(argc, argv, nil, appDelegateClassName); + } +} +diff --git a/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj b/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj +new file mode 100644 +index 00000000000..85e7047dfb7 --- /dev/null +++ b/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj @@ -0,0 +1,506 @@ @@ -4148,6 +4334,9 @@ index 04a0d1fe65e..58695d56657 100644 + }; + rootObject = EE989E462DCD6E780036B268 /* Project object */; +} +diff --git a/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme b/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme +new file mode 100644 +index 00000000000..c3f3f894a1f --- /dev/null +++ b/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme @@ -0,0 +1,97 @@ @@ -4248,6 +4437,9 @@ index 04a0d1fe65e..58695d56657 100644 + revealArchiveInOrganizer = "YES"> + + +diff --git a/Apple/testbed/tvOSTestbed.xctestplan b/Apple/testbed/tvOSTestbed.xctestplan +new file mode 100644 +index 00000000000..f996facc178 --- /dev/null +++ b/Apple/testbed/tvOSTestbed.xctestplan @@ -0,0 +1,46 @@ @@ -4298,6 +4490,9 @@ index 04a0d1fe65e..58695d56657 100644 + "version" : 1 +} \ No newline at end of file +diff --git a/Apple/testbed/tvOSTestbed/AppDelegate.h b/Apple/testbed/tvOSTestbed/AppDelegate.h +new file mode 100644 +index 00000000000..112c9ed64b8 --- /dev/null +++ b/Apple/testbed/tvOSTestbed/AppDelegate.h @@ -0,0 +1,11 @@ @@ -4312,6 +4507,9 @@ index 04a0d1fe65e..58695d56657 100644 + + +@end +diff --git a/Apple/testbed/tvOSTestbed/AppDelegate.m b/Apple/testbed/tvOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..bd91fb2d7d6 --- /dev/null +++ b/Apple/testbed/tvOSTestbed/AppDelegate.m @@ -0,0 +1,19 @@ @@ -4334,6 +4532,9 @@ index 04a0d1fe65e..58695d56657 100644 +} + +@end +diff --git a/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard b/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard +new file mode 100644 +index 00000000000..660ba53de4f --- /dev/null +++ b/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,24 @@ @@ -4361,6 +4562,9 @@ index 04a0d1fe65e..58695d56657 100644 + + + +diff --git a/Apple/testbed/tvOSTestbed/app/README b/Apple/testbed/tvOSTestbed/app/README +new file mode 100644 +index 00000000000..46c0e8e2a29 --- /dev/null +++ b/Apple/testbed/tvOSTestbed/app/README @@ -0,0 +1,7 @@ @@ -4371,6 +4575,9 @@ index 04a0d1fe65e..58695d56657 100644 + +When the test suite runs, this folder will be on the PYTHONPATH, and will be the +working directory for the test suite. +diff --git a/Apple/testbed/tvOSTestbed/app_packages/README b/Apple/testbed/tvOSTestbed/app_packages/README +new file mode 100644 +index 00000000000..02c2beccfbd --- /dev/null +++ b/Apple/testbed/tvOSTestbed/app_packages/README @@ -0,0 +1,7 @@ @@ -4381,6 +4588,9 @@ index 04a0d1fe65e..58695d56657 100644 +Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH. +diff --git a/Apple/testbed/tvOSTestbed/main.m b/Apple/testbed/tvOSTestbed/main.m +new file mode 100644 +index 00000000000..d5808fbb933 --- /dev/null +++ b/Apple/testbed/tvOSTestbed/main.m @@ -0,0 +1,16 @@ @@ -4400,6 +4610,9 @@ index 04a0d1fe65e..58695d56657 100644 + return UIApplicationMain(argc, argv, nil, appDelegateClassName); + } +} +diff --git a/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +new file mode 100644 +index 00000000000..f08f6098999 --- /dev/null +++ b/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist @@ -0,0 +1,52 @@ @@ -4455,6 +4668,9 @@ index 04a0d1fe65e..58695d56657 100644 + + + +diff --git a/Apple/tvOS/README.rst b/Apple/tvOS/README.rst +new file mode 100644 +index 00000000000..1f793252caf --- /dev/null +++ b/Apple/tvOS/README.rst @@ -0,0 +1,108 @@ @@ -4566,6 +4782,9 @@ index 04a0d1fe65e..58695d56657 100644 + +Using a framework-based Python on tvOS +====================================== +diff --git a/Apple/tvOS/Resources/Info.plist.in b/Apple/tvOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..ab3050804b8 --- /dev/null +++ b/Apple/tvOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ @@ -4603,81 +4822,129 @@ index 04a0d1fe65e..58695d56657 100644 + @TVOS_DEPLOYMENT_TARGET@ + + +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar b/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar +new file mode 100755 +index 00000000000..e302748a13c --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang +new file mode 100755 +index 00000000000..7fb6d3d901c --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ +new file mode 100755 +index 00000000000..33bfb1367c3 --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp b/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp +new file mode 100755 +index 00000000000..641c1bc8d18 --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar +new file mode 100755 +index 00000000000..87ef5015aae --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang +new file mode 100755 +index 00000000000..c8719cb0318 --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ +new file mode 100755 +index 00000000000..e3f0e720f7f --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp +new file mode 100755 +index 00000000000..f9a37b72a61 --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip +new file mode 100755 +index 00000000000..a8cce95233e --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip b/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip +new file mode 100755 +index 00000000000..ee1d2b95ff1 --- /dev/null +++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${TVOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +new file mode 100755 +index 00000000000..87ef5015aae --- /dev/null +++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang +new file mode 100755 +index 00000000000..ea0cc26cbd9 --- /dev/null +++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ +new file mode 100755 +index 00000000000..f18f3603169 --- /dev/null +++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp +new file mode 100755 +index 00000000000..b98054d1ce2 --- /dev/null +++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip +new file mode 100755 +index 00000000000..f6a884b4aef --- /dev/null +++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch x86_64 "$@" +diff --git a/Apple/tvOS/Resources/pyconfig.h b/Apple/tvOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..4acff2c6051 --- /dev/null +++ b/Apple/tvOS/Resources/pyconfig.h @@ -0,0 +1,7 @@ @@ -4688,6 +4955,9 @@ index 04a0d1fe65e..58695d56657 100644 +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif +diff --git a/Apple/watchOS/README.rst b/Apple/watchOS/README.rst +new file mode 100644 +index 00000000000..35221478452 --- /dev/null +++ b/Apple/watchOS/README.rst @@ -0,0 +1,108 @@ @@ -4799,6 +5069,9 @@ index 04a0d1fe65e..58695d56657 100644 + +Using a framework-based Python on watchOS +====================================== +diff --git a/Apple/watchOS/Resources/Info.plist.in b/Apple/watchOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..e83ddfd2a43 --- /dev/null +++ b/Apple/watchOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ @@ -4836,81 +5109,129 @@ index 04a0d1fe65e..58695d56657 100644 + @WATCHOS_DEPLOYMENT_TARGET@ + + +diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar +new file mode 100755 +index 00000000000..dda2b211bd5 --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang +new file mode 100755 +index 00000000000..fe834d3efe4 --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ +new file mode 100755 +index 00000000000..757f3a26d8f --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp +new file mode 100755 +index 00000000000..fdb57d9e010 --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip +new file mode 100755 +index 00000000000..e28e3f7597a --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip b/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip +new file mode 100755 +index 00000000000..efe5a1260ad --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk watchos${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar +new file mode 100755 +index 00000000000..029f9a32073 --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang +new file mode 100755 +index 00000000000..285036d4010 --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ +new file mode 100755 +index 00000000000..c8f60ebec51 --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp +new file mode 100755 +index 00000000000..b411fc25aa4 --- /dev/null +++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar +new file mode 100755 +index 00000000000..dda2b211bd5 --- /dev/null +++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" +diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang +new file mode 100755 +index 00000000000..4776b9b5348 --- /dev/null +++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ +new file mode 100755 +index 00000000000..e9b0c5f4b87 --- /dev/null +++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp +new file mode 100755 +index 00000000000..d3b821c5f7f --- /dev/null +++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/bash +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip +new file mode 100755 +index 00000000000..105c78281f9 --- /dev/null +++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch x86_64 "$@" +diff --git a/Apple/watchOS/Resources/pyconfig.h b/Apple/watchOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..f842b987b2e --- /dev/null +++ b/Apple/watchOS/Resources/pyconfig.h @@ -0,0 +1,11 @@ @@ -4925,6 +5246,9 @@ index 04a0d1fe65e..58695d56657 100644 +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif +diff --git a/Doc/includes/wasm-ios-notavail.rst b/Doc/includes/wasm-ios-notavail.rst +new file mode 100644 +index 00000000000..c820665f5e4 --- /dev/null +++ b/Doc/includes/wasm-ios-notavail.rst @@ -0,0 +1,8 @@ @@ -5892,6 +6216,9 @@ index e1a3111f36a..f55a12f1ab8 100644 mac.rst + ios.rst editors.rst +diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst +new file mode 100644 +index 00000000000..0f29de64e41 --- /dev/null +++ b/Doc/using/ios.rst @@ -0,0 +1,359 @@ @@ -6287,6 +6614,9 @@ index 986d693d03f..da45aabca28 100644 Other Resources =============== +diff --git a/Lib/_apple_support.py b/Lib/_apple_support.py +new file mode 100644 +index 00000000000..92febdcf587 --- /dev/null +++ b/Lib/_apple_support.py @@ -0,0 +1,66 @@ @@ -6356,6 +6686,9 @@ index 986d693d03f..da45aabca28 100644 + self.log_write(self.level, b.replace(b"\x00", b"\xc0\x80")) + + return len(b) +diff --git a/Lib/_ios_support.py b/Lib/_ios_support.py +new file mode 100644 +index 00000000000..db3fe23e45b --- /dev/null +++ b/Lib/_ios_support.py @@ -0,0 +1,71 @@ @@ -7103,7 +7436,7 @@ index 74ebb5e5b8a..d3e5fddf69b 100644 "LDFLAGS", "LDSHARED", diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 79e3d1c6422..9461443efd5 100644 +index 9270f3f8d62..35b2b2c653c 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -45,7 +45,7 @@ @@ -7256,6 +7589,9 @@ index ec6d1dee4e9..48900f50508 100644 # consider that sysctl values should not change while tests are running _sysctl_cache = {} +diff --git a/Lib/test/test_apple.py b/Lib/test/test_apple.py +new file mode 100644 +index 00000000000..f14db75e2f2 --- /dev/null +++ b/Lib/test/test_apple.py @@ -0,0 +1,155 @@ @@ -7815,13 +8151,13 @@ index 01578539eb3..90669f5ff45 100644 'built with -NDEBUG') def test_refcount_errors(self): diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py -index 4f311c2d498..b77cd4c67d6 100644 +index ce501a94516..4451d8835bc 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py -@@ -7,9 +7,9 @@ - import sys +@@ -8,9 +8,9 @@ import unittest import warnings + from test import support -from test.support import is_emscripten -from test.support import os_helper -from test.support import warnings_helper @@ -7831,7 +8167,7 @@ index 4f311c2d498..b77cd4c67d6 100644 from test.support.script_helper import assert_python_ok from test.support.os_helper import FakePath -@@ -483,12 +483,16 @@ +@@ -497,12 +497,16 @@ self.assertIsInstance(abspath(path), str) def test_nonascii_abspath(self): @@ -7855,7 +8191,7 @@ index 4f311c2d498..b77cd4c67d6 100644 elif os_helper.TESTFN_NONASCII: name = os_helper.TESTFN_NONASCII diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py -index cfd8a101dcc..b8ac2544be5 100644 +index b0b09daab00..60416bc7cc3 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -30,8 +30,9 @@ @@ -7897,7 +8233,7 @@ index cfd8a101dcc..b8ac2544be5 100644 filename = name break body = self.check_status_and_reason(response, HTTPStatus.OK) -@@ -697,6 +698,7 @@ +@@ -711,6 +712,7 @@ @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, "This test can't be run reliably as root (issue #13308).") @@ -8742,6 +9078,44 @@ index ff86291bc57..59370264ab3 100644 @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") def test_open_undecodable_uri(self): +diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py +index 0b169c37d57..fb9d9191fde 100644 +--- a/Lib/test/test_ssl.py ++++ b/Lib/test/test_ssl.py +@@ -5039,15 +5039,27 @@ + return # Expect the full test setup to always work on Linux. + if (isinstance(err, ConnectionResetError) or + (isinstance(err, OSError) and err.errno == errno.EINVAL) or +- re.search('wrong.version.number', getattr(err, "reason", ""), re.I)): ++ re.search( ++ # Matches the following error messages: ++ # '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)' ++ # '[SSL: RECORD_LAYER_FAILURE] record layer failure (_ssl.c:1109)' ++ # '[SSL: HTTP_REQUEST] http request (_ssl.c:1143)' ++ r'wrong.version.number|record.layer.failure|http.request', ++ str(getattr(err, "reason", "")), ++ re.IGNORECASE, ++ ) ++ ): + # On Windows the TCP RST leads to a ConnectionResetError + # (ECONNRESET) which Linux doesn't appear to surface to userspace. + # If wrap_socket() winds up on the "if connected:" path and doing +- # the actual wrapping... we get an SSLError from OpenSSL. Typically +- # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario +- # we're specifically trying to test. The way this test is written +- # is known to work on Linux. We'll skip it anywhere else that it +- # does not present as doing so. ++ # the actual wrapping... we get an SSLError from OpenSSL. This is ++ # typically WRONG_VERSION_NUMBER. The same happens on iOS, but ++ # RECORD_LAYER_FAILURE or HTTP_REQUEST is the error. ++ # ++ # While appropriate, these scenarios aren't what we're specifically ++ # trying to test. The way this test is written is known to work on ++ # Linux. We'll skip it anywhere else that it does not present as ++ # doing so. + try: + self.skipTest(f"Could not recreate conditions on {sys.platform}:" + f" {err=}") diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index c77fec3d39d..ca55d429aec 100644 --- a/Lib/test/test_stat.py @@ -9136,6 +9510,9 @@ index 5d72524c087..48976e4e11f 100755 def main(): import getopt +diff --git a/Mac/Resources/app-store-compliance.patch b/Mac/Resources/app-store-compliance.patch +new file mode 100644 +index 00000000000..2ccb22b9482 --- /dev/null +++ b/Mac/Resources/app-store-compliance.patch @@ -0,0 +1 @@ @@ -9476,7 +9853,7 @@ index 29f3bab60a5..7d32bb3661d 100644 #define TYPE_NULL '0' diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c -index 9248e971d9c..9bac4fed20b 100644 +index 9248e971d9c..69c9969aa50 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -35,7 +35,21 @@ @@ -9530,7 +9907,7 @@ index 9248e971d9c..9bac4fed20b 100644 status = add_main_module(interp); if (_PyStatus_EXCEPTION(status)) { return status; -@@ -2481,6 +2509,69 @@ +@@ -2481,6 +2509,71 @@ return res; } @@ -9547,7 +9924,9 @@ index 9248e971d9c..9bac4fed20b 100644 + + // Pass the user-provided text through explicit %s formatting + // to avoid % literals being interpreted as a formatting directive. -+ os_log_with_type(OS_LOG_DEFAULT, logtype, "%s", text); ++ // Using {public} ensures "dynamic" string messages are visible ++ // in the log without special configuration. ++ os_log_with_type(OS_LOG_DEFAULT, logtype, "%{public}s", text); + Py_RETURN_NONE; +} + @@ -10154,7 +10533,7 @@ index d74fb6deac9..1bb6a05dc11 100755 # Local variables: diff --git a/configure b/configure -index a1ad0ae2510..ee391d354f6 100755 +index a1ad0ae2510..dc17fe5111d 100755 --- a/configure +++ b/configure @@ -838,6 +838,8 @@ @@ -11355,7 +11734,7 @@ index a1ad0ae2510..ee391d354f6 100755 for ac_func in \ accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ - copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ -+ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ ++ copy_file_range ctermid dup explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ - fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ @@ -11369,7 +11748,7 @@ index a1ad0ae2510..ee391d354f6 100755 lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ - pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ -+ pipe2 plock poll posix_fadvise posix_fallocate \ ++ plock poll posix_fadvise posix_fallocate \ pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ @@ -11394,7 +11773,7 @@ index a1ad0ae2510..ee391d354f6 100755 +# header definition prevents usage - autoconf doesn't use the headers), or +# raise an error if used at runtime. Force these symbols off. +if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ for ac_func in getentropy getgroups system ++ for ac_func in dup3 getentropy getgroups pipe2 system +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -11672,7 +12051,7 @@ index a1ad0ae2510..ee391d354f6 100755 "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; diff --git a/configure.ac b/configure.ac -index 7b4000fa9c3..fff8109b034 100644 +index 7b4000fa9c3..4718fe46c9b 100644 --- a/configure.ac +++ b/configure.ac @@ -303,6 +303,161 @@ @@ -12749,7 +13128,7 @@ index 7b4000fa9c3..fff8109b034 100644 AC_CHECK_FUNCS([ \ accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ - copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ -+ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \ ++ copy_file_range ctermid dup explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ - fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ @@ -12763,7 +13142,7 @@ index 7b4000fa9c3..fff8109b034 100644 lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ - pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ -+ pipe2 plock poll posix_fadvise posix_fallocate \ ++ plock poll posix_fadvise posix_fallocate \ pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ @@ -12793,7 +13172,7 @@ index 7b4000fa9c3..fff8109b034 100644 +# header definition prevents usage - autoconf doesn't use the headers), or +# raise an error if used at runtime. Force these symbols off. +if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ AC_CHECK_FUNCS([ getentropy getgroups system ]) ++ AC_CHECK_FUNCS([dup3 getentropy getgroups pipe2 system]) +fi + +# tvOS/watchOS have some additional methods that can be found, but not used. @@ -13008,76 +13387,121 @@ index 7b4000fa9c3..fff8109b034 100644 [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], +diff --git a/iOS/Resources/bin/arm64-apple-ios-ar b/iOS/Resources/bin/arm64-apple-ios-ar +new file mode 100755 +index 00000000000..3cf3eb21874 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-clang b/iOS/Resources/bin/arm64-apple-ios-clang +new file mode 100755 +index 00000000000..f50d5b5142f --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-clang++ b/iOS/Resources/bin/arm64-apple-ios-clang++ +new file mode 100755 +index 00000000000..0794731d7dc --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-cpp b/iOS/Resources/bin/arm64-apple-ios-cpp +new file mode 100755 +index 00000000000..24fa1506bab --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/iOS/Resources/bin/arm64-apple-ios-simulator-ar +new file mode 100755 +index 00000000000..b836b6db902 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/iOS/Resources/bin/arm64-apple-ios-simulator-clang +new file mode 100755 +index 00000000000..4891a00876e --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ +new file mode 100755 +index 00000000000..58b2a5f6f18 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +new file mode 100755 +index 00000000000..c9df94e8b7c --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-strip b/iOS/Resources/bin/arm64-apple-ios-simulator-strip +new file mode 100755 +index 00000000000..fd59d309b73 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-strip b/iOS/Resources/bin/arm64-apple-ios-strip +new file mode 100755 +index 00000000000..75e823a3d02 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-strip @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar +new file mode 100755 +index 00000000000..b836b6db902 --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang +new file mode 100755 +index 00000000000..f4739a7b945 --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ +new file mode 100755 +index 00000000000..c348ae4c103 --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +new file mode 100755 +index 00000000000..6d7f8084c9f --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-strip b/iOS/Resources/bin/x86_64-apple-ios-simulator-strip +new file mode 100755 +index 00000000000..c5cfb289291 --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-strip @@ -0,0 +1,2 @@