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__