From 187c64abd08b455cbc06a33b0acc17ed7e77e7ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 01:14:06 +0000 Subject: [PATCH 1/4] Bump actions/setup-python from 5.2.0 to 5.3.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.2.0 to 5.3.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5.2.0...v5.3.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- .github/workflows/publish.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fc4a013..2a78d77 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -109,7 +109,7 @@ jobs: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index e90bf31..a9f69ed 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.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: "3.X" From 6f5fa58885abbfebbdd1d2c0af4b3379ca15e205 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Dec 2024 12:19:08 +0800 Subject: [PATCH 2/4] Switch main branch to Python 3.14. --- Makefile | 2 +- README.rst | 2 +- patch/Python/Python.patch | 1440 ++++++++++++++++++++++++++++++++++--- 3 files changed, 1335 insertions(+), 109 deletions(-) diff --git a/Makefile b/Makefile index 28d053c..d77f285 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.13.0 +PYTHON_VERSION=3.14.0a2 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 d388d0b..94c0ae9 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.13**. +**This branch builds a packaged version of Python 3.14**. 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 02dbce8..36d93a0 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,8 +1,202 @@ +diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst +index 6194d7446c7..55a9dd1f25f 100644 +--- a/Doc/c-api/init_config.rst ++++ b/Doc/c-api/init_config.rst +@@ -1279,6 +1279,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`. +diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst +index 4d4eb2031ee..aa43f75ec35 100644 +--- a/Doc/using/ios.rst ++++ b/Doc/using/ios.rst +@@ -292,10 +292,12 @@ + 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*; ++ * 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*; ++ * 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: +@@ -324,6 +326,49 @@ + * 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. + ++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:`iOS/README.rst` ++for details), create a clone of the Python iOS testbed project by running: ++ ++.. code-block:: bash ++ ++ $ python iOS/testbed clone --framework --app --app app-testbed ++ ++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). ++ ++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. ++ + App Store Compliance + ==================== + +diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst +index 3eabf22e499..b509f2a0607 100644 +--- a/Doc/whatsnew/3.14.rst ++++ b/Doc/whatsnew/3.14.rst +@@ -205,6 +205,13 @@ + making it a :term:`generic type`. + (Contributed by Brian Schubert in :gh:`126012`.) + ++* iOS and macOS apps can now be configured to redirect ``stdout`` and ++ ``stderr`` content to the system log. (Contributed by Russell Keith-Magee in ++ :gh:`127592`.) ++ ++* The iOS testbed is now able to stream test output while the test is running. ++ The testbed can also be used to run the test suite of projects other than ++ CPython itself. (Contributed by Russell Keith-Magee in :gh:`127592`.) + + New modules + =========== +diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h +index f69c586a4f9..8ef19f67706 100644 +--- a/Include/cpython/initconfig.h ++++ b/Include/cpython/initconfig.h +@@ -179,6 +179,9 @@ + int use_frozen_modules; + int safe_path; + int int_max_str_digits; ++#ifdef __APPLE__ ++ int use_system_logger; ++#endif + + int cpu_count; + #ifdef Py_GIL_DISABLED +--- /dev/null ++++ b/Lib/_apple_support.py +@@ -0,0 +1,66 @@ ++import io ++import sys ++ ++ ++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) ++ ++ ++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) ++ ++ 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) diff --git a/Lib/platform.py b/Lib/platform.py -index 5958382276e..5db5eb276a2 100755 +index 239e660cd16..8e007c3c3b5 100644 --- a/Lib/platform.py +++ b/Lib/platform.py -@@ -521,6 +521,54 @@ +@@ -520,6 +520,54 @@ return IOSVersionInfo(system, release, model, is_simulator) @@ -57,7 +251,7 @@ index 5958382276e..5db5eb276a2 100755 def _java_getprop(name, default): """This private helper is deprecated in 3.13 and will be removed in 3.15""" from java.lang import System -@@ -884,14 +932,25 @@ +@@ -883,14 +931,25 @@ csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' @@ -86,7 +280,7 @@ index 5958382276e..5db5eb276a2 100755 def from_subprocess(): """ Fall back to `uname -p` -@@ -1051,9 +1110,13 @@ +@@ -1050,9 +1109,13 @@ system = 'Android' release = android_ver().release @@ -101,7 +295,7 @@ index 5958382276e..5db5eb276a2 100755 vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' -@@ -1343,6 +1406,10 @@ +@@ -1342,6 +1405,10 @@ # macOS and iOS both report as a "Darwin" kernel if sys.platform == "ios": system, release, _, _ = ios_ver() @@ -113,10 +307,10 @@ index 5958382276e..5db5eb276a2 100755 macos_release = mac_ver()[0] if macos_release: diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py -index 80aef344711..d18a7564866 100644 +index 67a071963d8..eefcac66cb5 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py -@@ -645,6 +645,14 @@ +@@ -669,6 +669,14 @@ release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") osname = sys.platform machine = sys.implementation._multiarch @@ -131,6 +325,238 @@ index 80aef344711..d18a7564866 100644 else: import _osx_support osname, release, machine = _osx_support.get_platform_osx( +--- /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 ++from unittest.mock import Mock, call ++ ++if not is_apple: ++ raise unittest.SkipTest("Apple-specific") ++ ++ ++# Test redirection of stdout and stderr to the Apple system log. ++class TestAppleSystemLogOutput(unittest.TestCase): ++ maxDiff = None ++ ++ def assert_writes(self, output): ++ self.assertEqual( ++ self.log_write.mock_calls, ++ [ ++ call(self.log_level, line) ++ for line in output ++ ] ++ ) ++ ++ self.log_write.reset_mock() ++ ++ def setUp(self): ++ self.log_write = Mock() ++ self.log_level = 42 ++ self.log = SystemLog(self.log_write, self.log_level, errors="replace") ++ ++ def test_repr(self): ++ self.assertEqual(repr(self.log), "") ++ self.assertEqual(repr(self.log.buffer), "") ++ ++ def test_log_config(self): ++ self.assertIs(self.log.writable(), True) ++ self.assertIs(self.log.readable(), False) ++ ++ 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) ++ ++ 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_capi/test_config.py b/Lib/test/test_capi/test_config.py +index 77730ad2f32..a3179efe4a8 100644 +--- a/Lib/test/test_capi/test_config.py ++++ b/Lib/test/test_capi/test_config.py +@@ -110,6 +110,10 @@ + options.extend(( + ("_pystats", bool, None), + )) ++ if support.is_apple: ++ options.extend(( ++ ("use_system_logger", bool, None), ++ )) + + for name, option_type, sys_attr in options: + with self.subTest(name=name, option_type=option_type, +diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py +index bf861ef06ee..468f821370e 100644 +--- a/Lib/test/test_embed.py ++++ b/Lib/test/test_embed.py +@@ -649,6 +649,8 @@ + CONFIG_COMPAT.update({ + 'legacy_windows_stdio': False, + }) ++ if support.is_apple: ++ CONFIG_COMPAT['use_system_logger'] = False + + CONFIG_PYTHON = dict(CONFIG_COMPAT, + _config_init=API_PYTHON, +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 8d94ba361fd..6c1c95d4dd9 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -2132,7 +2132,6 @@ + # 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 \ +@@ -2151,29 +2150,12 @@ + 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 +- +- # 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 + +- # 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'])" ++ # Clone the testbed project into the XCFOLDER ++ $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" + +- @if test -e $(XCFOLDER)/failed ; then \ +- exit 1; \ +- fi ++ # Run the testbed project ++ $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W + + # Like test, but using --slow-ci which enables all test resources and use + # longer timeout. Run an optional pybuildbot.identify script to include diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c index ec0857a4a99..2350e9dc821 100644 --- a/Misc/platform_triplet.c @@ -162,11 +588,192 @@ index ec0857a4a99..2350e9dc821 100644 // Older macOS SDKs do not define TARGET_OS_OSX # elif !defined(TARGET_OS_OSX) || TARGET_OS_OSX PLATFORM_TRIPLET=darwin +diff --git a/Python/initconfig.c b/Python/initconfig.c +index 438f8a5c1cf..7851b86db1f 100644 +--- a/Python/initconfig.c ++++ b/Python/initconfig.c +@@ -168,6 +168,9 @@ + SPEC(tracemalloc, UINT, READ_ONLY, NO_SYS), + SPEC(use_frozen_modules, BOOL, READ_ONLY, NO_SYS), + SPEC(use_hash_seed, BOOL, READ_ONLY, NO_SYS), ++#ifdef __APPLE__ ++ SPEC(use_system_logger, BOOL, PUBLIC, NO_SYS), ++#endif + SPEC(user_site_directory, BOOL, READ_ONLY, NO_SYS), // sys.flags.no_user_site + SPEC(warn_default_encoding, BOOL, READ_ONLY, NO_SYS), + +@@ -884,6 +887,9 @@ + assert(config->cpu_count != 0); + // config->use_frozen_modules is initialized later + // by _PyConfig_InitImportConfig(). ++#ifdef __APPLE__ ++ assert(config->use_system_logger >= 0); ++#endif + #ifdef Py_STATS + assert(config->_pystats >= 0); + #endif +@@ -986,6 +992,9 @@ + config->_is_python_build = 0; + config->code_debug_ranges = 1; + config->cpu_count = -1; ++#ifdef __APPLE__ ++ config->use_system_logger = 0; ++#endif + #ifdef Py_GIL_DISABLED + config->enable_gil = _PyConfig_GIL_DEFAULT; + config->tlbc_enabled = 1; +@@ -1015,6 +1024,9 @@ + #ifdef MS_WINDOWS + config->legacy_windows_stdio = 0; + #endif ++#ifdef __APPLE__ ++ config->use_system_logger = 0; ++#endif + } + + +@@ -1049,6 +1061,9 @@ + #ifdef MS_WINDOWS + config->legacy_windows_stdio = 0; + #endif ++#ifdef __APPLE__ ++ config->use_system_logger = 0; ++#endif + } + + +diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c +index 23882d08384..64985527606 100644 +--- a/Python/pylifecycle.c ++++ b/Python/pylifecycle.c +@@ -45,7 +45,9 @@ + #endif + + #if defined(__APPLE__) ++# include + # include ++# include + #endif + + #ifdef HAVE_SIGNAL_H +@@ -75,6 +77,9 @@ + #ifdef __ANDROID__ + static PyStatus init_android_streams(PyThreadState *tstate); + #endif ++#if defined(__APPLE__) ++static PyStatus init_apple_streams(PyThreadState *tstate); ++#endif + static void wait_for_thread_shutdown(PyThreadState *tstate); + static void finalize_subinterpreters(void); + static void call_ll_exitfuncs(_PyRuntimeState *runtime); +@@ -1257,6 +1262,14 @@ + return status; + } + #endif ++#if defined(__APPLE__) ++ if (config->use_system_logger) { ++ status = init_apple_streams(tstate); ++ if (_PyStatus_EXCEPTION(status)) { ++ return status; ++ } ++ } ++#endif + + #ifdef Py_DEBUG + run_presite(tstate); +@@ -2931,6 +2944,75 @@ + + #endif // __ANDROID__ + ++#if defined(__APPLE__) ++ ++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; ++ } ++ ++ // 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; ++} ++ ++ ++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__ ++ + + static void + _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp, +diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h +index c8cdb933bb1..584b050fc4b 100644 +--- a/Python/stdlib_module_names.h ++++ b/Python/stdlib_module_names.h +@@ -6,6 +6,7 @@ + "_abc", + "_aix_support", + "_android_support", ++"_apple_support", + "_ast", + "_asyncio", + "_bisect", diff --git a/configure b/configure -index 7cdd386c387..5e968a26b17 100755 +index 5b44a3d6992..83803f12853 100755 --- a/configure +++ b/configure -@@ -978,6 +978,8 @@ +@@ -979,6 +979,8 @@ CFLAGS CC HAS_XCRUN @@ -175,7 +782,7 @@ index 7cdd386c387..5e968a26b17 100755 IPHONEOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET -@@ -4054,6 +4056,12 @@ +@@ -4053,6 +4055,12 @@ *-apple-ios*) ac_sys_system=iOS ;; @@ -188,7 +795,7 @@ index 7cdd386c387..5e968a26b17 100755 *-*-vxworks*) ac_sys_system=VxWorks ;; -@@ -4108,7 +4116,7 @@ +@@ -4130,7 +4138,7 @@ # On cross-compile builds, configure will look for a host-specific compiler by # prepending the user-provided host triple to the required binary name. # @@ -197,7 +804,7 @@ index 7cdd386c387..5e968a26b17 100755 # 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 -@@ -4123,6 +4131,14 @@ +@@ -4145,6 +4153,14 @@ 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 ;; @@ -212,7 +819,7 @@ index 7cdd386c387..5e968a26b17 100755 *) esac fi -@@ -4131,6 +4147,14 @@ +@@ -4153,6 +4169,14 @@ 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 ;; @@ -227,7 +834,7 @@ index 7cdd386c387..5e968a26b17 100755 *) esac fi -@@ -4139,6 +4163,14 @@ +@@ -4161,6 +4185,14 @@ 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 ;; @@ -242,7 +849,7 @@ index 7cdd386c387..5e968a26b17 100755 *) esac fi -@@ -4147,6 +4179,14 @@ +@@ -4169,6 +4201,14 @@ 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++ ;; @@ -257,7 +864,7 @@ index 7cdd386c387..5e968a26b17 100755 *) esac fi -@@ -4267,8 +4307,10 @@ +@@ -4289,8 +4329,10 @@ case $enableval in yes) case $ac_sys_system in @@ -270,7 +877,7 @@ index 7cdd386c387..5e968a26b17 100755 *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 esac esac -@@ -4277,6 +4319,8 @@ +@@ -4299,6 +4341,8 @@ no) case $ac_sys_system in iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; @@ -279,7 +886,7 @@ index 7cdd386c387..5e968a26b17 100755 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -4383,6 +4427,36 @@ +@@ -4405,6 +4449,36 @@ ac_config_files="$ac_config_files iOS/Resources/Info.plist" @@ -316,7 +923,7 @@ index 7cdd386c387..5e968a26b17 100755 ;; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 -@@ -4394,6 +4468,8 @@ +@@ -4416,6 +4490,8 @@ case $ac_sys_system in iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; @@ -325,7 +932,7 @@ index 7cdd386c387..5e968a26b17 100755 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -4447,8 +4523,8 @@ +@@ -4469,8 +4545,8 @@ case "$withval" in yes) case $ac_sys_system in @@ -336,7 +943,7 @@ index 7cdd386c387..5e968a26b17 100755 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 ;; -@@ -4466,8 +4542,8 @@ +@@ -4488,8 +4564,8 @@ else $as_nop case $ac_sys_system in @@ -347,7 +954,7 @@ index 7cdd386c387..5e968a26b17 100755 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5 printf "%s\n" "applying default app store compliance patch" >&6; } -@@ -4521,6 +4597,50 @@ +@@ -4543,6 +4619,50 @@ ;; esac ;; @@ -398,7 +1005,7 @@ index 7cdd386c387..5e968a26b17 100755 *-*-vxworks*) _host_ident=$host_cpu ;; -@@ -4599,9 +4719,13 @@ +@@ -4621,9 +4741,13 @@ define_xopen_source=no;; Darwin/[12][0-9].*) define_xopen_source=no;; @@ -413,7 +1020,7 @@ index 7cdd386c387..5e968a26b17 100755 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -4664,7 +4788,10 @@ +@@ -4686,7 +4810,10 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' @@ -425,7 +1032,7 @@ index 7cdd386c387..5e968a26b17 100755 # checks for alternative programs -@@ -4705,6 +4832,16 @@ +@@ -4727,6 +4854,16 @@ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ;; #( @@ -442,7 +1049,7 @@ index 7cdd386c387..5e968a26b17 100755 *) : ;; esac -@@ -7006,6 +7143,10 @@ +@@ -7031,6 +7168,10 @@ MULTIARCH="" ;; #( iOS) : MULTIARCH="" ;; #( @@ -453,7 +1060,7 @@ index 7cdd386c387..5e968a26b17 100755 FreeBSD*) : MULTIARCH="" ;; #( *) : -@@ -7026,7 +7167,7 @@ +@@ -7051,7 +7192,7 @@ printf "%s\n" "$MULTIARCH" >&6; } case $ac_sys_system in #( @@ -462,7 +1069,7 @@ index 7cdd386c387..5e968a26b17 100755 SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( *) : SOABI_PLATFORM=$PLATFORM_TRIPLET -@@ -7077,6 +7218,14 @@ +@@ -7102,6 +7243,14 @@ PY_SUPPORT_TIER=3 ;; #( aarch64-apple-ios*/clang) : PY_SUPPORT_TIER=3 ;; #( @@ -477,7 +1084,7 @@ index 7cdd386c387..5e968a26b17 100755 aarch64-*-linux-android/clang) : PY_SUPPORT_TIER=3 ;; #( x86_64-*-linux-android/clang) : -@@ -7547,7 +7696,7 @@ +@@ -7531,7 +7680,7 @@ case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; @@ -486,7 +1093,7 @@ index 7cdd386c387..5e968a26b17 100755 LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; -@@ -7613,7 +7762,7 @@ +@@ -7597,7 +7746,7 @@ BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; @@ -495,7 +1102,7 @@ index 7cdd386c387..5e968a26b17 100755 LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) -@@ -12952,7 +13101,7 @@ +@@ -13150,7 +13299,7 @@ BLDSHARED="$LDSHARED" fi ;; @@ -504,7 +1111,7 @@ index 7cdd386c387..5e968a26b17 100755 LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" -@@ -13085,7 +13234,7 @@ +@@ -13283,7 +13432,7 @@ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys @@ -513,7 +1120,7 @@ index 7cdd386c387..5e968a26b17 100755 LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -13109,7 +13258,7 @@ +@@ -13307,7 +13456,7 @@ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' fi LINKFORSHARED="$LINKFORSHARED" @@ -522,7 +1129,7 @@ index 7cdd386c387..5e968a26b17 100755 LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi ;; -@@ -14508,7 +14657,7 @@ +@@ -14759,7 +14908,7 @@ ctypes_malloc_closure=yes ;; #( @@ -531,7 +1138,7 @@ index 7cdd386c387..5e968a26b17 100755 ctypes_malloc_closure=yes ;; #( -@@ -17959,12 +18108,6 @@ +@@ -18262,12 +18411,6 @@ then : printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h @@ -544,7 +1151,7 @@ index 7cdd386c387..5e968a26b17 100755 fi ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero" if test "x$ac_cv_func_explicit_bzero" = xyes -@@ -18025,18 +18168,6 @@ +@@ -18328,18 +18471,6 @@ then : printf "%s\n" "#define HAVE_FEXECVE 1" >>confdefs.h @@ -563,7 +1170,7 @@ index 7cdd386c387..5e968a26b17 100755 fi ac_fn_c_check_func "$LINENO" "fpathconf" "ac_cv_func_fpathconf" if test "x$ac_cv_func_fpathconf" = xyes -@@ -18463,24 +18594,6 @@ +@@ -18766,24 +18897,6 @@ then : printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h @@ -588,7 +1195,7 @@ index 7cdd386c387..5e968a26b17 100755 fi ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread" if test "x$ac_cv_func_pread" = xyes -@@ -18769,12 +18882,6 @@ +@@ -19072,12 +19185,6 @@ then : printf "%s\n" "#define HAVE_SIGACTION 1" >>confdefs.h @@ -601,7 +1208,7 @@ index 7cdd386c387..5e968a26b17 100755 fi ac_fn_c_check_func "$LINENO" "sigfillset" "ac_cv_func_sigfillset" if test "x$ac_cv_func_sigfillset" = xyes -@@ -19043,11 +19150,11 @@ +@@ -19346,11 +19453,11 @@ fi @@ -615,7 +1222,7 @@ index 7cdd386c387..5e968a26b17 100755 ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" if test "x$ac_cv_func_getentropy" = xyes then : -@@ -19069,6 +19176,53 @@ +@@ -19372,6 +19479,53 @@ fi @@ -669,7 +1276,7 @@ index 7cdd386c387..5e968a26b17 100755 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC options needed to detect all undeclared functions" >&5 printf %s "checking for $CC options needed to detect all undeclared functions... " >&6; } if test ${ac_cv_c_undeclared_builtin_options+y} -@@ -21865,7 +22019,8 @@ +@@ -22247,7 +22401,8 @@ # check for openpty, login_tty, and forkpty @@ -679,7 +1286,7 @@ index 7cdd386c387..5e968a26b17 100755 for ac_func in openpty do : -@@ -21961,7 +22116,7 @@ +@@ -22343,7 +22498,7 @@ fi done @@ -688,7 +1295,7 @@ index 7cdd386c387..5e968a26b17 100755 printf %s "checking for library containing login_tty... " >&6; } if test ${ac_cv_search_login_tty+y} then : -@@ -22118,6 +22273,7 @@ +@@ -22500,6 +22655,7 @@ fi done @@ -696,7 +1303,7 @@ index 7cdd386c387..5e968a26b17 100755 # check for long file support functions ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64" -@@ -22364,10 +22520,10 @@ +@@ -22746,10 +22902,10 @@ done @@ -709,7 +1316,7 @@ index 7cdd386c387..5e968a26b17 100755 then for ac_func in clock_settime -@@ -24597,8 +24753,8 @@ +@@ -24977,8 +25133,8 @@ LIBPYTHON="\$(BLDLIBRARY)" fi @@ -720,7 +1327,7 @@ index 7cdd386c387..5e968a26b17 100755 MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi -@@ -27246,7 +27402,7 @@ +@@ -27730,7 +27886,7 @@ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 printf "%s\n" "$as_me: checking for device files" >&6;} @@ -729,7 +1336,7 @@ index 7cdd386c387..5e968a26b17 100755 ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else -@@ -27679,7 +27835,7 @@ +@@ -28162,7 +28318,7 @@ with_ensurepip=no ;; #( WASI) : with_ensurepip=no ;; #( @@ -738,7 +1345,7 @@ index 7cdd386c387..5e968a26b17 100755 with_ensurepip=no ;; #( *) : with_ensurepip=upgrade -@@ -28704,7 +28860,7 @@ +@@ -29091,7 +29247,7 @@ ;; #( Darwin) : ;; #( @@ -747,7 +1354,7 @@ index 7cdd386c387..5e968a26b17 100755 -@@ -32469,6 +32625,8 @@ +@@ -32989,6 +33145,8 @@ "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" ;; @@ -757,7 +1364,7 @@ index 7cdd386c387..5e968a26b17 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 24e28a1e2de..3e008102154 100644 +index 7904f8990c4..f2367cd47d3 100644 --- a/configure.ac +++ b/configure.ac @@ -330,6 +330,12 @@ @@ -773,7 +1380,7 @@ index 24e28a1e2de..3e008102154 100644 *-*-vxworks*) ac_sys_system=VxWorks ;; -@@ -382,7 +388,7 @@ +@@ -401,7 +407,7 @@ # On cross-compile builds, configure will look for a host-specific compiler by # prepending the user-provided host triple to the required binary name. # @@ -782,7 +1389,7 @@ index 24e28a1e2de..3e008102154 100644 # 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 -@@ -397,6 +403,14 @@ +@@ -416,6 +422,14 @@ 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 ;; @@ -797,7 +1404,7 @@ index 24e28a1e2de..3e008102154 100644 *) esac fi -@@ -405,6 +419,14 @@ +@@ -424,6 +438,14 @@ 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 ;; @@ -812,7 +1419,7 @@ index 24e28a1e2de..3e008102154 100644 *) esac fi -@@ -413,6 +435,14 @@ +@@ -432,6 +454,14 @@ 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 ;; @@ -827,7 +1434,7 @@ index 24e28a1e2de..3e008102154 100644 *) esac fi -@@ -421,6 +451,14 @@ +@@ -440,6 +470,14 @@ 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++ ;; @@ -842,7 +1449,7 @@ index 24e28a1e2de..3e008102154 100644 *) esac fi -@@ -535,8 +573,10 @@ +@@ -554,8 +592,10 @@ case $enableval in yes) case $ac_sys_system in @@ -855,7 +1462,7 @@ index 24e28a1e2de..3e008102154 100644 *) AC_MSG_ERROR([Unknown platform for framework build]) esac esac -@@ -545,6 +585,8 @@ +@@ -564,6 +604,8 @@ no) case $ac_sys_system in iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; @@ -864,7 +1471,7 @@ index 24e28a1e2de..3e008102154 100644 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -647,6 +689,34 @@ +@@ -666,6 +708,34 @@ AC_CONFIG_FILES([iOS/Resources/Info.plist]) ;; @@ -899,7 +1506,7 @@ index 24e28a1e2de..3e008102154 100644 *) AC_MSG_ERROR([Unknown platform for framework build]) ;; -@@ -655,6 +725,8 @@ +@@ -674,6 +744,8 @@ ],[ case $ac_sys_system in iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; @@ -908,7 +1515,7 @@ index 24e28a1e2de..3e008102154 100644 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -707,8 +779,8 @@ +@@ -726,8 +798,8 @@ case "$withval" in yes) case $ac_sys_system in @@ -919,7 +1526,7 @@ index 24e28a1e2de..3e008102154 100644 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ;; *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;; -@@ -722,8 +794,8 @@ +@@ -741,8 +813,8 @@ esac ],[ case $ac_sys_system in @@ -930,7 +1537,7 @@ index 24e28a1e2de..3e008102154 100644 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" AC_MSG_RESULT([applying default app store compliance patch]) ;; -@@ -771,6 +843,46 @@ +@@ -790,6 +862,46 @@ ;; esac ;; @@ -977,7 +1584,7 @@ index 24e28a1e2de..3e008102154 100644 *-*-vxworks*) _host_ident=$host_cpu ;; -@@ -848,9 +960,13 @@ +@@ -867,9 +979,13 @@ define_xopen_source=no;; Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) define_xopen_source=no;; @@ -992,7 +1599,7 @@ index 24e28a1e2de..3e008102154 100644 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -909,8 +1025,11 @@ +@@ -928,8 +1044,11 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' @@ -1005,7 +1612,7 @@ index 24e28a1e2de..3e008102154 100644 # checks for alternative programs -@@ -944,11 +1063,17 @@ +@@ -963,11 +1082,17 @@ ], ) @@ -1024,7 +1631,7 @@ index 24e28a1e2de..3e008102154 100644 ], ) -@@ -1136,6 +1261,8 @@ +@@ -1156,6 +1281,8 @@ AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], [iOS], [MULTIARCH=""], @@ -1033,7 +1640,7 @@ index 24e28a1e2de..3e008102154 100644 [FreeBSD*], [MULTIARCH=""], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] ) -@@ -1157,7 +1284,7 @@ +@@ -1177,7 +1304,7 @@ 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], @@ -1042,7 +1649,7 @@ index 24e28a1e2de..3e008102154 100644 [SOABI_PLATFORM=$PLATFORM_TRIPLET] ) -@@ -1191,6 +1318,10 @@ +@@ -1211,6 +1338,10 @@ [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 @@ -1053,7 +1660,7 @@ index 24e28a1e2de..3e008102154 100644 [aarch64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64 [x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64 -@@ -1522,7 +1653,7 @@ +@@ -1520,7 +1651,7 @@ case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; @@ -1062,7 +1669,7 @@ index 24e28a1e2de..3e008102154 100644 LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) AC_MSG_ERROR([Unknown platform for framework build]);; -@@ -1587,7 +1718,7 @@ +@@ -1585,7 +1716,7 @@ BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; @@ -1071,7 +1678,7 @@ index 24e28a1e2de..3e008102154 100644 LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) -@@ -3462,7 +3593,7 @@ +@@ -3407,7 +3538,7 @@ BLDSHARED="$LDSHARED" fi ;; @@ -1080,7 +1687,7 @@ index 24e28a1e2de..3e008102154 100644 LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" -@@ -3586,7 +3717,7 @@ +@@ -3531,7 +3662,7 @@ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys @@ -1089,7 +1696,7 @@ index 24e28a1e2de..3e008102154 100644 LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -3610,7 +3741,7 @@ +@@ -3555,7 +3686,7 @@ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' fi LINKFORSHARED="$LINKFORSHARED" @@ -1098,7 +1705,7 @@ index 24e28a1e2de..3e008102154 100644 LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi ;; -@@ -3994,7 +4125,7 @@ +@@ -3975,7 +4106,7 @@ dnl when do we need USING_APPLE_OS_LIBFFI? ctypes_malloc_closure=yes ], @@ -1107,7 +1714,7 @@ index 24e28a1e2de..3e008102154 100644 ctypes_malloc_closure=yes ], [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] -@@ -5088,9 +5219,9 @@ +@@ -5093,9 +5224,9 @@ # checks for library functions AC_CHECK_FUNCS([ \ accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \ @@ -1119,7 +1726,7 @@ index 24e28a1e2de..3e008102154 100644 gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ getpeername getpgid getpid getppid getpriority _getpty \ -@@ -5098,15 +5229,14 @@ +@@ -5103,15 +5234,14 @@ getspnam getuid getwd grantpt 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 \ @@ -1137,7 +1744,7 @@ index 24e28a1e2de..3e008102154 100644 sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ -@@ -5121,12 +5251,20 @@ +@@ -5126,12 +5256,20 @@ AC_CHECK_FUNCS([lchmod]) fi @@ -1161,7 +1768,7 @@ index 24e28a1e2de..3e008102154 100644 fi AC_CHECK_DECL([dirfd], -@@ -5377,20 +5515,22 @@ +@@ -5385,20 +5523,22 @@ ]) # check for openpty, login_tty, and forkpty @@ -1198,7 +1805,7 @@ index 24e28a1e2de..3e008102154 100644 # check for long file support functions AC_CHECK_FUNCS([fseek64 fseeko fstatvfs ftell64 ftello statvfs]) -@@ -5429,10 +5569,10 @@ +@@ -5437,10 +5577,10 @@ ]) ]) @@ -1211,7 +1818,7 @@ index 24e28a1e2de..3e008102154 100644 then AC_CHECK_FUNCS([clock_settime], [], [ AC_CHECK_LIB([rt], [clock_settime], [ -@@ -6183,8 +6323,8 @@ +@@ -6191,8 +6331,8 @@ LIBPYTHON="\$(BLDLIBRARY)" fi @@ -1222,7 +1829,7 @@ index 24e28a1e2de..3e008102154 100644 MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi -@@ -6792,7 +6932,7 @@ +@@ -6856,7 +6996,7 @@ dnl NOTE: Inform user how to proceed with files when cross compiling. dnl Some cross-compile builds are predictable; they won't ever dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. @@ -1231,7 +1838,7 @@ index 24e28a1e2de..3e008102154 100644 ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else -@@ -7049,7 +7189,7 @@ +@@ -7112,7 +7252,7 @@ AS_CASE([$ac_sys_system], [Emscripten], [with_ensurepip=no], [WASI], [with_ensurepip=no], @@ -1240,7 +1847,7 @@ index 24e28a1e2de..3e008102154 100644 [with_ensurepip=upgrade] ) ]) -@@ -7458,7 +7598,7 @@ +@@ -7506,7 +7646,7 @@ [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])], dnl The _scproxy module is available on macOS [Darwin], [], @@ -1249,6 +1856,85 @@ index 24e28a1e2de..3e008102154 100644 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 +diff --git a/iOS/README.rst b/iOS/README.rst +index e33455eef8f..13b88514493 100644 +--- a/iOS/README.rst ++++ b/iOS/README.rst +@@ -285,52 +285,42 @@ + * 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. ++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 + ----------------------- + +-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 ++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. + +-To test on an x86-64 simulator, run:: ++You can generate your own standalone testbed instance by running:: + +- $ 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 ++ $ python iOS/testbed clone --framework iOS/Frameworks/arm64-iphonesimulator my-testbed + +-To test on a physical device:: ++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. + +- $ rm -rf iOS/testbed/Python.xcframework/ios-arm64/* +- $ cp -r iOS/Frameworks/arm64-iphoneos/* iOS/testbed/Python.xcframework/ios-arm64 ++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:: + +-Alternatively, you can configure your build to install directly into the +-testbed project. For a simulator, use:: ++ $ python my-testbed run -- test -W test_os + +- --enable-framework=$(pwd)/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator ++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. + +-For a physical device, use:: ++You can also open the testbed project in Xcode by running:: + +- --enable-framework=$(pwd)/iOS/testbed/Python.xcframework/ios-arm64 ++ $ open my-testbed/iOSTestbed.xcodeproj + ++This will allow you to use the full Xcode suite of tools for debugging. + + Testing on an iOS device + ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/iOS/Resources/Info.plist.in b/iOS/Resources/Info.plist.in index c3e261ecd9e..26ef7a95de4 100644 --- a/iOS/Resources/Info.plist.in @@ -1269,6 +1955,546 @@ index c3e261ecd9e..26ef7a95de4 100644 CFBundleSupportedPlatforms iPhoneOS +diff --git a/iOS/Resources/bin/arm64-apple-ios-ar b/iOS/Resources/bin/arm64-apple-ios-ar +index 8122332b9c1..3cf3eb21874 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-ar ++++ b/iOS/Resources/bin/arm64-apple-ios-ar +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphoneos${IOS_SDK_VERSION} ar $@ ++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 +index 4d525751eba..c39519cd1f8 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-clang ++++ b/iOS/Resources/bin/arm64-apple-ios-clang +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios $@ ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-clang++ b/iOS/Resources/bin/arm64-apple-ios-clang++ +index f24bec11268..d9b12925f38 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-clang++ ++++ b/iOS/Resources/bin/arm64-apple-ios-clang++ +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios $@ ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-cpp b/iOS/Resources/bin/arm64-apple-ios-cpp +index 891bb25bb43..24da23d3448 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-cpp ++++ b/iOS/Resources/bin/arm64-apple-ios-cpp +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E $@ ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/iOS/Resources/bin/arm64-apple-ios-simulator-ar +index 74ed3bc6df1..b836b6db902 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-simulator-ar ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ ++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 +index 32574cad284..92e8d853d6e 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-simulator-clang ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator $@ ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ +index ef37d05b512..076469cc70c 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios-simulator $@ ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios-simulator "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +index 6aaf6fbe188..c57f28cee5b 100755 +--- a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp ++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator -E $@ ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator -E "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar +index 74ed3bc6df1..b836b6db902 100755 +--- a/iOS/Resources/bin/x86_64-apple-ios-simulator-ar ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ ++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 +index bcbe91f6061..17cbe0c8a1e 100755 +--- a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator $@ ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ +index 86f03ea32bc..565d47b24c2 100755 +--- a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios-simulator $@ ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios-simulator "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +index e6a42d9b85d..63fc8e8de2d 100755 +--- a/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp ++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +@@ -1,2 +1,2 @@ + #!/bin/sh +-xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator -E $@ ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator -E "$@" +--- /dev/null ++++ b/iOS/testbed/__main__.py +@@ -0,0 +1,395 @@ ++import argparse ++import asyncio ++import json ++import plistlib ++import shutil ++import subprocess ++import sys ++from contextlib import asynccontextmanager ++from datetime import datetime ++from pathlib import Path ++ ++ ++DECODE_ARGS = ("UTF-8", "backslashreplace") ++ ++ ++# Work around a bug involving sys.exit and TaskGroups ++# (https://github.com/python/cpython/issues/101515). ++def exit(*args): ++ raise MySystemExit(*args) ++ ++ ++class MySystemExit(Exception): ++ pass ++ ++ ++# 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" ++ ) ++ 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(): ++ # 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" ++ ] ++ ++ ++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 ++ 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): ++ # 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) ++ ++ ++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) ++ ++ 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) ++ ++ print("Cloning testbed project:") ++ print(f" Cloning {source}...", end="", flush=True) ++ shutil.copytree(source, target, symlinks=True) ++ print(" done") ++ ++ 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.symlink_to( ++ framework.relative_to(xc_framework_path.parent, walk_up=True) ++ ) ++ 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.symlink_to( ++ framework.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.", ++ ) ++ ++ 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", ++ ) ++ ++ 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, ++ target=Path(context.location), ++ 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) ++ ++ 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) ++ ++ ++if __name__ == "__main__": ++ main() +diff --git a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj +index 6819ac0eeed..c7d63909ee2 100644 +--- a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj ++++ b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj +@@ -263,6 +263,7 @@ + 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; +@@ -282,6 +283,7 @@ + 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 */ + +diff --git a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m +index db00d43da85..6db38253396 100644 +--- a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m ++++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m +@@ -24,8 +24,11 @@ + + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + +- // Disable all color, as the Xcode log can't display color ++ // 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("PY_COLORS", "0", true); + + // Arguments to pass into the test suite runner. + // argv[0] must identify the process; any subsequent arg +@@ -50,6 +53,8 @@ + // 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 --- /dev/null +++ b/tvOS/README.rst @@ -0,0 +1,108 @@ @@ -1421,62 +2647,62 @@ index c3e261ecd9e..26ef7a95de4 100644 +++ b/tvOS/Resources/bin/arm64-apple-tvos-ar @@ -0,0 +1,2 @@ +#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} ar $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 $@ ++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 @@ @@ -1668,62 +2894,62 @@ index c3e261ecd9e..26ef7a95de4 100644 +++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar $@ ++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 $@ ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos-simulator "$@" --- /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 $@ ++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 $@ ++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 $@ ++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 $@ ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos "$@" --- /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 $@ ++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 $@ ++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 $@ ++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 $@ ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator "$@" --- /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 $@ ++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 $@ ++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 @@ From 7c9ec06b7037634261b79f1e4d49488ad56e8ec6 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Dec 2024 13:02:13 +0800 Subject: [PATCH 3/4] Fixes #239 - Purge .orig files from release packages. --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index d77f285..1abcbcf 100644 --- a/Makefile +++ b/Makefile @@ -316,6 +316,9 @@ $$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe make install \ 2>&1 | tee -a ../python-$(PYTHON_VERSION).install.log + # 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 @@ -549,6 +552,9 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ # 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 + # Remove any .orig files produced by the patching process + find $$(PYTHON_INSTALL_VERSION-macosx) -name "*.orig" -exec rm {} \; + # Rewrite the framework to make it standalone patch/make-relocatable.sh $$(PYTHON_INSTALL_VERSION-macosx) 2>&1 > /dev/null From 5eb3316e440418b4a66f70119f3a1635f2c4ca0f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Dec 2024 13:02:39 +0800 Subject: [PATCH 4/4] Include iOS testbed in iOS release artefacts. --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 1abcbcf..04b1958 100644 --- a/Makefile +++ b/Makefile @@ -608,6 +608,11 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ @echo ">>> Create helper links in XCframework for $(os)" $$(foreach sdk,$$(SDKS-$(os)),ln -si $$(SDK_SLICE-$$(sdk)) $$(PYTHON_XCFRAMEWORK-$(os))/$$(sdk); ) +ifeq ($(os),iOS) + @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 +endif + @echo ">>> Create VERSIONS file for $(os)" echo "Python version: $(PYTHON_VERSION) " > support/$(PYTHON_VER)/$(os)/VERSIONS echo "Build: $(BUILD_NUMBER)" >> support/$(PYTHON_VER)/$(os)/VERSIONS