diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6befbc46..5a4c155e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -105,7 +105,7 @@ jobs: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.4.0 + uses: actions/setup-python@v5.6.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. @@ -120,7 +120,7 @@ jobs: make ${{ matrix.target }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} - name: Upload build artefacts - uses: actions/upload-artifact@v4.6.1 + uses: actions/upload-artifact@v4.6.2 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 5d2ce753..587abe83 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.4.0 + uses: actions/setup-python@v5.6.0 with: python-version: "3.X" @@ -48,3 +48,6 @@ jobs: # 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 + # visionOS build + curl -o visionOS-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 }}-visionOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp visionOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/visionOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-visionOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 61da403c..fd78eeec 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,7 +40,7 @@ jobs: needs: [ config, ci ] steps: - name: Get build artifacts - uses: actions/download-artifact@v4.1.9 + uses: actions/download-artifact@v4.3.0 with: pattern: Python-* path: dist diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6aad904..dfed555e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,44 @@ BeeWare <3's contributions! -Please be aware, BeeWare operates under a Code of Conduct. +Please be aware that BeeWare operates under a [Code of +Conduct](https://beeware.org/community/behavior/code-of-conduct/). -See [CONTRIBUTING to BeeWare](https://beeware.org/contributing) for details. +See [CONTRIBUTING to BeeWare](https://beeware.org/contributing) for general +project contribution guidelines. +Unless a fix is version specific, PRs should genereally be made against the +`main` branch of this repo, targeting the current development version of Python. +Project maintainers will manage the process of backporting changes to older +Python versions. + +## Changes to `Python.patch` + +Additional handling is required if you need to make modifications to the patch +applied to Python sources (`patch/Python/Python.patch`). + +Any iOS or macOS-specific changes should be submitted to the [upstream CPython +repository](https://github.com/python/cpython). macOS and iOS are both +officially supported Python platforms, and the code distributed by this project +for those platforms is unmodified from the official repository. + +Changes to to support other platforms can be included in a PR for this repo, but +they must also be submitted as a pull request against the `MAJOR.MINOR-patched` +branch on [the `freakboy3742` fork of the CPython +repo](https://github.com/freakboy3742/cpython). This is required to ensure that +any contributed changes can be easily reproduced in future patches as more +changes are made. + +Note that the `MAJOR.MINOR-patched` branch of that fork is maintained in the format +of a *patch tree*, which is a branch that consists of an entirely linear sequence of +commits applied on top of another branch (in the case of the fork, `MAJOR.MINOR`), +each of which adds a significant new feature. Therefore, a bug fix for an existing commit +in the patch tree *will* be merged when appropriate, but its changes will get combined +with that existing commit that adds the feature. A feature addition PR will be squashed +into a single, new commit, and then put on top of the patch tree. + +This also means that if another contributor gets a pull request merged into +`MAJOR.MINOR-patched`, you must *rebase* your changes on top of the updated +`MAJOR.MINOR-patched` branch, as opposed to *merging* `MAJOR.MINOR-patched` into your +branch, since the "history" of a patch tree is likely to change in a way that is +incompatible with merge commits. diff --git a/Makefile b/Makefile index ebebe027..63e53c85 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.2 +PYTHON_VERSION=3.13.3 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+") @@ -26,31 +26,35 @@ 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.16-2 +XZ_VERSION=5.6.4-2 # Supported OS -OS_LIST=macOS iOS tvOS watchOS +OS_LIST=macOS iOS tvOS watchOS visionOS CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar # macOS targets TARGETS-macOS=macosx.x86_64 macosx.arm64 +TRIPLE_OS-macOS=macos VERSION_MIN-macOS=11.0 # iOS targets TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64 +TRIPLE_OS-iOS=ios VERSION_MIN-iOS=13.0 # tvOS targets TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64 +TRIPLE_OS-tvOS=tvos VERSION_MIN-tvOS=12.0 # watchOS targets TARGETS-watchOS=watchsimulator.x86_64 watchsimulator.arm64 watchos.arm64_32 +TRIPLE_OS-watchOS=watchos VERSION_MIN-watchOS=4.0 # The architecture of the machine doing the build @@ -128,10 +132,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 @@ -398,15 +402,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 @@ -450,6 +452,7 @@ $$(PYTHON_LIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_LIB-$$ mkdir -p $$(dir $$(PYTHON_LIB-$(sdk))) lipo -create -output $$@ $$^ \ 2>&1 | tee -a install/$(os)/$(sdk)/python-$(PYTHON_VERSION).lipo.log + dsymutil $$@ -o $$(PYTHON_INSTALL-$(sdk))/Python.dSYM $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist: $$(PYTHON_LIB-$(sdk)) @echo ">>> Install Info.plist for the $(sdk) SDK" @@ -481,11 +484,15 @@ $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h: $$(PYTHON_LIB-$(sdk)) mkdir -p $$(PYTHON_INSTALL-$(sdk))/include ln -si ../Python.framework/Headers $$(PYTHON_INSTALL-$(sdk))/include/python$(PYTHON_VER) +ifeq ($(os), visionOS) + echo "Skipping arch-specific header copying for visionOS" +else # Add the individual headers from each target in an arch-specific name $$(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 +endif $$(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))) @@ -510,6 +517,14 @@ $$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK- # 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))); ) + # Create dSYM files for each module + $$(foreach module,$$(wildcard $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/lib-dynload/*),dsymutil $$(PYTHON_STDLIB-$(sdk))/lib-dynload/$$(notdir $$(module)); ) + + # 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 + cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$(sdk))/lib-dynload/_hashlib.xcprivacy + cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$(sdk))/lib-dynload/_ssl.xcprivacy endif $(sdk): $$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT @@ -647,10 +662,11 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ $$(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))/platform-config $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) + $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/Python.dSYM $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) -ifeq ($(os),iOS) +ifeq ($(filter $(os),iOS visionOS),$(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))))))/$(os)/testbed clone --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed endif @echo ">>> Create VERSIONS file for $(os)" diff --git a/README.rst b/README.rst index c35fde7d..ecf8c94c 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,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. +into a macOS, iOS, tvOS, watchOS, or visionOS project. **This branch builds a packaged version of Python 3.13**. Other Python versions are available by cloning other branches of the main @@ -16,21 +16,24 @@ repository: It works by downloading, patching, and building a fat binary of Python and selected pre-requisites, and packaging them as frameworks that can be -incorporated into an XCode project. The binary modules in the Python standard +incorporated into an Xcode project. The binary modules in the Python standard library are distributed as binaries that can be dynamically loaded at runtime. The macOS package is a re-bundling of the official macOS binary, modified so that it is relocatable, with the IDLE, Tkinter and turtle packages removed, and the App Store compliance patch applied. -The iOS, tvOS and watchOS packages compiled by this project use the official -`PEP 730 `__ code that is part of Python 3.13 -to provide iOS support; the relevant patches have been backported to 3.9-3.12. -Additional patches have been applied to add tvOS and watchOS support. +The iOS, tvOS, watchOS, and visionOS packages compiled by this project use the +official `PEP 730 `__ code that is part of +Python 3.13 to provide iOS support; the relevant patches have been backported +to 3.9-3.12. Additional patches have been applied to add tvOS, watchOS, and +visionOS support. The binaries support x86_64 and arm64 for macOS; arm64 for iOS and appleTV -devices; and arm64_32 for watchOS devices. It also supports device simulators on -both x86_64 and M1 hardware. This should enable the code to run on: +devices; arm64_32 for watchOS devices; and arm64 for visionOS devices. It also +supports device simulators on both x86_64 and M1 hardware, except for visionOS, +for which x86_64 simulators are officially unsupported. This should enable the +code to run on: * macOS 11 (Big Sur) or later, on: * MacBook (including MacBooks using Apple Silicon) @@ -49,6 +52,8 @@ both x86_64 and M1 hardware. This should enable the code to run on: * Apple TV (4th gen or later) * watchOS 4.0 or later, on: * Apple Watch (4th gen or later) +* visionOS 2.0 or later, on: + * Apple Vision Pro Quickstart ---------- @@ -69,6 +74,7 @@ repository, and then in the root directory, and run: * ``make iOS`` to build everything for iOS. * ``make tvOS`` to build everything for tvOS. * ``make watchOS`` to build everything for watchOS. +* ``make visionOS`` to build everything for visionOS. This should: @@ -76,16 +82,16 @@ This should: 2. Patch them as required for compatibility with the selected OS 3. Build the packages as Xcode-compatible XCFrameworks. -The resulting support packages will be packaged as a ``.tar.gz`` file +The resulting support packages will be packaged as ``.tar.gz`` files in the ``dist`` folder. Each support package contains: * ``VERSIONS``, a text file describing the specific versions of code used to build the support package; -* ``Python.xcframework``, a multi-architecture build of the Python runtime library +* ``Python.xcframework``, a multi-architecture build of the Python runtime library. -On iOS/tvOS/watchOS, the ``Python.xcframework`` contains a +On iOS/tvOS/watchOS/visionOS, the ``Python.xcframework`` contains a slice for each supported ABI (device and simulator). The folder containing the slice can also be used as a ``PYTHONHOME``, as it contains a ``bin``, ``include`` and ``lib`` directory. @@ -131,8 +137,8 @@ Building binary wheels This project packages the Python standard library, but does not address building binary wheels. Binary wheels for macOS can be obtained from PyPI. `Mobile Forge `__ is a project that provides the -tooling to build build binary wheels for iOS (and potentially for tvOS and -watchOS, although that hasn't been tested). +tooling to build build binary wheels for iOS (and potentially for tvOS, watchOS, +and visionOS, although that hasn't been tested). Historical support ------------------ diff --git a/USAGE.md b/USAGE.md index 096f71b0..a42f0aa9 100644 --- a/USAGE.md +++ b/USAGE.md @@ -2,10 +2,10 @@ ## The easy way -The easist way to use these packages is by creating a project with `Briefcase -`__. Briefcase will download pre-compiled -versions of these support packages, and add them to an Xcode project (or -pre-build stub application, in the case of macOS). +The easist way to use these packages is by creating a project with +(Briefcase)[https://github.com/beeware/briefcase]. Briefcase will download +pre-compiled versions of these support packages, and add them to an Xcode project +(or pre-build stub application, in the case of macOS). ## The manual way @@ -22,8 +22,72 @@ guides: * [macOS](https://docs.python.org/3/using/mac.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. +For tvOS, watchOS, and visionOS, you should be able to broadly follow the instructions +in the iOS guide, changing some platform names in the first script. The testbed projects +generated on iOS and visionOS may be used as rough references as well. + +### 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 *appPath = [NSString stringWithFormat:@"%@/app", resourcePath, nil]; + + setenv("PYTHONHOME", [pythonHome UTF8String], 1); + setenv("PYTHONPATH", [appPath UTF8String], 1); + + Py_Initialize(); + + // we now have a Python interpreter ready to be used + ``` + +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 } + let appPath = Bundle.main.path(forResource: "app", ofType: nil) + + setenv("PYTHONHOME", pythonHome, 1) + setenv("PYTHONPATH", appPath, 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. ### Using Objective C 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 bc2c2c57..39c3cfc3 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,52 +1,3 @@ -diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst -index cd78fe18e35..612aa2aa711 100644 ---- a/Doc/c-api/init_config.rst -+++ b/Doc/c-api/init_config.rst -@@ -1271,17 +1271,6 @@ - - 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 aa43f75ec35..dff694941d0 100644 ---- a/Doc/using/ios.rst -+++ b/Doc/using/ios.rst -@@ -296,8 +296,6 @@ - * 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: -diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h -index 20f5c9ad9bb..5da5ef9e543 100644 ---- a/Include/cpython/initconfig.h -+++ b/Include/cpython/initconfig.h -@@ -179,9 +179,6 @@ - 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 diff --git a/Lib/platform.py b/Lib/platform.py index 8895177e326..eab586011ed 100755 --- a/Lib/platform.py @@ -162,10 +113,10 @@ index 8895177e326..eab586011ed 100755 macos_release = mac_ver()[0] if macos_release: diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py -index 7bcb737ff2c..9cac5d7d807 100644 +index 510c7b9568a..810b08879f6 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py -@@ -669,6 +669,14 @@ +@@ -676,6 +676,14 @@ release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") osname = sys.platform machine = sys.implementation._multiarch @@ -180,63 +131,6 @@ index 7bcb737ff2c..9cac5d7d807 100644 else: import _osx_support osname, release, machine = _osx_support.get_platform_osx( -diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py -index 056a5306ced..42ee7b50a2a 100644 ---- a/Lib/test/test__colorize.py -+++ b/Lib/test/test__colorize.py -@@ -10,7 +10,7 @@ - @contextlib.contextmanager - def clear_env(): - with EnvironmentVarGuard() as mock_env: -- for var in "FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS": -+ for var in "FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS", "TERM": - mock_env.unset(var) - yield mock_env - -diff --git a/Lib/test/test_apple.py b/Lib/test/test_apple.py -index ab5296afad1..f14db75e2f2 100644 ---- a/Lib/test/test_apple.py -+++ b/Lib/test/test_apple.py -@@ -1,10 +1,10 @@ - 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. -diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py -index ed459794952..a354f856c80 100644 ---- a/Lib/test/test_embed.py -+++ b/Lib/test/test_embed.py -@@ -627,8 +627,6 @@ - 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/Makefile.pre.in b/Makefile.pre.in -index 46a37ded970..7636c6d9823 100644 ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -2060,7 +2060,7 @@ - # 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) -+XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID - .PHONY: testios - testios: - @if test "$(MACHDEP)" != "ios"; then \ diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c index ec0857a4a99..2350e9dc821 100644 --- a/Misc/platform_triplet.c @@ -268,134 +162,6 @@ 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 5746416c826..201f457bb09 100644 ---- a/Python/initconfig.c -+++ b/Python/initconfig.c -@@ -129,9 +129,6 @@ - #ifdef Py_DEBUG - SPEC(run_presite, WSTR_OPT), - #endif --#ifdef __APPLE__ -- SPEC(use_system_logger, BOOL), --#endif - - {NULL, 0, 0}, - }; -@@ -748,9 +745,6 @@ - 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 -@@ -853,9 +847,6 @@ - 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; - #endif -@@ -884,9 +875,6 @@ - #ifdef MS_WINDOWS - config->legacy_windows_stdio = 0; - #endif --#ifdef __APPLE__ -- config->use_system_logger = 0; --#endif - } - - -@@ -922,9 +910,6 @@ - #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 ba835ef4c84..f31e3267f52 100644 ---- a/Python/pylifecycle.c -+++ b/Python/pylifecycle.c -@@ -47,20 +47,15 @@ - # include - # include - // The os_log unified logging APIs were introduced in macOS 10.12, iOS 10.0, --// tvOS 10.0, and watchOS 3.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 HAS_APPLE_SYSTEM_LOG 1 --# elif defined(TARGET_OS_OSX) && TARGET_OS_OSX --# if defined(MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12 --# define HAS_APPLE_SYSTEM_LOG 1 --# else --# define HAS_APPLE_SYSTEM_LOG 0 --# endif -+# define USE_APPLE_SYSTEM_LOG 1 - # else --# define HAS_APPLE_SYSTEM_LOG 0 -+# define USE_APPLE_SYSTEM_LOG 0 - # endif - --# if HAS_APPLE_SYSTEM_LOG -+# if USE_APPLE_SYSTEM_LOG - # include - # endif - #endif -@@ -92,7 +87,7 @@ - #ifdef __ANDROID__ - static PyStatus init_android_streams(PyThreadState *tstate); - #endif --#if defined(__APPLE__) && HAS_APPLE_SYSTEM_LOG -+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG - static PyStatus init_apple_streams(PyThreadState *tstate); - #endif - static void wait_for_thread_shutdown(PyThreadState *tstate); -@@ -1280,12 +1275,10 @@ - return status; - } - #endif --#if defined(__APPLE__) && HAS_APPLE_SYSTEM_LOG -- 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 - -@@ -2957,7 +2950,7 @@ - - #endif // __ANDROID__ - --#if defined(__APPLE__) && HAS_APPLE_SYSTEM_LOG -+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG - - static PyObject * - apple_log_write_impl(PyObject *self, PyObject *args) -@@ -3018,7 +3011,7 @@ - return status; - } - --#endif // __APPLE__ && HAS_APPLE_SYSTEM_LOG -+#endif // __APPLE__ && USE_APPLE_SYSTEM_LOG - - - static void diff --git a/configure b/configure index 1cd1f690f7b..34922ae651e 100755 --- a/configure @@ -1503,294 +1269,150 @@ index c3e261ecd9e..26ef7a95de4 100644 CFBundleSupportedPlatforms iPhoneOS +diff --git a/iOS/Resources/bin/arm64-apple-ios-clang b/iOS/Resources/bin/arm64-apple-ios-clang +index c39519cd1f8..f50d5b5142f 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${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-clang++ b/iOS/Resources/bin/arm64-apple-ios-clang++ +index d9b12925f38..0794731d7dc 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${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-cpp b/iOS/Resources/bin/arm64-apple-ios-cpp +index 24da23d3448..24fa1506bab 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${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/iOS/Resources/bin/arm64-apple-ios-simulator-clang +index 92e8d853d6e..4891a00876e 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${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ +index 076469cc70c..58b2a5f6f18 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${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +index c57f28cee5b..c9df94e8b7c 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${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang +index 17cbe0c8a1e..f4739a7b945 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${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++ +index 565d47b24c2..c348ae4c103 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${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 +index 63fc8e8de2d..6d7f8084c9f 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${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py -index b4499f5ac17..b436c9af99d 100644 +index b436c9af99d..c05497ede3a 100644 --- a/iOS/testbed/__main__.py +++ b/iOS/testbed/__main__.py -@@ -1,11 +1,14 @@ - import argparse - import asyncio -+import fcntl - import json -+import os - import plistlib - import re - import shutil - import subprocess - import sys -+import tempfile - from contextlib import asynccontextmanager - from datetime import datetime - from pathlib import Path -@@ -36,6 +39,46 @@ - pass - +@@ -123,6 +123,36 @@ + ) -+class SimulatorLock: -+ # An fcntl-based filesystem lock that can be used to ensure that -+ def __init__(self, timeout): -+ self.filename = Path(tempfile.gettempdir()) / "python-ios-testbed" -+ self.timeout = timeout -+ -+ self.fd = None -+ -+ async def acquire(self): -+ # Ensure the lockfile exists -+ self.filename.touch(exist_ok=True) -+ -+ # Try `timeout` times to acquire the lock file, with a 1 second pause -+ # between each attempt. Report status every 10 seconds. -+ for i in range(0, self.timeout): -+ try: -+ fd = os.open(self.filename, os.O_RDWR | os.O_TRUNC, 0o644) -+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) -+ except OSError: -+ os.close(fd) -+ if i % 10 == 0: -+ print("... waiting", flush=True) -+ await asyncio.sleep(1) -+ else: -+ self.fd = fd -+ return -+ -+ # If we reach the end of the loop, we've exceeded the allowed number of -+ # attempts. -+ raise ValueError("Unable to obtain lock on iOS simulator creation") -+ -+ def release(self): -+ # If a lock is held, release it. -+ if self.fd is not None: -+ # Release the lock. -+ fcntl.flock(self.fd, fcntl.LOCK_UN) -+ os.close(self.fd) -+ self.fd = None -+ -+ - # 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. -@@ -82,22 +125,32 @@ ++# Select a simulator device to use. ++async def select_simulator_device(): ++ # 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) ++ ++ # Any 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"] ++ ) ++ ) ++ ++ return se_simulators[-1][1] ++ ++ # 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): -+async def find_device(initial_devices, lock): - while True: - new_devices = set(await list_devices()).difference(initial_devices) - if len(new_devices) == 0: -@@ -105,15 +158,16 @@ - 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}") -+ print(f"UDID: {udid}", flush=True) -+ lock.release() - return udid - else: - exit(f"Found more than one new device: {new_devices}") - - --async def log_stream_task(initial_devices): -+async def log_stream_task(initial_devices, lock): - # 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) -+ udid = await asyncio.wait_for(find_device(initial_devices, lock), 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 -@@ -161,7 +215,7 @@ + try: +@@ -371,12 +401,16 @@ + plistlib.dump(info, f) - async def xcode_test(location, simulator, verbose): - # Run the test suite on the named simulator -- print("Starting xcodebuild...") -+ print("Starting xcodebuild...", flush=True) - args = [ - "xcodebuild", - "test", -@@ -230,33 +284,69 @@ - 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( - 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.unlink(missing_ok=True) - sim_framework_path.symlink_to( - framework.relative_to(sim_framework_path.parent, walk_up=True) - ) - 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) -@@ -285,7 +375,17 @@ +-async def run_testbed(simulator: str, args: list[str], verbose: bool=False): ++async def run_testbed(simulator: str | None, args: list[str], verbose: bool=False): location = Path(__file__).parent print("Updating plist...", end="", flush=True) update_plist(location, args) -- print(" done.") -+ print(" done.", flush=True) -+ -+ # We need to get an exclusive lock on simulator creation, to avoid issues -+ # with multiple simulators starting and being unable to tell which -+ # simulator is due to which testbed instance. See -+ # https://github.com/python/cpython/issues/130294 for details. Wait up to -+ # 10 minutes for a simulator to boot. -+ print("Obtaining lock on simulator creation...", flush=True) -+ simulator_lock = SimulatorLock(timeout=10*60) -+ await simulator_lock.acquire() -+ print("Simulator lock acquired.", flush=True) - - # 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 -@@ -294,13 +394,15 @@ - - try: - async with asyncio.TaskGroup() as tg: -- tg.create_task(log_stream_task(initial_devices)) -+ tg.create_task(log_stream_task(initial_devices, simulator_lock)) - 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] -+ finally: -+ simulator_lock.release() - - - def main(): -@@ -372,8 +474,8 @@ - - 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], - ) -diff --git a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m -index 6db38253396..d417b4cd63e 100644 ---- a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m -+++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m -@@ -28,7 +28,7 @@ - // 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 -@@ -53,8 +53,6 @@ - // 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 + print(" done.", flush=True) + ++ if simulator is None: ++ simulator = await select_simulator_device() ++ print(f"Running test on {simulator}", flush=True) ++ + # We need to get an exclusive lock on simulator creation, to avoid issues + # with multiple simulators starting and being unable to tell which + # simulator is due to which testbed instance. See +@@ -453,8 +487,10 @@ + ) + run.add_argument( + "--simulator", +- default="iPhone SE (3rd Generation)", +- help="The name of the simulator to use (default: 'iPhone SE (3rd Generation)')", ++ help=( ++ "The name of the simulator to use (eg: 'iPhone 16e'). Defaults to ", ++ "the most recently released 'entry level' iPhone device." ++ ) + ) + run.add_argument( + "-v", "--verbose", --- /dev/null +++ b/tvOS/README.rst @@ -0,0 +1,108 @@ diff --git a/patch/Python/_cross_target.py.tmpl b/patch/Python/_cross_target.py.tmpl index 9f75af53..fbe178fc 100644 --- a/patch/Python/_cross_target.py.tmpl +++ b/patch/Python/_cross_target.py.tmpl @@ -11,6 +11,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 @@ -67,5 +69,9 @@ 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.prefix +sysconfig._BASE_EXEC_PREFIX = sys.base_exec_prefix + # Force sysconfig data to be loaded (and cached). sysconfig._init_config_vars() diff --git a/patch/Python/release.visionOS.exclude b/patch/Python/release.visionOS.exclude new file mode 100644 index 00000000..f1d0f75e --- /dev/null +++ b/patch/Python/release.visionOS.exclude @@ -0,0 +1,6 @@ +# This is a list of support package path patterns that we exclude +# from all Python-Apple-support tarballs. +# It is used by `tar -X` during the Makefile build. +# Remove pyc files. These take up space, but since most stdlib modules are +# never imported by user code, they mostly have no value. +*/__pycache__