diff --git a/Makefile b/Makefile
index dcbb49b3..9efef48d 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,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.14.2
+PYTHON_VERSION=3.14.6
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+")
@@ -30,7 +30,7 @@ PYTHON_VER=$(basename $(PYTHON_VERSION))
BZIP2_VERSION=1.0.8-2
LIBFFI_VERSION=3.4.7-2
MPDECIMAL_VERSION=4.0.0-2
-OPENSSL_VERSION=3.0.18-1
+OPENSSL_VERSION=3.5.7-1
XZ_VERSION=5.6.4-2
ZSTD_VERSION=1.5.7-1
@@ -314,7 +314,7 @@ $$(PYTHON_SRCDIR-$(target))/configure: \
# Apply target Python patches
cd $$(PYTHON_SRCDIR-$(target)) && patch -p1 < $(PROJECT_DIR)/patch/Python/Python.patch
# Make sure the binary scripts are executable
- chmod 755 $$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin/*
+ chmod 755 $$(PYTHON_SRCDIR-$(target))/Platforms/Apple/$(os)/Resources/bin/*
# Touch the configure script to ensure that Make identifies it as up to date.
touch $$(PYTHON_SRCDIR-$(target))/configure
@@ -322,7 +322,7 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \
$$(PYTHON_SRCDIR-$(target))/configure
# Configure target Python
cd $$(PYTHON_SRCDIR-$(target)) && \
- PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \
+ PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Platforms/Apple/$(os)/Resources/bin:$(PATH)" \
./configure \
LIBLZMA_CFLAGS="-I$$(XZ_INSTALL-$(target))/include" \
LIBLZMA_LIBS="-L$$(XZ_INSTALL-$(target))/lib -llzma" \
@@ -346,14 +346,14 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \
$$(PYTHON_SRCDIR-$(target))/python.exe: $$(PYTHON_SRCDIR-$(target))/Makefile
@echo ">>> Build Python for $(target)"
cd $$(PYTHON_SRCDIR-$(target)) && \
- PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \
+ PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Platforms/Apple/$(os)/Resources/bin:$(PATH)" \
make -j8 all \
2>&1 | tee -a ../python-$(PYTHON_VERSION).build.log
$$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe
@echo ">>> Install Python for $(target)"
cd $$(PYTHON_SRCDIR-$(target)) && \
- PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \
+ PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Platforms/Apple/$(os)/Resources/bin:$(PATH)" \
make install \
2>&1 | tee -a ../python-$(PYTHON_VERSION).install.log
@@ -521,7 +521,7 @@ $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h: $$(PYTHON_LIB-$(sdk))
$$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_INCLUDE-$$(target))/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig-$$(ARCH-$$(target)).h; )
# Copy the cross-target header from the source folder of the first target in the $(sdk) SDK
- cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/Apple/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h
+ cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/Platforms/Apple/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h
$$(PYTHON_PLATFORM_CONFIG-$(sdk))/sitecustomize.py: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target)))
@@ -678,8 +678,8 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \
@echo ">>> Install build tools for $(os)"
mkdir $$(PYTHON_XCFRAMEWORK-$(os))/build
- cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Apple/testbed/Python.xcframework/build/utils.sh $$(PYTHON_XCFRAMEWORK-$(os))/build
- cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Apple/testbed/Python.xcframework/build/$$(PLATFORM_NAME-$(os))-dylib-Info-template.plist $$(PYTHON_XCFRAMEWORK-$(os))/build
+ cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Platforms/Apple/testbed/Python.xcframework/build/utils.sh $$(PYTHON_XCFRAMEWORK-$(os))/build
+ cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Platforms/Apple/testbed/Python.xcframework/build/$$(PLATFORM_NAME-$(os))-dylib-Info-template.plist $$(PYTHON_XCFRAMEWORK-$(os))/build
@echo ">>> Install stdlib for $(os)"
mkdir -p $$(PYTHON_XCFRAMEWORK-$(os))/lib
@@ -707,7 +707,7 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \
ifeq ($(filter $(os),iOS tvOS visionOS),$(os))
@echo ">>> Clone testbed project for $(os)"
- $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Apple/testbed clone --platform $(os) --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed
+ $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Platforms/Apple/testbed clone --platform $(os) --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed
endif
@echo ">>> Create VERSIONS file for $(os)"
diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch
index fec095dd..94fd56b9 100644
--- a/patch/Python/Python.patch
+++ b/patch/Python/Python.patch
@@ -1,219 +1,2304 @@
-diff --git a/Apple/__main__.py b/Apple/__main__.py
-index 256966e76c2..a7bbc8b289e 100644
---- a/Apple/__main__.py
-+++ b/Apple/__main__.py
-@@ -5,17 +5,19 @@
- # This script simplifies the process of configuring, compiling and packaging an
- # XCframework for an Apple platform.
- #
--# At present, it only supports iOS, but it has been constructed so that it
--# could be used on any Apple platform.
-+# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been
-+# constructed so that it could be used on any Apple platform.
- #
- # The simplest entry point is:
- #
- # $ python Apple ci iOS
- #
-+# (replace iOS with tvOS, visionOS or watchOS as required.)
-+#
- # which will:
- # * Clean any pre-existing build artefacts
- # * Configure and make a Python that can be used for the build
--# * Configure and make a Python for each supported iOS architecture and ABI
-+# * Configure and make a Python for each supported iOS/tvOS architecture and ABI
- # * Combine the outputs of the builds from the previous step into a single
- # XCframework, merging binaries into a "fat" binary if necessary
- # * Clone a copy of the testbed, configured to use the XCframework
-@@ -75,6 +77,32 @@
- "x86_64-apple-ios-simulator": "x86_64-iphonesimulator",
- },
- },
-+ "tvOS": {
-+ "tvos-arm64": {
-+ "arm64-apple-tvos": "arm64-appletvos",
-+ },
-+ "tvos-arm64_x86_64-simulator": {
-+ "arm64-apple-tvos-simulator": "arm64-appletvsimulator",
-+ "x86_64-apple-tvos-simulator": "x86_64-appletvsimulator",
-+ },
-+ },
-+ "visionOS": {
-+ "xros-arm64": {
-+ "arm64-apple-xros": "arm64-xros",
-+ },
-+ "xros-arm64-simulator": {
-+ "arm64-apple-xros-simulator": "arm64-xrsimulator",
-+ },
-+ },
-+ "watchOS": {
-+ "watchos-arm64_32": {
-+ "arm64_32-apple-watchos": "arm64_32-watchos",
-+ },
-+ "watchos-arm64_x86_64-simulator": {
-+ "arm64-apple-watchos-simulator": "arm64-watchsimulator",
-+ "x86_64-apple-watchos-simulator": "x86_64-watchsimulator",
-+ },
-+ },
- }
+diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
+index eaa3986bf6a..6329771a3a8 100644
+--- a/.pre-commit-config.yaml
++++ b/.pre-commit-config.yaml
+@@ -3,9 +3,9 @@
+ rev: a27a2e47c7751b639d2b5badf0ef6ff11fee893f # frozen: v0.15.4
+ hooks:
+ - id: ruff-check
+- name: Run Ruff (lint) on Apple/
+- args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml]
+- files: ^Apple/
++ name: Run Ruff (lint) on Platforms/Apple/
++ args: [--exit-non-zero-on-fix, --config=Platforms/Apple/.ruff.toml]
++ files: ^Platforms/Apple/
+ - id: ruff-check
+ name: Run Ruff (lint) on Doc/
+ args: [--exit-non-zero-on-fix]
+@@ -35,9 +35,9 @@
+ args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml]
+ files: ^Tools/wasm/
+ - id: ruff-format
+- name: Run Ruff (format) on Apple/
+- args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml]
+- files: ^Apple
++ name: Run Ruff (format) on Platforms/Apple/
++ args: [--exit-non-zero-on-fix, --config=Platforms/Apple/.ruff.toml]
++ files: ^Platforms/Apple/
+ - id: ruff-format
+ name: Run Ruff (format) on Doc/
+ args: [--exit-non-zero-on-fix]
+diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
+index 7223fc49977..37e3d0df3a9 100644
+--- a/Lib/ctypes/__init__.py
++++ b/Lib/ctypes/__init__.py
+@@ -452,9 +452,9 @@
+ else:
+ def _load_library(self, name, mode, handle, winmode):
+- # If the filename that has been provided is an iOS/tvOS/watchOS
+- # .fwork file, dereference the location to the true origin of the
+- # binary.
++ # If the filename that has been provided is an iOS, tvOS, visionOS
++ # or watchOS .fwork file, dereference the location to the true
++ # origin of the binary.
+ if name and name.endswith(".fwork"):
+ with open(name) as f:
+ name = _os.path.join(
+diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
+index 3b21658433b..23780f7ffc0 100644
+--- a/Lib/ctypes/util.py
++++ b/Lib/ctypes/util.py
+@@ -132,7 +132,7 @@
+ if (name := _get_module_filename(h)) is not None]
+ return libraries
-@@ -136,11 +164,24 @@
- print(f"export {key}={shlex.quote(value)}")
+-elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}:
++elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "visionos", "watchos"}:
+ from ctypes.macholib.dyld import dyld_find as _dyld_find
+ def find_library(name):
+ possible = ['lib%s.dylib' % name,
+@@ -450,7 +450,7 @@
+ # https://man.openbsd.org/dl_iterate_phdr
+ # https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
+ if (os.name == "posix" and
+- sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
++ sys.platform not in {"darwin", "ios", "tvos", "watchos", "visionos"}):
+ import ctypes
+ if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
+diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py
+index 6a828ae75ed..e07de99fec2 100644
+--- a/Lib/importlib/_bootstrap_external.py
++++ b/Lib/importlib/_bootstrap_external.py
+@@ -52,7 +52,7 @@
-+def platform_for_host(host):
-+ """Determine the platform for a given host triple."""
-+ for plat, slices in HOSTS.items():
-+ for _, candidates in slices.items():
-+ for candidate in candidates:
-+ if candidate == host:
-+ return plat
-+ raise KeyError(host)
-+
-+
- def apple_env(host: str) -> EnvironmentT:
- """Construct an Apple development environment for the given host."""
- env = {
- "PATH": ":".join([
-- str(PYTHON_DIR / "Apple/iOS/Resources/bin"),
-+ str(
-+ PYTHON_DIR
-+ / f"Apple/{platform_for_host(host)}/Resources/bin"
-+ ),
- str(subdir(host) / "prefix"),
- "/usr/bin",
- "/bin",
-@@ -302,8 +343,8 @@
- Downloads binaries if they aren't already present. Downloads will be stored
- in provided cache directory.
+ # Bootstrap-related code ######################################################
+ _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win',
+-_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos'
++_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'visionos', 'watchos'
+ _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY
+ + _CASE_INSENSITIVE_PLATFORMS_STR_KEY)
-- On iOS, as a safety mechanism, any dynamic libraries will be purged from
-- the unpacked dependencies.
-+ On non-macOS platforms, as a safety mechanism, any dynamic libraries will be
-+ purged from the unpacked dependencies.
+@@ -1538,7 +1538,7 @@
"""
- # To create new builds of these dependencies, usually all that's necessary
- # is to push a tag to the cpython-apple-source-deps repository, and GitHub
-@@ -328,9 +369,9 @@
- )
- shutil.unpack_archive(archive_path, prefix_dir)
+ extension_loaders = []
+ if hasattr(_imp, 'create_dynamic'):
+- if sys.platform in {"ios", "tvos", "watchos"}:
++ if sys.platform in {"ios", "tvos", "visionos", "watchos"}:
+ extension_loaders = [(AppleFrameworkLoader, [
+ suffix.replace(".so", ".fwork")
+ for suffix in _imp.extension_suffixes()
+diff --git a/Lib/platform.py b/Lib/platform.py
+index b017b841311..c8c794199f3 100644
+--- a/Lib/platform.py
++++ b/Lib/platform.py
+@@ -539,6 +539,78 @@
+ return IOSVersionInfo(system, release, model, is_simulator)
-- # Dynamic libraries will be preferentially linked over static;
-- # On iOS, ensure that no dylibs are available in the prefix folder.
-- if platform == "iOS":
-+ # Dynamic libraries will be preferentially linked over static; On non-macOS
-+ # platforms, ensure that no dylibs are available in the prefix folder.
-+ if platform != "macOS":
- for dylib in prefix_dir.glob("**/*.dylib"):
- dylib.unlink()
-@@ -392,6 +433,7 @@
- f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}",
- f"--with-build-python={build_python_path()}",
- "--with-system-libmpdec",
-+ "--enable-ipv6",
- "--enable-framework",
- # Dependent libraries.
- f"--with-openssl={prefix_dir}",
-@@ -432,7 +474,10 @@
- :param host_triple: The host triple (e.g., arm64-apple-ios-simulator)
- :param multiarch: The multiarch identifier (e.g., arm64-simulator)
++# A namedtuple for tvOS version information.
++TVOSVersionInfo = collections.namedtuple(
++ "TVOSVersionInfo",
++ ["system", "release", "model", "is_simulator"]
++)
++
++
++def tvos_ver(system="", release="", model="", is_simulator=False):
++ """Get tvOS version information, and return it as a namedtuple:
++ (system, release, model, is_simulator).
++
++ If values can't be determined, they are set to values provided as
++ parameters.
++ """
++ if sys.platform == "tvos":
++ # TODO: Can the iOS implementation be used here?
++ import _ios_support
++ result = _ios_support.get_platform_ios()
++ if result is not None:
++ return TVOSVersionInfo(*result)
++
++ return TVOSVersionInfo(system, release, model, is_simulator)
++
++
++# A namedtuple for watchOS version information.
++WatchOSVersionInfo = collections.namedtuple(
++ "WatchOSVersionInfo",
++ ["system", "release", "model", "is_simulator"]
++)
++
++
++def watchos_ver(system="", release="", model="", is_simulator=False):
++ """Get watchOS version information, and return it as a namedtuple:
++ (system, release, model, is_simulator).
++
++ If values can't be determined, they are set to values provided as
++ parameters.
++ """
++ if sys.platform == "watchos":
++ # TODO: Can the iOS implementation be used here?
++ import _ios_support
++ result = _ios_support.get_platform_ios()
++ if result is not None:
++ return WatchOSVersionInfo(*result)
++
++ return WatchOSVersionInfo(system, release, model, is_simulator)
++
++
++# A namedtuple for visionOS version information.
++VisionOSVersionInfo = collections.namedtuple(
++ "VisionOSVersionInfo",
++ ["system", "release", "model", "is_simulator"]
++)
++
++
++def visionos_ver(system="", release="", model="", is_simulator=False):
++ """Get visionOS version information, and return it as a namedtuple:
++ (system, release, model, is_simulator).
++
++ If values can't be determined, they are set to values provided as
++ parameters.
++ """
++ if sys.platform == "visionos":
++ # TODO: Can the iOS implementation be used here?
++ import _ios_support
++ result = _ios_support.get_platform_ios()
++ if result is not None:
++ return VisionOSVersionInfo(*result)
++
++ return VisionOSVersionInfo(system, release, model, is_simulator)
++
++
+ def _java_getprop(name, default):
+ """This private helper is deprecated in 3.13 and will be removed in 3.15"""
+ from java.lang import System
+@@ -738,7 +810,7 @@
+ default in case the command should fail.
+
"""
-- return CROSS_BUILD_DIR / f"{host_triple}/Apple/iOS/Frameworks/{multiarch}"
-+ return (
-+ CROSS_BUILD_DIR
-+ / f"{host_triple}/Apple/{platform_for_host(host_triple)}/Frameworks/{multiarch}"
-+ )
+- if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}:
++ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'visionos', 'watchos'}:
+ # XXX Others too ?
+ return default
+@@ -902,14 +974,30 @@
+ csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
+ return 'Alpha' if cpu_number >= 128 else 'VAX'
- def package_version(prefix_path: Path) -> str:
-@@ -653,7 +698,7 @@
- host_path = (
- CROSS_BUILD_DIR
- / host_triple
-- / "Apple/iOS/Frameworks"
-+ / f"Apple/{platform}/Frameworks"
- / multiarch
- )
- host_framework = host_path / "Python.framework"
-@@ -699,18 +744,20 @@
- # Create an XCframework
- version = create_xcframework(context.platform)
+- # On the iOS simulator, os.uname returns the architecture as uname.machine.
+- # On device it returns the model name for some reason; but there's only one
+- # CPU architecture for iOS devices, so we know the right answer.
++ # On the iOS/tvOS/visionOS/watchOS simulator, os.uname returns the
++ # architecture as uname.machine. On device it returns the model name for
++ # some reason; but there's only one CPU architecture for devices, so we know
++ # the right answer.
+ def get_ios():
+ if sys.implementation._multiarch.endswith("simulator"):
+ return os.uname().machine
+ return 'arm64'
-- # Clone testbed
-- print()
-- run([
-- sys.executable,
-- "Apple/testbed",
-- "clone",
-- "--platform",
-- context.platform,
-- "--framework",
-- CROSS_BUILD_DIR / context.platform / "Python.xcframework",
-- CROSS_BUILD_DIR / context.platform / "testbed",
-- ])
-+ # watchOS doesn't have a testbed (yet!)
-+ if context.platform != "watchOS":
-+ # Clone testbed
-+ print()
-+ run([
-+ sys.executable,
-+ "Apple/testbed",
-+ "clone",
-+ "--platform",
-+ context.platform,
-+ "--framework",
-+ CROSS_BUILD_DIR / context.platform / "Python.xcframework",
-+ CROSS_BUILD_DIR / context.platform / "testbed",
-+ ])
++ def get_tvos():
++ if sys.implementation._multiarch.endswith("simulator"):
++ return os.uname().machine
++ return 'arm64'
++
++ def get_visionos():
++ if sys.implementation._multiarch.endswith("simulator"):
++ return os.uname().machine
++ return 'arm64'
++
++ def get_watchos():
++ if sys.implementation._multiarch.endswith("simulator"):
++ return os.uname().machine
++ return 'arm64_32'
++
+ def from_subprocess():
+ """
+ Fall back to `uname -p`
+@@ -1069,9 +1157,15 @@
+ system = 'Android'
+ release = android_ver().release
- # Build the final archive
- archive_name = (
-diff --git a/Apple/iOS/Resources/Info.plist.in b/Apple/iOS/Resources/Info.plist.in
-index c3e261ecd9e..26ef7a95de4 100644
---- a/Apple/iOS/Resources/Info.plist.in
-+++ b/Apple/iOS/Resources/Info.plist.in
-@@ -17,13 +17,13 @@
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
-- @VERSION@
-+ %VERSION%
- CFBundleLongVersionString
- %VERSION%, (c) 2001-2024 Python Software Foundation.
- CFBundleSignature
- ????
- CFBundleVersion
-- 1
-+ %VERSION%
- CFBundleSupportedPlatforms
-
- iPhoneOS
-diff --git a/Apple/testbed/Python.xcframework/Info.plist b/Apple/testbed/Python.xcframework/Info.plist
-index c6418de6e74..0587f4735f7 100644
---- a/Apple/testbed/Python.xcframework/Info.plist
-+++ b/Apple/testbed/Python.xcframework/Info.plist
-@@ -35,6 +35,98 @@
- SupportedPlatformVariant
- simulator
-
-+
-+ BinaryPath
-+ Python.framework/Python
-+ LibraryIdentifier
-+ tvos-arm64
-+ LibraryPath
-+ Python.framework
-+ SupportedArchitectures
-+
-+ arm64
-+
-+ SupportedPlatform
-+ tvos
-+
-+
-+ BinaryPath
+- # Normalize responses on iOS
++ # Normalize responses on Apple mobile platforms
+ if sys.platform == 'ios':
+ system, release, _, _ = ios_ver()
++ if sys.platform == 'tvos':
++ system, release, _, _ = tvos_ver()
++ if sys.platform == 'visionos':
++ system, release, _, _ = visionos_ver()
++ if sys.platform == 'watchos':
++ system, release, _, _ = watchos_ver()
+
+ vals = system, node, release, version, machine
+ # Replace 'unknown' values with the more portable ''
+@@ -1361,6 +1455,12 @@
+ # macOS and iOS both report as a "Darwin" kernel
+ if sys.platform == "ios":
+ system, release, _, _ = ios_ver()
++ elif sys.platform == "tvos":
++ system, release, _, _ = tvos_ver()
++ elif sys.platform == "visionos":
++ system, release, _, _ = visionos_ver()
++ elif sys.platform == "watchos":
++ system, release, _, _ = watchos_ver()
+ else:
+ macos_release = mac_ver()[0]
+ if macos_release:
+diff --git a/Lib/site.py b/Lib/site.py
+index aeb7c6cfc71..3fa222ea148 100644
+--- a/Lib/site.py
++++ b/Lib/site.py
+@@ -298,8 +298,8 @@
+ if env_base:
+ return env_base
+
+- # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
+- if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
++ # Emscripten, iOS, tvOS, visionOS, VxWorks, WASI, and watchOS have no home directories
++ if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "visionos", "wasi", "watchos"}:
+ return None
+
+ def joinuser(*args):
+diff --git a/Lib/subprocess.py b/Lib/subprocess.py
+index 52b7b711770..173e6a6994a 100644
+--- a/Lib/subprocess.py
++++ b/Lib/subprocess.py
+@@ -75,7 +75,7 @@
+ _mswindows = True
+
+ # some platforms do not support subprocesses
+-_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"}
++_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "visionos", "watchos"}
+
+ if _mswindows:
+ import _winapi
+diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py
+index faf8273bd0b..7e9df528709 100644
+--- a/Lib/sysconfig/__init__.py
++++ b/Lib/sysconfig/__init__.py
+@@ -23,6 +23,9 @@
+ _ALWAYS_STR = {
+ 'IPHONEOS_DEPLOYMENT_TARGET',
+ 'MACOSX_DEPLOYMENT_TARGET',
++ 'TVOS_DEPLOYMENT_TARGET',
++ 'WATCHOS_DEPLOYMENT_TARGET',
++ 'XROS_DEPLOYMENT_TARGET',
+ }
+
+ _INSTALL_SCHEMES = {
+@@ -119,7 +122,7 @@
+ # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories.
+ # Use _PYTHON_HOST_PLATFORM to get the correct platform when cross-compiling.
+ system_name = os.environ.get('_PYTHON_HOST_PLATFORM', sys.platform).split('-')[0]
+- if system_name in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
++ if system_name in {"emscripten", "ios", "tvos", "visionos", "vxworks", "wasi", "watchos"}:
+ return None
+
+ def joinuser(*args):
+@@ -742,6 +745,18 @@
+ release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
+ osname = sys.platform
+ machine = sys.implementation._multiarch
++ elif sys.platform == "tvos":
++ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "12.0")
++ osname = sys.platform
++ machine = sys.implementation._multiarch
++ elif sys.platform == "watchos":
++ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "4.0")
++ osname = sys.platform
++ machine = sys.implementation._multiarch
++ elif sys.platform == "visionos":
++ release = get_config_vars().get("XROS_DEPLOYMENT_TARGET", "2.0")
++ osname = sys.platform
++ machine = sys.implementation._multiarch
+ else:
+ import _osx_support
+ osname, release, machine = _osx_support.get_platform_osx(
+diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
+index c1bb6138a1b..ddd4f8bf669 100644
+--- a/Lib/test/datetimetester.py
++++ b/Lib/test/datetimetester.py
+@@ -7169,9 +7169,9 @@
+ self.assertEqual(dt_orig, dt_rt)
+
+ def test_type_check_in_subinterp(self):
+- # iOS requires the use of the custom framework loader,
++ # Apple mobile platforms require the use of the custom framework loader,
+ # not the ExtensionFileLoader.
+- if sys.platform == "ios":
++ if support.is_apple_mobile:
+ extension_loader = "AppleFrameworkLoader"
+ else:
+ extension_loader = "ExtensionFileLoader"
+diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
+index 701d34bba2d..f6aff1a6e1f 100644
+--- a/Lib/test/support/__init__.py
++++ b/Lib/test/support/__init__.py
+@@ -585,7 +585,7 @@
+ sys.platform == "android", f"Android blocks {name} with SELinux"
+ )
+
+-if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
++if sys.platform not in {"win32", "vxworks", "ios", "tvos", "visionos", "watchos"}:
+ unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
+ else:
+ unix_shell = None
+@@ -604,7 +604,7 @@
+ def skip_wasi_stack_overflow():
+ return unittest.skipIf(is_wasi, "Exhausts stack on WASI")
+
+-is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"}
++is_apple_mobile = sys.platform in {"ios", "tvos", "visionos", "watchos"}
+ is_apple = is_apple_mobile or sys.platform == "darwin"
+
+ has_fork_support = hasattr(os, "fork") and not (
+diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py
+index a32d5d81d2b..f9421619e98 100644
+--- a/Lib/test/test__interpreters.py
++++ b/Lib/test/test__interpreters.py
+@@ -612,6 +612,7 @@
+ f'assert(obj == {obj!r})',
+ )
+
++ @support.requires_subprocess()
+ def test_os_exec(self):
+ expected = 'spam spam spam spam spam'
+ subinterp = _interpreters.create()
+diff --git a/Lib/test/test_ctypes/test_dllist.py b/Lib/test/test_ctypes/test_dllist.py
+index 15603dc3d77..bff6c0fb95f 100644
+--- a/Lib/test/test_ctypes/test_dllist.py
++++ b/Lib/test/test_ctypes/test_dllist.py
+@@ -7,7 +7,7 @@
+
+
+ WINDOWS = os.name == "nt"
+-APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos"}
++APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos", "visionos"}
+
+ if WINDOWS:
+ KNOWN_LIBRARIES = ["KERNEL32.DLL"]
+diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
+index e879e48571f..07a2c6c76ce 100644
+--- a/Lib/test/test_platform.py
++++ b/Lib/test/test_platform.py
+@@ -271,13 +271,21 @@
+ if sys.platform == "android":
+ self.assertEqual(res.system, "Android")
+ self.assertEqual(res.release, platform.android_ver().release)
+- elif sys.platform == "ios":
++ elif support.is_apple_mobile:
+ # Platform module needs ctypes for full operation. If ctypes
+ # isn't available, there's no ObjC module, and dummy values are
+ # returned.
+ if _ctypes:
+- self.assertIn(res.system, {"iOS", "iPadOS"})
+- self.assertEqual(res.release, platform.ios_ver().release)
++ if sys.platform == "ios":
++ # iPads also identify as iOS
++ self.assertIn(res.system, {"iOS", "iPadOS"})
++ else:
++ # All other platforms - sys.platform is the lower case
++ # form of system (e.g., visionOS->visionos)
++ self.assertEqual(res.system.lower(), sys.platform)
++ # Use the platform-specific version method
++ platform_ver = getattr(platform, f"{sys.platform}_ver")
++ self.assertEqual(res.release, platform_ver().release)
+ else:
+ self.assertEqual(res.system, "")
+ self.assertEqual(res.release, "")
+diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
+index bfbcf112b0b..5634b6d2c41 100644
+--- a/Lib/test/test_webbrowser.py
++++ b/Lib/test/test_webbrowser.py
+@@ -253,7 +253,8 @@
+ arguments=[f'openURL({URL},new-tab)'])
+
+
+-@unittest.skipUnless(sys.platform == "ios", "Test only applicable to iOS")
++@unittest.skipUnless(sys.platform in {"ios", "visionOS"},
++ "Test only applicable to iOS and visionOS")
+ class IOSBrowserTest(unittest.TestCase):
+ def _obj_ref(self, *args):
+ # Construct a string representation of the arguments that can be used
+diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
+index 97aad6eea50..ec1f55c76ba 100644
+--- a/Lib/webbrowser.py
++++ b/Lib/webbrowser.py
+@@ -499,7 +499,8 @@
+ # macOS can use below Unix support (but we prefer using the macOS
+ # specific stuff)
+
+- if sys.platform == "ios":
++ if sys.platform in {"ios", "visionos"}:
++ # iOS and visionOS provide a browser; tvOS and watchOS don't.
+ register("iosbrowser", None, IOSBrowser(), preferred=True)
+
+ if sys.platform == "serenityos":
+@@ -666,9 +667,10 @@
+ return not rc
+
+ #
+-# Platform support for iOS
++# Platform support for Apple Mobile platforms that provide a browser
++# (i.e., iOS and visionOS)
+ #
+-if sys.platform == "ios":
++if sys.platform in {"ios", "visionos"}:
+ from _ios_support import objc
+ if objc:
+ # If objc exists, we know ctypes is also importable.
+diff --git a/Makefile.pre.in b/Makefile.pre.in
+index f86d7363e09..eb3358ba7b9 100644
+--- a/Makefile.pre.in
++++ b/Makefile.pre.in
+@@ -211,6 +211,12 @@
+ # the build, and is only listed here so it will be included in sysconfigdata.
+ IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@
+
++# visionOS Deployment target is *actually* used during the build, by the
++# compiler shims; export.
++XROS_DEPLOYMENT_TARGET=@XROS_DEPLOYMENT_TARGET@
++@EXPORT_XROS_DEPLOYMENT_TARGET@export XROS_DEPLOYMENT_TARGET
++
++
+ # Option to install to strip binaries
+ STRIPFLAG=-s
+
+@@ -2321,7 +2327,7 @@
+ fi
+
+ # Clone the testbed project into the XCFOLDER
+- $(PYTHON_FOR_BUILD) $(srcdir)/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)"
++ $(PYTHON_FOR_BUILD) $(srcdir)/Platforms/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)"
+
+ # Run the testbed project
+ $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W
+@@ -3230,10 +3236,10 @@
+ -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';'
+ -rm -f Include/pydtrace_probes.h
+ -rm -f profile-gen-stamp
+- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/bin
+- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/lib
+- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/include
+- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/Python.framework
++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/bin
++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/lib
++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/include
++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/Python.framework
+
+ .PHONY: profile-removal
+ profile-removal:
+@@ -3267,7 +3273,7 @@
+ config.cache config.log pyconfig.h Modules/config.c
+ -rm -rf build platform
+ -rm -rf $(PYTHONFRAMEWORKDIR)
+- -rm -rf Apple/iOS/Frameworks
++ -rm -rf Platforms/Apple/iOS/Frameworks
+ -rm -rf iOSTestbed.*
+ -rm -f python-config.py python-config
+ -rm -rf cross-build
+diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c
+index f5cd73bdea8..6c1863c943b 100644
+--- a/Misc/platform_triplet.c
++++ b/Misc/platform_triplet.c
+@@ -257,6 +257,32 @@
+ # else
+ PLATFORM_TRIPLET=arm64-iphoneos
+ # endif
++# elif defined(TARGET_OS_TV) && TARGET_OS_TV
++# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
++# if __x86_64__
++PLATFORM_TRIPLET=x86_64-appletvsimulator
++# else
++PLATFORM_TRIPLET=arm64-appletvsimulator
++# endif
++# else
++PLATFORM_TRIPLET=arm64-appletvos
++# endif
++# elif defined(TARGET_OS_WATCH) && TARGET_OS_WATCH
++# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
++# if __x86_64__
++PLATFORM_TRIPLET=x86_64-watchsimulator
++# else
++PLATFORM_TRIPLET=arm64-watchsimulator
++# endif
++# else
++PLATFORM_TRIPLET=arm64_32-watchos
++# endif
++# elif defined(TARGET_OS_VISION) && TARGET_OS_VISION
++# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
++PLATFORM_TRIPLET=arm64-xrsimulator
++# else
++PLATFORM_TRIPLET=arm64-xros
++# endif
+ // Older macOS SDKs do not define TARGET_OS_OSX
+ # elif !defined(TARGET_OS_OSX) || TARGET_OS_OSX
+ PLATFORM_TRIPLET=darwin
+diff --git a/Platforms/Apple/.ruff.toml b/Platforms/Apple/.ruff.toml
+new file mode 100644
+index 00000000000..f5d74fdb6af
+--- /dev/null
++++ b/Platforms/Apple/.ruff.toml
+@@ -0,0 +1,22 @@
++extend = "../../.ruff.toml" # Inherit the project-wide settings
++
++[format]
++preview = true
++docstring-code-format = true
++
++[lint]
++select = [
++ "C4", # flake8-comprehensions
++ "E", # pycodestyle
++ "F", # pyflakes
++ "I", # isort
++ "ISC", # flake8-implicit-str-concat
++ "LOG", # flake8-logging
++ "PGH", # pygrep-hooks
++ "PT", # flake8-pytest-style
++ "PYI", # flake8-pyi
++ "RUF100", # Ban unused `# noqa` comments
++ "UP", # pyupgrade
++ "W", # pycodestyle
++ "YTT", # flake8-2020
++]
+diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py
+new file mode 100644
+index 00000000000..22b8ee4a824
+--- /dev/null
++++ b/Platforms/Apple/__main__.py
+@@ -0,0 +1,1153 @@
++#!/usr/bin/env python3
++##########################################################################
++# Apple XCframework build script
++#
++# This script simplifies the process of configuring, compiling and packaging an
++# XCframework for an Apple platform.
++#
++# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been
++# constructed so that it could be used on any Apple platform.
++#
++# The simplest entry point is:
++#
++# $ python Platforms/Apple ci iOS
++#
++# (replace iOS with tvOS, visionOS or watchOS as required.)
++#
++# which will:
++# * Clean any pre-existing build artefacts
++# * Configure and make a Python that can be used for the build
++# * Configure and make a Python for each supported iOS/tvOS/watchOS/visionOS
++# architecture and ABI
++# * Combine the outputs of the builds from the previous step into a single
++# XCframework, merging binaries into a "fat" binary if necessary
++# * Clone a copy of the testbed, configured to use the XCframework
++# * Construct a tarball containing the release artefacts
++# * Run the test suite using the generated XCframework.
++#
++# This is the complete sequence that would be needed in CI to build and test
++# a candidate release artefact.
++#
++# Each individual step can be invoked individually - there are commands to
++# clean, configure-build, make-build, configure-host, make-host, package, and
++# test.
++#
++# There is also a build command that can be used to combine the configure and
++# make steps for the build Python, an individual host, all hosts, or all
++# builds.
++##########################################################################
++from __future__ import annotations
++
++import argparse
++import os
++import platform
++import re
++import shlex
++import shutil
++import signal
++import subprocess
++import sys
++import sysconfig
++import time
++from collections.abc import Callable, Sequence
++from contextlib import contextmanager
++from datetime import datetime, timezone
++from os.path import basename, relpath
++from pathlib import Path
++from subprocess import CalledProcessError
++
++EnvironmentT = dict[str, str]
++ArgsT = Sequence[str | Path]
++
++SCRIPT_NAME = Path(__file__).name
++PYTHON_DIR = Path(__file__).resolve().parent.parent.parent
++
++CROSS_BUILD_DIR = PYTHON_DIR / "cross-build"
++
++HOSTS: dict[str, dict[str, dict[str, str]]] = {
++ # Structure of this data:
++ # * Platform identifier
++ # * an XCframework slice that must exist for that platform
++ # * a host triple: the multiarch spec for that host
++ "iOS": {
++ "ios-arm64": {
++ "arm64-apple-ios": "arm64-iphoneos",
++ },
++ "ios-arm64_x86_64-simulator": {
++ "arm64-apple-ios-simulator": "arm64-iphonesimulator",
++ "x86_64-apple-ios-simulator": "x86_64-iphonesimulator",
++ },
++ },
++ "tvOS": {
++ "tvos-arm64": {
++ "arm64-apple-tvos": "arm64-appletvos",
++ },
++ "tvos-arm64_x86_64-simulator": {
++ "arm64-apple-tvos-simulator": "arm64-appletvsimulator",
++ "x86_64-apple-tvos-simulator": "x86_64-appletvsimulator",
++ },
++ },
++ "visionOS": {
++ "xros-arm64": {
++ "arm64-apple-xros": "arm64-xros",
++ },
++ "xros-arm64-simulator": {
++ "arm64-apple-xros-simulator": "arm64-xrsimulator",
++ },
++ },
++ "watchOS": {
++ "watchos-arm64_32": {
++ "arm64_32-apple-watchos": "arm64_32-watchos",
++ },
++ "watchos-arm64_x86_64-simulator": {
++ "arm64-apple-watchos-simulator": "arm64-watchsimulator",
++ "x86_64-apple-watchos-simulator": "x86_64-watchsimulator",
++ },
++ },
++}
++
++
++def subdir(name: str, create: bool = False) -> Path:
++ """Ensure that a cross-build directory for the given name exists."""
++ path = CROSS_BUILD_DIR / name
++ if not path.exists():
++ if not create:
++ sys.exit(
++ f"{path} does not exist. Create it by running the appropriate "
++ f"`configure` subcommand of {SCRIPT_NAME}."
++ )
++ else:
++ path.mkdir(parents=True)
++ return path
++
++
++def run(
++ command: ArgsT,
++ *,
++ host: str | None = None,
++ env: EnvironmentT | None = None,
++ log: bool | None = True,
++ **kwargs,
++) -> subprocess.CompletedProcess:
++ """Run a command in an Apple development environment.
++
++ Optionally logs the executed command to the console.
++ """
++ kwargs.setdefault("check", True)
++ if env is None:
++ env = os.environ.copy()
++
++ if host:
++ host_env = apple_env(host)
++ print_env(host_env)
++ env.update(host_env)
++
++ if log:
++ print(">", join_command(command))
++ return subprocess.run(command, env=env, **kwargs)
++
++
++def join_command(args: str | Path | ArgsT) -> str:
++ """Format a command so it can be copied into a shell.
++
++ Similar to `shlex.join`, but also accepts arguments which are Paths, or a
++ single string/Path outside of a list.
++ """
++ if isinstance(args, (str, Path)):
++ return str(args)
++ else:
++ return shlex.join(map(str, args))
++
++
++def print_env(env: EnvironmentT) -> None:
++ """Format the environment so it can be pasted into a shell."""
++ for key, value in sorted(env.items()):
++ print(f"export {key}={shlex.quote(value)}")
++
++
++def platform_for_host(host):
++ """Determine the platform for a given host triple."""
++ for plat, slices in HOSTS.items():
++ for _, candidates in slices.items():
++ for candidate in candidates:
++ if candidate == host:
++ return plat
++ raise KeyError(host)
++
++
++def apple_env(host: str) -> EnvironmentT:
++ """Construct an Apple development environment for the given host."""
++ env = {
++ "PATH": ":".join([
++ str(PYTHON_DIR / f"Platforms/Apple/{platform_for_host(host)}/Resources/bin"),
++ str(subdir(host) / "prefix"),
++ "/usr/bin",
++ "/bin",
++ "/usr/sbin",
++ "/sbin",
++ "/Library/Apple/usr/bin",
++ ]),
++ }
++
++ return env
++
++
++def delete_path(name: str) -> None:
++ """Delete the named cross-build directory, if it exists."""
++ path = CROSS_BUILD_DIR / name
++ if path.exists():
++ print(f"Deleting {path} ...")
++ shutil.rmtree(path)
++
++
++def all_host_triples(platform: str) -> list[str]:
++ """Return all host triples for the given platform.
++
++ The host triples are the platform definitions used as input to configure
++ (e.g., "arm64-apple-ios-simulator").
++ """
++ triples = []
++ for slice_name, slice_parts in HOSTS[platform].items():
++ triples.extend(list(slice_parts))
++ return triples
++
++
++def clean(context: argparse.Namespace, target: str | None = None) -> None:
++ """The implementation of the "clean" command."""
++ if target is None:
++ target = context.host
++
++ # If we're explicitly targeting the build, there's no platform or
++ # distribution artefacts. If we're cleaning tests, we keep all built
++ # artefacts. Otherwise, the built artefacts must be dirty, so we remove
++ # them.
++ if target not in {"build", "test"}:
++ paths = ["dist", context.platform] + list(HOSTS[context.platform])
++ else:
++ paths = []
++
++ if target in {"all", "build"}:
++ paths.append("build")
++
++ if target in {"all", "hosts"}:
++ paths.extend(all_host_triples(context.platform))
++ elif target not in {"build", "test", "package"}:
++ paths.append(target)
++
++ if target in {"all", "hosts", "test"}:
++ paths.extend([
++ path.name
++ for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*")
++ ])
++
++ for path in paths:
++ delete_path(path)
++
++
++def build_python_path() -> Path:
++ """The path to the build Python binary."""
++ build_dir = subdir("build")
++ binary = build_dir / "python"
++ if not binary.is_file():
++ binary = binary.with_suffix(".exe")
++ if not binary.is_file():
++ raise FileNotFoundError(
++ f"Unable to find `python(.exe)` in {build_dir}"
++ )
++
++ return binary
++
++
++@contextmanager
++def group(text: str):
++ """A context manager that outputs a log marker around a section of a build.
++
++ If running in a GitHub Actions environment, the GitHub syntax for
++ collapsible log sections is used.
++ """
++ if "GITHUB_ACTIONS" in os.environ:
++ print(f"::group::{text}")
++ else:
++ print(f"===== {text} " + "=" * (70 - len(text)))
++
++ yield
++
++ if "GITHUB_ACTIONS" in os.environ:
++ print("::endgroup::")
++ else:
++ print()
++
++
++@contextmanager
++def cwd(subdir: Path):
++ """A context manager that sets the current working directory."""
++ orig = os.getcwd()
++ os.chdir(subdir)
++ yield
++ os.chdir(orig)
++
++
++def configure_build_python(context: argparse.Namespace) -> None:
++ """The implementation of the "configure-build" command."""
++ if context.clean:
++ clean(context, "build")
++
++ with (
++ group("Configuring build Python"),
++ cwd(subdir("build", create=True)),
++ ):
++ command = [relpath(PYTHON_DIR / "configure")]
++ if context.args:
++ command.extend(context.args)
++ run(command)
++
++
++def make_build_python(context: argparse.Namespace) -> None:
++ """The implementation of the "make-build" command."""
++ with (
++ group("Compiling build Python"),
++ cwd(subdir("build")),
++ ):
++ run(["make", "-j", str(os.cpu_count())])
++
++
++def apple_target(host: str) -> str:
++ """Return the Apple platform identifier for a given host triple."""
++ for _, platform_slices in HOSTS.items():
++ for slice_name, slice_parts in platform_slices.items():
++ for host_triple, multiarch in slice_parts.items():
++ if host == host_triple:
++ return ".".join(multiarch.split("-")[::-1])
++
++ raise KeyError(host)
++
++
++def apple_multiarch(host: str) -> str:
++ """Return the multiarch descriptor for a given host triple."""
++ for _, platform_slices in HOSTS.items():
++ for slice_name, slice_parts in platform_slices.items():
++ for host_triple, multiarch in slice_parts.items():
++ if host == host_triple:
++ return multiarch
++
++ raise KeyError(host)
++
++
++def unpack_deps(
++ platform: str,
++ host: str,
++ prefix_dir: Path,
++ cache_dir: Path,
++) -> None:
++ """Unpack binary dependencies into a provided directory.
++
++ Downloads binaries if they aren't already present. Downloads will be stored
++ in provided cache directory.
++
++ On non-macOS platforms, as a safety mechanism, any dynamic libraries will
++ be purged from the unpacked dependencies.
++ """
++ # To create new builds of these dependencies, usually all that's necessary
++ # is to push a tag to the cpython-apple-source-deps repository, and GitHub
++ # Actions will do the rest.
++ #
++ # If you're a member of the Python core team, and you'd like to be able to
++ # push these tags yourself, please contact Malcolm Smith or Russell
++ # Keith-Magee.
++ deps_url = "https://github.com/beeware/cpython-apple-source-deps/releases/download"
++ for name_ver in [
++ "BZip2-1.0.8-2",
++ "libFFI-3.4.7-2",
++ "OpenSSL-3.5.7-1",
++ "XZ-5.6.4-2",
++ "mpdecimal-4.0.0-2",
++ "zstd-1.5.7-1",
++ ]:
++ filename = f"{name_ver.lower()}-{apple_target(host)}.tar.gz"
++ archive_path = download(
++ f"{deps_url}/{name_ver}/{filename}",
++ target_dir=cache_dir,
++ )
++ shutil.unpack_archive(archive_path, prefix_dir)
++
++ # Dynamic libraries will be preferentially linked over static; On non-macOS
++ # platforms, ensure that no dylibs are available in the prefix folder.
++ if platform != "macOS":
++ for dylib in prefix_dir.glob("**/*.dylib"):
++ dylib.unlink()
++
++
++def download(url: str, target_dir: Path) -> Path:
++ """Download the specified URL into the given directory.
++
++ :return: The path to the downloaded archive.
++ """
++ target_path = Path(target_dir).resolve()
++ target_path.mkdir(exist_ok=True, parents=True)
++
++ out_path = target_path / basename(url)
++ if not Path(out_path).is_file():
++ run([
++ "curl",
++ "-Lf",
++ "--retry",
++ "5",
++ "--retry-all-errors",
++ "-o",
++ out_path,
++ url,
++ ])
++ else:
++ print(f"Using cached version of {basename(url)}")
++ return out_path
++
++
++def configure_host_python(
++ context: argparse.Namespace,
++ host: str | None = None,
++) -> None:
++ """The implementation of the "configure-host" command."""
++ if host is None:
++ host = context.host
++
++ if context.clean:
++ clean(context, host)
++
++ host_dir = subdir(host, create=True)
++ prefix_dir = host_dir / "prefix"
++
++ with group(f"Downloading dependencies ({host})"):
++ if not prefix_dir.exists():
++ prefix_dir.mkdir()
++ cache_dir = (
++ Path(context.cache_dir).resolve()
++ if context.cache_dir
++ else CROSS_BUILD_DIR / "downloads"
++ )
++ unpack_deps(context.platform, host, prefix_dir, cache_dir)
++ else:
++ print("Dependencies already installed")
++
++ with (
++ group(f"Configuring host Python ({host})"),
++ cwd(host_dir),
++ ):
++ command = [
++ # Basic cross-compiling configuration
++ relpath(PYTHON_DIR / "configure"),
++ f"--host={host}",
++ f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}",
++ f"--with-build-python={build_python_path()}",
++ "--with-system-libmpdec",
++ "--enable-ipv6",
++ "--enable-framework",
++ # Dependent libraries.
++ f"--with-openssl={prefix_dir}",
++ f"LIBLZMA_CFLAGS=-I{prefix_dir}/include",
++ f"LIBLZMA_LIBS=-L{prefix_dir}/lib -llzma",
++ f"LIBFFI_CFLAGS=-I{prefix_dir}/include",
++ f"LIBFFI_LIBS=-L{prefix_dir}/lib -lffi",
++ f"LIBMPDEC_CFLAGS=-I{prefix_dir}/include",
++ f"LIBMPDEC_LIBS=-L{prefix_dir}/lib -lmpdec",
++ f"LIBZSTD_CFLAGS=-I{prefix_dir}/include",
++ f"LIBZSTD_LIBS=-L{prefix_dir}/lib -lzstd",
++ ]
++
++ if context.args:
++ command.extend(context.args)
++ run(command, host=host)
++
++
++def make_host_python(
++ context: argparse.Namespace,
++ host: str | None = None,
++) -> None:
++ """The implementation of the "make-host" command."""
++ if host is None:
++ host = context.host
++
++ with (
++ group(f"Compiling host Python ({host})"),
++ cwd(subdir(host)),
++ ):
++ run(["make", "-j", str(os.cpu_count())], host=host)
++ run(["make", "install"], host=host)
++
++
++def framework_path(host_triple: str, multiarch: str) -> Path:
++ """The path to a built single-architecture framework product.
++
++ :param host_triple: The host triple (e.g., arm64-apple-ios-simulator)
++ :param multiarch: The multiarch identifier (e.g., arm64-simulator)
++ """
++ return (
++ (
++ CROSS_BUILD_DIR
++
++ / f"{host_triple}/Platforms/Apple/{platform_for_host(host_triple)}"
++ / f"Frameworks/{multiarch}"
++ )
++ )
++
++
++def package_version(prefix_path: Path) -> str:
++ """Extract the Python version being built from patchlevel.h."""
++ for path in prefix_path.glob("**/patchlevel.h"):
++ text = path.read_text(encoding="utf-8")
++ if match := re.search(
++ r'\n\s*#define\s+PY_VERSION\s+"(.+)"\s*\n', text
++ ):
++ version = match[1]
++ # If not building against a tagged commit, add a timestamp to the
++ # version. Follow the PyPA version number rules, as this will make
++ # it easier to process with other tools. The version will have a
++ # `+` suffix once any official release has been made; a freshly
++ # forked main branch will have a version of 3.X.0a0.
++ if version.endswith("a0"):
++ version += "+"
++ if version.endswith("+"):
++ version += datetime.now(timezone.utc).strftime("%Y%m%d.%H%M%S")
++
++ return version
++
++ sys.exit("Unable to determine Python version being packaged.")
++
++
++def lib_platform_files(dirname, names):
++ """A file filter that ignores platform-specific files in lib."""
++ path = Path(dirname)
++ if (
++ path.parts[-3] == "lib"
++ and path.parts[-2].startswith("python")
++ and path.parts[-1] == "lib-dynload"
++ ):
++ return names
++ elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
++ ignored_names = {
++ name
++ for name in names
++ if (
++ name.startswith("_sysconfigdata_")
++ or name.startswith("_sysconfig_vars_")
++ or name == "build-details.json"
++ )
++ }
++ elif path.parts[-1] == "lib":
++ ignored_names = {
++ name
++ for name in names
++ if name.startswith("libpython") and name.endswith(".dylib")
++ }
++ else:
++ ignored_names = set()
++
++ return ignored_names
++
++
++def lib_non_platform_files(dirname, names):
++ """A file filter that ignores anything *except* platform-specific files
++ in the lib directory.
++ """
++ path = Path(dirname)
++ if path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
++ return (
++ set(names) - lib_platform_files(dirname, names) - {"lib-dynload"}
++ )
++ else:
++ return set()
++
++
++def create_xcframework(platform: str) -> str:
++ """Build an XCframework from the component parts for the platform.
++
++ :return: The version number of the Python version that was packaged.
++ """
++ package_path = CROSS_BUILD_DIR / platform
++ try:
++ package_path.mkdir()
++ except FileExistsError:
++ raise RuntimeError(
++ f"{platform} XCframework already exists; do you need to run "
++ "with --clean?"
++ ) from None
++
++ frameworks = []
++ # Merge Frameworks for each component SDK. If there's only one architecture
++ # for the SDK, we can use the compiled Python.framework as-is. However, if
++ # there's more than architecture, we need to merge the individual built
++ # frameworks into a merged "fat" framework.
++ for slice_name, slice_parts in HOSTS[platform].items():
++ # Some parts are the same across all slices, so we use can any of the
++ # host frameworks as the source for the merged version. Use the first
++ # one on the list, as it's as representative as any other.
++ first_host_triple, first_multiarch = next(iter(slice_parts.items()))
++ first_framework = (
++ framework_path(first_host_triple, first_multiarch)
++ / "Python.framework"
++ )
++
++ if len(slice_parts) == 1:
++ # The first framework is the only framework, so copy it.
++ print(f"Copying framework for {slice_name}...")
++ frameworks.append(first_framework)
++ else:
++ print(f"Merging framework for {slice_name}...")
++ slice_path = CROSS_BUILD_DIR / slice_name
++ slice_framework = slice_path / "Python.framework"
++ slice_framework.mkdir(exist_ok=True, parents=True)
++
++ # Copy the Info.plist
++ shutil.copy(
++ first_framework / "Info.plist",
++ slice_framework / "Info.plist",
++ )
++
++ # Copy the headers
++ shutil.copytree(
++ first_framework / "Headers",
++ slice_framework / "Headers",
++ )
++
++ # Create the "fat" library binary for the slice
++ run(
++ ["lipo", "-create", "-output", slice_framework / "Python"]
++ + [
++ (
++ framework_path(host_triple, multiarch)
++ / "Python.framework/Python"
++ )
++ for host_triple, multiarch in slice_parts.items()
++ ]
++ )
++
++ # Add this merged slice to the list to be added to the XCframework
++ frameworks.append(slice_framework)
++
++ print()
++ print("Build XCframework...")
++ cmd = [
++ "xcodebuild",
++ "-create-xcframework",
++ "-output",
++ package_path / "Python.xcframework",
++ ]
++ for framework in frameworks:
++ cmd.extend(["-framework", framework])
++
++ run(cmd)
++
++ # Extract the package version from the merged framework
++ version = package_version(package_path / "Python.xcframework")
++ version_tag = ".".join(version.split(".")[:2])
++
++ # On non-macOS platforms, each framework in XCframework only contains the
++ # headers, libPython, plus an Info.plist. Other resources like the standard
++ # library and binary shims aren't allowed to live in framework; they need
++ # to be copied in separately.
++ print()
++ print("Copy additional resources...")
++ has_common_stdlib = False
++ for slice_name, slice_parts in HOSTS[platform].items():
++ # Some parts are the same across all slices, so we can any of the
++ # host frameworks as the source for the merged version.
++ first_host_triple, first_multiarch = next(iter(slice_parts.items()))
++ first_path = framework_path(first_host_triple, first_multiarch)
++ first_framework = first_path / "Python.framework"
++
++ slice_path = package_path / f"Python.xcframework/{slice_name}"
++ slice_framework = slice_path / "Python.framework"
++
++ # Copy the binary helpers
++ print(f" - {slice_name} binaries")
++ shutil.copytree(first_path / "bin", slice_path / "bin")
++
++ # Copy the include path (a symlink to the framework headers)
++ print(f" - {slice_name} include files")
++ shutil.copytree(
++ first_path / "include",
++ slice_path / "include",
++ symlinks=True,
++ )
++
++ # Copy in the cross-architecture pyconfig.h
++ shutil.copy(
++ PYTHON_DIR / f"Platforms/Apple/{platform}/Resources/pyconfig.h",
++ slice_framework / "Headers/pyconfig.h",
++ )
++
++ print(f" - {slice_name} shared library")
++ # Create a simlink for the fat library
++ shared_lib = slice_path / f"lib/libpython{version_tag}.dylib"
++ shared_lib.parent.mkdir()
++ shared_lib.symlink_to("../Python.framework/Python")
++
++ print(f" - {slice_name} architecture-specific files")
++ for host_triple, multiarch in slice_parts.items():
++ print(f" - {multiarch} standard library")
++ arch, _ = multiarch.split("-", 1)
++
++ if not has_common_stdlib:
++ print(" - using this architecture as the common stdlib")
++ shutil.copytree(
++ framework_path(host_triple, multiarch) / "lib",
++ package_path / "Python.xcframework/lib",
++ ignore=lib_platform_files,
++ symlinks=True,
++ )
++ has_common_stdlib = True
++
++ shutil.copytree(
++ framework_path(host_triple, multiarch) / "lib",
++ slice_path / f"lib-{arch}",
++ ignore=lib_non_platform_files,
++ symlinks=True,
++ )
++
++ # Copy the host's pyconfig.h to an architecture-specific name.
++ arch = multiarch.split("-")[0]
++ host_path = (
++ CROSS_BUILD_DIR
++ / host_triple
++ / f"Platforms/Apple/{platform}/Frameworks"
++ / multiarch
++ )
++ host_framework = host_path / "Python.framework"
++ shutil.copy(
++ host_framework / "Headers/pyconfig.h",
++ slice_framework / f"Headers/pyconfig-{arch}.h",
++ )
++
++ # Apple identifies certain libraries as "security risks"; if you
++ # statically link those libraries into a Framework, you become
++ # responsible for providing a privacy manifest for that framework.
++ xcprivacy_file = {
++ "OpenSSL": subdir(host_triple)
++ / "prefix/share/OpenSSL.xcprivacy"
++ }
++ print(f" - {multiarch} xcprivacy files")
++ for module, lib in [
++ ("_hashlib", "OpenSSL"),
++ ("_ssl", "OpenSSL"),
++ ]:
++ shutil.copy(
++ xcprivacy_file[lib],
++ slice_path
++ / f"lib-{arch}/python{version_tag}"
++ / f"lib-dynload/{module}.xcprivacy",
++ )
++
++ print(" - build tools")
++ shutil.copytree(
++ PYTHON_DIR / "Platforms/Apple/testbed/Python.xcframework/build",
++ package_path / "Python.xcframework/build",
++ )
++
++ return version
++
++
++def package(context: argparse.Namespace) -> None:
++ """The implementation of the "package" command."""
++ if context.clean:
++ clean(context, "package")
++
++ with group("Building package"):
++ # Create an XCframework
++ version = create_xcframework(context.platform)
++
++ # watchOS doesn't have a testbed (yet!)
++ if context.platform != "watchOS":
++ # Clone testbed
++ print()
++ run([
++ sys.executable,
++ "Platforms/Apple/testbed",
++ "clone",
++ "--platform",
++ context.platform,
++ "--framework",
++ CROSS_BUILD_DIR / context.platform / "Python.xcframework",
++ CROSS_BUILD_DIR / context.platform / "testbed",
++ ])
++
++ # Build the final archive
++ archive_name = (
++ CROSS_BUILD_DIR
++ / "dist"
++ / f"python-{version}-{context.platform}-XCframework"
++ )
++
++ print()
++ print("Create package archive...")
++ shutil.make_archive(
++ str(CROSS_BUILD_DIR / archive_name),
++ format="gztar",
++ root_dir=CROSS_BUILD_DIR / context.platform,
++ base_dir=".",
++ )
++ print()
++ print(f"{archive_name.relative_to(PYTHON_DIR)}.tar.gz created.")
++
++
++def build(context: argparse.Namespace, host: str | None = None) -> None:
++ """The implementation of the "build" command."""
++ if host is None:
++ host = context.host
++
++ if context.clean:
++ clean(context, host)
++
++ if host in {"all", "build"}:
++ for step in [
++ configure_build_python,
++ make_build_python,
++ ]:
++ step(context)
++
++ if host == "build":
++ hosts = []
++ elif host in {"all", "hosts"}:
++ hosts = all_host_triples(context.platform)
++ else:
++ hosts = [host]
++
++ for step_host in hosts:
++ for step in [
++ configure_host_python,
++ make_host_python,
++ ]:
++ step(context, host=step_host)
++
++ if host == "all":
++ package(context)
++
++
++def test(context: argparse.Namespace, host: str | None = None) -> None: # noqa: PT028
++ """The implementation of the "test" command."""
++ if host is None:
++ host = context.host
++
++ if context.clean:
++ clean(context, "test")
++
++ with group(f"Test {'XCframework' if host in {'all', 'hosts'} else host}"):
++ timestamp = str(time.time_ns())[:-6]
++ testbed_dir = (
++ CROSS_BUILD_DIR / f"{context.platform}-testbed.{timestamp}"
++ )
++ if host in {"all", "hosts"}:
++ framework_path = (
++ CROSS_BUILD_DIR / context.platform / "Python.xcframework"
++ )
++ else:
++ build_arch = platform.machine()
++ host_arch = host.split("-")[0]
++
++ if not host.endswith("-simulator"):
++ print("Skipping test suite non-simulator build.")
++ return
++ elif build_arch != host_arch:
++ print(
++ f"Skipping test suite for an {host_arch} build "
++ f"on an {build_arch} machine."
++ )
++ return
++ else:
++ framework_path = (
++ CROSS_BUILD_DIR
++ / host
++ / f"Platforms/Apple/{context.platform}"
++ / f"Frameworks/{apple_multiarch(host)}"
++ )
++
++ run([
++ sys.executable,
++ "Platforms/Apple/testbed",
++ "clone",
++ "--platform",
++ context.platform,
++ "--framework",
++ framework_path,
++ testbed_dir,
++ ])
++
++ run(
++ [
++ sys.executable,
++ testbed_dir,
++ "run",
++ "--verbose",
++ ]
++ + (
++ ["--simulator", str(context.simulator)]
++ if context.simulator
++ else []
++ )
++ + [
++ "--",
++ "test",
++ f"--{context.ci_mode or 'fast'}-ci",
++ "--single-process",
++ "--no-randomize",
++ # Timeout handling requires subprocesses; explicitly setting
++ # the timeout to -1 disables the faulthandler.
++ "--timeout=-1",
++ # Adding Python options requires the use of a subprocess to
++ # start a new Python interpreter.
++ "--dont-add-python-opts",
++ ]
++ )
++
++
++def apple_sim_host(platform_name: str) -> str:
++ """Determine the native simulator target for this platform."""
++ for _, slice_parts in HOSTS[platform_name].items():
++ for host_triple in slice_parts:
++ parts = host_triple.split("-")
++ if parts[0] == platform.machine() and parts[-1] == "simulator":
++ return host_triple
++
++ raise KeyError(platform_name)
++
++
++def ci(context: argparse.Namespace) -> None:
++ """The implementation of the "ci" command.
++
++ In "Fast" mode, this compiles the build python, and the simulator for the
++ build machine's architecture; and runs the test suite with `--fast-ci`
++ configuration.
++
++ In "Slow" mode, it compiles the build python, plus all candidate
++ architectures (both device and simulator); then runs the test suite with
++ `--slow-ci` configuration.
++ """
++ clean(context, "all")
++ if context.ci_mode == "slow":
++ # In slow mode, build and test the full XCframework
++ build(context, host="all")
++ test(context, host="all")
++ else:
++ # In fast mode, just build the simulator platform.
++ sim_host = apple_sim_host(context.platform)
++ build(context, host="build")
++ build(context, host=sim_host)
++ test(context, host=sim_host)
++
++
++def parse_args() -> argparse.Namespace:
++ parser = argparse.ArgumentParser(
++ description=(
++ "A tool for managing the build, package and test process of "
++ "CPython on Apple platforms."
++ ),
++ )
++ parser.suggest_on_error = True
++ subcommands = parser.add_subparsers(dest="subcommand", required=True)
++
++ clean = subcommands.add_parser(
++ "clean",
++ help="Delete all build directories",
++ )
++
++ configure_build = subcommands.add_parser(
++ "configure-build", help="Run `configure` for the build Python"
++ )
++ make_build = subcommands.add_parser(
++ "make-build", help="Run `make` for the build Python"
++ )
++ configure_host = subcommands.add_parser(
++ "configure-host",
++ help="Run `configure` for a specific platform and target",
++ )
++ make_host = subcommands.add_parser(
++ "make-host",
++ help="Run `make` for a specific platform and target",
++ )
++ package = subcommands.add_parser(
++ "package",
++ help="Create a release package for the platform",
++ )
++ build = subcommands.add_parser(
++ "build",
++ help="Build all platform targets and create the XCframework",
++ )
++ test = subcommands.add_parser(
++ "test",
++ help="Run the testbed for a specific platform",
++ )
++ ci = subcommands.add_parser(
++ "ci",
++ help="Run build, package, and test",
++ )
++
++ # platform argument
++ for cmd in [clean, configure_host, make_host, package, build, test, ci]:
++ cmd.add_argument(
++ "platform",
++ choices=HOSTS.keys(),
++ help="The target platform to build",
++ )
++
++ # host triple argument
++ for cmd in [configure_host, make_host]:
++ cmd.add_argument(
++ "host",
++ help="The host triple to build (e.g., arm64-apple-ios-simulator)",
++ )
++ # optional host triple argument
++ for cmd in [clean, build, test]:
++ cmd.add_argument(
++ "host",
++ nargs="?",
++ default="all",
++ help=(
++ "The host triple to build (e.g., arm64-apple-ios-simulator), "
++ "or 'build' for just the build platform, or 'hosts' for all "
++ "host platforms, or 'all' for the build platform and all "
++ "hosts. Defaults to 'all'"
++ ),
++ )
++
++ # --cross-build-dir argument
++ for cmd in [
++ clean,
++ configure_build,
++ make_build,
++ configure_host,
++ make_host,
++ build,
++ package,
++ test,
++ ci,
++ ]:
++ cmd.add_argument(
++ "--cross-build-dir",
++ action="store",
++ default=os.environ.get("CROSS_BUILD_DIR"),
++ dest="cross_build_dir",
++ type=Path,
++ help=(
++ "Path to the cross-build directory "
++ f"(default: {CROSS_BUILD_DIR}). Can also be set "
++ "with the CROSS_BUILD_DIR environment variable."
++ ),
++ )
++
++ # --clean option
++ for cmd in [configure_build, configure_host, build, package, test, ci]:
++ cmd.add_argument(
++ "--clean",
++ action="store_true",
++ default=False,
++ dest="clean",
++ help="Delete the relevant build directories first",
++ )
++
++ # --cache-dir option
++ for cmd in [configure_host, build, ci]:
++ cmd.add_argument(
++ "--cache-dir",
++ default=os.environ.get("CACHE_DIR"),
++ help="The directory to store cached downloads.",
++ )
++
++ # --simulator option
++ for cmd in [test, ci]:
++ cmd.add_argument(
++ "--simulator",
++ help=(
++ "The name of the simulator to use (eg: 'iPhone 16e'). "
++ "Defaults to the most recently released 'entry level' "
++ "iPhone device. Device architecture and OS version can also "
++ "be specified; e.g., "
++ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would "
++ "run on an ARM64 iPhone 16 Pro simulator running iOS 26.0."
++ ),
++ )
++ group = cmd.add_mutually_exclusive_group()
++ group.add_argument(
++ "--fast-ci",
++ action="store_const",
++ dest="ci_mode",
++ const="fast",
++ help="Add test arguments for GitHub Actions",
++ )
++ group.add_argument(
++ "--slow-ci",
++ action="store_const",
++ dest="ci_mode",
++ const="slow",
++ help="Add test arguments for buildbots",
++ )
++
++ for subcommand in [configure_build, configure_host, build, ci]:
++ subcommand.add_argument(
++ "args", nargs="*", help="Extra arguments to pass to `configure`"
++ )
++
++ return parser.parse_args()
++
++
++def print_called_process_error(e: subprocess.CalledProcessError) -> None:
++ for stream_name in ["stdout", "stderr"]:
++ content = getattr(e, stream_name)
++ stream = getattr(sys, stream_name)
++ if content:
++ stream.write(content)
++ if not content.endswith("\n"):
++ stream.write("\n")
++
++ # shlex uses single quotes, so we surround the command with double quotes.
++ print(
++ f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}'
++ )
++
++
++def main() -> None:
++ # Handle SIGTERM the same way as SIGINT. This ensures that if we're
++ # terminated by the buildbot worker, we'll make an attempt to clean up our
++ # subprocesses.
++ def signal_handler(*args):
++ os.kill(os.getpid(), signal.SIGINT)
++
++ signal.signal(signal.SIGTERM, signal_handler)
++
++ # Process command line arguments
++ context = parse_args()
++
++ # Set the CROSS_BUILD_DIR if an argument was provided
++ if context.cross_build_dir:
++ global CROSS_BUILD_DIR
++ CROSS_BUILD_DIR = context.cross_build_dir.resolve()
++
++ dispatch: dict[str, Callable] = {
++ "clean": clean,
++ "configure-build": configure_build_python,
++ "make-build": make_build_python,
++ "configure-host": configure_host_python,
++ "make-host": make_host_python,
++ "package": package,
++ "build": build,
++ "test": test,
++ "ci": ci,
++ }
++
++ try:
++ dispatch[context.subcommand](context)
++ except CalledProcessError as e:
++ print()
++ print_called_process_error(e)
++ sys.exit(1)
++ except RuntimeError as e:
++ print()
++ print(e)
++ sys.exit(2)
++
++
++if __name__ == "__main__":
++ # Under the buildbot, stdout is not a TTY, but we must still flush after
++ # every line to make sure our output appears in the correct order relative
++ # to the output of our subprocesses.
++ for stream in [sys.stdout, sys.stderr]:
++ stream.reconfigure(line_buffering=True)
++
++ main()
+diff --git a/Platforms/Apple/iOS/README.md b/Platforms/Apple/iOS/README.md
+new file mode 100644
+index 00000000000..faeeead1df0
+--- /dev/null
++++ b/Platforms/Apple/iOS/README.md
+@@ -0,0 +1,339 @@
++# Python on iOS README
++
++**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).**
++
++This document provides a quick overview of some iOS specific features in the
++Python distribution.
++
++These instructions are only needed if you're planning to compile Python for iOS
++yourself. Most users should *not* need to do this. If you're looking to
++experiment with writing an iOS app in Python, tools such as [BeeWare's
++Briefcase](https://briefcase.readthedocs.io) and [Kivy's
++Buildozer](https://buildozer.readthedocs.io) will provide a much more
++approachable user experience.
++
++## Compilers for building on iOS
++
++Building for iOS requires the use of Apple's Xcode tooling. It is strongly
++recommended that you use the most recent stable release of Xcode. This will
++require the use of the most (or second-most) recently released macOS version,
++as Apple does not maintain Xcode for older macOS versions. The Xcode Command
++Line Tools are not sufficient for iOS development; you need a *full* Xcode
++install.
++
++If you want to run your code on the iOS simulator, you'll also need to install
++an iOS Simulator Platform. You should be prompted to select an iOS Simulator
++Platform when you first run Xcode. Alternatively, you can add an iOS Simulator
++Platform by selecting an open the Platforms tab of the Xcode Settings panel.
++
++## Building Python on iOS
++
++### ABIs and Architectures
++
++iOS apps can be deployed on physical devices, and on the iOS simulator. Although
++the API used on these devices is identical, the ABI is different - you need to
++link against different libraries for an iOS device build (`iphoneos`) or an
++iOS simulator build (`iphonesimulator`).
++
++Apple uses the `XCframework` format to allow specifying a single dependency
++that supports multiple ABIs. An `XCframework` is a wrapper around multiple
++ABI-specific frameworks that share a common API.
++
++iOS can also support different CPU architectures within each ABI. At present,
++there is only a single supported architecture on physical devices - ARM64.
++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple
++Silicon machines), and x86_64 (for running on older Intel-based machines).
++
++To support multiple CPU architectures on a single platform, Apple uses a "fat
++binary" format - a single physical file that contains support for multiple
++architectures. It is possible to compile and use a "thin" single architecture
++version of a binary for testing purposes; however, the "thin" binary will not be
++portable to machines using other architectures.
++
++### Building a multi-architecture iOS XCframework
++
++The `Platforms/Apple` subfolder of the Python repository acts as a build script that
++can be used to coordinate the compilation of a complete iOS XCframework. To use
++it, run::
++
++ python Platforms/Apple build iOS
++
++This will:
++
++* Configure and compile a version of Python to run on the build machine
++* Download pre-compiled binary dependencies for each platform
++* Configure and build a `Python.framework` for each required architecture and
++ iOS SDK
++* Merge the multiple `Python.framework` folders into a single `Python.xcframework`
++* Produce a `.tar.gz` archive in the `cross-build/dist` folder containing
++ the `Python.xcframework`, plus a copy of the Testbed app pre-configured to
++ use the XCframework.
++
++The `Platforms/Apple` build script has other entry points that will perform the
++individual parts of the overall `build` target, plus targets to test the
++build, clean the `cross-build` folder of iOS build products, and perform a
++complete "build and test" CI run. The `--clean` flag can also be used on
++individual commands to ensure that a stale build product are removed before
++building.
++
++### Building a single-architecture framework
++
++If you're using the `Platforms/Apple` build script, you won't need to build
++individual frameworks. However, if you do need to manually configure an iOS
++Python build for a single framework, the following options are available.
++
++#### iOS specific arguments to configure
++
++* `--enable-framework[=DIR]`
++
++ This argument specifies the location where the Python.framework will be
++ installed. If `DIR` is not specified, the framework will be installed into
++ a subdirectory of the `iOS/Frameworks` folder.
++
++ This argument *must* be provided when configuring iOS builds. iOS does not
++ support non-framework builds.
++
++* `--with-framework-name=NAME`
++
++ Specify the name for the Python framework; defaults to `Python`.
++
++ > [!NOTE]
++ > Unless you know what you're doing, changing the name of the Python
++ > framework on iOS is not advised. If you use this option, you won't be able
++ > to run the `Platforms/Apple` build script without making significant manual
++ > alterations, and you won't be able to use any binary packages unless you
++ > compile them yourself using your own framework name.
++
++#### Building Python for iOS
++
++The Python build system will create a `Python.framework` that supports a
++*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a
++framework to contain non-library content, so the iOS build will produce a
++`bin` and `lib` folder in the same output folder as `Python.framework`.
++The `lib` folder will be needed at runtime to support the Python library.
++
++If you want to use Python in a real iOS project, you need to produce multiple
++`Python.framework` builds, one for each ABI and architecture. iOS builds of
++Python *must* be constructed as framework builds. To support this, you must
++provide the `--enable-framework` flag when configuring the build. The build
++also requires the use of cross-compilation. The minimal commands for building
++Python for the ARM64 iOS simulator will look something like:
++```
++export PATH="$(pwd)/Platforms/Apple/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
++./configure \
++ --enable-framework \
++ --host=arm64-apple-ios-simulator \
++ --build=arm64-apple-darwin \
++ --with-build-python=/path/to/python.exe
++make
++make install
++```
++
++In this invocation:
++
++* `Platforms/Apple/iOS/Resources/bin` has been added to the path, providing some shims for the
++ compilers and linkers needed by the build. Xcode requires the use of `xcrun`
++ to invoke compiler tooling. However, if `xcrun` is pre-evaluated and the
++ result passed to `configure`, these results can embed user- and
++ version-specific paths into the sysconfig data, which limits the portability
++ of the compiled Python. Alternatively, if `xcrun` is used *as* the compiler,
++ it requires that compiler variables like `CC` include spaces, which can
++ cause significant problems with many C configuration systems which assume that
++ `CC` will be a single executable.
++
++ To work around this problem, the `Platforms/Apple/iOS/Resources/bin` folder contains some
++ wrapper scripts that present as simple compilers and linkers, but wrap
++ underlying calls to `xcrun`. This allows configure to use a `CC`
++ definition without spaces, and without user- or version-specific paths, while
++ retaining the ability to adapt to the local Xcode install. These scripts are
++ included in the `bin` directory of an iOS install.
++
++ These scripts will, by default, use the currently active Xcode installation.
++ If you want to use a different Xcode installation, you can use
++ `xcode-select` to set a new default Xcode globally, or you can use the
++ `DEVELOPER_DIR` environment variable to specify an Xcode install. The
++ scripts will use the default `iphoneos`/`iphonesimulator` SDK version for
++ the select Xcode install; if you want to use a different SDK, you can set the
++ `IOS_SDK_VERSION` environment variable. (e.g, setting
++ `IOS_SDK_VERSION=17.1` would cause the scripts to use the `iphoneos17.1`
++ and `iphonesimulator17.1` SDKs, regardless of the Xcode default.)
++
++ The path has also been cleared of any user customizations. A common source of
++ bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS
++ build. Resetting the path to a known "bare bones" value is the easiest way to
++ avoid these problems.
++
++* `--host` is the architecture and ABI that you want to build, in GNU compiler
++ triple format. This will be one of:
++
++ - `arm64-apple-ios` for ARM64 iOS devices.
++ - `arm64-apple-ios-simulator` for the iOS simulator running on Apple
++ Silicon devices.
++ - `x86_64-apple-ios-simulator` for the iOS simulator running on Intel
++ devices.
++
++* `--build` is the GNU compiler triple for the machine that will be running
++ the compiler. This is one of:
++
++ - `arm64-apple-darwin` for Apple Silicon devices.
++ - `x86_64-apple-darwin` for Intel devices.
++
++* `/path/to/python.exe` is the path to a Python binary on the machine that
++ will be running the compiler. This is needed because the Python compilation
++ process involves running some Python code. On a normal desktop build of
++ Python, you can compile a python interpreter and then use that interpreter to
++ run Python code. However, the binaries produced for iOS won't run on macOS, so
++ you need to provide an external Python interpreter. This interpreter must be
++ the same version as the Python that is being compiled. To be completely safe,
++ this should be the *exact* same commit hash. However, the longer a Python
++ release has been stable, the more likely it is that this constraint can be
++ relaxed - the same micro version will often be sufficient.
++
++* The `install` target for iOS builds is slightly different to other
++ platforms. On most platforms, `make install` will install the build into
++ the final runtime location. This won't be the case for iOS, as the final
++ runtime location will be on a physical device.
++
++ However, you still need to run the `install` target for iOS builds, as it
++ performs some final framework assembly steps. The location specified with
++ `--enable-framework` will be the location where `make install` will
++ assemble the complete iOS framework. This completed framework can then
++ be copied and relocated as required.
++
++For a full CPython build, you also need to specify the paths to iOS builds of
++the binary libraries that CPython depends on (such as XZ, LibFFI and OpenSSL).
++This can be done by defining library specific environment variables (such as
++`LIBLZMA_CFLAGS`, `LIBLZMA_LIBS`), and the `--with-openssl` configure
++option. Versions of these libraries pre-compiled for iOS can be found in [this
++repository](https://github.com/beeware/cpython-apple-source-deps/releases).
++LibFFI is especially important, as many parts of the standard library
++(including the `platform`, `sysconfig` and `webbrowser` modules) require
++the use of the `ctypes` module at runtime.
++
++By default, Python will be compiled with an iOS deployment target (i.e., the
++minimum supported iOS version) of 13.0. To specify a different deployment
++target, provide the version number as part of the `--host` argument - for
++example, `--host=arm64-apple-ios15.4-simulator` would compile an ARM64
++simulator build with a deployment target of 15.4.
++
++## Testing Python on iOS
++
++### Testing a multi-architecture framework
++
++Once you have a built an XCframework, you can test that framework by running:
++
++ $ python Platforms/Apple test iOS
++
++This test will attempt to find an "SE-class" simulator (i.e., an iPhone SE, or
++iPhone 16e, or similar), and run the test suite on the most recent version of
++iOS that is available. You can specify a simulator using the `--simulator`
++command line argument, providing the name of the simulator (e.g., `--simulator
++'iPhone 16 Pro'`). You can also use this argument to control the OS version used
++for testing; `--simulator 'iPhone 16 Pro,OS=18.2'` would attempt to run the
++tests on an iPhone 16 Pro running iOS 18.2.
++
++If the test runner is executed on GitHub Actions, the `GITHUB_ACTIONS`
++environment variable will be exposed to the iOS process at runtime.
++
++### Testing a single-architecture framework
++
++The `Platforms/Apple/testbed` folder that contains an Xcode project that is able to run
++the Python test suite on Apple platforms. This project converts the Python test
++suite into a single test case in Xcode's XCTest framework. The single XCTest
++passes if the test suite passes.
++
++To run the test suite, configure a Python build for an iOS simulator (i.e.,
++`--host=arm64-apple-ios-simulator` or `--host=x86_64-apple-ios-simulator`
++), specifying a framework build (i.e. `--enable-framework`). Ensure that your
++`PATH` has been configured to include the `Platforms/Apple/iOS/Resources/bin` folder and
++exclude any non-iOS tools, then run:
++```
++make all
++make install
++make testios
++```
++
++This will:
++
++* Build an iOS framework for your chosen architecture;
++* Finalize the single-platform framework;
++* Make a clean copy of the testbed project;
++* Install the Python iOS framework into the copy of the testbed project; and
++* Run the test suite on an "entry-level device" simulator (i.e., an iPhone SE,
++ iPhone 16e, or a similar).
++
++On success, the test suite will exit and report successful completion of the
++test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15
++minutes to run; a couple of extra minutes is required to compile the testbed
++project, and then boot and prepare the iOS simulator.
++
++### Debugging test failures
++
++Running `python Platforms/Apple test iOS` generates a standalone version of the
++`Platforms/Apple/testbed` project, and runs the full test suite. It does this using
++`Platforms/Apple/testbed` itself - the folder is an executable module that can be used
++to create and run a clone of the testbed project. The standalone version of the
++testbed will be created in a directory named
++`cross-build/iOS-testbed.`.
++
++You can generate your own standalone testbed instance by running:
++```
++python cross-build/iOS/testbed clone my-testbed
++```
++
++In this invocation, `my-testbed` is the name of the folder for the new
++testbed clone.
++
++If you've built your own XCframework, or you only want to test a single architecture,
++you can construct a standalone testbed instance by running:
++```
++python Platforms/Apple/testbed clone --platform iOS --framework my-testbed
++```
++
++The framework path can be the path path to a `Python.xcframework`, or the
++path to a folder that contains a single-platform `Python.framework`.
++
++You can then use the `my-testbed` folder to run the Python test suite,
++passing in any command line arguments you may require. For example, if you're
++trying to diagnose a failure in the `os` module, you might run:
++```
++python my-testbed run -- test -W test_os
++```
++
++This is the equivalent of running `python -m test -W test_os` on a desktop
++Python build. Any arguments after the `--` will be passed to testbed as if
++they were arguments to `python -m` on a desktop machine.
++
++### Testing in Xcode
++
++You can also open the testbed project in Xcode by running:
++```
++open my-testbed/iOSTestbed.xcodeproj
++```
++
++This will allow you to use the full Xcode suite of tools for debugging.
++
++The arguments used to run the test suite are defined as part of the test plan.
++To modify the test plan, select the test plan node of the project tree (it
++should be the first child of the root node), and select the "Configurations"
++tab. Modify the "Arguments Passed On Launch" value to change the testing
++arguments.
++
++The test plan also disables parallel testing, and specifies the use of the
++`Testbed.lldbinit` file for providing configuration of the debugger. The
++default debugger configuration disables automatic breakpoints on the
++`SIGINT`, `SIGUSR1`, `SIGUSR2`, and `SIGXFSZ` signals.
++
++### Testing on an iOS device
++
++To test on an iOS device, the app needs to be signed with known developer
++credentials. To obtain these credentials, you must have an iOS Developer
++account, and your Xcode install will need to be logged into your account (see
++the Accounts tab of the Preferences dialog).
++
++Once the project is open, and you're signed into your Apple Developer account,
++select the root node of the project tree (labeled "iOSTestbed"), then the
++"Signing & Capabilities" tab in the details page. Select a development team
++(this will likely be your own name), and plug in a physical device to your
++macOS machine with a USB cable. You should then be able to select your physical
++device from the list of targets in the pulldown in the Xcode titlebar.
+diff --git a/Platforms/Apple/iOS/Resources/Info.plist.in b/Platforms/Apple/iOS/Resources/Info.plist.in
+new file mode 100644
+index 00000000000..26ef7a95de4
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/Info.plist.in
+@@ -0,0 +1,34 @@
++
++
++
++
++ CFBundleDevelopmentRegion
++ en
++ CFBundleExecutable
++ Python
++ CFBundleGetInfoString
++ Python Runtime and Library
++ CFBundleIdentifier
++ @PYTHONFRAMEWORKIDENTIFIER@
++ CFBundleInfoDictionaryVersion
++ 6.0
++ CFBundleName
++ Python
++ CFBundlePackageType
++ FMWK
++ CFBundleShortVersionString
++ %VERSION%
++ CFBundleLongVersionString
++ %VERSION%, (c) 2001-2024 Python Software Foundation.
++ CFBundleSignature
++ ????
++ CFBundleVersion
++ %VERSION%
++ CFBundleSupportedPlatforms
++
++ iPhoneOS
++
++ MinimumOSVersion
++ @IPHONEOS_DEPLOYMENT_TARGET@
++
++
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-ar b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-ar
+new file mode 100755
+index 00000000000..3cf3eb21874
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-ar
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang
+new file mode 100755
+index 00000000000..f50d5b5142f
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang++
+new file mode 100755
+index 00000000000..0794731d7dc
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang++
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-cpp b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-cpp
+new file mode 100755
+index 00000000000..24fa1506bab
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-cpp
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar
+new file mode 100755
+index 00000000000..b836b6db902
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang
+new file mode 100755
+index 00000000000..4891a00876e
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
+new file mode 100755
+index 00000000000..58b2a5f6f18
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
+new file mode 100755
+index 00000000000..c9df94e8b7c
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip
+new file mode 100755
+index 00000000000..fd59d309b73
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-strip b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-strip
+new file mode 100755
+index 00000000000..75e823a3d02
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar
+new file mode 100755
+index 00000000000..b836b6db902
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
+new file mode 100755
+index 00000000000..f4739a7b945
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
+new file mode 100755
+index 00000000000..c348ae4c103
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp
+new file mode 100755
+index 00000000000..6d7f8084c9f
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip
+new file mode 100755
+index 00000000000..c5cfb289291
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@"
+diff --git a/Platforms/Apple/iOS/Resources/pyconfig.h b/Platforms/Apple/iOS/Resources/pyconfig.h
+new file mode 100644
+index 00000000000..4acff2c6051
+--- /dev/null
++++ b/Platforms/Apple/iOS/Resources/pyconfig.h
+@@ -0,0 +1,7 @@
++#ifdef __arm64__
++#include "pyconfig-arm64.h"
++#endif
++
++#ifdef __x86_64__
++#include "pyconfig-x86_64.h"
++#endif
+diff --git a/Platforms/Apple/testbed/Python.xcframework/Info.plist b/Platforms/Apple/testbed/Python.xcframework/Info.plist
+new file mode 100644
+index 00000000000..0587f4735f7
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/Info.plist
+@@ -0,0 +1,136 @@
++
++
++
++
++ AvailableLibraries
++
++
++ BinaryPath
++ Python.framework/Python
++ LibraryIdentifier
++ ios-arm64
++ LibraryPath
++ Python.framework
++ SupportedArchitectures
++
++ arm64
++
++ SupportedPlatform
++ ios
++
++
++ BinaryPath
++ Python.framework/Python
++ LibraryIdentifier
++ ios-arm64_x86_64-simulator
++ LibraryPath
++ Python.framework
++ SupportedArchitectures
++
++ arm64
++ x86_64
++
++ SupportedPlatform
++ ios
++ SupportedPlatformVariant
++ simulator
++
++
++ BinaryPath
++ Python.framework/Python
++ LibraryIdentifier
++ tvos-arm64
++ LibraryPath
++ Python.framework
++ SupportedArchitectures
++
++ arm64
++
++ SupportedPlatform
++ tvos
++
++
++ BinaryPath
+ Python.framework/Python
+ LibraryIdentifier
+ tvos-arm64_x86_64-simulator
@@ -290,254 +2375,2023 @@ index c6418de6e74..0587f4735f7 100644
+ SupportedPlatform
+ watchos
+
-
- CFBundlePackageType
- XFWK
++
++ CFBundlePackageType
++ XFWK
++ XCFrameworkFormatVersion
++ 1.0
++
++
+diff --git a/Platforms/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist
+new file mode 100644
+index 00000000000..d6caa01c1e4
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist
+@@ -0,0 +1,26 @@
++
++
++
++
++ CFBundleDevelopmentRegion
++ en
++ CFBundleExecutable
++
++ CFBundleIdentifier
++
++ CFBundleInfoDictionaryVersion
++ 6.0
++ CFBundlePackageType
++ APPL
++ CFBundleShortVersionString
++ 1.0
++ CFBundleSupportedPlatforms
++
++ iPhoneOS
++
++ MinimumOSVersion
++ 13.0
++ CFBundleVersion
++ 1
++
++
+diff --git a/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist
+new file mode 100644
+index 00000000000..a20d476fa7b
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist
+@@ -0,0 +1,26 @@
++
++
++
++
++ CFBundleDevelopmentRegion
++ en
++ CFBundleExecutable
++
++ CFBundleIdentifier
++
++ CFBundleInfoDictionaryVersion
++ 6.0
++ CFBundlePackageType
++ APPL
++ CFBundleShortVersionString
++ 1.0
++ CFBundleSupportedPlatforms
++
++ tvOS
++
++ MinimumOSVersion
++ 9.0
++ CFBundleVersion
++ 1
++
++
+diff --git a/Platforms/Apple/testbed/Python.xcframework/build/utils.sh b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh
+new file mode 100755
+index 00000000000..2d3b9320b3c
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh
+@@ -0,0 +1,180 @@
++# Utility methods for use in an Xcode project.
++#
++# An iOS XCframework cannot include any content other than the library binary
++# and relevant metadata. However, Python requires a standard library at runtime.
++# Therefore, it is necessary to add a build step to an Xcode app target that
++# processes the standard library and puts the content into the final app.
++#
++# In general, these tools will be invoked after bundle resources have been
++# copied into the app, but before framework embedding (and signing).
++#
++# The following is an example script, assuming that:
++# * Python.xcframework is in the root of the project
++# * There is an `app` folder that contains the app code
++# * There is an `app_packages` folder that contains installed Python packages.
++# -----
++# set -e
++# source $PROJECT_DIR/Python.xcframework/build/build_utils.sh
++# install_python Python.xcframework app app_packages
++# -----
++
++# Copy the standard library from the XCframework into the app bundle.
++#
++# Accepts one argument:
++# 1. The path, relative to the root of the Xcode project, where the Python
++# XCframework can be found.
++install_stdlib() {
++ PYTHON_XCFRAMEWORK_PATH=$1
++
++ mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib"
++ if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then
++ echo "Installing Python modules for iOS Simulator"
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/ios-arm64-simulator" ]; then
++ SLICE_FOLDER="ios-arm64-simulator"
++ else
++ SLICE_FOLDER="ios-arm64_x86_64-simulator"
++ fi
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then
++ echo "Installing Python modules for iOS Device"
++ SLICE_FOLDER="ios-arm64"
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then
++ echo "Installing Python modules for tvOS Simulator"
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then
++ SLICE_FOLDER="tvos-arm64-simulator"
++ else
++ SLICE_FOLDER="tvos-arm64_x86_64-simulator"
++ fi
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then
++ echo "Installing Python modules for tvOS Device"
++ SLICE_FOLDER="tvos-arm64"
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then
++ echo "Installing Python modules for watchOS Simulator"
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then
++ SLICE_FOLDER="watchos-arm64-simulator"
++ else
++ SLICE_FOLDER="watchos-arm64_x86_64-simulator"
++ fi
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then
++ echo "Installing Python modules for watchOS Device"
++ SLICE_FOLDER="watchos-arm64"
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xrsimulator" ]; then
++ echo "Installing Python modules for visionOS Simulator"
++ SLICE_FOLDER="xros-arm64-simulator"
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xros" ]; then
++ echo "Installing Python modules for visionOS Device"
++ SLICE_FOLDER="xros-arm64"
++ else
++ echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME"
++ exit 1
++ fi
++
++ # If the XCframework has a shared lib folder, then it's a full framework.
++ # Copy both the common and slice-specific part of the lib directory.
++ # Otherwise, it's a single-arch framework; use the "full" lib folder.
++ # Don't include any libpython symlink; that can't be included at runtime
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then
++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib'
++ rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib'
++ else
++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib'
++ fi
++}
++
++# Convert a single .so library into a framework that iOS can load.
++#
++# Accepts three arguments:
++# 1. The path, relative to the root of the Xcode project, where the Python
++# XCframework can be found.
++# 2. The base path, relative to the installed location in the app bundle, that
++# needs to be processed. Any .so file found in this path (or a subdirectory
++# of it) will be processed.
++# 2. The full path to a single .so file to process. This path should include
++# the base path.
++install_dylib () {
++ PYTHON_XCFRAMEWORK_PATH=$1
++ INSTALL_BASE=$2
++ FULL_EXT=$3
++
++ # The name of the extension file
++ EXT=$(basename "$FULL_EXT")
++ # The name and location of the module
++ MODULE_PATH=$(dirname "$FULL_EXT")
++ MODULE_NAME=$(echo $EXT | cut -d "." -f 1)
++ # The location of the extension file, relative to the bundle
++ RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/}
++ # The path to the extension file, relative to the install base
++ PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}
++ # The full dotted name of the extension module, constructed from the file path.
++ FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" ".");
++ # A bundle identifier; not actually used, but required by Xcode framework packaging
++ FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-")
++ # The name of the framework folder.
++ FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework"
++
++ # If the framework folder doesn't exist, create it.
++ if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then
++ echo "Creating framework for $RELATIVE_EXT"
++ mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER"
++ cp "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/build/$PLATFORM_FAMILY_NAME-dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
++ plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
++ plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
++ fi
++
++ echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
++ mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
++ # Create a placeholder .fwork file where the .so was
++ echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork
++ # Create a back reference to the .so file location in the framework
++ echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin"
++
++ # If the framework provides an xcprivacy file, install it.
++ if [ -e "$MODULE_PATH/$MODULE_NAME.xcprivacy" ]; then
++ echo "Installing XCPrivacy file for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
++ XCPRIVACY_FILE="$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/PrivacyInfo.xcprivacy"
++ if [ -e "$XCPRIVACY_FILE" ]; then
++ rm -rf "$XCPRIVACY_FILE"
++ fi
++ mv "$MODULE_PATH/$MODULE_NAME.xcprivacy" "$XCPRIVACY_FILE"
++ fi
++
++ echo "Signing framework as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..."
++ /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER"
++}
++
++# Process all the dynamic libraries in a path into Framework format.
++#
++# Accepts two arguments:
++# 1. The path, relative to the root of the Xcode project, where the Python
++# XCframework can be found.
++# 2. The base path, relative to the installed location in the app bundle, that
++# needs to be processed. Any .so file found in this path (or a subdirectory
++# of it) will be processed.
++process_dylibs () {
++ PYTHON_XCFRAMEWORK_PATH=$1
++ LIB_PATH=$2
++ find "$CODESIGNING_FOLDER_PATH/$LIB_PATH" -name "*.so" | while read FULL_EXT; do
++ install_dylib $PYTHON_XCFRAMEWORK_PATH "$LIB_PATH/" "$FULL_EXT"
++ done
++}
++
++# The entry point for post-processing a Python XCframework.
++#
++# Accepts 1 or more arguments:
++# 1. The path, relative to the root of the Xcode project, where the Python
++# XCframework can be found. If the XCframework is in the root of the project,
++# 2+. The path of a package, relative to the root of the packaged app, that contains
++# library content that should be processed for binary libraries.
++install_python() {
++ PYTHON_XCFRAMEWORK_PATH=$1
++ shift
++
++ install_stdlib $PYTHON_XCFRAMEWORK_PATH
++ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib" | grep -E "^python3\.\d+$")
++ echo "Install Python $PYTHON_VER standard library extension modules..."
++ process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload
++
++ for package_path in $@; do
++ echo "Installing $package_path extension modules ..."
++ process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path
++ done
++}
+diff --git a/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist
+new file mode 100644
+index 00000000000..6f8c0bc2095
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist
+@@ -0,0 +1,26 @@
++
++
++
++
++ CFBundleDevelopmentRegion
++ en
++ CFBundleExecutable
++
++ CFBundleIdentifier
++
++ CFBundleInfoDictionaryVersion
++ 6.0
++ CFBundlePackageType
++ APPL
++ CFBundleShortVersionString
++ 1.0
++ CFBundleSupportedPlatforms
++
++ watchOS
++
++ MinimumOSVersion
++ 4.0
++ CFBundleVersion
++ 1
++
++
+diff --git a/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist
+new file mode 100644
+index 00000000000..e91673b4fbc
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist
+@@ -0,0 +1,30 @@
++
++
++
++
++ CFBundleDevelopmentRegion
++ en
++ CFBundleInfoDictionaryVersion
++ 6.0
++ CFBundleExecutable
++
++ CFBundleIdentifier
++
++ CFBundlePackageType
++ FMWK
++ CFBundleShortVersionString
++ 1.0
++ CFBundleSupportedPlatforms
++
++ XROS
++
++ CFBundleVersion
++ 1
++ MinimumOSVersion
++ 2.0
++ UIDeviceFamily
++
++ 7
++
++
++
+diff --git a/Platforms/Apple/testbed/Python.xcframework/ios-arm64/README b/Platforms/Apple/testbed/Python.xcframework/ios-arm64/README
+new file mode 100644
+index 00000000000..c1b076d12cd
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/ios-arm64/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling an iOS on-device
++build for testing purposes.
+diff --git a/Platforms/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README
+new file mode 100644
+index 00000000000..ae334e5d769
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling an iOS simulator
++build for testing purposes (either x86_64 or ARM64).
+diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README
+new file mode 100644
+index 00000000000..ebd648d04bd
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling a tvOS
++on-device build for testing purposes.
+diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README
+new file mode 100644
+index 00000000000..f8163468d8c
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling a tvOS
++simulator build for testing purposes (either x86_64 or ARM64).
+diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README
+new file mode 100644
+index 00000000000..696af231df3
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling a watchOS on-device
++build for testing purposes.
+diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README
+new file mode 100644
+index 00000000000..d38e1e98276
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling a watchOS
++simulator build for testing purposes (either x86_64 or ARM64).
+diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README
+new file mode 100644
+index 00000000000..7bb0ad4b6ba
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling an visionOS simulator
++build for testing purposes (either x86_64 or ARM64).
+diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README
+new file mode 100644
+index 00000000000..2fc2112e477
+--- /dev/null
++++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
++
++It should be used as a target for `--enable-framework` when compiling an visionOS on-device
++build for testing purposes.
+diff --git a/Platforms/Apple/testbed/Testbed.lldbinit b/Platforms/Apple/testbed/Testbed.lldbinit
+new file mode 100644
+index 00000000000..4cf00dd0f9d
+--- /dev/null
++++ b/Platforms/Apple/testbed/Testbed.lldbinit
+@@ -0,0 +1,4 @@
++process handle SIGINT -n true -p true -s false
++process handle SIGUSR1 -n true -p true -s false
++process handle SIGUSR2 -n true -p true -s false
++process handle SIGXFSZ -n true -p true -s false
+diff --git a/Platforms/Apple/testbed/TestbedTests/TestbedTests.m b/Platforms/Apple/testbed/TestbedTests/TestbedTests.m
+new file mode 100644
+index 00000000000..f7788c47f2c
+--- /dev/null
++++ b/Platforms/Apple/testbed/TestbedTests/TestbedTests.m
+@@ -0,0 +1,200 @@
++#import
++#import
++
++@interface TestbedTests : XCTestCase
++
++@end
++
++@implementation TestbedTests
++
++
++- (void)testPython {
++ const char **argv;
++ int exit_code;
++ int failed;
++ PyStatus status;
++ PyPreConfig preconfig;
++ PyConfig config;
++ PyObject *app_packages_path;
++ PyObject *method_args;
++ PyObject *result;
++ PyObject *site_module;
++ PyObject *site_addsitedir_attr;
++ PyObject *sys_module;
++ PyObject *sys_path_attr;
++ NSArray *test_args;
++ NSString *python_home;
++ NSString *path;
++ wchar_t *wtmp_str;
++
++ NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
++
++ // Set some other common environment indicators to disable color, as the
++ // Xcode log can't display color. Stdout will report that it is *not* a
++ // TTY.
++ setenv("NO_COLOR", "1", true);
++ setenv("PYTHON_COLORS", "0", true);
++
++ if (getenv("GITHUB_ACTIONS")) {
++ NSLog(@"Running in a GitHub Actions environment");
++ }
++ // Arguments to pass into the test suite runner.
++ // argv[0] must identify the process; any subsequent arg
++ // will be handled as if it were an argument to `python -m test`
++ // The processInfo arguments contain the binary that is running,
++ // followed by the arguments defined in the test plan. This means:
++ // run_module = test_args[1]
++ // argv = ["Testbed"] + test_args[2:]
++ test_args = [[NSProcessInfo processInfo] arguments];
++ if (test_args == NULL) {
++ NSLog(@"Unable to identify test arguments.");
++ }
++ NSLog(@"Test arguments: %@", test_args);
++ argv = malloc(sizeof(char *) * ([test_args count] - 1));
++ argv[0] = "Testbed";
++ for (int i = 1; i < [test_args count] - 1; i++) {
++ argv[i] = [[test_args objectAtIndex:i+1] UTF8String];
++ }
++
++ // Generate an isolated Python configuration.
++ NSLog(@"Configuring isolated Python...");
++ PyPreConfig_InitIsolatedConfig(&preconfig);
++ PyConfig_InitIsolatedConfig(&config);
++
++ // Configure the Python interpreter:
++ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale.
++ // See https://docs.python.org/3/library/os.html#python-utf-8-mode.
++ preconfig.utf8_mode = 1;
++ // 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
++ // after it has been signed.
++ config.write_bytecode = 0;
++ // Ensure that signal handlers are installed
++ config.install_signal_handlers = 1;
++ // Run the test module.
++ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:1] UTF8String], NULL);
++ // For debugging - enable verbose mode.
++ // config.verbose = 1;
++
++ NSLog(@"Pre-initializing Python runtime...");
++ status = Py_PreInitialize(&preconfig);
++ if (PyStatus_Exception(status)) {
++ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg);
++ PyConfig_Clear(&config);
++ return;
++ }
++
++ // Set the home for the Python interpreter
++ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil];
++ NSLog(@"PythonHome: %@", python_home);
++ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL);
++ status = PyConfig_SetString(&config, &config.home, wtmp_str);
++ if (PyStatus_Exception(status)) {
++ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg);
++ PyConfig_Clear(&config);
++ return;
++ }
++ PyMem_RawFree(wtmp_str);
++
++ // Read the site config
++ status = PyConfig_Read(&config);
++ if (PyStatus_Exception(status)) {
++ XCTFail(@"Unable to read site config: %s", status.err_msg);
++ PyConfig_Clear(&config);
++ return;
++ }
++
++ NSLog(@"Configure argc/argv...");
++ status = PyConfig_SetBytesArgv(&config, [test_args count] - 1, (char**) argv);
++ if (PyStatus_Exception(status)) {
++ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg);
++ PyConfig_Clear(&config);
++ return;
++ }
++
++ NSLog(@"Initializing Python runtime...");
++ status = Py_InitializeFromConfig(&config);
++ if (PyStatus_Exception(status)) {
++ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg);
++ PyConfig_Clear(&config);
++ return;
++ }
++
++ // Add app_packages as a site directory. This both adds to sys.path,
++ // and ensures that any .pth files in that directory will be executed.
++ site_module = PyImport_ImportModule("site");
++ if (site_module == NULL) {
++ XCTFail(@"Could not import site module");
++ return;
++ }
++
++ site_addsitedir_attr = PyObject_GetAttrString(site_module, "addsitedir");
++ if (site_addsitedir_attr == NULL || !PyCallable_Check(site_addsitedir_attr)) {
++ XCTFail(@"Could not access site.addsitedir");
++ return;
++ }
++
++ path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil];
++ NSLog(@"App packages path: %@", path);
++ wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
++ app_packages_path = PyUnicode_FromWideChar(wtmp_str, wcslen(wtmp_str));
++ if (app_packages_path == NULL) {
++ XCTFail(@"Could not convert app_packages path to unicode");
++ return;
++ }
++ PyMem_RawFree(wtmp_str);
++
++ method_args = Py_BuildValue("(O)", app_packages_path);
++ if (method_args == NULL) {
++ XCTFail(@"Could not create arguments for site.addsitedir");
++ return;
++ }
++
++ result = PyObject_CallObject(site_addsitedir_attr, method_args);
++ if (result == NULL) {
++ XCTFail(@"Could not add app_packages directory using site.addsitedir");
++ return;
++ }
++
++ // Add test code to sys.path
++ sys_module = PyImport_ImportModule("sys");
++ if (sys_module == NULL) {
++ XCTFail(@"Could not import sys module");
++ return;
++ }
++
++ sys_path_attr = PyObject_GetAttrString(sys_module, "path");
++ if (sys_path_attr == NULL) {
++ XCTFail(@"Could not access sys.path");
++ return;
++ }
++
++ path = [NSString stringWithFormat:@"%@/app", resourcePath, nil];
++ NSLog(@"App path: %@", path);
++ wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
++ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
++ if (failed) {
++ XCTFail(@"Unable to add app to sys.path");
++ return;
++ }
++ PyMem_RawFree(wtmp_str);
++
++ // Ensure the working directory is the app folder.
++ chdir([path UTF8String]);
++
++ // Start the test suite. Print a separator to differentiate Python startup logs from app logs
++ NSLog(@"---------------------------------------------------------------------------");
++
++ exit_code = Py_RunMain();
++ XCTAssertEqual(exit_code, 0, @"Test suite did not pass");
++
++ NSLog(@"---------------------------------------------------------------------------");
++
++ Py_Finalize();
++}
++
++
++@end
+diff --git a/Platforms/Apple/testbed/__main__.py b/Platforms/Apple/testbed/__main__.py
+new file mode 100644
+index 00000000000..3f5b570a195
+--- /dev/null
++++ b/Platforms/Apple/testbed/__main__.py
+@@ -0,0 +1,456 @@
++import argparse
++import json
++import os
++import re
++import shlex
++import shutil
++import subprocess
++import sys
++from pathlib import Path
++
++TEST_SLICES = {
++ "iOS": "ios-arm64_x86_64-simulator",
++ "tvOS": "tvos-arm64_x86_64-simulator",
++ "visionOS": "xros-arm64-simulator",
++ "watchOS": "watchos-arm64_x86_64-simulator",
++}
++
++DECODE_ARGS = ("UTF-8", "backslashreplace")
++
++# The system log prefixes each line:
++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ...
++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ...
++
++LOG_PREFIX_REGEX = re.compile(
++ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD
++ r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ
++ r"\s+.*Testbed\[\d+:\w+\] " # Process/thread ID
++)
++
++
++# Select a simulator device to use.
++def select_simulator_device(platform):
++ # List the testing simulators, in JSON format
++ raw_json = subprocess.check_output(["xcrun", "simctl", "list", "-j"])
++ json_data = json.loads(raw_json)
++
++ if platform == "iOS":
++ # Any iOS device will do; we'll look for "SE" devices - but the name
++ # isn't consistent over time. Older Xcode versions will use "iPhone SE
++ # (Nth generation)"; As of 2025, they've started using "iPhone 16e".
++ #
++ # When Xcode is updated after a new release, new devices will be
++ # available and old ones will be dropped from the set available on the
++ # latest iOS version. Select the one with the highest minimum runtime
++ # version - this is an indicator of the "newest" released device, which
++ # should always be supported on the "most recent" iOS version.
++ se_simulators = sorted(
++ (devicetype["minRuntimeVersion"], devicetype["name"])
++ for devicetype in json_data["devicetypes"]
++ if devicetype["productFamily"] == "iPhone"
++ and (
++ (
++ "iPhone " in devicetype["name"]
++ and devicetype["name"].endswith("e")
++ )
++ or "iPhone SE " in devicetype["name"]
++ )
++ )
++ simulator = se_simulators[-1][1]
++ elif platform == "tvOS":
++ # Find the most recent tvOS release.
++ simulators = sorted(
++ (devicetype["minRuntimeVersion"], devicetype["name"])
++ for devicetype in json_data["devicetypes"]
++ if devicetype["productFamily"] == "Apple TV"
++ )
++ simulator = simulators[-1][1]
++ elif platform == "visionOS":
++ # Find the most recent visionOS release.
++ simulators = sorted(
++ (devicetype["minRuntimeVersion"], devicetype["name"])
++ for devicetype in json_data["devicetypes"]
++ if devicetype["productFamily"] == "Apple Vision"
++ )
++ simulator = simulators[-1][1]
++ elif platform == "watchOS":
++ raise NotImplementedError("Don't know how to launch watchOS (yet)")
++ else:
++ raise ValueError(f"Unknown platform {platform}")
++
++ return simulator
++
++
++def xcode_test(location: Path, platform: str, simulator: str, verbose: bool):
++ # Build and run the test suite on the named simulator.
++ args = [
++ "-project",
++ str(location / f"{platform}Testbed.xcodeproj"),
++ "-scheme",
++ f"{platform}Testbed",
++ "-destination",
++ f"platform={platform} Simulator,name={simulator}",
++ "-derivedDataPath",
++ str(location / "DerivedData"),
++ ]
++ verbosity_args = [] if verbose else ["-quiet"]
++
++ print("Building test project...")
++ subprocess.run(
++ ["xcodebuild", "build-for-testing"] + args + verbosity_args,
++ check=True,
++ )
++
++ # Any environment variable prefixed with TEST_RUNNER_ is exposed into the
++ # test runner environment. There are some variables (like those identifying
++ # CI platforms) that can be useful to have access to.
++ test_env = os.environ.copy()
++ if "GITHUB_ACTIONS" in os.environ:
++ test_env["TEST_RUNNER_GITHUB_ACTIONS"] = os.environ["GITHUB_ACTIONS"]
++
++ print("Running test project...")
++ # Test execution *can't* be run -quiet; verbose mode
++ # is how we see the output of the test output.
++ process = subprocess.Popen(
++ ["xcodebuild", "test-without-building"] + args,
++ stdout=subprocess.PIPE,
++ stderr=subprocess.STDOUT,
++ env=test_env,
++ )
++ while line := (process.stdout.readline()).decode(*DECODE_ARGS):
++ # Strip the timestamp/process prefix from each log line
++ line = LOG_PREFIX_REGEX.sub("", line)
++ sys.stdout.write(line)
++ sys.stdout.flush()
++
++ status = process.wait(timeout=5)
++ exit(status)
++
++
++def copy(src, tgt):
++ """An all-purpose copy.
++
++ If src is a file, it is copied. If src is a symlink, it is copied *as a
++ symlink*. If src is a directory, the full tree is duplicated, with symlinks
++ being preserved.
++ """
++ if src.is_file() or src.is_symlink():
++ shutil.copyfile(src, tgt, follow_symlinks=False)
++ else:
++ shutil.copytree(src, tgt, symlinks=True)
++
++
++def clone_testbed(
++ source: Path,
++ target: Path,
++ framework: Path,
++ platform: str,
++ apps: list[Path],
++) -> None:
++ if target.exists():
++ print(f"{target} already exists; aborting without creating project.")
++ sys.exit(10)
++
++ if framework is None:
++ if not (
++ source / "Python.xcframework" / TEST_SLICES[platform] / "bin"
++ ).is_dir():
++ print(
++ f"The testbed being cloned ({source}) does not contain "
++ "a framework with slices. Re-run with --framework"
++ )
++ sys.exit(11)
++ else:
++ if not framework.is_dir():
++ print(f"{framework} does not exist.")
++ sys.exit(12)
++ elif not (
++ framework.suffix == ".xcframework"
++ or (framework / "Python.framework").is_dir()
++ ):
++ print(
++ f"{framework} is not an XCframework, "
++ f"or a simulator slice of a framework build."
++ )
++ sys.exit(13)
++
++ print("Cloning testbed project:")
++ print(f" Cloning {source}...", end="")
++ # Only copy the files for the platform being cloned plus the files common
++ # to all platforms. The XCframework will be copied later, if needed.
++ target.mkdir(parents=True)
++
++ for name in [
++ "__main__.py",
++ "TestbedTests",
++ "Testbed.lldbinit",
++ f"{platform}Testbed",
++ f"{platform}Testbed.xcodeproj",
++ f"{platform}Testbed.xctestplan",
++ ]:
++ copy(source / name, target / name)
++
++ print(" done")
++
++ orig_xc_framework_path = source / "Python.xcframework"
++ xc_framework_path = target / "Python.xcframework"
++ test_framework_path = xc_framework_path / TEST_SLICES[platform]
++ if framework is not None:
++ if framework.suffix == ".xcframework":
++ print(" Installing XCFramework...", end="")
++ xc_framework_path.symlink_to(
++ framework.relative_to(xc_framework_path.parent, walk_up=True)
++ )
++ print(" done")
++ else:
++ print(" Installing simulator framework...", end="")
++ # We're only installing a slice of a framework; we need
++ # to do a full tree copy to make sure we don't damage
++ # symlinked content.
++ shutil.copytree(orig_xc_framework_path, xc_framework_path)
++ if test_framework_path.is_dir():
++ shutil.rmtree(test_framework_path)
++ else:
++ test_framework_path.unlink(missing_ok=True)
++ test_framework_path.symlink_to(
++ framework.relative_to(test_framework_path.parent, walk_up=True)
++ )
++ print(" done")
++ else:
++ copy(orig_xc_framework_path, xc_framework_path)
++
++ 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="")
++ resolved_xc_framework_path = (
++ source / xc_framework_path.readlink()
++ ).resolve()
++ xc_framework_path.unlink()
++ xc_framework_path.symlink_to(
++ resolved_xc_framework_path.relative_to(
++ xc_framework_path.parent, walk_up=True
++ )
++ )
++ print(" done")
++ elif (
++ test_framework_path.is_symlink()
++ and not test_framework_path.readlink().is_absolute()
++ ):
++ print(" Rewriting symlink to simulator framework...", end="")
++ # Simulator framework is a relative symlink. Rewrite the symlink
++ # relative to the new location.
++ orig_test_framework_path = (
++ source / "Python.XCframework" / test_framework_path.readlink()
++ ).resolve()
++ test_framework_path.unlink()
++ test_framework_path.symlink_to(
++ orig_test_framework_path.relative_to(
++ test_framework_path.parent, walk_up=True
++ )
++ )
++ print(" done")
++ else:
++ print(" Using pre-existing Python framework.")
++
++ for app_src in apps:
++ print(f" Installing app {app_src.name!r}...", end="")
++ app_target = target / f"Testbed/app/{app_src.name}"
++ if app_target.is_dir():
++ shutil.rmtree(app_target)
++ shutil.copytree(app_src, app_target)
++ print(" done")
++
++ print(f"Successfully cloned testbed: {target.resolve()}")
++
++
++def update_test_plan(testbed_path, platform, args):
++ # Modify the test plan to use the requested test arguments.
++ test_plan_path = testbed_path / f"{platform}Testbed.xctestplan"
++ with test_plan_path.open("r", encoding="utf-8") as f:
++ test_plan = json.load(f)
++
++ test_plan["defaultOptions"]["commandLineArgumentEntries"] = [
++ {"argument": shlex.quote(arg)} for arg in args
++ ]
++
++ with test_plan_path.open("w", encoding="utf-8") as f:
++ json.dump(test_plan, f, indent=2)
++
++
++def run_testbed(
++ platform: str,
++ simulator: str | None,
++ args: list[str],
++ verbose: bool = False,
++):
++ location = Path(__file__).parent
++ print("Updating test plan...", end="")
++ update_test_plan(location, platform, args)
++ print(" done.")
++
++ if simulator is None:
++ simulator = select_simulator_device(platform)
++ print(f"Running test on {simulator}")
++
++ xcode_test(
++ location,
++ platform=platform,
++ simulator=simulator,
++ verbose=verbose,
++ )
++
++
++def main():
++ # Look for directories like `iOSTestbed` as an indicator of the platforms
++ # that the testbed folder supports. The original source testbed can support
++ # many platforms, but when cloned, only one platform is preserved.
++ available_platforms = [
++ platform
++ for platform in ["iOS", "tvOS", "visionOS", "watchOS"]
++ if (Path(__file__).parent / f"{platform}Testbed").is_dir()
++ ]
++
++ parser = argparse.ArgumentParser(
++ description=(
++ "Manages the process of testing an Apple Python project "
++ "through Xcode."
++ ),
++ )
++
++ subcommands = parser.add_subparsers(dest="subcommand")
++ clone = subcommands.add_parser(
++ "clone",
++ description=(
++ "Clone the testbed project, copying in a Python framework and"
++ "any specified application code."
++ ),
++ help="Clone a testbed project to a new location.",
++ )
++ clone.add_argument(
++ "--framework",
++ help=(
++ "The location of the XCFramework (or simulator-only slice of an "
++ "XCFramework) to use when running the testbed"
++ ),
++ )
++ clone.add_argument(
++ "--platform",
++ dest="platform",
++ choices=available_platforms,
++ default=available_platforms[0],
++ help=f"The platform to target (default: {available_platforms[0]})",
++ )
++ clone.add_argument(
++ "--app",
++ dest="apps",
++ action="append",
++ default=[],
++ help="The location of any code to include in the testbed project",
++ )
++ clone.add_argument(
++ "location",
++ help="The path where the testbed will be cloned.",
++ )
++
++ run = subcommands.add_parser(
++ "run",
++ usage=(
++ "%(prog)s [-h] [--simulator SIMULATOR] -- "
++ " [ ...]"
++ ),
++ description=(
++ "Run a testbed project. The arguments provided after `--` will be "
++ "passed to the running test process as if they were arguments to "
++ "`python -m`."
++ ),
++ help="Run a testbed project",
++ )
++ run.add_argument(
++ "--platform",
++ dest="platform",
++ choices=available_platforms,
++ default=available_platforms[0],
++ help=f"The platform to target (default: {available_platforms[0]})",
++ )
++ run.add_argument(
++ "--simulator",
++ help=(
++ "The name of the simulator to use (eg: 'iPhone 16e'). Defaults to "
++ "the most recently released 'entry level' iPhone device. Device "
++ "architecture and OS version can also be specified; e.g., "
++ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would run on "
++ "an ARM64 iPhone 16 Pro simulator running iOS 26.0."
++ ),
++ )
++ run.add_argument(
++ "-v",
++ "--verbose",
++ action="store_true",
++ help="Enable verbose output",
++ )
++
++ try:
++ pos = sys.argv.index("--")
++ testbed_args = sys.argv[1:pos]
++ test_args = sys.argv[pos + 1 :]
++ except ValueError:
++ testbed_args = sys.argv[1:]
++ test_args = []
++
++ context = parser.parse_args(testbed_args)
++
++ if context.subcommand == "clone":
++ clone_testbed(
++ source=Path(__file__).parent.resolve(),
++ target=Path(context.location).resolve(),
++ framework=Path(context.framework).resolve()
++ if context.framework
++ else None,
++ platform=context.platform,
++ apps=[Path(app) for app in context.apps],
++ )
++ elif context.subcommand == "run":
++ if test_args:
++ if not (
++ Path(__file__).parent
++ / "Python.xcframework"
++ / TEST_SLICES[context.platform]
++ / "bin"
++ ).is_dir():
++ print(
++ "Testbed does not contain a compiled Python framework. "
++ f"Use `python {sys.argv[0]} clone ...` to create a "
++ "runnable clone of this testbed."
++ )
++ sys.exit(20)
++
++ run_testbed(
++ platform=context.platform,
++ simulator=context.simulator,
++ verbose=context.verbose,
++ args=test_args,
++ )
++ else:
++ print(
++ "Must specify test arguments "
++ f"(e.g., {sys.argv[0]} run -- test)"
++ )
++ print()
++ parser.print_help(sys.stderr)
++ sys.exit(21)
++ else:
++ parser.print_help(sys.stderr)
++ sys.exit(1)
++
++
++if __name__ == "__main__":
++ # Under the buildbot, stdout is not a TTY, but we must still flush after
++ # every line to make sure our output appears in the correct order relative
++ # to the output of our subprocesses.
++ for stream in [sys.stdout, sys.stderr]:
++ stream.reconfigure(line_buffering=True)
++ main()
+diff --git a/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj
+new file mode 100644
+index 00000000000..f8835a3bc58
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj
+@@ -0,0 +1,557 @@
++// !$*UTF8*$!
++{
++ archiveVersion = 1;
++ classes = {
++ };
++ objectVersion = 56;
++ objects = {
++
++/* Begin PBXBuildFile section */
++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; };
++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; };
++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; };
++ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; };
++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */; };
++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; };
++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; };
++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
++ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; };
++ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; };
++/* End PBXBuildFile section */
++
++/* Begin PBXContainerItemProxy section */
++ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = {
++ isa = PBXContainerItemProxy;
++ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */;
++ proxyType = 1;
++ remoteGlobalIDString = 607A66112B0EFA380010BFC8;
++ remoteInfo = iOSTestbed;
++ };
++/* End PBXContainerItemProxy section */
++
++/* Begin PBXCopyFilesBuildPhase section */
++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = {
++ isa = PBXCopyFilesBuildPhase;
++ buildActionMask = 2147483647;
++ dstPath = "";
++ dstSubfolderSpec = 10;
++ files = (
++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */,
++ );
++ name = "Embed Frameworks";
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = {
++ isa = PBXCopyFilesBuildPhase;
++ buildActionMask = 2147483647;
++ dstPath = "";
++ dstSubfolderSpec = 10;
++ files = (
++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */,
++ );
++ name = "Embed Frameworks";
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++/* End PBXCopyFilesBuildPhase section */
++
++/* Begin PBXFileReference section */
++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; };
++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
++ 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
++ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestbedTests.m; sourceTree = ""; };
++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; };
++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = ""; };
++ 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; };
++ 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; };
++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = iOSTestbed.xctestplan; sourceTree = ""; };
++/* End PBXFileReference section */
++
++/* Begin PBXFrameworksBuildPhase section */
++ 607A660F2B0EFA380010BFC8 /* Frameworks */ = {
++ isa = PBXFrameworksBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */,
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = {
++ isa = PBXFrameworksBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */,
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++/* End PBXFrameworksBuildPhase section */
++
++/* Begin PBXGroup section */
++ 607A66092B0EFA380010BFC8 = {
++ isa = PBXGroup;
++ children = (
++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */,
++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */,
++ 607A66142B0EFA380010BFC8 /* iOSTestbed */,
++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */,
++ 607A66132B0EFA380010BFC8 /* Products */,
++ 607A664F2B0EFFE00010BFC8 /* Frameworks */,
++ );
++ sourceTree = "";
++ };
++ 607A66132B0EFA380010BFC8 /* Products */ = {
++ isa = PBXGroup;
++ children = (
++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */,
++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */,
++ );
++ name = Products;
++ sourceTree = "";
++ };
++ 607A66142B0EFA380010BFC8 /* iOSTestbed */ = {
++ isa = PBXGroup;
++ children = (
++ 608619552CB7819B00F46182 /* app */,
++ 608619532CB77BA900F46182 /* app_packages */,
++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */,
++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */,
++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */,
++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */,
++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */,
++ 607A66272B0EFA390010BFC8 /* main.m */,
++ );
++ path = iOSTestbed;
++ sourceTree = "";
++ };
++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = {
++ isa = PBXGroup;
++ children = (
++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */,
++ );
++ path = TestbedTests;
++ sourceTree = "";
++ };
++ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = {
++ isa = PBXGroup;
++ children = (
++ );
++ name = Frameworks;
++ sourceTree = "";
++ };
++/* End PBXGroup section */
++
++/* Begin PBXNativeTarget section */
++ 607A66112B0EFA380010BFC8 /* iOSTestbed */ = {
++ isa = PBXNativeTarget;
++ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */;
++ buildPhases = (
++ 607A660E2B0EFA380010BFC8 /* Sources */,
++ 607A660F2B0EFA380010BFC8 /* Frameworks */,
++ 607A66102B0EFA380010BFC8 /* Resources */,
++ 607A66552B0F061D0010BFC8 /* Process Python libraries */,
++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */,
++ );
++ buildRules = (
++ );
++ dependencies = (
++ );
++ name = iOSTestbed;
++ productName = iOSTestbed;
++ productReference = 607A66122B0EFA380010BFC8 /* iOSTestbed.app */;
++ productType = "com.apple.product-type.application";
++ };
++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */ = {
++ isa = PBXNativeTarget;
++ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */;
++ buildPhases = (
++ 607A66292B0EFA3A0010BFC8 /* Sources */,
++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */,
++ 607A662B2B0EFA3A0010BFC8 /* Resources */,
++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */,
++ );
++ buildRules = (
++ );
++ dependencies = (
++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */,
++ );
++ name = iOSTestbedTests;
++ productName = iOSTestbedTests;
++ productReference = 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */;
++ productType = "com.apple.product-type.bundle.unit-test";
++ };
++/* End PBXNativeTarget section */
++
++/* Begin PBXProject section */
++ 607A660A2B0EFA380010BFC8 /* Project object */ = {
++ isa = PBXProject;
++ attributes = {
++ BuildIndependentTargetsInParallel = 1;
++ LastUpgradeCheck = 1500;
++ TargetAttributes = {
++ 607A66112B0EFA380010BFC8 = {
++ CreatedOnToolsVersion = 15.0.1;
++ };
++ 607A662C2B0EFA3A0010BFC8 = {
++ CreatedOnToolsVersion = 15.0.1;
++ TestTargetID = 607A66112B0EFA380010BFC8;
++ };
++ };
++ };
++ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */;
++ compatibilityVersion = "Xcode 14.0";
++ developmentRegion = en;
++ hasScannedForEncodings = 0;
++ knownRegions = (
++ en,
++ Base,
++ );
++ mainGroup = 607A66092B0EFA380010BFC8;
++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */;
++ projectDirPath = "";
++ projectRoot = "";
++ targets = (
++ 607A66112B0EFA380010BFC8 /* iOSTestbed */,
++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */,
++ );
++ };
++/* End PBXProject section */
++
++/* Begin PBXResourcesBuildPhase section */
++ 607A66102B0EFA380010BFC8 /* Resources */ = {
++ isa = PBXResourcesBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */,
++ 608619562CB7819B00F46182 /* app in Resources */,
++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */,
++ 608619542CB77BA900F46182 /* app_packages in Resources */,
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++ 607A662B2B0EFA3A0010BFC8 /* Resources */ = {
++ isa = PBXResourcesBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++/* End PBXResourcesBuildPhase section */
++
++/* Begin PBXShellScriptBuildPhase section */
++ 607A66552B0F061D0010BFC8 /* Process Python libraries */ = {
++ isa = PBXShellScriptBuildPhase;
++ alwaysOutOfDate = 1;
++ buildActionMask = 2147483647;
++ files = (
++ );
++ inputFileListPaths = (
++ );
++ inputPaths = (
++ );
++ name = "Process Python libraries";
++ outputFileListPaths = (
++ );
++ outputPaths = (
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ shellPath = /bin/sh;
++ shellScript = "set -e\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\ninstall_python Python.xcframework app app_packages\n";
++ showEnvVarsInLog = 0;
++ };
++/* End PBXShellScriptBuildPhase section */
++
++/* Begin PBXSourcesBuildPhase section */
++ 607A660E2B0EFA380010BFC8 /* Sources */ = {
++ isa = PBXSourcesBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */,
++ 607A66282B0EFA390010BFC8 /* main.m in Sources */,
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++ 607A66292B0EFA3A0010BFC8 /* Sources */ = {
++ isa = PBXSourcesBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */,
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++/* End PBXSourcesBuildPhase section */
++
++/* Begin PBXTargetDependency section */
++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = {
++ isa = PBXTargetDependency;
++ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */;
++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */;
++ };
++/* End PBXTargetDependency section */
++
++/* Begin PBXVariantGroup section */
++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = {
++ isa = PBXVariantGroup;
++ children = (
++ 607A66242B0EFA390010BFC8 /* Base */,
++ );
++ name = LaunchScreen.storyboard;
++ sourceTree = "";
++ };
++/* End PBXVariantGroup section */
++
++/* Begin XCBuildConfiguration section */
++ 607A663F2B0EFA3A0010BFC8 /* Debug */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ALWAYS_SEARCH_USER_PATHS = NO;
++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
++ CLANG_ANALYZER_NONNULL = YES;
++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
++ CLANG_ENABLE_MODULES = YES;
++ CLANG_ENABLE_OBJC_ARC = YES;
++ CLANG_ENABLE_OBJC_WEAK = YES;
++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
++ CLANG_WARN_BOOL_CONVERSION = YES;
++ CLANG_WARN_COMMA = YES;
++ CLANG_WARN_CONSTANT_CONVERSION = YES;
++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
++ CLANG_WARN_EMPTY_BODY = YES;
++ CLANG_WARN_ENUM_CONVERSION = YES;
++ CLANG_WARN_INFINITE_RECURSION = YES;
++ CLANG_WARN_INT_CONVERSION = YES;
++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
++ CLANG_WARN_STRICT_PROTOTYPES = YES;
++ CLANG_WARN_SUSPICIOUS_MOVE = YES;
++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
++ CLANG_WARN_UNREACHABLE_CODE = YES;
++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
++ COPY_PHASE_STRIP = NO;
++ DEBUG_INFORMATION_FORMAT = dwarf;
++ ENABLE_STRICT_OBJC_MSGSEND = YES;
++ ENABLE_TESTABILITY = YES;
++ ENABLE_USER_SCRIPT_SANDBOXING = YES;
++ GCC_C_LANGUAGE_STANDARD = gnu17;
++ GCC_DYNAMIC_NO_PIC = NO;
++ GCC_NO_COMMON_BLOCKS = YES;
++ GCC_OPTIMIZATION_LEVEL = 0;
++ GCC_PREPROCESSOR_DEFINITIONS = (
++ "DEBUG=1",
++ "$(inherited)",
++ );
++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
++ GCC_WARN_UNDECLARED_SELECTOR = YES;
++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
++ GCC_WARN_UNUSED_FUNCTION = YES;
++ GCC_WARN_UNUSED_VARIABLE = YES;
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
++ MTL_FAST_MATH = YES;
++ ONLY_ACTIVE_ARCH = YES;
++ SDKROOT = iphoneos;
++ };
++ name = Debug;
++ };
++ 607A66402B0EFA3A0010BFC8 /* Release */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ALWAYS_SEARCH_USER_PATHS = NO;
++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
++ CLANG_ANALYZER_NONNULL = YES;
++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
++ CLANG_ENABLE_MODULES = YES;
++ CLANG_ENABLE_OBJC_ARC = YES;
++ CLANG_ENABLE_OBJC_WEAK = YES;
++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
++ CLANG_WARN_BOOL_CONVERSION = YES;
++ CLANG_WARN_COMMA = YES;
++ CLANG_WARN_CONSTANT_CONVERSION = YES;
++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
++ CLANG_WARN_EMPTY_BODY = YES;
++ CLANG_WARN_ENUM_CONVERSION = YES;
++ CLANG_WARN_INFINITE_RECURSION = YES;
++ CLANG_WARN_INT_CONVERSION = YES;
++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
++ CLANG_WARN_STRICT_PROTOTYPES = YES;
++ CLANG_WARN_SUSPICIOUS_MOVE = YES;
++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
++ CLANG_WARN_UNREACHABLE_CODE = YES;
++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
++ COPY_PHASE_STRIP = NO;
++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
++ ENABLE_NS_ASSERTIONS = NO;
++ ENABLE_STRICT_OBJC_MSGSEND = YES;
++ ENABLE_USER_SCRIPT_SANDBOXING = YES;
++ GCC_C_LANGUAGE_STANDARD = gnu17;
++ GCC_NO_COMMON_BLOCKS = YES;
++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
++ GCC_WARN_UNDECLARED_SELECTOR = YES;
++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
++ GCC_WARN_UNUSED_FUNCTION = YES;
++ GCC_WARN_UNUSED_VARIABLE = YES;
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
++ MTL_ENABLE_DEBUG_INFO = NO;
++ MTL_FAST_MATH = YES;
++ SDKROOT = iphoneos;
++ VALIDATE_PRODUCT = YES;
++ };
++ name = Release;
++ };
++ 607A66422B0EFA3A0010BFC8 /* Debug */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = "";
++ ENABLE_USER_SCRIPT_SANDBOXING = NO;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist";
++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
++ INFOPLIST_KEY_UIMainStoryboardFile = Main;
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LD_RUNPATH_SEARCH_PATHS = (
++ "$(inherited)",
++ "@executable_path/Frameworks",
++ );
++ MARKETING_VERSION = 3.13.0a1;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = YES;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ };
++ name = Debug;
++ };
++ 607A66432B0EFA3A0010BFC8 /* Release */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = "";
++ ENABLE_TESTABILITY = YES;
++ ENABLE_USER_SCRIPT_SANDBOXING = NO;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist";
++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
++ INFOPLIST_KEY_UIMainStoryboardFile = Main;
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LD_RUNPATH_SEARCH_PATHS = (
++ "$(inherited)",
++ "@executable_path/Frameworks",
++ );
++ MARKETING_VERSION = 3.13.0a1;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = YES;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ };
++ name = Release;
++ };
++ 607A66452B0EFA3A0010BFC8 /* Debug */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ BUNDLE_LOADER = "$(TEST_HOST)";
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = 3HEZE76D99;
++ GENERATE_INFOPLIST_FILE = YES;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ MARKETING_VERSION = 1.0;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = NO;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed";
++ };
++ name = Debug;
++ };
++ 607A66462B0EFA3A0010BFC8 /* Release */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ BUNDLE_LOADER = "$(TEST_HOST)";
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = 3HEZE76D99;
++ GENERATE_INFOPLIST_FILE = YES;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ MARKETING_VERSION = 1.0;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = NO;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed";
++ };
++ name = Release;
++ };
++/* End XCBuildConfiguration section */
++
++/* Begin XCConfigurationList section */
++ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */ = {
++ isa = XCConfigurationList;
++ buildConfigurations = (
++ 607A663F2B0EFA3A0010BFC8 /* Debug */,
++ 607A66402B0EFA3A0010BFC8 /* Release */,
++ );
++ defaultConfigurationIsVisible = 0;
++ defaultConfigurationName = Release;
++ };
++ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */ = {
++ isa = XCConfigurationList;
++ buildConfigurations = (
++ 607A66422B0EFA3A0010BFC8 /* Debug */,
++ 607A66432B0EFA3A0010BFC8 /* Release */,
++ );
++ defaultConfigurationIsVisible = 0;
++ defaultConfigurationName = Release;
++ };
++ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */ = {
++ isa = XCConfigurationList;
++ buildConfigurations = (
++ 607A66452B0EFA3A0010BFC8 /* Debug */,
++ 607A66462B0EFA3A0010BFC8 /* Release */,
++ );
++ defaultConfigurationIsVisible = 0;
++ defaultConfigurationName = Release;
++ };
++/* End XCConfigurationList section */
++ };
++ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */;
++}
+diff --git a/Platforms/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme
+new file mode 100644
+index 00000000000..3c330a4152b
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme
+@@ -0,0 +1,97 @@
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
+diff --git a/Platforms/Apple/testbed/iOSTestbed.xctestplan b/Platforms/Apple/testbed/iOSTestbed.xctestplan
+new file mode 100644
+index 00000000000..0c4ab9eb2ba
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed.xctestplan
+@@ -0,0 +1,46 @@
++{
++ "configurations" : [
++ {
++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5",
++ "name" : "Test Scheme Action",
++ "options" : {
++
++ }
++ }
++ ],
++ "defaultOptions" : {
++ "commandLineArgumentEntries" : [
++ {
++ "argument" : "test"
++ },
++ {
++ "argument" : "-uall"
++ },
++ {
++ "argument" : "--single-process"
++ },
++ {
++ "argument" : "--rerun"
++ },
++ {
++ "argument" : "-W"
++ }
++ ],
++ "targetForVariableExpansion" : {
++ "containerPath" : "container:iOSTestbed.xcodeproj",
++ "identifier" : "607A66112B0EFA380010BFC8",
++ "name" : "iOSTestbed"
++ }
++ },
++ "testTargets" : [
++ {
++ "parallelizable" : false,
++ "target" : {
++ "containerPath" : "container:iOSTestbed.xcodeproj",
++ "identifier" : "607A662C2B0EFA3A0010BFC8",
++ "name" : "iOSTestbedTests"
++ }
++ }
++ ],
++ "version" : 1
++}
+diff --git a/Platforms/Apple/testbed/iOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.h
+new file mode 100644
+index 00000000000..f695b3b5efc
--- /dev/null
-+++ b/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist
-@@ -0,0 +1,26 @@
++++ b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.h
+@@ -0,0 +1,11 @@
++//
++// AppDelegate.h
++// iOSTestbed
++//
++
++#import
++
++@interface AppDelegate : UIResponder
++
++
++@end
+diff --git a/Platforms/Apple/testbed/iOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.m
+new file mode 100644
+index 00000000000..e5085399d0c
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.m
+@@ -0,0 +1,19 @@
++//
++// AppDelegate.m
++// iOSTestbed
++//
++
++#import "AppDelegate.h"
++
++@interface AppDelegate ()
++
++@end
++
++@implementation AppDelegate
++
++
++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
++ return YES;
++}
++
++@end
+diff --git a/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
+new file mode 100644
+index 00000000000..eb878970081
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
+@@ -0,0 +1,11 @@
++{
++ "colors" : [
++ {
++ "idiom" : "universal"
++ }
++ ],
++ "info" : {
++ "author" : "xcode",
++ "version" : 1
++ }
++}
+diff --git a/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json
+new file mode 100644
+index 00000000000..13613e3ee1a
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json
+@@ -0,0 +1,13 @@
++{
++ "images" : [
++ {
++ "idiom" : "universal",
++ "platform" : "ios",
++ "size" : "1024x1024"
++ }
++ ],
++ "info" : {
++ "author" : "xcode",
++ "version" : 1
++ }
++}
+diff --git a/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json
+new file mode 100644
+index 00000000000..73c00596a7f
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json
+@@ -0,0 +1,6 @@
++{
++ "info" : {
++ "author" : "xcode",
++ "version" : 1
++ }
++}
+diff --git a/Platforms/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard b/Platforms/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard
+new file mode 100644
+index 00000000000..5daafe73a86
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard
+@@ -0,0 +1,9 @@
+
-+
-+
-+
-+ CFBundleDevelopmentRegion
-+ en
-+ CFBundleExecutable
-+
-+ CFBundleIdentifier
-+
-+ CFBundleInfoDictionaryVersion
-+ 6.0
-+ CFBundlePackageType
-+ APPL
-+ CFBundleShortVersionString
-+ 1.0
-+ CFBundleSupportedPlatforms
-+
-+ tvOS
-+
-+ MinimumOSVersion
-+ 9.0
-+ CFBundleVersion
-+ 1
-+
-+
-diff --git a/Apple/testbed/Python.xcframework/build/utils.sh b/Apple/testbed/Python.xcframework/build/utils.sh
-index e7155d8b30e..aa56a29dcbd 100755
---- a/Apple/testbed/Python.xcframework/build/utils.sh
-+++ b/Apple/testbed/Python.xcframework/build/utils.sh
-@@ -34,9 +34,38 @@
- else
- SLICE_FOLDER="ios-arm64_x86_64-simulator"
- fi
-- else
-+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then
- echo "Installing Python modules for iOS Device"
- SLICE_FOLDER="ios-arm64"
-+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then
-+ echo "Installing Python modules for tvOS Simulator"
-+ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then
-+ SLICE_FOLDER="tvos-arm64-simulator"
-+ else
-+ SLICE_FOLDER="tvos-arm64_x86_64-simulator"
-+ fi
-+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then
-+ echo "Installing Python modules for tvOS Device"
-+ SLICE_FOLDER="tvos-arm64"
-+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then
-+ echo "Installing Python modules for watchOS Simulator"
-+ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then
-+ SLICE_FOLDER="watchos-arm64-simulator"
-+ else
-+ SLICE_FOLDER="watchos-arm64_x86_64-simulator"
-+ fi
-+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then
-+ echo "Installing Python modules for watchOS Device"
-+ SLICE_FOLDER="watchos-arm64"
-+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xrsimulator" ]; then
-+ echo "Installing Python modules for visionOS Simulator"
-+ SLICE_FOLDER="xros-arm64-simulator"
-+ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xros" ]; then
-+ echo "Installing Python modules for visionOS Device"
-+ SLICE_FOLDER="xros-arm64"
-+ else
-+ echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME"
-+ exit 1
- fi
-
- # If the XCframework has a shared lib folder, then it's a full framework.
++
++
++
++
++
++
++
++
+diff --git a/Platforms/Apple/testbed/iOSTestbed/app/README b/Platforms/Apple/testbed/iOSTestbed/app/README
+new file mode 100644
+index 00000000000..46c0e8e2a29
--- /dev/null
-+++ b/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist
-@@ -0,0 +1,26 @@
++++ b/Platforms/Apple/testbed/iOSTestbed/app/README
+@@ -0,0 +1,7 @@
++This folder can contain any Python application code.
++
++During the build, any binary modules found in this folder will be processed into
++Framework form.
++
++When the test suite runs, this folder will be on the PYTHONPATH, and will be the
++working directory for the test suite.
+diff --git a/Platforms/Apple/testbed/iOSTestbed/app_packages/README b/Platforms/Apple/testbed/iOSTestbed/app_packages/README
+new file mode 100644
+index 00000000000..02c2beccfbd
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed/app_packages/README
+@@ -0,0 +1,7 @@
++This folder can be a target for installing any Python dependencies needed by the
++test suite.
++
++During the build, any binary modules found in this folder will be processed into
++Framework form.
++
++When the test suite runs, this folder will be on the PYTHONPATH.
+diff --git a/Platforms/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist b/Platforms/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist
+new file mode 100644
+index 00000000000..fea45e1fad6
+--- /dev/null
++++ b/Platforms/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist
+@@ -0,0 +1,52 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
++ CFBundleDisplayName
++ ${PRODUCT_NAME}
+ CFBundleExecutable
-+
++ ${EXECUTABLE_NAME}
+ CFBundleIdentifier
-+
++ org.python.iOSTestbed
+ CFBundleInfoDictionaryVersion
+ 6.0
++ CFBundleName
++ ${PRODUCT_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
-+ CFBundleSupportedPlatforms
-+
-+ watchOS
-+
-+ MinimumOSVersion
-+ 4.0
++ CFBundleSignature
++ ????
+ CFBundleVersion
+ 1
-+
-+
---- /dev/null
-+++ b/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist
-@@ -0,0 +1,30 @@
-+
-+
-+
-+
-+ CFBundleDevelopmentRegion
-+ en
-+ CFBundleInfoDictionaryVersion
-+ 6.0
-+ CFBundleExecutable
-+
-+ CFBundleIdentifier
-+
-+ CFBundlePackageType
-+ FMWK
-+ CFBundleShortVersionString
-+ 1.0
-+ CFBundleSupportedPlatforms
++ LSRequiresIPhoneOS
++
++ UIRequiresFullScreen
++
++ UILaunchStoryboardName
++ Launch Screen
++ UISupportedInterfaceOrientations
+
-+ XROS
++ UIInterfaceOrientationPortrait
++ UIInterfaceOrientationLandscapeLeft
++ UIInterfaceOrientationLandscapeRight
+
-+ CFBundleVersion
-+ 1
-+ MinimumOSVersion
-+ 2.0
-+ UIDeviceFamily
++ UISupportedInterfaceOrientations~ipad
+
-+ 7
++ UIInterfaceOrientationPortrait
++ UIInterfaceOrientationPortraitUpsideDown
++ UIInterfaceOrientationLandscapeLeft
++ UIInterfaceOrientationLandscapeRight
+
++ UIApplicationSceneManifest
++
++ UIApplicationSupportsMultipleScenes
++
++ UISceneConfigurations
++
++
+
+
+diff --git a/Platforms/Apple/testbed/iOSTestbed/main.m b/Platforms/Apple/testbed/iOSTestbed/main.m
+new file mode 100644
+index 00000000000..e32bd78c9b4
--- /dev/null
-+++ b/Apple/testbed/Python.xcframework/tvos-arm64/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
-+
-+It should be used as a target for `--enable-framework` when compiling a tvOS
-+on-device build for testing purposes.
---- /dev/null
-+++ b/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
-+
-+It should be used as a target for `--enable-framework` when compiling a tvOS
-+simulator build for testing purposes (either x86_64 or ARM64).
---- /dev/null
-+++ b/Apple/testbed/Python.xcframework/watchos-arm64_32/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
-+
-+It should be used as a target for `--enable-framework` when compiling a watchOS on-device
-+build for testing purposes.
---- /dev/null
-+++ b/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
-+
-+It should be used as a target for `--enable-framework` when compiling a watchOS
-+simulator build for testing purposes (either x86_64 or ARM64).
---- /dev/null
-+++ b/Apple/testbed/Python.xcframework/xros-arm64-simulator/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
++++ b/Platforms/Apple/testbed/iOSTestbed/main.m
+@@ -0,0 +1,16 @@
++//
++// main.m
++// iOSTestbed
++//
+
-+It should be used as a target for `--enable-framework` when compiling an visionOS simulator
-+build for testing purposes (either x86_64 or ARM64).
---- /dev/null
-+++ b/Apple/testbed/Python.xcframework/xros-arm64/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
++#import
++#import "AppDelegate.h"
+
-+It should be used as a target for `--enable-framework` when compiling an visionOS on-device
-+build for testing purposes.
-diff --git a/Apple/testbed/__main__.py b/Apple/testbed/__main__.py
-index 0dd77ab8b82..0b9bdba36a4 100644
---- a/Apple/testbed/__main__.py
-+++ b/Apple/testbed/__main__.py
-@@ -10,6 +10,9 @@
-
- TEST_SLICES = {
- "iOS": "ios-arm64_x86_64-simulator",
-+ "tvOS": "tvos-arm64_x86_64-simulator",
-+ "visionOS": "xros-arm64-simulator",
-+ "watchOS": "watchos-arm64_x86_64-simulator",
- }
-
- DECODE_ARGS = ("UTF-8", "backslashreplace")
-@@ -21,7 +24,7 @@
- LOG_PREFIX_REGEX = re.compile(
- r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD
- r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ
-- r"\s+iOSTestbed\[\d+:\w+\]" # Process/thread ID
-+ r"\s+.*Testbed\[\d+:\w+\]" # Process/thread ID
- )
-
-
-@@ -54,6 +57,24 @@
- )
- )
- simulator = se_simulators[-1][1]
-+ elif platform == "tvOS":
-+ # Find the most recent tvOS release.
-+ simulators = sorted(
-+ (devicetype["minRuntimeVersion"], devicetype["name"])
-+ for devicetype in json_data["devicetypes"]
-+ if devicetype["productFamily"] == "Apple TV"
-+ )
-+ simulator = simulators[-1][1]
-+ elif platform == "visionOS":
-+ # Find the most recent visionOS release.
-+ simulators = sorted(
-+ (devicetype["minRuntimeVersion"], devicetype["name"])
-+ for devicetype in json_data["devicetypes"]
-+ if devicetype["productFamily"] == "Apple Vision"
-+ )
-+ simulator = simulators[-1][1]
-+ elif platform == "watchOS":
-+ raise NotImplementedError(f"Don't know how to launch watchOS (yet)")
- else:
- raise ValueError(f"Unknown platform {platform}")
-
-@@ -289,7 +310,7 @@
- # many platforms, but when cloned, only one platform is preserved.
- available_platforms = [
- platform
-- for platform in ["iOS"]
-+ for platform in ["iOS", "tvOS", "visionOS", "watchOS"]
- if (Path(__file__).parent / f"{platform}Testbed").is_dir()
- ]
-
-@@ -343,7 +364,7 @@
- ),
- description=(
- "Run a testbed project. The arguments provided after `--` will be "
-- "passed to the running iOS process as if they were arguments to "
-+ "passed to the running test process as if they were arguments to "
- "`python -m`."
- ),
- help="Run a testbed project",
++int main(int argc, char * argv[]) {
++ NSString * appDelegateClassName;
++ @autoreleasepool {
++ appDelegateClassName = NSStringFromClass([AppDelegate class]);
++
++ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
++ }
++}
+diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj
+new file mode 100644
+index 00000000000..5ddfba882f4
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj
++++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj
@@ -0,0 +1,505 @@
+// !$*UTF8*$!
+{
@@ -1044,8 +4898,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ };
+ rootObject = EE989E462DCD6E780036B268 /* Project object */;
+}
+diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme
+new file mode 100644
+index 00000000000..c3f3f894a1f
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme
++++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme
@@ -0,0 +1,97 @@
+
+
+
+
+diff --git a/Platforms/Apple/testbed/tvOSTestbed.xctestplan b/Platforms/Apple/testbed/tvOSTestbed.xctestplan
+new file mode 100644
+index 00000000000..f996facc178
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed.xctestplan
++++ b/Platforms/Apple/testbed/tvOSTestbed.xctestplan
@@ -0,0 +1,46 @@
+{
+ "configurations" : [
@@ -1194,8 +5054,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ "version" : 1
+}
\ No newline at end of file
+diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h
+new file mode 100644
+index 00000000000..112c9ed64b8
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed/AppDelegate.h
++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h
@@ -0,0 +1,11 @@
+//
+// AppDelegate.h
@@ -1208,8 +5071,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+
+@end
+diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m
+new file mode 100644
+index 00000000000..bd91fb2d7d6
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed/AppDelegate.m
++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m
@@ -0,0 +1,19 @@
+//
+// AppDelegate.m
@@ -1230,8 +5096,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+}
+
+@end
+diff --git a/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard
+new file mode 100644
+index 00000000000..660ba53de4f
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard
++++ b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,24 @@
+
+
@@ -1257,8 +5126,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+
+
+diff --git a/Platforms/Apple/testbed/tvOSTestbed/app/README b/Platforms/Apple/testbed/tvOSTestbed/app/README
+new file mode 100644
+index 00000000000..46c0e8e2a29
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed/app/README
++++ b/Platforms/Apple/testbed/tvOSTestbed/app/README
@@ -0,0 +1,7 @@
+This folder can contain any Python application code.
+
@@ -1267,8 +5139,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+When the test suite runs, this folder will be on the PYTHONPATH, and will be the
+working directory for the test suite.
+diff --git a/Platforms/Apple/testbed/tvOSTestbed/app_packages/README b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README
+new file mode 100644
+index 00000000000..02c2beccfbd
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed/app_packages/README
++++ b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README
@@ -0,0 +1,7 @@
+This folder can be a target for installing any Python dependencies needed by the
+test suite.
@@ -1277,8 +5152,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+Framework form.
+
+When the test suite runs, this folder will be on the PYTHONPATH.
+diff --git a/Platforms/Apple/testbed/tvOSTestbed/main.m b/Platforms/Apple/testbed/tvOSTestbed/main.m
+new file mode 100644
+index 00000000000..d5808fbb933
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed/main.m
++++ b/Platforms/Apple/testbed/tvOSTestbed/main.m
@@ -0,0 +1,16 @@
+//
+// main.m
@@ -1296,8 +5174,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+ }
+}
+diff --git a/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist
+new file mode 100644
+index 00000000000..f08f6098999
--- /dev/null
-+++ b/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist
++++ b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist
@@ -0,0 +1,52 @@
+
+
@@ -1351,8 +5232,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+
+
+diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj
+new file mode 100644
+index 00000000000..0fd5df716b1
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj
++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj
@@ -0,0 +1,558 @@
+// !$*UTF8*$!
+{
@@ -1912,8 +5796,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ };
+ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */;
+}
+diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme
+new file mode 100644
+index 00000000000..b8397ea7cf1
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme
++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme
@@ -0,0 +1,97 @@
+
+
+
+
+diff --git a/Platforms/Apple/testbed/visionOSTestbed.xctestplan b/Platforms/Apple/testbed/visionOSTestbed.xctestplan
+new file mode 100644
+index 00000000000..73a2be8c22a
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed.xctestplan
++++ b/Platforms/Apple/testbed/visionOSTestbed.xctestplan
@@ -0,0 +1,46 @@
+{
+ "configurations" : [
@@ -2062,8 +5952,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ "version" : 1
+}
\ No newline at end of file
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h
+new file mode 100644
+index 00000000000..8fe7ec8e64e
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/AppDelegate.h
++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h
@@ -0,0 +1,11 @@
+//
+// AppDelegate.h
@@ -2076,8 +5969,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+
+@end
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m
+new file mode 100644
+index 00000000000..b3ff14f7255
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/AppDelegate.m
++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m
@@ -0,0 +1,19 @@
+//
+// AppDelegate.m
@@ -2098,8 +5994,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+}
+
+@end
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
+new file mode 100644
+index 00000000000..eb878970081
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
@@ -2112,8 +6011,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ "version" : 1
+ }
+}
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json
+new file mode 100644
+index 00000000000..13613e3ee1a
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json
++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
@@ -2128,8 +6030,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ "version" : 1
+ }
+}
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json
+new file mode 100644
+index 00000000000..73c00596a7f
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json
++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
@@ -2137,8 +6042,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ "version" : 1
+ }
+}
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/app/README b/Platforms/Apple/testbed/visionOSTestbed/app/README
+new file mode 100644
+index 00000000000..af22c685f87
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/app/README
++++ b/Platforms/Apple/testbed/visionOSTestbed/app/README
@@ -0,0 +1,7 @@
+This folder can contain any Python application code.
+
@@ -2147,8 +6055,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+When the test suite runs, this folder will be on the PYTHONPATH, and will be the
+working directory for the test suite.
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/app_packages/README b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README
+new file mode 100644
+index 00000000000..42d7fdeb813
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/app_packages/README
++++ b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README
@@ -0,0 +1,7 @@
+This folder can be a target for installing any Python dependencies needed by the
+test suite.
@@ -2157,8 +6068,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+iOS Framework form.
+
+When the test suite runs, this folder will be on the PYTHONPATH.
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/main.m b/Platforms/Apple/testbed/visionOSTestbed/main.m
+new file mode 100644
+index 00000000000..2bb491f25c8
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/main.m
++++ b/Platforms/Apple/testbed/visionOSTestbed/main.m
@@ -0,0 +1,16 @@
+//
+// main.m
@@ -2176,8 +6090,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+ }
+}
+diff --git a/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist
+new file mode 100644
+index 00000000000..fce9298555d
--- /dev/null
-+++ b/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist
++++ b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist
@@ -0,0 +1,56 @@
+
+
@@ -2235,8 +6152,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+
+
+diff --git a/Platforms/Apple/tvOS/README.rst b/Platforms/Apple/tvOS/README.rst
+new file mode 100644
+index 00000000000..1f793252caf
--- /dev/null
-+++ b/Apple/tvOS/README.rst
++++ b/Platforms/Apple/tvOS/README.rst
@@ -0,0 +1,108 @@
+=====================
+Python on tvOS README
@@ -2346,8 +6266,11 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+
+Using a framework-based Python on tvOS
+======================================
+diff --git a/Platforms/Apple/tvOS/Resources/Info.plist.in b/Platforms/Apple/tvOS/Resources/Info.plist.in
+new file mode 100644
+index 00000000000..ab3050804b8
--- /dev/null
-+++ b/Apple/tvOS/Resources/Info.plist.in
++++ b/Platforms/Apple/tvOS/Resources/Info.plist.in
@@ -0,0 +1,34 @@
+
+
@@ -2383,297 +6306,144 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ @TVOS_DEPLOYMENT_TARGET@
+
+
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar
+new file mode 100755
+index 00000000000..e302748a13c
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang
+new file mode 100755
+index 00000000000..7fb6d3d901c
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++
+new file mode 100755
+index 00000000000..33bfb1367c3
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp
+new file mode 100755
+index 00000000000..641c1bc8d18
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} -E "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar
+new file mode 100755
+index 00000000000..87ef5015aae
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang
+new file mode 100755
+index 00000000000..c8719cb0318
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++
+new file mode 100755
+index 00000000000..e3f0e720f7f
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp
+new file mode 100755
+index 00000000000..f9a37b72a61
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip
+new file mode 100755
+index 00000000000..a8cce95233e
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip
@@ -0,0 +1,2 @@
+#!/bin/sh
+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip
+new file mode 100755
+index 00000000000..ee1d2b95ff1
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip
++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip
@@ -0,0 +1,2 @@
+#!/bin/sh
+xcrun --sdk iphoneos${TVOS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar
+new file mode 100755
+index 00000000000..87ef5015aae
--- /dev/null
-+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
---- /dev/null
-+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
---- /dev/null
-+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@"
---- /dev/null
-+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch x86_64 "$@"
---- /dev/null
-+++ b/Apple/tvOS/Resources/pyconfig.h
-@@ -0,0 +1,7 @@
-+#ifdef __arm64__
-+#include "pyconfig-arm64.h"
-+#endif
-+
-+#ifdef __x86_64__
-+#include "pyconfig-x86_64.h"
-+#endif
---- /dev/null
-+++ b/Apple/visionOS/Resources/Info.plist.in
-@@ -0,0 +1,34 @@
-+
-+
-+
-+
-+ CFBundleDevelopmentRegion
-+ en
-+ CFBundleExecutable
-+ Python
-+ CFBundleGetInfoString
-+ Python Runtime and Library
-+ CFBundleIdentifier
-+ @PYTHONFRAMEWORKIDENTIFIER@
-+ CFBundleInfoDictionaryVersion
-+ 6.0
-+ CFBundleName
-+ Python
-+ CFBundlePackageType
-+ FMWK
-+ CFBundleShortVersionString
-+ %VERSION%
-+ CFBundleLongVersionString
-+ %VERSION%, (c) 2001-2023 Python Software Foundation.
-+ CFBundleSignature
-+ ????
-+ CFBundleVersion
-+ %VERSION%
-+ CFBundleSupportedPlatforms
-+
-+ XROS
-+
-+ MinimumOSVersion
-+ @XROS_DEPLOYMENT_TARGET@
-+
-+
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-ar
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xros${XROS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-clang
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xros${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} -E "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xrsimulator${XROS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator -E "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk xrsimulator${XROS_SDK_VERSION} strip -arch arm64 "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-strip
++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar
@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk xros${XROS_SDK_VERSION} strip -arch arm64 "$@"
---- /dev/null
-+++ b/Apple/visionOS/Resources/pyconfig.h
-@@ -0,0 +1,3 @@
-+#ifdef __arm64__
-+#include "pyconfig-arm64.h"
-+#endif
---- /dev/null
-+++ b/Apple/watchOS/README.rst
-@@ -0,0 +1,108 @@
-+========================
-+Python on watchOS README
-+========================
-+
-+:Authors:
-+ Russell Keith-Magee (2023-11)
-+
-+This document provides a quick overview of some watchOS specific features in the
-+Python distribution.
-+
-+Compilers for building on watchOS
-+=================================
-+
-+Building for watchOS requires the use of Apple's Xcode tooling. It is strongly
-+recommended that you use the most recent stable release of Xcode, on the
-+most recently released macOS.
-+
-+watchOS specific arguments to configure
-+=======================================
-+
-+* ``--enable-framework[=DIR]``
-+
-+ This argument specifies the location where the Python.framework will
-+ be installed.
-+
-+* ``--with-framework-name=NAME``
-+
-+ Specify the name for the python framework, defaults to ``Python``.
-+
-+
-+Building and using Python on watchOS
-+====================================
-+
-+ABIs and Architectures
-+----------------------
-+
-+watchOS apps can be deployed on physical devices, and on the watchOS simulator.
-+Although the API used on these devices is identical, the ABI is different - you
-+need to link against different libraries for an watchOS device build
-+(``watchos``) or an watchOS simulator build (``watchsimulator``). Apple uses the
-+XCframework format to allow specifying a single dependency that supports
-+multiple ABIs. An XCframework is a wrapper around multiple ABI-specific
-+frameworks.
-+
-+watchOS can also support different CPU architectures within each ABI. At present,
-+there is only a single support ed architecture on physical devices - ARM64.
-+However, the *simulator* supports 2 architectures - ARM64 (for running on Apple
-+Silicon machines), and x86_64 (for running on older Intel-based machines.)
-+
-+To support multiple CPU architectures on a single platform, Apple uses a "fat
-+binary" format - a single physical file that contains support for multiple
-+architectures.
-+
-+How do I build Python for watchOS?
-+-------------------------------
-+
-+The Python build system will build a ``Python.framework`` that supports a
-+*single* ABI with a *single* architecture. If you want to use Python in an watchOS
-+project, you need to:
-+
-+1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture;
-+2. Merge the binaries for each architecture on a given ABI into a single "fat" binary;
-+3. Merge the "fat" frameworks for each ABI into a single XCframework.
-+
-+watchOS builds of Python *must* be constructed as framework builds. To support this,
-+you must provide the ``--enable-framework`` flag when configuring the build.
-+
-+The build also requires the use of cross-compilation. The commands for building
-+Python for watchOS will look somethign like::
-+
-+ $ ./configure \
-+ --enable-framework=/path/to/install \
-+ --host=aarch64-apple-watchos \
-+ --build=aarch64-apple-darwin \
-+ --with-build-python=/path/to/python.exe
-+ $ make
-+ $ make install
-+
-+In this invocation:
-+
-+* ``/path/to/install`` is the location where the final Python.framework will be
-+ output.
-+
-+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
-+ triple format. This will be one of:
-+
-+ - ``arm64_32-apple-watchos`` for ARM64-32 watchOS devices.
-+ - ``aarch64-apple-watchos-simulator`` for the watchOS simulator running on Apple
-+ Silicon devices.
-+ - ``x86_64-apple-watchos-simulator`` for the watchOS simulator running on Intel
-+ devices.
-+
-+* ``--build`` is the GNU compiler triple for the machine that will be running
-+ the compiler. This is one of:
-+
-+ - ``aarch64-apple-darwin`` for Apple Silicon devices.
-+ - ``x86_64-apple-darwin`` for Intel devices.
-+
-+* ``/path/to/python.exe`` is the path to a Python binary on the machine that
-+ will be running the compiler. This is needed because the Python compilation
-+ process involves running some Python code. On a normal desktop build of
-+ Python, you can compile a python interpreter and then use that interpreter to
-+ run Python code. However, the binaries produced for watchOS won't run on macOS, so
-+ you need to provide an external Python interpreter. This interpreter must be
-+ the version as the Python that is being compiled.
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang
+new file mode 100755
+index 00000000000..ea0cc26cbd9
+--- /dev/null
++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++
+new file mode 100755
+index 00000000000..f18f3603169
+--- /dev/null
++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp
+new file mode 100755
+index 00000000000..b98054d1ce2
+--- /dev/null
++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip
+new file mode 100755
+index 00000000000..f6a884b4aef
+--- /dev/null
++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch x86_64 "$@"
+diff --git a/Platforms/Apple/tvOS/Resources/pyconfig.h b/Platforms/Apple/tvOS/Resources/pyconfig.h
+new file mode 100644
+index 00000000000..4acff2c6051
+--- /dev/null
++++ b/Platforms/Apple/tvOS/Resources/pyconfig.h
+@@ -0,0 +1,7 @@
++#ifdef __arm64__
++#include "pyconfig-arm64.h"
++#endif
+
-+Using a framework-based Python on watchOS
-+======================================
++#ifdef __x86_64__
++#include "pyconfig-x86_64.h"
++#endif
+diff --git a/Platforms/Apple/visionOS/Resources/Info.plist.in b/Platforms/Apple/visionOS/Resources/Info.plist.in
+new file mode 100644
+index 00000000000..2679440da67
--- /dev/null
-+++ b/Apple/watchOS/Resources/Info.plist.in
++++ b/Platforms/Apple/visionOS/Resources/Info.plist.in
@@ -0,0 +1,34 @@
+
+
@@ -2703,575 +6473,392 @@ index 0dd77ab8b82..0b9bdba36a4 100644
+ %VERSION%
+ CFBundleSupportedPlatforms
+
-+ watchOS
++ XROS
+
+ MinimumOSVersion
-+ @WATCHOS_DEPLOYMENT_TARGET@
++ @XROS_DEPLOYMENT_TARGET@
+
+
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-ar b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-ar
+new file mode 100755
+index 00000000000..9fd78a205f3
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
---- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
---- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@"
---- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch arm64 "$@"
---- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} strip -arch arm64 "$@"
++xcrun --sdk xros${XROS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang
+new file mode 100755
+index 00000000000..9a1a757cbd0
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@"
++xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++
+new file mode 100755
+index 00000000000..f64fcfc11cd
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@"
++xcrun --sdk xros${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp
+new file mode 100755
+index 00000000000..d6492eff052
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@"
++xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} -E "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar
+new file mode 100755
+index 00000000000..b202330fb5d
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} -E "$@"
++xcrun --sdk xrsimulator${XROS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang
+new file mode 100755
+index 00000000000..87b0aba0751
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
++xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++
+new file mode 100755
+index 00000000000..c89d48f1cb8
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
++xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp
+new file mode 100755
+index 00000000000..ee11e018ae0
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp
@@ -0,0 +1,2 @@
+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
++xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator -E "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip
+new file mode 100644
+index 00000000000..376aff61a23
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@"
++#!/bin/sh
++xcrun --sdk xrsimulator${XROS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-strip b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-strip
+new file mode 100644
+index 00000000000..2de3ba5d31c
--- /dev/null
-+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip
++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-strip
@@ -0,0 +1,2 @@
+#!/bin/sh
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch x86_64 "$@"
++xcrun --sdk xros${XROS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/visionOS/Resources/pyconfig.h b/Platforms/Apple/visionOS/Resources/pyconfig.h
+new file mode 100644
+index 00000000000..2a433078400
--- /dev/null
-+++ b/Apple/watchOS/Resources/pyconfig.h
-@@ -0,0 +1,11 @@
++++ b/Platforms/Apple/visionOS/Resources/pyconfig.h
+@@ -0,0 +1,3 @@
+#ifdef __arm64__
-+# ifdef __LP64__
+#include "pyconfig-arm64.h"
-+# else
-+#include "pyconfig-arm64_32.h"
-+# endif
+#endif
+diff --git a/Platforms/Apple/watchOS/README.rst b/Platforms/Apple/watchOS/README.rst
+new file mode 100644
+index 00000000000..35221478452
+--- /dev/null
++++ b/Platforms/Apple/watchOS/README.rst
+@@ -0,0 +1,108 @@
++========================
++Python on watchOS README
++========================
+
-+#ifdef __x86_64__
-+#include "pyconfig-x86_64.h"
-+#endif
-diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
-index 04ec0270148..cd14815a3bf 100644
---- a/Lib/ctypes/__init__.py
-+++ b/Lib/ctypes/__init__.py
-@@ -452,9 +452,9 @@
-
- else:
- def _load_library(self, name, mode, handle, winmode):
-- # If the filename that has been provided is an iOS/tvOS/watchOS
-- # .fwork file, dereference the location to the true origin of the
-- # binary.
-+ # If the filename that has been provided is an iOS, tvOS, visionOS
-+ # or watchOS .fwork file, dereference the location to the true
-+ # origin of the binary.
- if name and name.endswith(".fwork"):
- with open(name) as f:
- name = _os.path.join(
-diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
-index 378f12167c6..591c69adfb7 100644
---- a/Lib/ctypes/util.py
-+++ b/Lib/ctypes/util.py
-@@ -126,7 +126,7 @@
- if (name := _get_module_filename(h)) is not None]
- return libraries
-
--elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}:
-+elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "visionos", "watchos"}:
- from ctypes.macholib.dyld import dyld_find as _dyld_find
- def find_library(name):
- possible = ['lib%s.dylib' % name,
-@@ -444,7 +444,7 @@
- # https://man.openbsd.org/dl_iterate_phdr
- # https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
- if (os.name == "posix" and
-- sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
-+ sys.platform not in {"darwin", "ios", "tvos", "watchos", "visionos"}):
- import ctypes
- if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
-
-diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py
-index 95ce14b2c39..dd6eb6489b9 100644
---- a/Lib/importlib/_bootstrap_external.py
-+++ b/Lib/importlib/_bootstrap_external.py
-@@ -52,7 +52,7 @@
-
- # Bootstrap-related code ######################################################
- _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win',
--_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos'
-+_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'visionos', 'watchos'
- _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY
- + _CASE_INSENSITIVE_PLATFORMS_STR_KEY)
-
-@@ -1538,7 +1538,7 @@
- """
- extension_loaders = []
- if hasattr(_imp, 'create_dynamic'):
-- if sys.platform in {"ios", "tvos", "watchos"}:
-+ if sys.platform in {"ios", "tvos", "visionos", "watchos"}:
- extension_loaders = [(AppleFrameworkLoader, [
- suffix.replace(".so", ".fwork")
- for suffix in _imp.extension_suffixes()
-diff --git a/Lib/platform.py b/Lib/platform.py
-index b017b841311..c8c794199f3 100644
---- a/Lib/platform.py
-+++ b/Lib/platform.py
-@@ -539,6 +539,78 @@
- return IOSVersionInfo(system, release, model, is_simulator)
-
-
-+# A namedtuple for tvOS version information.
-+TVOSVersionInfo = collections.namedtuple(
-+ "TVOSVersionInfo",
-+ ["system", "release", "model", "is_simulator"]
-+)
-+
-+
-+def tvos_ver(system="", release="", model="", is_simulator=False):
-+ """Get tvOS version information, and return it as a namedtuple:
-+ (system, release, model, is_simulator).
-+
-+ If values can't be determined, they are set to values provided as
-+ parameters.
-+ """
-+ if sys.platform == "tvos":
-+ # TODO: Can the iOS implementation be used here?
-+ import _ios_support
-+ result = _ios_support.get_platform_ios()
-+ if result is not None:
-+ return TVOSVersionInfo(*result)
-+
-+ return TVOSVersionInfo(system, release, model, is_simulator)
-+
-+
-+# A namedtuple for watchOS version information.
-+WatchOSVersionInfo = collections.namedtuple(
-+ "WatchOSVersionInfo",
-+ ["system", "release", "model", "is_simulator"]
-+)
-+
-+
-+def watchos_ver(system="", release="", model="", is_simulator=False):
-+ """Get watchOS version information, and return it as a namedtuple:
-+ (system, release, model, is_simulator).
-+
-+ If values can't be determined, they are set to values provided as
-+ parameters.
-+ """
-+ if sys.platform == "watchos":
-+ # TODO: Can the iOS implementation be used here?
-+ import _ios_support
-+ result = _ios_support.get_platform_ios()
-+ if result is not None:
-+ return WatchOSVersionInfo(*result)
-+
-+ return WatchOSVersionInfo(system, release, model, is_simulator)
-+
-+
-+# A namedtuple for visionOS version information.
-+VisionOSVersionInfo = collections.namedtuple(
-+ "VisionOSVersionInfo",
-+ ["system", "release", "model", "is_simulator"]
-+)
-+
-+
-+def visionos_ver(system="", release="", model="", is_simulator=False):
-+ """Get visionOS version information, and return it as a namedtuple:
-+ (system, release, model, is_simulator).
-+
-+ If values can't be determined, they are set to values provided as
-+ parameters.
-+ """
-+ if sys.platform == "visionos":
-+ # TODO: Can the iOS implementation be used here?
-+ import _ios_support
-+ result = _ios_support.get_platform_ios()
-+ if result is not None:
-+ return VisionOSVersionInfo(*result)
-+
-+ return VisionOSVersionInfo(system, release, model, is_simulator)
-+
-+
- def _java_getprop(name, default):
- """This private helper is deprecated in 3.13 and will be removed in 3.15"""
- from java.lang import System
-@@ -738,7 +810,7 @@
- default in case the command should fail.
-
- """
-- if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}:
-+ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'visionos', 'watchos'}:
- # XXX Others too ?
- return default
-
-@@ -902,14 +974,30 @@
- csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
- return 'Alpha' if cpu_number >= 128 else 'VAX'
-
-- # On the iOS simulator, os.uname returns the architecture as uname.machine.
-- # On device it returns the model name for some reason; but there's only one
-- # CPU architecture for iOS devices, so we know the right answer.
-+ # On the iOS/tvOS/visionOS/watchOS simulator, os.uname returns the
-+ # architecture as uname.machine. On device it returns the model name for
-+ # some reason; but there's only one CPU architecture for devices, so we know
-+ # the right answer.
- def get_ios():
- if sys.implementation._multiarch.endswith("simulator"):
- return os.uname().machine
- return 'arm64'
-
-+ def get_tvos():
-+ if sys.implementation._multiarch.endswith("simulator"):
-+ return os.uname().machine
-+ return 'arm64'
-+
-+ def get_visionos():
-+ if sys.implementation._multiarch.endswith("simulator"):
-+ return os.uname().machine
-+ return 'arm64'
++:Authors:
++ Russell Keith-Magee (2023-11)
+
-+ def get_watchos():
-+ if sys.implementation._multiarch.endswith("simulator"):
-+ return os.uname().machine
-+ return 'arm64_32'
++This document provides a quick overview of some watchOS specific features in the
++Python distribution.
+
- def from_subprocess():
- """
- Fall back to `uname -p`
-@@ -1069,9 +1157,15 @@
- system = 'Android'
- release = android_ver().release
-
-- # Normalize responses on iOS
-+ # Normalize responses on Apple mobile platforms
- if sys.platform == 'ios':
- system, release, _, _ = ios_ver()
-+ if sys.platform == 'tvos':
-+ system, release, _, _ = tvos_ver()
-+ if sys.platform == 'visionos':
-+ system, release, _, _ = visionos_ver()
-+ if sys.platform == 'watchos':
-+ system, release, _, _ = watchos_ver()
-
- vals = system, node, release, version, machine
- # Replace 'unknown' values with the more portable ''
-@@ -1361,6 +1455,12 @@
- # macOS and iOS both report as a "Darwin" kernel
- if sys.platform == "ios":
- system, release, _, _ = ios_ver()
-+ elif sys.platform == "tvos":
-+ system, release, _, _ = tvos_ver()
-+ elif sys.platform == "visionos":
-+ system, release, _, _ = visionos_ver()
-+ elif sys.platform == "watchos":
-+ system, release, _, _ = watchos_ver()
- else:
- macos_release = mac_ver()[0]
- if macos_release:
-diff --git a/Lib/site.py b/Lib/site.py
-index aeb7c6cfc71..3fa222ea148 100644
---- a/Lib/site.py
-+++ b/Lib/site.py
-@@ -298,8 +298,8 @@
- if env_base:
- return env_base
-
-- # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
-- if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
-+ # Emscripten, iOS, tvOS, visionOS, VxWorks, WASI, and watchOS have no home directories
-+ if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "visionos", "wasi", "watchos"}:
- return None
-
- def joinuser(*args):
-diff --git a/Lib/subprocess.py b/Lib/subprocess.py
-index 6911cd8e859..164204de8ac 100644
---- a/Lib/subprocess.py
-+++ b/Lib/subprocess.py
-@@ -75,7 +75,7 @@
- _mswindows = True
-
- # some platforms do not support subprocesses
--_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"}
-+_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "visionos", "watchos"}
-
- if _mswindows:
- import _winapi
-diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py
-index 2ecbff222fe..542d3f21cae 100644
---- a/Lib/sysconfig/__init__.py
-+++ b/Lib/sysconfig/__init__.py
-@@ -23,6 +23,9 @@
- _ALWAYS_STR = {
- 'IPHONEOS_DEPLOYMENT_TARGET',
- 'MACOSX_DEPLOYMENT_TARGET',
-+ 'TVOS_DEPLOYMENT_TARGET',
-+ 'WATCHOS_DEPLOYMENT_TARGET',
-+ 'XROS_DEPLOYMENT_TARGET',
- }
-
- _INSTALL_SCHEMES = {
-@@ -119,7 +122,7 @@
- # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories.
- # Use _PYTHON_HOST_PLATFORM to get the correct platform when cross-compiling.
- system_name = os.environ.get('_PYTHON_HOST_PLATFORM', sys.platform).split('-')[0]
-- if system_name in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
-+ if system_name in {"emscripten", "ios", "tvos", "visionos", "vxworks", "wasi", "watchos"}:
- return None
-
- def joinuser(*args):
-@@ -734,6 +737,18 @@
- release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
- osname = sys.platform
- machine = sys.implementation._multiarch
-+ elif sys.platform == "tvos":
-+ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "12.0")
-+ osname = sys.platform
-+ machine = sys.implementation._multiarch
-+ elif sys.platform == "watchos":
-+ release = get_config_vars().get("WATCHOS_DEPLOYMENT_TARGET", "4.0")
-+ osname = sys.platform
-+ machine = sys.implementation._multiarch
-+ elif sys.platform == "visionos":
-+ release = get_config_vars().get("XROS_DEPLOYMENT_TARGET", "2.0")
-+ osname = sys.platform
-+ machine = sys.implementation._multiarch
- else:
- import _osx_support
- osname, release, machine = _osx_support.get_platform_osx(
-diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
-index 0540f94fe93..52037e48722 100644
---- a/Lib/test/datetimetester.py
-+++ b/Lib/test/datetimetester.py
-@@ -7165,9 +7165,9 @@
- self.assertEqual(dt_orig, dt_rt)
-
- def test_type_check_in_subinterp(self):
-- # iOS requires the use of the custom framework loader,
-+ # Apple mobile platforms require the use of the custom framework loader,
- # not the ExtensionFileLoader.
-- if sys.platform == "ios":
-+ if support.is_apple_mobile:
- extension_loader = "AppleFrameworkLoader"
- else:
- extension_loader = "ExtensionFileLoader"
-diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
-index da72b0c7dab..6da12ff86f6 100644
---- a/Lib/test/support/__init__.py
-+++ b/Lib/test/support/__init__.py
-@@ -573,7 +573,7 @@
- sys.platform == "android", f"Android blocks {name} with SELinux"
- )
-
--if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
-+if sys.platform not in {"win32", "vxworks", "ios", "tvos", "visionos", "watchos"}:
- unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
- else:
- unix_shell = None
-@@ -592,7 +592,7 @@
- def skip_wasi_stack_overflow():
- return unittest.skipIf(is_wasi, "Exhausts stack on WASI")
-
--is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"}
-+is_apple_mobile = sys.platform in {"ios", "tvos", "visionos", "watchos"}
- is_apple = is_apple_mobile or sys.platform == "darwin"
-
- has_fork_support = hasattr(os, "fork") and not (
-diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py
-index a32d5d81d2b..f9421619e98 100644
---- a/Lib/test/test__interpreters.py
-+++ b/Lib/test/test__interpreters.py
-@@ -612,6 +612,7 @@
- f'assert(obj == {obj!r})',
- )
-
-+ @support.requires_subprocess()
- def test_os_exec(self):
- expected = 'spam spam spam spam spam'
- subinterp = _interpreters.create()
-diff --git a/Lib/test/test_ctypes/test_dllist.py b/Lib/test/test_ctypes/test_dllist.py
-index 15603dc3d77..bff6c0fb95f 100644
---- a/Lib/test/test_ctypes/test_dllist.py
-+++ b/Lib/test/test_ctypes/test_dllist.py
-@@ -7,7 +7,7 @@
-
-
- WINDOWS = os.name == "nt"
--APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos"}
-+APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos", "visionos"}
-
- if WINDOWS:
- KNOWN_LIBRARIES = ["KERNEL32.DLL"]
-diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
-index e879e48571f..07a2c6c76ce 100644
---- a/Lib/test/test_platform.py
-+++ b/Lib/test/test_platform.py
-@@ -271,13 +271,21 @@
- if sys.platform == "android":
- self.assertEqual(res.system, "Android")
- self.assertEqual(res.release, platform.android_ver().release)
-- elif sys.platform == "ios":
-+ elif support.is_apple_mobile:
- # Platform module needs ctypes for full operation. If ctypes
- # isn't available, there's no ObjC module, and dummy values are
- # returned.
- if _ctypes:
-- self.assertIn(res.system, {"iOS", "iPadOS"})
-- self.assertEqual(res.release, platform.ios_ver().release)
-+ if sys.platform == "ios":
-+ # iPads also identify as iOS
-+ self.assertIn(res.system, {"iOS", "iPadOS"})
-+ else:
-+ # All other platforms - sys.platform is the lower case
-+ # form of system (e.g., visionOS->visionos)
-+ self.assertEqual(res.system.lower(), sys.platform)
-+ # Use the platform-specific version method
-+ platform_ver = getattr(platform, f"{sys.platform}_ver")
-+ self.assertEqual(res.release, platform_ver().release)
- else:
- self.assertEqual(res.system, "")
- self.assertEqual(res.release, "")
-diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
-index 4c3ea1cd8df..04a210e5c86 100644
---- a/Lib/test/test_webbrowser.py
-+++ b/Lib/test/test_webbrowser.py
-@@ -236,7 +236,8 @@
- arguments=[f'openURL({URL},new-tab)'])
-
-
--@unittest.skipUnless(sys.platform == "ios", "Test only applicable to iOS")
-+@unittest.skipUnless(sys.platform in {"ios", "visionOS"},
-+ "Test only applicable to iOS and visionOS")
- class IOSBrowserTest(unittest.TestCase):
- def _obj_ref(self, *args):
- # Construct a string representation of the arguments that can be used
-diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
-index f2e2394089d..2efbbfb0014 100644
---- a/Lib/webbrowser.py
-+++ b/Lib/webbrowser.py
-@@ -488,7 +488,8 @@
- # macOS can use below Unix support (but we prefer using the macOS
- # specific stuff)
-
-- if sys.platform == "ios":
-+ if sys.platform in {"ios", "visionos"}:
-+ # iOS and visionOS provide a browser; tvOS and watchOS don't.
- register("iosbrowser", None, IOSBrowser(), preferred=True)
-
- if sys.platform == "serenityos":
-@@ -653,9 +654,10 @@
- return not rc
-
- #
--# Platform support for iOS
-+# Platform support for Apple Mobile platforms that provide a browser
-+# (i.e., iOS and visionOS)
- #
--if sys.platform == "ios":
-+if sys.platform in {"ios", "visionos"}:
- from _ios_support import objc
- if objc:
- # If objc exists, we know ctypes is also importable.
-diff --git a/Makefile.pre.in b/Makefile.pre.in
-index 08ad5f4921d..8c1c9843c00 100644
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -209,6 +209,12 @@
- # the build, and is only listed here so it will be included in sysconfigdata.
- IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@
-
-+# visionOS Deployment target is *actually* used during the build, by the
-+# compiler shims; export.
-+XROS_DEPLOYMENT_TARGET=@XROS_DEPLOYMENT_TARGET@
-+@EXPORT_XROS_DEPLOYMENT_TARGET@export XROS_DEPLOYMENT_TARGET
++Compilers for building on watchOS
++=================================
++
++Building for watchOS requires the use of Apple's Xcode tooling. It is strongly
++recommended that you use the most recent stable release of Xcode, on the
++most recently released macOS.
++
++watchOS specific arguments to configure
++=======================================
++
++* ``--enable-framework[=DIR]``
+
++ This argument specifies the location where the Python.framework will
++ be installed.
+
- # Option to install to strip binaries
- STRIPFLAG=-s
-
-diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c
-index f5cd73bdea8..6c1863c943b 100644
---- a/Misc/platform_triplet.c
-+++ b/Misc/platform_triplet.c
-@@ -257,6 +257,32 @@
- # else
- PLATFORM_TRIPLET=arm64-iphoneos
- # endif
-+# elif defined(TARGET_OS_TV) && TARGET_OS_TV
-+# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
-+# if __x86_64__
-+PLATFORM_TRIPLET=x86_64-appletvsimulator
-+# else
-+PLATFORM_TRIPLET=arm64-appletvsimulator
-+# endif
-+# else
-+PLATFORM_TRIPLET=arm64-appletvos
-+# endif
-+# elif defined(TARGET_OS_WATCH) && TARGET_OS_WATCH
-+# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
-+# if __x86_64__
-+PLATFORM_TRIPLET=x86_64-watchsimulator
-+# else
-+PLATFORM_TRIPLET=arm64-watchsimulator
-+# endif
-+# else
-+PLATFORM_TRIPLET=arm64_32-watchos
-+# endif
-+# elif defined(TARGET_OS_VISION) && TARGET_OS_VISION
-+# if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
-+PLATFORM_TRIPLET=arm64-xrsimulator
-+# else
-+PLATFORM_TRIPLET=arm64-xros
-+# endif
- // Older macOS SDKs do not define TARGET_OS_OSX
- # elif !defined(TARGET_OS_OSX) || TARGET_OS_OSX
- PLATFORM_TRIPLET=darwin
++* ``--with-framework-name=NAME``
++
++ Specify the name for the python framework, defaults to ``Python``.
++
++
++Building and using Python on watchOS
++====================================
++
++ABIs and Architectures
++----------------------
++
++watchOS apps can be deployed on physical devices, and on the watchOS simulator.
++Although the API used on these devices is identical, the ABI is different - you
++need to link against different libraries for an watchOS device build
++(``watchos``) or an watchOS simulator build (``watchsimulator``). Apple uses the
++XCframework format to allow specifying a single dependency that supports
++multiple ABIs. An XCframework is a wrapper around multiple ABI-specific
++frameworks.
++
++watchOS can also support different CPU architectures within each ABI. At present,
++there is only a single support ed architecture on physical devices - ARM64.
++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple
++Silicon machines), and x86_64 (for running on older Intel-based machines.)
++
++To support multiple CPU architectures on a single platform, Apple uses a "fat
++binary" format - a single physical file that contains support for multiple
++architectures.
++
++How do I build Python for watchOS?
++-------------------------------
++
++The Python build system will build a ``Python.framework`` that supports a
++*single* ABI with a *single* architecture. If you want to use Python in an watchOS
++project, you need to:
++
++1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture;
++2. Merge the binaries for each architecture on a given ABI into a single "fat" binary;
++3. Merge the "fat" frameworks for each ABI into a single XCframework.
++
++watchOS builds of Python *must* be constructed as framework builds. To support this,
++you must provide the ``--enable-framework`` flag when configuring the build.
++
++The build also requires the use of cross-compilation. The commands for building
++Python for watchOS will look somethign like::
++
++ $ ./configure \
++ --enable-framework=/path/to/install \
++ --host=aarch64-apple-watchos \
++ --build=aarch64-apple-darwin \
++ --with-build-python=/path/to/python.exe
++ $ make
++ $ make install
++
++In this invocation:
++
++* ``/path/to/install`` is the location where the final Python.framework will be
++ output.
++
++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
++ triple format. This will be one of:
++
++ - ``arm64_32-apple-watchos`` for ARM64-32 watchOS devices.
++ - ``aarch64-apple-watchos-simulator`` for the watchOS simulator running on Apple
++ Silicon devices.
++ - ``x86_64-apple-watchos-simulator`` for the watchOS simulator running on Intel
++ devices.
++
++* ``--build`` is the GNU compiler triple for the machine that will be running
++ the compiler. This is one of:
++
++ - ``aarch64-apple-darwin`` for Apple Silicon devices.
++ - ``x86_64-apple-darwin`` for Intel devices.
++
++* ``/path/to/python.exe`` is the path to a Python binary on the machine that
++ will be running the compiler. This is needed because the Python compilation
++ process involves running some Python code. On a normal desktop build of
++ Python, you can compile a python interpreter and then use that interpreter to
++ run Python code. However, the binaries produced for watchOS won't run on macOS, so
++ you need to provide an external Python interpreter. This interpreter must be
++ the version as the Python that is being compiled.
++
++Using a framework-based Python on watchOS
++======================================
+diff --git a/Platforms/Apple/watchOS/Resources/Info.plist.in b/Platforms/Apple/watchOS/Resources/Info.plist.in
+new file mode 100644
+index 00000000000..e83ddfd2a43
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/Info.plist.in
+@@ -0,0 +1,34 @@
++
++
++
++
++ CFBundleDevelopmentRegion
++ en
++ CFBundleExecutable
++ Python
++ CFBundleGetInfoString
++ Python Runtime and Library
++ CFBundleIdentifier
++ @PYTHONFRAMEWORKIDENTIFIER@
++ CFBundleInfoDictionaryVersion
++ 6.0
++ CFBundleName
++ Python
++ CFBundlePackageType
++ FMWK
++ CFBundleShortVersionString
++ %VERSION%
++ CFBundleLongVersionString
++ %VERSION%, (c) 2001-2023 Python Software Foundation.
++ CFBundleSignature
++ ????
++ CFBundleVersion
++ %VERSION%
++ CFBundleSupportedPlatforms
++
++ watchOS
++
++ MinimumOSVersion
++ @WATCHOS_DEPLOYMENT_TARGET@
++
++
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar
+new file mode 100755
+index 00000000000..dda2b211bd5
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang
+new file mode 100755
+index 00000000000..fe834d3efe4
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++
+new file mode 100755
+index 00000000000..757f3a26d8f
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp
+new file mode 100755
+index 00000000000..fdb57d9e010
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip
+new file mode 100755
+index 00000000000..e28e3f7597a
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip
+new file mode 100755
+index 00000000000..efe5a1260ad
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk watchos${WATCHOS_SDK_VERSION} strip -arch arm64 "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar
+new file mode 100755
+index 00000000000..029f9a32073
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang
+new file mode 100755
+index 00000000000..285036d4010
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++
+new file mode 100755
+index 00000000000..c8f60ebec51
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp
+new file mode 100755
+index 00000000000..b411fc25aa4
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} -E "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar
+new file mode 100755
+index 00000000000..dda2b211bd5
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang
+new file mode 100755
+index 00000000000..4776b9b5348
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++
+new file mode 100755
+index 00000000000..e9b0c5f4b87
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp
+new file mode 100755
+index 00000000000..d3b821c5f7f
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip
+new file mode 100755
+index 00000000000..105c78281f9
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch x86_64 "$@"
+diff --git a/Platforms/Apple/watchOS/Resources/pyconfig.h b/Platforms/Apple/watchOS/Resources/pyconfig.h
+new file mode 100644
+index 00000000000..f842b987b2e
+--- /dev/null
++++ b/Platforms/Apple/watchOS/Resources/pyconfig.h
+@@ -0,0 +1,11 @@
++#ifdef __arm64__
++# ifdef __LP64__
++#include "pyconfig-arm64.h"
++# else
++#include "pyconfig-arm64_32.h"
++# endif
++#endif
++
++#ifdef __x86_64__
++#include "pyconfig-x86_64.h"
++#endif
diff --git a/config.sub b/config.sub
index 1bb6a05dc11..49febd56a37 100755
--- a/config.sub
@@ -3295,10 +6882,10 @@ index 1bb6a05dc11..49febd56a37 100755
none--*)
# None (no kernel, i.e. freestanding / bare metal),
diff --git a/configure b/configure
-index 46eb0665bf4..73035fb9efa 100755
+index 2088290f0ee..d81f302d6aa 100755
--- a/configure
+++ b/configure
-@@ -983,6 +983,10 @@
+@@ -984,6 +984,10 @@
CFLAGS
CC
HAS_XCRUN
@@ -3309,7 +6896,7 @@ index 46eb0665bf4..73035fb9efa 100755
IPHONEOS_DEPLOYMENT_TARGET
EXPORT_MACOSX_DEPLOYMENT_TARGET
CONFIGURE_MACOSX_DEPLOYMENT_TARGET
-@@ -4117,6 +4121,15 @@
+@@ -4118,6 +4122,15 @@
*-apple-ios*)
ac_sys_system=iOS
;;
@@ -3325,7 +6912,7 @@ index 46eb0665bf4..73035fb9efa 100755
*-*-darwin*)
ac_sys_system=Darwin
;;
-@@ -4198,7 +4211,7 @@
+@@ -4199,7 +4212,7 @@
# On cross-compile builds, configure will look for a host-specific compiler by
# prepending the user-provided host triple to the required binary name.
#
@@ -3334,7 +6921,7 @@ index 46eb0665bf4..73035fb9efa 100755
# which isn't a binary that exists, and isn't very convenient, as it contains the
# iOS version. As the default cross-compiler name won't exist, configure falls
# back to gcc, which *definitely* won't work. We're providing wrapper scripts for
-@@ -4213,6 +4226,17 @@
+@@ -4214,6 +4227,17 @@
aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;;
aarch64-apple-ios*) AR=arm64-apple-ios-ar ;;
x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;;
@@ -3352,7 +6939,7 @@ index 46eb0665bf4..73035fb9efa 100755
*)
esac
fi
-@@ -4221,6 +4245,17 @@
+@@ -4222,6 +4246,17 @@
aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;;
aarch64-apple-ios*) CC=arm64-apple-ios-clang ;;
x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;;
@@ -3370,7 +6957,7 @@ index 46eb0665bf4..73035fb9efa 100755
*)
esac
fi
-@@ -4229,6 +4264,17 @@
+@@ -4230,6 +4265,17 @@
aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;;
aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;;
x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;;
@@ -3388,7 +6975,7 @@ index 46eb0665bf4..73035fb9efa 100755
*)
esac
fi
-@@ -4237,6 +4283,17 @@
+@@ -4238,6 +4284,17 @@
aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;;
aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;;
x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;;
@@ -3406,21 +6993,21 @@ index 46eb0665bf4..73035fb9efa 100755
*)
esac
fi
-@@ -4359,8 +4416,11 @@
+@@ -4360,8 +4417,11 @@
case $enableval in
yes)
case $ac_sys_system in
- Darwin) enableval=/Library/Frameworks ;;
- iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
+ Darwin) enableval=/Library/Frameworks ;;
-+ iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
-+ tvOS) enableval=Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;;
-+ visionOS) enableval=Apple/visionOS/Frameworks/\$\(MULTIARCH\) ;;
-+ watchOS) enableval=Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;;
++ iOS) enableval=Platforms/Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
++ tvOS) enableval=Platforms/Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;;
++ visionOS) enableval=Platforms/Apple/visionOS/Frameworks/\$\(MULTIARCH\) ;;
++ watchOS) enableval=Platforms/Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;;
*) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
esac
esac
-@@ -4369,6 +4429,9 @@
+@@ -4370,6 +4430,9 @@
no)
case $ac_sys_system in
iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;;
@@ -3430,10 +7017,15 @@ index 46eb0665bf4..73035fb9efa 100755
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
-@@ -4475,6 +4538,51 @@
-
- ac_config_files="$ac_config_files Apple/iOS/Resources/Info.plist"
+@@ -4472,9 +4535,54 @@
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+- RESSRCDIR=Apple/iOS/Resources
++ RESSRCDIR=Platforms/Apple/iOS/Resources
++
++ ac_config_files="$ac_config_files Platforms/Apple/iOS/Resources/Info.plist"
++
+ ;;
+ tvOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
@@ -3445,9 +7037,9 @@ index 46eb0665bf4..73035fb9efa 100755
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=Apple/tvOS/Resources
++ RESSRCDIR=Platforms/Apple/tvOS/Resources
+
-+ ac_config_files="$ac_config_files Apple/tvOS/Resources/Info.plist"
++ ac_config_files="$ac_config_files Platforms/Apple/tvOS/Resources/Info.plist"
+
+ ;;
+ visionOS) :
@@ -3460,9 +7052,9 @@ index 46eb0665bf4..73035fb9efa 100755
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=Apple/visionOS/Resources
++ RESSRCDIR=Platforms/Apple/visionOS/Resources
+
-+ ac_config_files="$ac_config_files Apple/visionOS/Resources/Info.plist"
++ ac_config_files="$ac_config_files Platforms/Apple/visionOS/Resources/Info.plist"
+
+ ;;
+ watchOS) :
@@ -3475,14 +7067,14 @@ index 46eb0665bf4..73035fb9efa 100755
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=Apple/watchOS/Resources
-+
-+ ac_config_files="$ac_config_files Apple/watchOS/Resources/Info.plist"
-+
++ RESSRCDIR=Platforms/Apple/watchOS/Resources
+
+- ac_config_files="$ac_config_files Apple/iOS/Resources/Info.plist"
++ ac_config_files="$ac_config_files Platforms/Apple/watchOS/Resources/Info.plist"
+
;;
*)
- as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
-@@ -4486,6 +4594,9 @@
+@@ -4487,6 +4595,9 @@
e)
case $ac_sys_system in
iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;;
@@ -3492,7 +7084,7 @@ index 46eb0665bf4..73035fb9efa 100755
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
-@@ -4540,8 +4651,8 @@
+@@ -4541,8 +4652,8 @@
case "$withval" in
yes)
case $ac_sys_system in
@@ -3503,7 +7095,7 @@ index 46eb0665bf4..73035fb9efa 100755
APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
;;
*) as_fn_error $? "no default app store compliance patch available for $ac_sys_system" "$LINENO" 5 ;;
-@@ -4559,8 +4670,8 @@
+@@ -4560,8 +4671,8 @@
else case e in #(
e)
case $ac_sys_system in
@@ -3514,7 +7106,7 @@ index 46eb0665bf4..73035fb9efa 100755
APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: applying default app store compliance patch" >&5
printf "%s\n" "applying default app store compliance patch" >&6; }
-@@ -4578,6 +4689,8 @@
+@@ -4579,6 +4690,8 @@
@@ -3523,7 +7115,7 @@ index 46eb0665bf4..73035fb9efa 100755
if test "$cross_compiling" = yes; then
case "$host" in
-@@ -4615,6 +4728,78 @@
+@@ -4616,6 +4729,78 @@
;;
esac
;;
@@ -3602,7 +7194,7 @@ index 46eb0665bf4..73035fb9efa 100755
*-*-darwin*)
case "$host_cpu" in
arm*)
-@@ -4705,9 +4890,15 @@
+@@ -4706,9 +4891,15 @@
define_xopen_source=no;;
Darwin/[12][0-9].*)
define_xopen_source=no;;
@@ -3619,7 +7211,7 @@ index 46eb0665bf4..73035fb9efa 100755
# On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
# defining NI_NUMERICHOST.
QNX/6.3.2)
-@@ -4770,7 +4961,14 @@
+@@ -4771,7 +4962,14 @@
CONFIGURE_MACOSX_DEPLOYMENT_TARGET=
EXPORT_MACOSX_DEPLOYMENT_TARGET='#'
@@ -3635,7 +7227,7 @@ index 46eb0665bf4..73035fb9efa 100755
# checks for alternative programs
-@@ -4811,6 +5009,16 @@
+@@ -4812,6 +5010,16 @@
as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
;; #(
@@ -3652,7 +7244,7 @@ index 46eb0665bf4..73035fb9efa 100755
*) :
;;
esac
-@@ -7180,6 +7388,12 @@
+@@ -7181,6 +7389,12 @@
MULTIARCH="" ;; #(
iOS) :
MULTIARCH="" ;; #(
@@ -3665,7 +7257,7 @@ index 46eb0665bf4..73035fb9efa 100755
FreeBSD*) :
MULTIARCH="" ;; #(
*) :
-@@ -7200,7 +7414,7 @@
+@@ -7201,7 +7415,7 @@
printf "%s\n" "$MULTIARCH" >&6; }
case $ac_sys_system in #(
@@ -3674,7 +7266,7 @@ index 46eb0665bf4..73035fb9efa 100755
SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #(
*) :
SOABI_PLATFORM=$PLATFORM_TRIPLET
-@@ -7263,6 +7477,18 @@
+@@ -7264,6 +7478,18 @@
PY_SUPPORT_TIER=3 ;; #(
aarch64-apple-ios*/clang) :
PY_SUPPORT_TIER=3 ;; #(
@@ -3693,7 +7285,7 @@ index 46eb0665bf4..73035fb9efa 100755
aarch64-*-linux-android/clang) :
PY_SUPPORT_TIER=3 ;; #(
x86_64-*-linux-android/clang) :
-@@ -7701,7 +7927,7 @@
+@@ -7702,7 +7928,7 @@
case $ac_sys_system in
Darwin)
LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';;
@@ -3702,7 +7294,7 @@ index 46eb0665bf4..73035fb9efa 100755
LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';;
*)
as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;;
-@@ -7767,7 +7993,7 @@
+@@ -7768,7 +7994,7 @@
BLDLIBRARY='-L. -lpython$(LDVERSION)'
RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
;;
@@ -3711,7 +7303,7 @@ index 46eb0665bf4..73035fb9efa 100755
LDLIBRARY='libpython$(LDVERSION).dylib'
;;
AIX*)
-@@ -13583,7 +13809,7 @@
+@@ -13677,7 +13903,7 @@
BLDSHARED="$LDSHARED"
fi
;;
@@ -3720,7 +7312,7 @@ index 46eb0665bf4..73035fb9efa 100755
LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
BLDSHARED="$LDSHARED"
-@@ -13716,7 +13942,7 @@
+@@ -13810,7 +14036,7 @@
Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
# -u libsys_s pulls in all symbols in libsys
@@ -3729,7 +7321,7 @@ index 46eb0665bf4..73035fb9efa 100755
LINKFORSHARED="$extra_undefs -framework CoreFoundation"
# Issue #18075: the default maximum stack size (8MBytes) is too
-@@ -13740,7 +13966,7 @@
+@@ -13834,7 +14060,7 @@
LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
fi
LINKFORSHARED="$LINKFORSHARED"
@@ -3738,7 +7330,7 @@ index 46eb0665bf4..73035fb9efa 100755
LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
fi
;;
-@@ -15517,7 +15743,7 @@
+@@ -15611,7 +15837,7 @@
ctypes_malloc_closure=yes
;; #(
@@ -3747,9 +7339,9 @@ index 46eb0665bf4..73035fb9efa 100755
ctypes_malloc_closure=yes
;; #(
-@@ -19287,12 +19513,6 @@
+@@ -19375,12 +19601,6 @@
then :
- printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h
+ printf "%s\n" "#define HAVE_DUP 1" >>confdefs.h
-fi
-ac_fn_c_check_func "$LINENO" "execv" "ac_cv_func_execv"
@@ -3760,7 +7352,7 @@ index 46eb0665bf4..73035fb9efa 100755
fi
ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero"
if test "x$ac_cv_func_explicit_bzero" = xyes
-@@ -19353,18 +19573,6 @@
+@@ -19441,18 +19661,6 @@
then :
printf "%s\n" "#define HAVE_FEXECVE 1" >>confdefs.h
@@ -3779,7 +7371,7 @@ index 46eb0665bf4..73035fb9efa 100755
fi
ac_fn_c_check_func "$LINENO" "fpathconf" "ac_cv_func_fpathconf"
if test "x$ac_cv_func_fpathconf" = xyes
-@@ -19797,24 +20005,6 @@
+@@ -19879,24 +20087,6 @@
then :
printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h
@@ -3804,7 +7396,7 @@ index 46eb0665bf4..73035fb9efa 100755
fi
ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread"
if test "x$ac_cv_func_pread" = xyes
-@@ -20133,12 +20323,6 @@
+@@ -20215,12 +20405,6 @@
then :
printf "%s\n" "#define HAVE_SIGACTION 1" >>confdefs.h
@@ -3817,7 +7409,7 @@ index 46eb0665bf4..73035fb9efa 100755
fi
ac_fn_c_check_func "$LINENO" "sigfillset" "ac_cv_func_sigfillset"
if test "x$ac_cv_func_sigfillset" = xyes
-@@ -20407,11 +20591,11 @@
+@@ -20489,11 +20673,11 @@
fi
@@ -3828,10 +7420,10 @@ index 46eb0665bf4..73035fb9efa 100755
# raise an error if used at runtime. Force these symbols off.
-if test "$ac_sys_system" != "iOS" ; then
+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" ; then
- ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy"
- if test "x$ac_cv_func_getentropy" = xyes
+ ac_fn_c_check_func "$LINENO" "dup3" "ac_cv_func_dup3"
+ if test "x$ac_cv_func_dup3" = xyes
then :
-@@ -20433,6 +20617,53 @@
+@@ -20527,6 +20711,53 @@
fi
@@ -3885,7 +7477,7 @@ index 46eb0665bf4..73035fb9efa 100755
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC options needed to detect all undeclared functions" >&5
printf %s "checking for $CC options needed to detect all undeclared functions... " >&6; }
if test ${ac_cv_c_undeclared_builtin_options+y}
-@@ -23904,7 +24135,8 @@
+@@ -23998,7 +24229,8 @@
# check for openpty, login_tty, and forkpty
@@ -3895,7 +7487,7 @@ index 46eb0665bf4..73035fb9efa 100755
for ac_func in openpty
do :
-@@ -24018,7 +24250,7 @@
+@@ -24112,7 +24344,7 @@
fi
done
@@ -3904,7 +7496,7 @@ index 46eb0665bf4..73035fb9efa 100755
printf %s "checking for library containing login_tty... " >&6; }
if test ${ac_cv_search_login_tty+y}
then :
-@@ -24201,6 +24433,7 @@
+@@ -24295,6 +24527,7 @@
fi
done
@@ -3912,7 +7504,7 @@ index 46eb0665bf4..73035fb9efa 100755
# check for long file support functions
ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64"
-@@ -24466,10 +24699,10 @@
+@@ -24560,10 +24793,10 @@
done
@@ -3925,7 +7517,7 @@ index 46eb0665bf4..73035fb9efa 100755
then
for ac_func in clock_settime
-@@ -24786,7 +25019,7 @@
+@@ -24880,7 +25113,7 @@
e) if test "$cross_compiling" = yes
then :
@@ -3934,7 +7526,7 @@ index 46eb0665bf4..73035fb9efa 100755
ac_cv_buggy_getaddrinfo="no"
elif test "${enable_ipv6+set}" = set; then
ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
-@@ -26808,8 +27041,8 @@
+@@ -26902,8 +27135,8 @@
LIBPYTHON="\$(BLDLIBRARY)"
fi
@@ -3945,7 +7537,7 @@ index 46eb0665bf4..73035fb9efa 100755
MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)"
fi
-@@ -29575,7 +29808,7 @@
+@@ -29669,7 +29902,7 @@
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5
printf "%s\n" "$as_me: checking for device files" >&6;}
@@ -3954,7 +7546,7 @@ index 46eb0665bf4..73035fb9efa 100755
ac_cv_file__dev_ptmx=no
ac_cv_file__dev_ptc=no
else
-@@ -30082,7 +30315,7 @@
+@@ -30222,7 +30455,7 @@
with_ensurepip=no ;; #(
WASI) :
with_ensurepip=no ;; #(
@@ -3963,7 +7555,7 @@ index 46eb0665bf4..73035fb9efa 100755
with_ensurepip=no ;; #(
*) :
with_ensurepip=upgrade
-@@ -31032,6 +31265,9 @@
+@@ -31172,6 +31405,9 @@
NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268
Darwin) _PYTHREAD_NAME_MAXLEN=63;;
iOS) _PYTHREAD_NAME_MAXLEN=63;;
@@ -3973,7 +7565,7 @@ index 46eb0665bf4..73035fb9efa 100755
FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268
OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268
*) _PYTHREAD_NAME_MAXLEN=;;
-@@ -31063,7 +31299,7 @@
+@@ -31203,7 +31439,7 @@
;; #(
Darwin) :
;; #(
@@ -3982,18 +7574,20 @@ index 46eb0665bf4..73035fb9efa 100755
-@@ -35231,6 +35467,9 @@
+@@ -35370,7 +35606,10 @@
+ "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;;
"Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;;
"Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;;
- "Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/iOS/Resources/Info.plist" ;;
-+ "Apple/tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/tvOS/Resources/Info.plist" ;;
-+ "Apple/visionOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/visionOS/Resources/Info.plist" ;;
-+ "Apple/watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/watchOS/Resources/Info.plist" ;;
+- "Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/iOS/Resources/Info.plist" ;;
++ "Platforms/Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/iOS/Resources/Info.plist" ;;
++ "Platforms/Apple/tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/tvOS/Resources/Info.plist" ;;
++ "Platforms/Apple/visionOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/visionOS/Resources/Info.plist" ;;
++ "Platforms/Apple/watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/watchOS/Resources/Info.plist" ;;
"Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;;
"Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;;
"Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;;
diff --git a/configure.ac b/configure.ac
-index bd4446e9488..6bef5ef8a56 100644
+index aed14946731..3fa418c611f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -330,6 +330,15 @@
@@ -4100,10 +7694,10 @@ index bd4446e9488..6bef5ef8a56 100644
- Darwin) enableval=/Library/Frameworks ;;
- iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
+ Darwin) enableval=/Library/Frameworks ;;
-+ iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
-+ tvOS) enableval=Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;;
-+ visionOS) enableval=Apple/visionOS/Frameworks/\$\(MULTIARCH\) ;;
-+ watchOS) enableval=Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;;
++ iOS) enableval=Platforms/Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
++ tvOS) enableval=Platforms/Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;;
++ visionOS) enableval=Platforms/Apple/visionOS/Frameworks/\$\(MULTIARCH\) ;;
++ watchOS) enableval=Platforms/Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;;
*) AC_MSG_ERROR([Unknown platform for framework build])
esac
esac
@@ -4117,10 +7711,16 @@ index bd4446e9488..6bef5ef8a56 100644
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
-@@ -670,6 +729,48 @@
+@@ -666,9 +725,51 @@
- AC_CONFIG_FILES([Apple/iOS/Resources/Info.plist])
- ;;
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
+- RESSRCDIR=Apple/iOS/Resources
++ RESSRCDIR=Platforms/Apple/iOS/Resources
+
+- AC_CONFIG_FILES([Apple/iOS/Resources/Info.plist])
++ AC_CONFIG_FILES([Platforms/Apple/iOS/Resources/Info.plist])
++ ;;
+ tvOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
@@ -4131,9 +7731,9 @@ index bd4446e9488..6bef5ef8a56 100644
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=Apple/tvOS/Resources
++ RESSRCDIR=Platforms/Apple/tvOS/Resources
+
-+ AC_CONFIG_FILES([Apple/tvOS/Resources/Info.plist])
++ AC_CONFIG_FILES([Platforms/Apple/tvOS/Resources/Info.plist])
+ ;;
+ visionOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
@@ -4145,9 +7745,9 @@ index bd4446e9488..6bef5ef8a56 100644
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=Apple/visionOS/Resources
++ RESSRCDIR=Platforms/Apple/visionOS/Resources
+
-+ AC_CONFIG_FILES([Apple/visionOS/Resources/Info.plist])
++ AC_CONFIG_FILES([Platforms/Apple/visionOS/Resources/Info.plist])
+ ;;
+ watchOS) :
+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
@@ -4159,13 +7759,12 @@ index bd4446e9488..6bef5ef8a56 100644
+
+ prefix=$PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=Apple/watchOS/Resources
++ RESSRCDIR=Platforms/Apple/watchOS/Resources
+
-+ AC_CONFIG_FILES([Apple/watchOS/Resources/Info.plist])
-+ ;;
++ AC_CONFIG_FILES([Platforms/Apple/watchOS/Resources/Info.plist])
+ ;;
*)
AC_MSG_ERROR([Unknown platform for framework build])
- ;;
@@ -678,6 +779,9 @@
],[
case $ac_sys_system in
@@ -4403,7 +8002,7 @@ index bd4446e9488..6bef5ef8a56 100644
LDLIBRARY='libpython$(LDVERSION).dylib'
;;
AIX*)
-@@ -3477,7 +3677,7 @@
+@@ -3502,7 +3702,7 @@
BLDSHARED="$LDSHARED"
fi
;;
@@ -4412,7 +8011,7 @@ index bd4446e9488..6bef5ef8a56 100644
LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
BLDSHARED="$LDSHARED"
-@@ -3601,7 +3801,7 @@
+@@ -3626,7 +3826,7 @@
Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
# -u libsys_s pulls in all symbols in libsys
@@ -4421,7 +8020,7 @@ index bd4446e9488..6bef5ef8a56 100644
LINKFORSHARED="$extra_undefs -framework CoreFoundation"
# Issue #18075: the default maximum stack size (8MBytes) is too
-@@ -3625,7 +3825,7 @@
+@@ -3650,7 +3850,7 @@
LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
fi
LINKFORSHARED="$LINKFORSHARED"
@@ -4430,7 +8029,7 @@ index bd4446e9488..6bef5ef8a56 100644
LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
fi
;;
-@@ -4113,7 +4313,7 @@
+@@ -4138,7 +4338,7 @@
dnl when do we need USING_APPLE_OS_LIBFFI?
ctypes_malloc_closure=yes
],
@@ -4439,29 +8038,29 @@ index bd4446e9488..6bef5ef8a56 100644
ctypes_malloc_closure=yes
],
[sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])]
-@@ -5239,9 +5439,9 @@
+@@ -5264,9 +5464,9 @@
# checks for library functions
AC_CHECK_FUNCS([ \
accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \
-- copy_file_range ctermid dladdr dup dup3 execv explicit_bzero explicit_memset \
-+ copy_file_range ctermid dladdr dup dup3 explicit_bzero explicit_memset \
+- copy_file_range ctermid dladdr dup execv explicit_bzero explicit_memset \
++ copy_file_range ctermid dladdr dup explicit_bzero explicit_memset \
faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \
- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \
+ fpathconf fstatat ftime ftruncate futimens futimes futimesat \
gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \
getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin getlogin_r \
getpeername getpgid getpid getppid getpriority _getpty \
-@@ -5249,8 +5449,7 @@
+@@ -5274,8 +5474,7 @@
getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \
lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
-- pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
+- plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
- posix_spawn_file_actions_addclosefrom_np \
-+ pipe2 plock poll posix_fadvise posix_fallocate posix_openpt \
++ plock poll posix_fadvise posix_fallocate posix_openpt \
pread preadv preadv2 process_vm_readv \
pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \
pthread_kill pthread_get_name_np pthread_getname_np pthread_set_name_np \
-@@ -5260,7 +5459,7 @@
+@@ -5285,7 +5484,7 @@
sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \
sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \
setitimer setlocale setpgid setpgrp setpriority setregid setresgid \
@@ -4470,7 +8069,7 @@ index bd4446e9488..6bef5ef8a56 100644
sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \
sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
-@@ -5275,12 +5474,20 @@
+@@ -5300,14 +5499,22 @@
AC_CHECK_FUNCS([lchmod])
fi
@@ -4480,21 +8079,22 @@ index bd4446e9488..6bef5ef8a56 100644
# header definition prevents usage - autoconf doesn't use the headers), or
# raise an error if used at runtime. Force these symbols off.
-if test "$ac_sys_system" != "iOS" ; then
-- AC_CHECK_FUNCS([getentropy getgroups system])
+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" ; then
-+ AC_CHECK_FUNCS([ getentropy getgroups system ])
-+fi
-+
+ AC_CHECK_FUNCS([dup3 getentropy getgroups pipe2 system])
+ fi
+
+# tvOS/watchOS have some additional methods that can be found, but not used.
+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
+ AC_CHECK_FUNCS([ \
+ execv fork fork1 posix_spawn posix_spawnp posix_spawn_file_actions_addclosefrom_np \
+ sigaltstack \
+ ])
- fi
-
++fi
++
AC_CHECK_DECL([dirfd],
-@@ -5575,20 +5782,22 @@
+ [AC_DEFINE([HAVE_DIRFD], [1],
+ [Define if you have the 'dirfd' function or macro.])],
+@@ -5600,20 +5807,22 @@
[@%:@include ])
# check for openpty, login_tty, and forkpty
@@ -4531,7 +8131,7 @@ index bd4446e9488..6bef5ef8a56 100644
# check for long file support functions
AC_CHECK_FUNCS([fseek64 fseeko fstatvfs ftell64 ftello statvfs])
-@@ -5627,10 +5836,10 @@
+@@ -5652,10 +5861,10 @@
])
])
@@ -4544,7 +8144,7 @@ index bd4446e9488..6bef5ef8a56 100644
then
AC_CHECK_FUNCS([clock_settime], [], [
AC_CHECK_LIB([rt], [clock_settime], [
-@@ -5788,7 +5997,7 @@
+@@ -5813,7 +6022,7 @@
[ac_cv_buggy_getaddrinfo=no],
[ac_cv_buggy_getaddrinfo=yes],
[
@@ -4553,7 +8153,7 @@ index bd4446e9488..6bef5ef8a56 100644
ac_cv_buggy_getaddrinfo="no"
elif test "${enable_ipv6+set}" = set; then
ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
-@@ -6381,8 +6590,8 @@
+@@ -6406,8 +6615,8 @@
LIBPYTHON="\$(BLDLIBRARY)"
fi
@@ -4564,7 +8164,7 @@ index bd4446e9488..6bef5ef8a56 100644
MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)"
fi
-@@ -6990,7 +7199,7 @@
+@@ -7015,7 +7224,7 @@
dnl NOTE: Inform user how to proceed with files when cross compiling.
dnl Some cross-compile builds are predictable; they won't ever
dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly.
@@ -4573,7 +8173,7 @@ index bd4446e9488..6bef5ef8a56 100644
ac_cv_file__dev_ptmx=no
ac_cv_file__dev_ptc=no
else
-@@ -7290,7 +7499,7 @@
+@@ -7343,7 +7552,7 @@
AS_CASE([$ac_sys_system],
[Emscripten], [with_ensurepip=no],
[WASI], [with_ensurepip=no],
@@ -4582,7 +8182,7 @@ index bd4446e9488..6bef5ef8a56 100644
[with_ensurepip=upgrade]
)
])
-@@ -7678,6 +7887,9 @@
+@@ -7731,6 +7940,9 @@
NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268
Darwin) _PYTHREAD_NAME_MAXLEN=63;;
iOS) _PYTHREAD_NAME_MAXLEN=63;;
@@ -4592,7 +8192,7 @@ index bd4446e9488..6bef5ef8a56 100644
FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268
OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268
*) _PYTHREAD_NAME_MAXLEN=;;
-@@ -7702,7 +7914,7 @@
+@@ -7755,7 +7967,7 @@
[VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])],
dnl The _scproxy module is available on macOS
[Darwin], [],