diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0b3d72b7..7b8701cf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -140,11 +140,6 @@ jobs: fail-fast: false matrix: platform: ["macOS", "iOS"] - include: - - briefcase-run-args: - - - platform: iOS - briefcase-run-args: ' -d "iPhone 17e::iOS 26.5"' steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -186,7 +181,7 @@ jobs: PYTHON_VER: ${{ needs.config.outputs.PYTHON_VER }} BUILD_NUMBER: ${{ needs.config.outputs.BUILD_NUMBER }} run: | - briefcase run ${{ matrix.platform }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${PYTHON_VER}-${{ matrix.platform }}-support.${BUILD_NUMBER}.tar.gz\' + briefcase run ${{ matrix.platform }} Xcode --test -C support_package=\'../dist/Python-${PYTHON_VER}-${{ matrix.platform }}-support.${BUILD_NUMBER}.tar.gz\' cpython-testbed: name: CPython testbed (${{ matrix.platform }}) @@ -195,12 +190,7 @@ jobs: strategy: fail-fast: false matrix: - platform: ["iOS", "tvOS", "visionOS"] - include: - - platform: "iOS" - testbed-args: '--simulator "iPhone 17e,arch=arm64,OS=26.5"' - - platform: "visionOS" - testbed-args: '--simulator "Apple Vision Pro,arch=arm64,OS=26.5"' + platform: ["iOS", "tvOS", "watchOS", "visionOS"] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/Makefile b/Makefile index b16b8b10..826a2246 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.13.16 +PYTHON_VERSION=3.13.17 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+") @@ -292,7 +292,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 @@ -300,7 +300,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" \ @@ -322,14 +322,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 @@ -497,7 +497,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))) @@ -656,8 +656,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 @@ -678,9 +678,9 @@ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ # Create symlink for dylib $$(foreach sdk,$$(SDKS-$(os)),ln -si ../Python.framework/Python $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk))/lib/libpython$(PYTHON_VER).dylib; ) -ifeq ($(filter $(os),iOS tvOS visionOS),$(os)) +ifeq ($(filter $(os),iOS tvOS visionOS watchOS),$(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/README.md b/README.md index b1274c24..dfda5ef9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This is a meta-package for building a version of Python that can be embedded int - [Python 3.12](https://github.com/beeware/Python-Apple-support/tree/3.12) - [Python 3.14](https://github.com/beeware/Python-Apple-support/tree/3.14) - [Python 3.15](https://github.com/beeware/Python-Apple-support/tree/3.15) +- [Python 3.16](https://github.com/beeware/Python-Apple-support/tree/3.16) It works by downloading, patching, and building a fat binary of Python and selected pre-requisites, and packaging them as frameworks that can be incorporated into an Xcode project. The binary modules in the Python standard library are distributed as binaries that can be dynamically loaded at runtime. @@ -35,7 +36,7 @@ The binaries support x86_64 and arm64 for macOS; arm64 for iOS and appleTV devic - tvOS 12.0 or later, on: - Apple TV (4th gen or later) -- watchOS 4.0 or later, on: +- watchOS 9.0 or later, on: - Apple Watch (4th gen or later) - visionOS 2.0 or later, on: diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index 2971ead3..18fa39ad 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,5 +1,5 @@ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml -index 7f8383e6b4a..2d812c5c0d6 100644 +index 9428c4d7cb5..b8c7de11542 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,6 +2,10 @@ @@ -7,9 +7,9 @@ index 7f8383e6b4a..2d812c5c0d6 100644 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] @@ -18,2942 +18,2783 @@ index 7f8383e6b4a..2d812c5c0d6 100644 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/Apple/.ruff.toml b/Apple/.ruff.toml -new file mode 100644 -index 00000000000..4cdc39ebee4 ---- /dev/null -+++ b/Apple/.ruff.toml -@@ -0,0 +1,22 @@ -+extend = "../.ruff.toml" # Inherit the project-wide settings +diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst +index 9921fd6114b..4ed18f4938c 100644 +--- a/Doc/using/ios.rst ++++ b/Doc/using/ios.rst +@@ -170,7 +170,7 @@ + To add Python to an iOS Xcode project: + + 1. Build or obtain a Python ``XCFramework``. See the instructions in +- :source:`iOS/README.rst` (in the CPython source distribution) for details on ++ :source:`Apple/iOS/README.md` (in the CPython source distribution) for details on + how to build a Python ``XCFramework``. At a minimum, you will need a build + that supports ``arm64-apple-ios``, plus one of either + ``arm64-apple-ios-simulator`` or ``x86_64-apple-ios-simulator``. +@@ -180,22 +180,19 @@ + of your project; however, you can use any other location that you want by + adjusting paths as needed. + +-3. Drag the ``iOS/Resources/dylib-Info-template.plist`` file into your project, +- and ensure it is associated with the app target. +- +-4. Add your application code as a folder in your Xcode project. In the ++3. Add your application code as a folder in your Xcode project. In the + following instructions, we'll assume that your user code is in a folder + named ``app`` in the root of your project; you can use any other location by + adjusting paths as needed. Ensure that this folder is associated with your + app target. + +-5. Select the app target by selecting the root node of your Xcode project, then ++4. Select the app target by selecting the root node of your Xcode project, then + the target name in the sidebar that appears. + +-6. In the "General" settings, under "Frameworks, Libraries and Embedded ++5. In the "General" settings, under "Frameworks, Libraries and Embedded + Content", add ``Python.xcframework``, with "Embed & Sign" selected. + +-7. In the "Build Settings" tab, modify the following: ++6. In the "Build Settings" tab, modify the following: + + - Build Options + +@@ -211,86 +208,24 @@ + + * Quoted Include In Framework Header: No + +-8. Add a build step that copies the Python standard library into your app. In +- the "Build Phases" tab, add a new "Run Script" build step *before* the +- "Embed Frameworks" step, but *after* the "Copy Bundle Resources" step. Name +- the step "Install Target Specific Python Standard Library", disable the +- "Based on dependency analysis" checkbox, and set the script content to: ++7. Add a build step that processes the Python standard library, and your own ++ Python binary dependencies. In the "Build Phases" tab, add a new "Run ++ Script" build step *before* the "Embed Frameworks" step, but *after* the ++ "Copy Bundle Resources" step. Name the step "Process Python libraries", ++ disable the "Based on dependency analysis" checkbox, and set the script ++ content to: + + .. code-block:: bash + +- set -e +- +- mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" +- if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then +- echo "Installing Python modules for iOS Simulator" +- rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" +- else +- echo "Installing Python modules for iOS Device" +- rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" +- fi ++ set -e ++ source $PROJECT_DIR/Python.xcframework/build/build_utils.sh ++ install_python Python.xcframework app + +- Note that the name of the simulator "slice" in the XCframework may be +- different, depending the CPU architectures your ``XCFramework`` supports. ++ If you have placed your XCframework somewhere other than the root of your ++ project, modify the path to the first argument. + +-9. Add a second build step that processes the binary extension modules in the +- standard library into "Framework" format. Add a "Run Script" build step +- *directly after* the one you added in step 8, named "Prepare Python Binary +- Modules". It should also have "Based on dependency analysis" unchecked, with +- the following script content: +- +- .. code-block:: bash +- +- set -e +- +- install_dylib () { +- INSTALL_BASE=$1 +- FULL_EXT=$2 +- +- # The name of the extension file +- EXT=$(basename "$FULL_EXT") +- # The location of the extension file, relative to the bundle +- RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} +- # The path to the extension file, relative to the install base +- PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} +- # The full dotted name of the extension module, constructed from the file path. +- FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); +- # A bundle identifier; not actually used, but required by Xcode framework packaging +- FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") +- # The name of the framework folder. +- FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" +- +- # If the framework folder doesn't exist, create it. +- if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then +- echo "Creating framework for $RELATIVE_EXT" +- mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" +- cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" +- plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" +- plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" +- fi +- +- echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" +- mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" +- # Create a placeholder .fwork file where the .so was +- echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork +- # Create a back reference to the .so file location in the framework +- echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" +- } +- +- PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") +- echo "Install Python $PYTHON_VER standard library extension modules..." +- find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do +- install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" +- done +- +- # Clean up dylib template +- rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" +- +- echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." +- find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \; +- +-10. Add Objective C code to initialize and use a Python interpreter in embedded +- mode. You should ensure that: ++8. Add Objective C code to initialize and use a Python interpreter in embedded ++ mode. You should ensure that: + + * UTF-8 mode (:c:member:`PyPreConfig.utf8_mode`) is *enabled*; + * Buffered stdio (:c:member:`PyConfig.buffered_stdio`) is *disabled*; +@@ -307,22 +242,19 @@ + Your app's bundle location can be determined using ``[[NSBundle mainBundle] + resourcePath]``. + +-Steps 8, 9 and 10 of these instructions assume that you have a single folder of ++Steps 7 and 8 of these instructions assume that you have a single folder of + pure Python application code, named ``app``. If you have third-party binary + modules in your app, some additional steps will be required: + + * You need to ensure that any folders containing third-party binaries are +- either associated with the app target, or copied in as part of step 8. Step 8 +- should also purge any binaries that are not appropriate for the platform a +- specific build is targeting (i.e., delete any device binaries if you're +- building an app targeting the simulator). +- +-* Any folders that contain third-party binaries must be processed into +- framework form by step 9. The invocation of ``install_dylib`` that processes +- the ``lib-dynload`` folder can be copied and adapted for this purpose. ++ either associated with the app target, or are explicitly copied as part of ++ step 7. Step 7 should also purge any binaries that are not appropriate for ++ the platform a specific build is targeting (i.e., delete any device binaries ++ if you're building an app targeting the simulator). + +-* If you're using a separate folder for third-party packages, ensure that folder +- is included as part of the :envvar:`PYTHONPATH` configuration in step 10. ++* If you're using a separate folder for third-party packages, ensure that ++ folder is added to the end of the call to ``install_python`` in step 7, and ++ as part of the :envvar:`PYTHONPATH` configuration in step 8. + + * If any of the folders that contain third-party packages will contain ``.pth`` + files, you should add that folder as a *site directory* (using +@@ -332,25 +264,30 @@ + Testing a Python package + ------------------------ + +-The CPython source tree contains :source:`a testbed project ` that ++The CPython source tree contains :source:`a testbed project ` that + is used to run the CPython test suite on the iOS simulator. This testbed can also + be used as a testbed project for running your Python library's test suite on iOS. + +-After building or obtaining an iOS XCFramework (See :source:`iOS/README.rst` +-for details), create a clone of the Python iOS testbed project by running: ++After building or obtaining an iOS XCFramework (see :source:`Apple/iOS/README.md` ++for details), create a clone of the Python iOS testbed project. If you used the ++``Apple`` build script to build the XCframework, you can run: + -+[format] -+preview = true -+docstring-code-format = true ++.. code-block:: bash + -+[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/Apple/__main__.py b/Apple/__main__.py -new file mode 100644 -index 00000000000..7f9c7686678 ---- /dev/null -+++ b/Apple/__main__.py -@@ -0,0 +1,1165 @@ -+#!/usr/bin/env python3 -+########################################################################## -+# Apple XCframework build script -+# -+# This script simplifies the process of configuring, compiling and packaging an -+# XCframework for an Apple platform. -+# -+# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been -+# constructed so that it could be used on any Apple platform. -+# -+# The simplest entry point is: -+# -+# $ python Apple ci iOS -+# -+# (replace iOS with tvOS, visionOS or watchOS as required.) -+# -+# which will: -+# * Clean any pre-existing build artefacts -+# * Configure and make a Python that can be used for the build -+# * Configure and make a Python for each supported iOS/tvOS/watchOS/visionOS -+# architecture and ABI -+# * Combine the outputs of the builds from the previous step into a single -+# XCframework, merging binaries into a "fat" binary if necessary -+# * Clone a copy of the testbed, configured to use the XCframework -+# * Construct a tarball containing the release artefacts -+# * Run the test suite using the generated XCframework. -+# -+# This is the complete sequence that would be needed in CI to build and test -+# a candidate release artefact. -+# -+# Each individual step can be invoked individually - there are commands to -+# clean, configure-build, make-build, configure-host, make-host, package, and -+# test. -+# -+# There is also a build command that can be used to combine the configure and -+# make steps for the build Python, an individual host, all hosts, or all -+# builds. -+########################################################################## -+from __future__ import annotations ++ $ python cross-build/iOS/testbed clone --app --app app-testbed + -+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 ++Or, if you've sourced your own XCframework, by running: + + .. code-block:: bash + +- $ python iOS/testbed clone --framework --app --app app-testbed ++ $ python Apple/testbed clone --platform iOS --framework --app --app app-testbed + +-You will need to modify the ``iOS/testbed`` reference to point to that +-directory in the CPython source tree; any folders specified with the ``--app`` +-flag will be copied into the cloned testbed project. The resulting testbed will +-be created in the ``app-testbed`` folder. In this example, the ``module1`` and +-``module2`` would be importable modules at runtime. If your project has +-additional dependencies, they can be installed into the +-``app-testbed/iOSTestbed/app_packages`` folder (using ``pip install --target +-app-testbed/iOSTestbed/app_packages`` or similar). ++Any folders specified with the ``--app`` flag will be copied into the cloned ++testbed project. The resulting testbed will be created in the ``app-testbed`` ++folder. In this example, the ``module1`` and ``module2`` would be importable ++modules at runtime. If your project has additional dependencies, they can be ++installed into the ``app-testbed/Testbed/app_packages`` folder (using ``pip ++install --target app-testbed/Testbed/app_packages`` or similar). + + You can then use the ``app-testbed`` folder to run the test suite for your app, + For example, if ``module1.tests`` was the entry point to your test suite, you +@@ -379,7 +316,7 @@ + arguments. + + The test plan also disables parallel testing, and specifies the use of the +-``iOSTestbed.lldbinit`` file for providing configuration of the debugger. 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. + +@@ -389,7 +326,12 @@ + The only mechanism for distributing apps to third-party iOS devices is to + submit the app to the iOS App Store; apps submitted for distribution must pass + Apple's app review process. This process includes a set of automated validation +-rules that inspect the submitted application bundle for problematic code. ++rules that inspect the submitted application bundle for problematic code. There ++are some steps that must be taken to ensure that your app will be able to pass ++these validation steps. + -+CROSS_BUILD_DIR = PYTHON_DIR / "cross-build" ++Incompatible code in the standard library ++----------------------------------------- + + The Python standard library contains some code that is known to violate these + automated rules. While these violations appear to be false positives, Apple's +@@ -400,3 +342,18 @@ + :source:`a patch file ` that will remove + all code that is known to cause issues with the App Store review process. This + patch is applied automatically when building for iOS. + -+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", -+ }, -+ }, -+} ++Privacy manifests ++----------------- + ++In April 2025, Apple introduced a requirement for `certain third-party ++libraries to provide a Privacy Manifest ++`__. ++As a result, if you have a binary module that uses one of the affected ++libraries, you must provide an ``.xcprivacy`` file for that library. ++OpenSSL is one library affected by this requirement, but there are others. + -+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 ++If you produce a binary module named ``mymodule.so``, and use you the Xcode ++build script described in step 7 above, you can place a ``mymodule.xcprivacy`` ++file next to ``mymodule.so``, and the privacy manifest will be installed into ++the required location when the binary module is converted into a framework. +diff --git a/Include/internal/mimalloc/mimalloc/prim.h b/Include/internal/mimalloc/mimalloc/prim.h +index 322ab29e6b4..bb54814238d 100644 +--- a/Include/internal/mimalloc/mimalloc/prim.h ++++ b/Include/internal/mimalloc/mimalloc/prim.h +@@ -174,12 +174,15 @@ + __asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + res = tcb[slot]; + #elif defined(__aarch64__) +- void** tcb; MI_UNUSED(ofs); ++ // Use a 64-bit temporary: on arm64_32 (ILP32), pointers are 32 bits ++ // but system registers are 64 bits. ++ uint64_t tp; MI_UNUSED(ofs); + #if defined(__APPLE__) // M1, issue #343 +- __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tp)); + #else +- __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tp)); + #endif ++ void** tcb = (void**)(uintptr_t)tp; + res = tcb[slot]; + #endif + return res; +@@ -201,12 +204,15 @@ + __asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + tcb[slot] = value; + #elif defined(__aarch64__) +- void** tcb; MI_UNUSED(ofs); ++ // Use a 64-bit temporary: on arm64_32 (ILP32), pointers are 32 bits ++ // but system registers are 64 bits. ++ uint64_t tp; MI_UNUSED(ofs); + #if defined(__APPLE__) // M1, issue #343 +- __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tp)); + #else +- __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); ++ __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tp)); + #endif ++ void** tcb = (void**)(uintptr_t)tp; + tcb[slot] = value; + #endif + } +diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py +index 62d375f9dca..fd0a2dc1616 100644 +--- a/Lib/asyncio/unix_events.py ++++ b/Lib/asyncio/unix_events.py +@@ -673,10 +673,10 @@ + # end polls as readable whenever unread data sits in the FIFO, and no + # event is delivered when the read end is closed, so it can only + # ever report a false disconnection (gh-145030). The same XNU +- # behaviour applies on iOS/tvOS/watchOS (sys.platform is not ++ # behaviour applies on iOS/tvOS/visionOS/watchOS (sys.platform is not + # "darwin" there). + is_named_fifo_without_close_event = ( +- sys.platform in {"darwin", "ios", "tvos", "watchos", "sunos5"} ++ sys.platform in {"darwin", "ios", "tvos", "visionos", "watchos", "sunos5"} + and is_fifo and pipe_stat.st_nlink > 0) + if is_socket or (is_fifo + and not sys.platform.startswith("aix") +diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py +index c430f9faa47..e733003e965 100644 +--- a/Lib/ctypes/__init__.py ++++ b/Lib/ctypes/__init__.py +@@ -380,9 +380,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 43f10dedce8..7cbc6039fa2 100644 +--- a/Lib/ctypes/util.py ++++ b/Lib/ctypes/util.py +@@ -67,7 +67,7 @@ + return fname + return None + +-elif os.name == "posix" and sys.platform 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, +diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py +index fcfe312747c..e02a897ea08 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) + +@@ -1806,7 +1806,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 8895177e326..9e1e0628671 100755 +--- a/Lib/platform.py ++++ b/Lib/platform.py +@@ -522,6 +522,78 @@ + return IOSVersionInfo(system, release, model, is_simulator) + + ++# A namedtuple for tvOS version information. ++TVOSVersionInfo = collections.namedtuple( ++ "TVOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) + + -+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. ++def tvos_ver(system="", release="", model="", is_simulator=False): ++ """Get tvOS version information, and return it as a namedtuple: ++ (system, release, model, is_simulator). + -+ Optionally logs the executed command to the console. ++ If values can't be determined, they are set to values provided as ++ parameters. + """ -+ kwargs.setdefault("check", True) -+ if env is None: -+ env = os.environ.copy() ++ 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) + -+ if host: -+ host_env = apple_env(host) -+ print_env(host_env) -+ env.update(host_env) ++ return TVOSVersionInfo(system, release, model, is_simulator) + -+ if log: -+ print(">", join_command(command)) -+ return subprocess.run(command, env=env, **kwargs) + ++# A namedtuple for watchOS version information. ++WatchOSVersionInfo = collections.namedtuple( ++ "WatchOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) + -+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. ++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 isinstance(args, (str, Path)): -+ return str(args) -+ else: -+ return shlex.join(map(str, args)) ++ if sys.platform == "watchos": ++ # TODO: Can the iOS implementation be used here? ++ import _ios_support ++ result = _ios_support.get_platform_ios() ++ if result is not None: ++ return WatchOSVersionInfo(*result) + ++ return WatchOSVersionInfo(system, release, model, is_simulator) + -+def print_env(env: EnvironmentT) -> None: -+ """Format the environment so it can be pasted into a shell.""" -+ for key, value in sorted(env.items()): -+ print(f"export {key}={shlex.quote(value)}") -+ -+ -+def platform_for_host(host): -+ """Determine the platform for a given host triple.""" -+ for plat, slices in HOSTS.items(): -+ for _, candidates in slices.items(): -+ for candidate in candidates: -+ if candidate == host: -+ return plat -+ raise KeyError(host) -+ -+ -+def apple_env(host: str) -> EnvironmentT: -+ """Construct an Apple development environment for the given host.""" -+ env = { -+ "PATH": ":".join([ -+ str(PYTHON_DIR / f"Apple/{platform_for_host(host)}/Resources/bin"), -+ str(subdir(host) / "prefix"), -+ "/usr/bin", -+ "/bin", -+ "/usr/sbin", -+ "/sbin", -+ "/Library/Apple/usr/bin", -+ ]), -+ } -+ -+ 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 ++# A namedtuple for visionOS version information. ++VisionOSVersionInfo = collections.namedtuple( ++ "VisionOSVersionInfo", ++ ["system", "release", "model", "is_simulator"] ++) + + -+@contextmanager -+def group(text: str): -+ """A context manager that outputs a log marker around a section of a build. ++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 running in a GitHub Actions environment, the GitHub syntax for -+ collapsible log sections is used. ++ If values can't be determined, they are set to values provided as ++ parameters. + """ -+ 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 pythoninfo_build_python(context: argparse.Namespace) -> None: -+ """The implementation of the "pythoninfo-build" command.""" -+ with ( -+ group("Display build info of the build Python"), -+ cwd(subdir("build")), -+ ): -+ run(["make", "pythoninfo"]) -+ -+ -+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) -+ ++ 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) + -+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 ++ return VisionOSVersionInfo(system, release, model, is_simulator) + -+ raise KeyError(host) + + 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 +@@ -721,7 +793,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 + +@@ -885,14 +957,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 unpack_deps( -+ platform: str, -+ host: str, -+ prefix_dir: Path, -+ cache_dir: Path, -+) -> None: -+ """Unpack binary dependencies into a provided directory. ++ def get_visionos(): ++ if sys.implementation._multiarch.endswith("simulator"): ++ return os.uname().machine ++ return 'arm64' + -+ Downloads binaries if they aren't already present. Downloads will be stored -+ in provided cache directory. ++ def get_watchos(): ++ if sys.implementation._multiarch.endswith("simulator"): ++ return os.uname().machine ++ return 'arm64_32' + -+ On non-macOS platforms, as a safety mechanism, any dynamic libraries will -+ be purged from the unpacked dependencies. -+ """ -+ # To create new builds of these dependencies, usually all that's necessary -+ # is to push a tag to the cpython-apple-source-deps repository, and GitHub -+ # Actions will do the rest. -+ # -+ # If you're a member of the Python core team, and you'd like to be able to -+ # push these tags yourself, please contact Malcolm Smith or Russell -+ # Keith-Magee. -+ deps_url = "https://github.com/beeware/cpython-apple-source-deps/releases/download" -+ for name_ver in [ -+ "BZip2-1.0.8-2", -+ "libFFI-3.4.7-2", -+ "OpenSSL-3.0.19-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 + def from_subprocess(): + """ + Fall back to `uname -p` +@@ -1052,9 +1140,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 '' +@@ -1344,6 +1438,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 041dca113a5..3dedc828524 100644 +--- a/Lib/site.py ++++ b/Lib/site.py +@@ -292,8 +292,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 e845f48a407..b64a0553973 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 43edebce347..c6136b661ac 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 = { +@@ -117,7 +120,7 @@ + return env_base + + # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories +- if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}: ++ if sys.platform in {"emscripten", "ios", "tvos", "visionos", "vxworks", "wasi", "watchos"}: + return None + + def joinuser(*args): +@@ -688,6 +691,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", "9.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 ca60e204cff..ab7aeba9e7d 100644 +--- a/Lib/test/datetimetester.py ++++ b/Lib/test/datetimetester.py +@@ -6897,9 +6897,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/pythoninfo.py b/Lib/test/pythoninfo.py +index e6b6271763d..5f378af91e1 100644 +--- a/Lib/test/pythoninfo.py ++++ b/Lib/test/pythoninfo.py +@@ -9,7 +9,7 @@ + + + MS_WINDOWS = (sys.platform == "win32") +-APPLE = (sys.platform in ("darwin", "ios", "tvos", "watchos")) ++APPLE = (sys.platform in ("darwin", "ios", "tvos", "watchos", "visionos")) + + COMMAND_TIMEOUT = 60.0 + +diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py +index abc525b80e0..7c414caa27c 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -66,6 +66,7 @@ + "force_not_colorized_test_class", + "make_clean_env", + "BrokenIter", ++ "on_github_actions" + ] + + +@@ -594,7 +595,7 @@ + + is_android = sys.platform == "android" + +-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 +605,7 @@ + is_emscripten = sys.platform == "emscripten" + is_wasi = sys.platform == "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 ( +@@ -1348,6 +1349,8 @@ + _opcode.ENABLE_SPECIALIZATION, "requires specialization")(test) + + ++on_github_actions = "GITHUB_ACTIONS" in os.environ + -+ with ( -+ group(f"Compiling host Python ({host})"), -+ cwd(subdir(host)), -+ ): -+ run(["make", "-j", str(os.cpu_count())], host=host) -+ run(["make", "install"], host=host) + #======================================================================= + # Check for the presence of docstrings. + +diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py +index 6fb2bc24fa3..88cb6691cd8 100644 +--- a/Lib/test/test__interpreters.py ++++ b/Lib/test/test__interpreters.py +@@ -695,6 +695,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_platform.py b/Lib/test/test_platform.py +index ed277276b51..c846a9bc5d7 100644 +--- a/Lib/test/test_platform.py ++++ b/Lib/test/test_platform.py +@@ -236,13 +236,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_socketserver.py b/Lib/test/test_socketserver.py +index 0f62f9eb200..2ca356606b2 100644 +--- a/Lib/test/test_socketserver.py ++++ b/Lib/test/test_socketserver.py +@@ -218,12 +218,16 @@ + self.dgram_examine) + + @requires_unix_sockets ++ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, ++ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") + def test_UnixDatagramServer(self): + self.run_server(socketserver.UnixDatagramServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) + + @requires_unix_sockets ++ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, ++ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") + def test_ThreadingUnixDatagramServer(self): + self.run_server(socketserver.ThreadingUnixDatagramServer, + socketserver.DatagramRequestHandler, +diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py +index 135f116d46b..c756e8e0ba7 100644 +--- a/Lib/test/test_webbrowser.py ++++ b/Lib/test/test_webbrowser.py +@@ -252,7 +252,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 be033d707a9..8e47e6576fe 100755 +--- a/Lib/webbrowser.py ++++ b/Lib/webbrowser.py +@@ -500,7 +500,8 @@ + # OS X can use below Unix support (but we prefer using the OS X + # 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": +@@ -629,9 +630,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/Mac/BuildScript/resources/install_certificates.command b/Mac/BuildScript/resources/install_certificates.command +index 700eb462b68..aa44961da7f 100755 +--- a/Mac/BuildScript/resources/install_certificates.command ++++ b/Mac/BuildScript/resources/install_certificates.command +@@ -1,4 +1,8 @@ + #!/bin/sh ++# Installing into the global environment is the point; ensure that the user ++# hasn't configured pip to always require a virtual environment. ++unset PIP_REQUIRE_VENV ++unset PIP_REQUIRE_VIRTUALENV + + /Library/Frameworks/Python.framework/Versions/@PYVER@/bin/python@PYVER@ << "EOF" + +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 8fdbbd83b4b..7e98a9bd23e 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -202,6 +202,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 + + -+def framework_path(host_triple: str, multiarch: str) -> Path: -+ """The path to a built single-architecture framework product. + # Option to install to strip binaries + STRIPFLAG=-s + +@@ -2094,7 +2100,7 @@ + fi + + # Clone the testbed project into the XCFOLDER +- $(PYTHON_FOR_BUILD) $(srcdir)/iOS/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 +@@ -2798,6 +2804,9 @@ + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) + sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist + $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(LIBDIR) ++ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(LDVERSION).dylib" ++ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(VERSION).dylib" + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) + for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ + $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ +@@ -2982,10 +2991,10 @@ + -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' + -rm -f Include/pydtrace_probes.h + -rm -f profile-gen-stamp +- -rm -rf iOS/testbed/Python.xcframework/ios-*/bin +- -rm -rf iOS/testbed/Python.xcframework/ios-*/lib +- -rm -rf iOS/testbed/Python.xcframework/ios-*/include +- -rm -rf iOS/testbed/Python.xcframework/ios-*/Python.framework ++ -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: +@@ -3019,7 +3028,7 @@ + config.cache config.log pyconfig.h Modules/config.c + -rm -rf build platform + -rm -rf $(PYTHONFRAMEWORKDIR) +- -rm -rf 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 + -+ :param host_triple: The host triple (e.g., arm64-apple-ios-simulator) -+ :param multiarch: The multiarch identifier (e.g., arm64-simulator) -+ """ -+ return ( -+ CROSS_BUILD_DIR -+ / f"{host_triple}/Apple/{platform_for_host(host_triple)}" -+ / f"Frameworks/{multiarch}" -+ ) ++[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/Apple/tvOS/README.rst b/Platforms/Apple/Apple/tvOS/README.rst +new file mode 100644 +index 00000000000..1f793252caf +--- /dev/null ++++ b/Platforms/Apple/Apple/tvOS/README.rst +@@ -0,0 +1,108 @@ ++===================== ++Python on tvOS README ++===================== + -+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.") ++:Authors: ++ Russell Keith-Magee (2023-11) + ++This document provides a quick overview of some tvOS specific features in the ++Python distribution. + -+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() ++Compilers for building on tvOS ++============================== + -+ return ignored_names ++Building for tvOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode, on the ++most recently released macOS. + ++tvOS specific arguments to configure ++=================================== + -+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() ++* ``--enable-framework[=DIR]`` + ++ This argument specifies the location where the Python.framework will ++ be installed. + -+def create_xcframework(platform: str) -> str: -+ """Build an XCframework from the component parts for the platform. ++* ``--with-framework-name=NAME`` + -+ :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 ++ Specify the name for the python framework, defaults to ``Python``. + -+ 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) ++Building and using Python on tvOS ++================================= + -+ # Copy the Info.plist -+ shutil.copy( -+ first_framework / "Info.plist", -+ slice_framework / "Info.plist", -+ ) ++ABIs and Architectures ++---------------------- + -+ # Copy the headers -+ shutil.copytree( -+ first_framework / "Headers", -+ slice_framework / "Headers", -+ ) ++tvOS apps can be deployed on physical devices, and on the tvOS simulator. ++Although the API used on these devices is identical, the ABI is different - you ++need to link against different libraries for an tvOS device build ++(``appletvos``) or an tvOS simulator build (``appletvsimulator``). Apple uses ++the XCframework format to allow specifying a single dependency that supports ++multiple ABIs. An XCframework is a wrapper around multiple ABI-specific ++frameworks. + -+ # 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() -+ ] -+ ) ++tvOS can also support different CPU architectures within each ABI. At present, ++there is only a single support ed architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines.) + -+ # Add this merged slice to the list to be added to the XCframework -+ frameworks.append(slice_framework) ++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. + -+ print() -+ print("Build XCframework...") -+ cmd = [ -+ "xcodebuild", -+ "-create-xcframework", -+ "-output", -+ package_path / "Python.xcframework", -+ ] -+ for framework in frameworks: -+ cmd.extend(["-framework", framework]) ++How do I build Python for tvOS? ++------------------------------- + -+ run(cmd) ++The Python build system will build a ``Python.framework`` that supports a ++*single* ABI with a *single* architecture. If you want to use Python in an tvOS ++project, you need to: + -+ # Extract the package version from the merged framework -+ version = package_version(package_path / "Python.xcframework") -+ version_tag = ".".join(version.split(".")[:2]) ++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. + -+ # 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" ++tvOS builds of Python *must* be constructed as framework builds. To support this, ++you must provide the ``--enable-framework`` flag when configuring the build. + -+ slice_path = package_path / f"Python.xcframework/{slice_name}" -+ slice_framework = slice_path / "Python.framework" ++The build also requires the use of cross-compilation. The commands for building ++Python for tvOS will look somethign like:: + -+ # Copy the binary helpers -+ print(f" - {slice_name} binaries") -+ shutil.copytree(first_path / "bin", slice_path / "bin") ++ $ ./configure \ ++ --enable-framework=/path/to/install \ ++ --host=aarch64-apple-tvos \ ++ --build=aarch64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++ $ make ++ $ make install + -+ # 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, -+ ) ++In this invocation: + -+ # Copy in the cross-architecture pyconfig.h -+ shutil.copy( -+ PYTHON_DIR / f"Apple/{platform}/Resources/pyconfig.h", -+ slice_framework / "Headers/pyconfig.h", -+ ) ++* ``/path/to/install`` is the location where the final Python.framework will be ++ output. + -+ 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") ++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: + -+ print(f" - {slice_name} architecture-specific files") -+ for host_triple, multiarch in slice_parts.items(): -+ print(f" - {multiarch} standard library") -+ arch, _ = multiarch.split("-", 1) ++ - ``aarch64-apple-tvos`` for ARM64 tvOS devices. ++ - ``aarch64-apple-tvos-simulator`` for the tvOS simulator running on Apple ++ Silicon devices. ++ - ``x86_64-apple-tvos-simulator`` for the tvOS simulator running on Intel ++ devices. + -+ 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 ++* ``--build`` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: + -+ shutil.copytree( -+ framework_path(host_triple, multiarch) / "lib", -+ slice_path / f"lib-{arch}", -+ ignore=lib_non_platform_files, -+ symlinks=True, -+ ) ++ - ``aarch64-apple-darwin`` for Apple Silicon devices. ++ - ``x86_64-apple-darwin`` for Intel devices. + -+ # Copy the host's pyconfig.h to an architecture-specific name. -+ arch = multiarch.split("-")[0] -+ host_path = ( -+ CROSS_BUILD_DIR -+ / host_triple -+ / f"Apple/{platform}/Frameworks" -+ / multiarch -+ ) -+ host_framework = host_path / "Python.framework" -+ shutil.copy( -+ host_framework / "Headers/pyconfig.h", -+ slice_framework / f"Headers/pyconfig-{arch}.h", -+ ) ++* ``/path/to/python.exe`` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for tvOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the version as the Python that is being compiled. + -+ # 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 / "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, -+ "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, -+ pythoninfo_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] ++Using a framework-based Python on tvOS ++====================================== +diff --git a/Platforms/Apple/Apple/tvOS/Resources/Info.plist.in b/Platforms/Apple/Apple/tvOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..ab3050804b8 +--- /dev/null ++++ b/Platforms/Apple/Apple/tvOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ @TVOS_DEPLOYMENT_TARGET@ ++ ++ +diff --git a/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar +new file mode 100755 +index 00000000000..e302748a13c +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang +new file mode 100755 +index 00000000000..7fb6d3d901c +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ +new file mode 100755 +index 00000000000..33bfb1367c3 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp +new file mode 100755 +index 00000000000..641c1bc8d18 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar +new file mode 100755 +index 00000000000..87ef5015aae +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang +new file mode 100755 +index 00000000000..c8719cb0318 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ +new file mode 100755 +index 00000000000..e3f0e720f7f +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp +new file mode 100755 +index 00000000000..f9a37b72a61 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip +new file mode 100755 +index 00000000000..a8cce95233e +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip b/Platforms/Apple/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip +new file mode 100755 +index 00000000000..ee1d2b95ff1 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar b/Platforms/Apple/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +new file mode 100755 +index 00000000000..87ef5015aae +--- /dev/null ++++ b/Platforms/Apple/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang b/Platforms/Apple/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang +new file mode 100755 +index 00000000000..ea0cc26cbd9 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ b/Platforms/Apple/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ +new file mode 100755 +index 00000000000..f18f3603169 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp b/Platforms/Apple/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp +new file mode 100755 +index 00000000000..b98054d1ce2 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip b/Platforms/Apple/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip +new file mode 100755 +index 00000000000..f6a884b4aef +--- /dev/null ++++ b/Platforms/Apple/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/Apple/tvOS/Resources/pyconfig.h b/Platforms/Apple/Apple/tvOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..4acff2c6051 +--- /dev/null ++++ b/Platforms/Apple/Apple/tvOS/Resources/pyconfig.h +@@ -0,0 +1,7 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif + -+ if not host.endswith("-simulator"): -+ print("Skipping test suite non-simulator build.") -+ return -+ elif build_arch != host_arch: -+ print( -+ f"Skipping test suite for an {host_arch} build " -+ f"on an {build_arch} machine." -+ ) -+ return -+ else: -+ framework_path = ( -+ CROSS_BUILD_DIR -+ / host -+ / f"Apple/{context.platform}" -+ / f"Frameworks/{apple_multiarch(host)}" -+ ) -+ -+ run([ -+ sys.executable, -+ "Apple/testbed", -+ "clone", -+ "--platform", -+ context.platform, -+ "--framework", -+ framework_path, -+ testbed_dir, -+ ]) ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +diff --git a/Platforms/Apple/Apple/visionOS/Resources/Info.plist.in b/Platforms/Apple/Apple/visionOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..2679440da67 +--- /dev/null ++++ b/Platforms/Apple/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@ ++ ++ +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-ar b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-ar +new file mode 100755 +index 00000000000..9fd78a205f3 +--- /dev/null ++++ b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xros${XROS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-clang b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-clang +new file mode 100755 +index 00000000000..9a1a757cbd0 +--- /dev/null ++++ b/Platforms/Apple/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} "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ +new file mode 100755 +index 00000000000..f64fcfc11cd +--- /dev/null ++++ b/Platforms/Apple/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} "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp +new file mode 100755 +index 00000000000..d6492eff052 +--- /dev/null ++++ b/Platforms/Apple/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 "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar +new file mode 100755 +index 00000000000..b202330fb5d +--- /dev/null ++++ b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xrsimulator${XROS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang +new file mode 100755 +index 00000000000..87b0aba0751 +--- /dev/null ++++ b/Platforms/Apple/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 "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ +new file mode 100755 +index 00000000000..c89d48f1cb8 +--- /dev/null ++++ b/Platforms/Apple/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 "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp +new file mode 100755 +index 00000000000..ee11e018ae0 +--- /dev/null ++++ b/Platforms/Apple/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 "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip +new file mode 100644 +index 00000000000..376aff61a23 +--- /dev/null ++++ b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk xrsimulator${XROS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-strip b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-strip +new file mode 100644 +index 00000000000..2de3ba5d31c +--- /dev/null ++++ b/Platforms/Apple/Apple/visionOS/Resources/bin/arm64-apple-xros-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk xros${XROS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/Apple/visionOS/Resources/pyconfig.h b/Platforms/Apple/Apple/visionOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..2a433078400 +--- /dev/null ++++ b/Platforms/Apple/Apple/visionOS/Resources/pyconfig.h +@@ -0,0 +1,3 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif +diff --git a/Platforms/Apple/Apple/watchOS/README.rst b/Platforms/Apple/Apple/watchOS/README.rst +new file mode 100644 +index 00000000000..35221478452 +--- /dev/null ++++ b/Platforms/Apple/Apple/watchOS/README.rst +@@ -0,0 +1,108 @@ ++======================== ++Python on watchOS README ++======================== + -+ 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", -+ ] -+ ) ++:Authors: ++ Russell Keith-Magee (2023-11) + ++This document provides a quick overview of some watchOS specific features in the ++Python distribution. + -+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 ++Compilers for building on watchOS ++================================= + -+ raise KeyError(platform_name) ++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 ++======================================= + -+def ci(context: argparse.Namespace) -> None: -+ """The implementation of the "ci" command. ++* ``--enable-framework[=DIR]`` + -+ 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. ++ This argument specifies the location where the Python.framework will ++ be installed. + -+ 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) ++* ``--with-framework-name=NAME`` + ++ Specify the name for the python framework, defaults to ``Python``. + -+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", -+ ) ++Building and using Python on watchOS ++==================================== + -+ 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" -+ ) -+ pythoninfo_build = subcommands.add_parser( -+ "pythoninfo-build", help="Display build info of 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", -+ ) ++ABIs and Architectures ++---------------------- + -+ # 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, -+ pythoninfo_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.", -+ ) ++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. + -+ # --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", -+ ) ++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.) + -+ for subcommand in [configure_build, configure_host, build, ci]: -+ subcommand.add_argument( -+ "args", nargs="*", help="Extra arguments to pass to `configure`" -+ ) ++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. + -+ return parser.parse_args() ++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: + -+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") ++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. + -+ # shlex uses single quotes, so we surround the command with double quotes. -+ print( -+ f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}' -+ ) ++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:: + -+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) ++ $ ./configure \ ++ --enable-framework=/path/to/install \ ++ --host=aarch64-apple-watchos \ ++ --build=aarch64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++ $ make ++ $ make install + -+ signal.signal(signal.SIGTERM, signal_handler) ++In this invocation: + -+ # Process command line arguments -+ context = parse_args() ++* ``/path/to/install`` is the location where the final Python.framework will be ++ output. + -+ # 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() ++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: + -+ dispatch: dict[str, Callable] = { -+ "clean": clean, -+ "configure-build": configure_build_python, -+ "make-build": make_build_python, -+ "pythoninfo-build": pythoninfo_build_python, -+ "configure-host": configure_host_python, -+ "make-host": make_host_python, -+ "package": package, -+ "build": build, -+ "test": test, -+ "ci": ci, -+ } ++ - ``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. + -+ 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) ++* ``--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. + -+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) ++* ``/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. + -+ main() -diff --git a/Apple/iOS/README.md b/Apple/iOS/README.md ++Using a framework-based Python on watchOS ++====================================== +diff --git a/Platforms/Apple/Apple/watchOS/Resources/Info.plist.in b/Platforms/Apple/Apple/watchOS/Resources/Info.plist.in new file mode 100644 -index 00000000000..7ee257b5d64 +index 00000000000..e83ddfd2a43 --- /dev/null -+++ b/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 `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 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 `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 `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. ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar +new file mode 100755 +index 00000000000..dda2b211bd5 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang +new file mode 100755 +index 00000000000..fe834d3efe4 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ +new file mode 100755 +index 00000000000..757f3a26d8f +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp +new file mode 100755 +index 00000000000..fdb57d9e010 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip +new file mode 100755 +index 00000000000..e28e3f7597a +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip +new file mode 100755 +index 00000000000..efe5a1260ad +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar +new file mode 100755 +index 00000000000..029f9a32073 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang +new file mode 100755 +index 00000000000..285036d4010 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ +new file mode 100755 +index 00000000000..c8f60ebec51 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp b/Platforms/Apple/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp +new file mode 100755 +index 00000000000..b411fc25aa4 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar b/Platforms/Apple/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar +new file mode 100755 +index 00000000000..dda2b211bd5 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang b/Platforms/Apple/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang +new file mode 100755 +index 00000000000..4776b9b5348 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ b/Platforms/Apple/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ +new file mode 100755 +index 00000000000..e9b0c5f4b87 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp b/Platforms/Apple/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp +new file mode 100755 +index 00000000000..d3b821c5f7f +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip b/Platforms/Apple/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip +new file mode 100755 +index 00000000000..105c78281f9 +--- /dev/null ++++ b/Platforms/Apple/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/Apple/watchOS/Resources/pyconfig.h b/Platforms/Apple/Apple/watchOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..f842b987b2e +--- /dev/null ++++ b/Platforms/Apple/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 + -+ This argument *must* be provided when configuring iOS builds. iOS does not -+ support non-framework builds. ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py +new file mode 100644 +index 00000000000..176ccabf04b +--- /dev/null ++++ b/Platforms/Apple/__main__.py +@@ -0,0 +1,1166 @@ ++#!/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 + -+* `--with-framework-name=NAME` ++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 + -+ Specify the name for the Python framework; defaults to `Python`. ++EnvironmentT = dict[str, str] ++ArgsT = Sequence[str | Path] + -+ > [!NOTE] -+ > Unless you know what you're doing, changing the name of the Python -+ > framework on iOS is not advised. If you use this option, you won't be able -+ > to run the `Apple` build script without making significant manual -+ > alterations, and you won't be able to use any binary packages unless you -+ > compile them yourself using your own framework name. ++SCRIPT_NAME = Path(__file__).name ++PYTHON_DIR = Path(__file__).resolve().parent.parent.parent + -+#### Building Python for iOS ++CROSS_BUILD_DIR = PYTHON_DIR / "cross-build" + -+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)/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 -+``` ++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", ++ }, ++ }, ++} + -+In this invocation: + -+* `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. ++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 + -+ To work around this problem, the `Apple/iOS/Resources/bin` folder contains some -+ wrapper scripts that present as simple compilers and linkers, but wrap -+ underlying calls to `xcrun`. This allows configure to use a `CC` -+ definition without spaces, and without user- or version-specific paths, while -+ retaining the ability to adapt to the local Xcode install. These scripts are -+ included in the `bin` directory of an iOS install. + -+ 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.) ++def run( ++ command: ArgsT, ++ *, ++ host: str | None = None, ++ env: EnvironmentT | None = None, ++ log: bool | None = True, ++ **kwargs, ++) -> subprocess.CompletedProcess: ++ """Run a command in an Apple development environment. + -+ The 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. ++ Optionally logs the executed command to the console. ++ """ ++ kwargs.setdefault("check", True) ++ if env is None: ++ env = os.environ.copy() + -+* `--host` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: ++ if host: ++ host_env = apple_env(host) ++ print_env(host_env) ++ env.update(host_env) + -+ - `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. ++ if log: ++ print(">", join_command(command)) ++ return subprocess.run(command, env=env, **kwargs) + -+* `--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. ++def join_command(args: str | Path | ArgsT) -> str: ++ """Format a command so it can be copied into a shell. + -+* `/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. ++ 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)) + -+* 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. ++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)}") + -+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. ++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) + -+## Testing Python on iOS + -+### Testing a multi-architecture framework ++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", ++ ]), ++ } + -+Once you have a built an XCframework, you can test that framework by running: ++ return env + -+ $ python 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. ++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) + -+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 ++def all_host_triples(platform: str) -> list[str]: ++ """Return all host triples for the given platform. + -+The `Apple/testbed` folder that contains an Xcode project that is able to run -+the Python test suite on Apple platforms. This project converts the Python test -+suite into a single test case in Xcode's XCTest framework. The single XCTest -+passes if the test suite passes. ++ 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 + -+To run the test suite, configure a Python build for an iOS simulator (i.e., -+`--host=arm64-apple-ios-simulator` or `--host=x86_64-apple-ios-simulator` -+), specifying a framework build (i.e. `--enable-framework`). Ensure that your -+`PATH` has been configured to include the `Apple/iOS/Resources/bin` folder and -+exclude any non-iOS tools, then run: -+``` -+make all -+make install -+make testios -+``` + -+This will: ++def clean(context: argparse.Namespace, target: str | None = None) -> None: ++ """The implementation of the "clean" command.""" ++ if target is None: ++ target = context.host + -+* 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). ++ # 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 = [] + -+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. ++ if target in {"all", "build"}: ++ paths.append("build") + -+### Debugging test failures ++ if target in {"all", "hosts"}: ++ paths.extend(all_host_triples(context.platform)) ++ elif target not in {"build", "test", "package"}: ++ paths.append(target) + -+Running `python Apple test iOS` generates a standalone version of the -+`Apple/testbed` project, and runs the full test suite. It does this using -+`Apple/testbed` itself - the folder is an executable module that can be used -+to create and run a clone of the testbed project. The standalone version of the -+testbed will be created in a directory named -+`cross-build/iOS-testbed.`. ++ if target in {"all", "hosts", "test"}: ++ paths.extend([ ++ path.name ++ for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*") ++ ]) + -+You can generate your own standalone testbed instance by running: -+``` -+python cross-build/iOS/testbed clone my-testbed -+``` ++ for path in paths: ++ delete_path(path) + -+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 Apple/testbed clone --platform iOS --framework my-testbed -+``` ++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}" ++ ) + -+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`. ++ return binary + -+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. ++@contextmanager ++def group(text: str): ++ """A context manager that outputs a log marker around a section of a build. + -+### Testing in Xcode ++ 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))) + -+You can also open the testbed project in Xcode by running: -+``` -+open my-testbed/iOSTestbed.xcodeproj -+``` ++ yield + -+This will allow you to use the full Xcode suite of tools for debugging. ++ if "GITHUB_ACTIONS" in os.environ: ++ print("::endgroup::") ++ else: ++ print() + -+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. ++@contextmanager ++def cwd(subdir: Path): ++ """A context manager that sets the current working directory.""" ++ orig = os.getcwd() ++ os.chdir(subdir) ++ yield ++ os.chdir(orig) + -+### 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). ++def configure_build_python(context: argparse.Namespace) -> None: ++ """The implementation of the "configure-build" command.""" ++ if context.clean: ++ clean(context, "build") + -+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/Apple/iOS/Resources/Info.plist.in b/Apple/iOS/Resources/Info.plist.in -new file mode 100644 -index 00000000000..26ef7a95de4 ---- /dev/null -+++ b/Apple/iOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ -+ -+ -+ -+ -+ 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/Apple/iOS/Resources/bin/arm64-apple-ios-ar b/Apple/iOS/Resources/bin/arm64-apple-ios-ar -new file mode 100755 -index 00000000000..3cf3eb21874 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-ar -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-clang b/Apple/iOS/Resources/bin/arm64-apple-ios-clang -new file mode 100755 -index 00000000000..f50d5b5142f ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ -new file mode 100755 -index 00000000000..0794731d7dc ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-cpp b/Apple/iOS/Resources/bin/arm64-apple-ios-cpp -new file mode 100755 -index 00000000000..24fa1506bab ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-cpp -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar -new file mode 100755 -index 00000000000..b836b6db902 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang -new file mode 100755 -index 00000000000..4891a00876e ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ -new file mode 100755 -index 00000000000..58b2a5f6f18 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp -new file mode 100755 -index 00000000000..c9df94e8b7c ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip -new file mode 100755 -index 00000000000..fd59d309b73 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@" -diff --git a/Apple/iOS/Resources/bin/arm64-apple-ios-strip b/Apple/iOS/Resources/bin/arm64-apple-ios-strip -new file mode 100755 -index 00000000000..75e823a3d02 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/arm64-apple-ios-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@" -diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar -new file mode 100755 -index 00000000000..b836b6db902 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" -diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang -new file mode 100755 -index 00000000000..f4739a7b945 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ -new file mode 100755 -index 00000000000..c348ae4c103 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp -new file mode 100755 -index 00000000000..6d7f8084c9f ---- /dev/null -+++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" -diff --git a/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip -new file mode 100755 -index 00000000000..c5cfb289291 ---- /dev/null -+++ b/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@" -diff --git a/Apple/iOS/Resources/pyconfig.h b/Apple/iOS/Resources/pyconfig.h -new file mode 100644 -index 00000000000..4acff2c6051 ---- /dev/null -+++ b/Apple/iOS/Resources/pyconfig.h -@@ -0,0 +1,7 @@ -+#ifdef __arm64__ -+#include "pyconfig-arm64.h" -+#endif ++ with ( ++ group("Configuring build Python"), ++ cwd(subdir("build", create=True)), ++ ): ++ command = [relpath(PYTHON_DIR / "configure")] ++ if context.args: ++ command.extend(context.args) ++ run(command) + -+#ifdef __x86_64__ -+#include "pyconfig-x86_64.h" -+#endif -diff --git a/Apple/testbed/Python.xcframework/Info.plist b/Apple/testbed/Python.xcframework/Info.plist -new file mode 100644 -index 00000000000..0587f4735f7 ---- /dev/null -+++ b/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 -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ x86_64 -+ -+ SupportedPlatform -+ tvos -+ SupportedPlatformVariant -+ simulator -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ xros-arm64-simulator -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ -+ SupportedPlatform -+ xros -+ SupportedPlatformVariant -+ simulator -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ xros-arm64 -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ -+ SupportedPlatform -+ xros -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ watchos-arm64_x86_64-simulator -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64 -+ x86_64 -+ -+ SupportedPlatform -+ watchos -+ SupportedPlatformVariant -+ simulator -+ -+ -+ BinaryPath -+ Python.framework/Python -+ LibraryIdentifier -+ watchos-arm64_32 -+ LibraryPath -+ Python.framework -+ SupportedArchitectures -+ -+ arm64_32 -+ -+ SupportedPlatform -+ watchos -+ -+ -+ CFBundlePackageType -+ XFWK -+ XCFrameworkFormatVersion -+ 1.0 -+ -+ -diff --git a/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist -new file mode 100644 -index 00000000000..d6caa01c1e4 ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ iPhoneOS -+ -+ MinimumOSVersion -+ 13.0 -+ CFBundleVersion -+ 1 -+ -+ -diff --git a/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist -new file mode 100644 -index 00000000000..a20d476fa7b ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ 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 -new file mode 100755 -index 00000000000..2d3b9320b3c ---- /dev/null -+++ b/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 ++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())]) + -+ 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 -+} ++def pythoninfo_build_python(context: argparse.Namespace) -> None: ++ """The implementation of the "pythoninfo-build" command.""" ++ with ( ++ group("Display build info of the build Python"), ++ cwd(subdir("build")), ++ ): ++ run(["make", "pythoninfo"]) + -+# 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" ++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]) + -+ # 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 ++ raise KeyError(host) + -+ 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 ++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 + -+ 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" -+} ++ raise KeyError(host) + -+# 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 ++def unpack_deps( ++ platform: str, ++ host: str, ++ prefix_dir: Path, ++ cache_dir: Path, ++) -> None: ++ """Unpack binary dependencies into a provided directory. + -+ 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 ++ Downloads binaries if they aren't already present. Downloads will be stored ++ in provided cache directory. + -+ for package_path in $@; do -+ echo "Installing $package_path extension modules ..." -+ process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path -+ done -+} -diff --git a/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist -new file mode 100644 -index 00000000000..6f8c0bc2095 ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ watchOS -+ -+ MinimumOSVersion -+ 4.0 -+ CFBundleVersion -+ 1 -+ -+ -diff --git a/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist b/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist -new file mode 100644 -index 00000000000..e91673b4fbc ---- /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 -+ -+ XROS -+ -+ CFBundleVersion -+ 1 -+ MinimumOSVersion -+ 2.0 -+ UIDeviceFamily -+ -+ 7 -+ -+ -+ -diff --git a/Apple/testbed/Python.xcframework/ios-arm64/README b/Apple/testbed/Python.xcframework/ios-arm64/README -new file mode 100644 -index 00000000000..c1b076d12cd ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/ios-arm64/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++ 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.9-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) + -+It should be used as a target for `--enable-framework` when compiling an iOS on-device -+build for testing purposes. -diff --git a/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README b/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README -new file mode 100644 -index 00000000000..ae334e5d769 ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++ # 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() + -+It should be used as a target for `--enable-framework` when compiling an iOS simulator -+build for testing purposes (either x86_64 or ARM64). -diff --git a/Apple/testbed/Python.xcframework/tvos-arm64/README b/Apple/testbed/Python.xcframework/tvos-arm64/README -new file mode 100644 -index 00000000000..ebd648d04bd ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/tvos-arm64/README -@@ -0,0 +1,4 @@ -+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/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README b/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README -new file mode 100644 -index 00000000000..f8163468d8c ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++def download(url: str, target_dir: Path) -> Path: ++ """Download the specified URL into the given directory. + -+It should be used as a target for `--enable-framework` when compiling a tvOS -+simulator build for testing purposes (either x86_64 or ARM64). -diff --git a/Apple/testbed/Python.xcframework/watchos-arm64_32/README b/Apple/testbed/Python.xcframework/watchos-arm64_32/README -new file mode 100644 -index 00000000000..696af231df3 ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/watchos-arm64_32/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++ :return: The path to the downloaded archive. ++ """ ++ target_path = Path(target_dir).resolve() ++ target_path.mkdir(exist_ok=True, parents=True) + -+It should be used as a target for `--enable-framework` when compiling a watchOS on-device -+build for testing purposes. -diff --git a/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README b/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README -new file mode 100644 -index 00000000000..d38e1e98276 ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++ 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 + -+It should be used as a target for `--enable-framework` when compiling a watchOS -+simulator build for testing purposes (either x86_64 or ARM64). -diff --git a/Apple/testbed/Python.xcframework/xros-arm64-simulator/README b/Apple/testbed/Python.xcframework/xros-arm64-simulator/README -new file mode 100644 -index 00000000000..7bb0ad4b6ba ---- /dev/null -+++ b/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/Apple/testbed/Python.xcframework/xros-arm64/README b/Apple/testbed/Python.xcframework/xros-arm64/README -new file mode 100644 -index 00000000000..2fc2112e477 ---- /dev/null -+++ b/Apple/testbed/Python.xcframework/xros-arm64/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++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 + -+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/Testbed.lldbinit b/Apple/testbed/Testbed.lldbinit -new file mode 100644 -index 00000000000..4cf00dd0f9d ---- /dev/null -+++ b/Apple/testbed/Testbed.lldbinit -@@ -0,0 +1,4 @@ -+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/Apple/testbed/TestbedTests/TestbedTests.m b/Apple/testbed/TestbedTests/TestbedTests.m -new file mode 100644 -index 00000000000..cc0d9224042 ---- /dev/null -+++ b/Apple/testbed/TestbedTests/TestbedTests.m -@@ -0,0 +1,198 @@ -+#import -+#import ++ if context.clean: ++ clean(context, host) + -+@interface TestbedTests : XCTestCase ++ host_dir = subdir(host, create=True) ++ prefix_dir = host_dir / "prefix" + -+@end ++ 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") + -+@implementation TestbedTests ++ 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) + -+- (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]; ++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 + -+ // 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); ++ with ( ++ group(f"Compiling host Python ({host})"), ++ cwd(subdir(host)), ++ ): ++ run(["make", "-j", str(os.cpu_count())], host=host) ++ run(["make", "install"], host=host) + -+ 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); ++def framework_path(host_triple: str, multiarch: str) -> Path: ++ """The path to a built single-architecture framework product. + -+ // Configure the Python interpreter: -+ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. -+ // See https://docs.python.org/3/library/os.html#python-utf-8-mode. -+ preconfig.utf8_mode = 1; -+ // Don't buffer stdio. We want output to appears in the log immediately -+ config.buffered_stdio = 0; -+ // Don't write bytecode; we can't modify the app bundle -+ // after it has been signed. -+ config.write_bytecode = 0; -+ // Ensure that signal handlers are installed -+ config.install_signal_handlers = 1; -+ // Run the test module. -+ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:1] UTF8String], NULL); -+ // For debugging - enable verbose mode. -+ // config.verbose = 1; ++ :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}" ++ ) + -+ 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); ++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") + -+ // 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; -+ } ++ return version + -+ 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; -+ } ++ sys.exit("Unable to determine Python version being packaged.") + -+ 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; -+ } ++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() + -+ 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; -+ } ++ return ignored_names + -+ 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; -+ } ++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() + -+ 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; -+ } ++def create_xcframework(platform: str) -> str: ++ """Build an XCframework from the component parts for the platform. + -+ sys_path_attr = PyObject_GetAttrString(sys_module, "path"); -+ if (sys_path_attr == NULL) { -+ XCTFail(@"Could not access sys.path"); -+ return; -+ } ++ :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 + -+ 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); ++ 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" ++ ) + -+ // Ensure the working directory is the app folder. -+ chdir([path UTF8String]); ++ 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) + -+ // Start the test suite. Print a separator to differentiate Python startup logs from app logs -+ NSLog(@"---------------------------------------------------------------------------"); ++ # Copy the Info.plist ++ shutil.copy( ++ first_framework / "Info.plist", ++ slice_framework / "Info.plist", ++ ) + -+ exit_code = Py_RunMain(); -+ XCTAssertEqual(exit_code, 0, @"Test suite did not pass"); ++ # Copy the headers ++ shutil.copytree( ++ first_framework / "Headers", ++ slice_framework / "Headers", ++ ) + -+ NSLog(@"---------------------------------------------------------------------------"); ++ # 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() ++ ] ++ ) + -+ Py_Finalize(); -+} ++ # 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]) + -+@end -diff --git a/Apple/testbed/__main__.py b/Apple/testbed/__main__.py -new file mode 100644 -index 00000000000..643034e9bca ---- /dev/null -+++ b/Apple/testbed/__main__.py -@@ -0,0 +1,462 @@ -+import argparse -+import json -+import os -+import re -+import shlex -+import shutil -+import subprocess -+import sys -+from pathlib import Path ++ run(cmd) + -+TEST_SLICES = { -+ "iOS": "ios-arm64_x86_64-simulator", -+ "tvOS": "tvos-arm64_x86_64-simulator", -+ "visionOS": "xros-arm64-simulator", -+ "watchOS": "watchos-arm64_x86_64-simulator", -+} ++ # Extract the package version from the merged framework ++ version = package_version(package_path / "Python.xcframework") ++ version_tag = ".".join(version.split(".")[:2]) + -+DECODE_ARGS = ("UTF-8", "backslashreplace") ++ # 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" + -+# 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] ... ++ slice_path = package_path / f"Python.xcframework/{slice_name}" ++ slice_framework = slice_path / "Python.framework" + -+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 -+) ++ # 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, ++ ) + -+# 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) ++ # Copy in the cross-architecture pyconfig.h ++ shutil.copy( ++ PYTHON_DIR / f"Platforms/Apple/{platform}/Resources/pyconfig.h", ++ slice_framework / "Headers/pyconfig.h", ++ ) + -+ 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") ++ 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, + ) -+ 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}") ++ has_common_stdlib = True + -+ return simulator ++ 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", ++ ) + -+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"] ++ # 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("Building test project...") -+ subprocess.run( -+ ["xcodebuild", "build-for-testing"] + args + verbosity_args, -+ check=True, ++ print(" - build tools") ++ shutil.copytree( ++ PYTHON_DIR / "Platforms/Apple/testbed/Python.xcframework/build", ++ package_path / "Python.xcframework/build", + ) + -+ # 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"] ++ return version + -+ 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 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) + -+def copy(src, tgt): -+ """An all-purpose copy. ++ # 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", ++ ]) + -+ 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) ++ # 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 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) ++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, ++ pythoninfo_build_python, ++ ]: ++ step(context) ++ ++ if host == "build": ++ hosts = [] ++ elif host in {"all", "hosts"}: ++ hosts = all_host_triples(context.platform) + 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) ++ hosts = [host] + -+ 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 step_host in hosts: ++ for step in [ ++ configure_host_python, ++ make_host_python, ++ ]: ++ step(context, host=step_host) + -+ for name in [ -+ "__main__.py", -+ "TestbedTests", -+ "Testbed.lldbinit", -+ f"{platform}Testbed", -+ f"{platform}Testbed.xcodeproj", -+ f"{platform}Testbed.xctestplan", -+ ]: -+ copy(source / name, target / name) ++ if host == "all": ++ package(context) + -+ 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) ++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 ( -+ 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 -+ ) ++ 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" + ) -+ 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") ++ build_arch = platform.machine() ++ host_arch = host.split("-")[0] + -+ print(f"Successfully cloned testbed: {target.resolve()}") ++ 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, ++ ]) + -+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) ++ 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", ++ ] ++ ) + -+ 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 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 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.") + -+ # xcodebuild doesn't guarantee that the CoreSimulatorService daemon is -+ # running prior to using a simulator, but calling `xcrun simctl list` does. -+ # Determining the default simulator that *would* be used is a cheap action; -+ # so use that as a way to ensure that the CoreSimulatorService daemon is -+ # running. -+ default_simulator = select_simulator_device(platform) -+ if simulator is None: -+ simulator = default_simulator -+ print(f"Running test on {simulator}") ++def ci(context: argparse.Namespace) -> None: ++ """The implementation of the "ci" command. + -+ xcode_test( -+ location, -+ platform=platform, -+ simulator=simulator, -+ verbose=verbose, -+ ) ++ 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 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() -+ ] + ++def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( -+ "Manages the process of testing an Apple Python project " -+ "through Xcode." ++ "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) + -+ 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.", ++ clean = subcommands.add_parser( ++ "clean", ++ help="Delete all build directories", + ) -+ clone.add_argument( -+ "--framework", -+ help=( -+ "The location of the XCFramework (or simulator-only slice of an " -+ "XCFramework) to use when running the testbed" -+ ), ++ ++ configure_build = subcommands.add_parser( ++ "configure-build", help="Run `configure` for the build Python" + ) -+ clone.add_argument( -+ "--platform", -+ dest="platform", -+ choices=available_platforms, -+ default=available_platforms[0], -+ help=f"The platform to target (default: {available_platforms[0]})", ++ make_build = subcommands.add_parser( ++ "make-build", help="Run `make` for the build Python" + ) -+ clone.add_argument( -+ "--app", -+ dest="apps", -+ action="append", -+ default=[], -+ help="The location of any code to include in the testbed project", ++ pythoninfo_build = subcommands.add_parser( ++ "pythoninfo-build", help="Display build info of the build Python" + ) -+ clone.add_argument( -+ "location", -+ help="The path where the testbed will be cloned.", ++ configure_host = subcommands.add_parser( ++ "configure-host", ++ help="Run `configure` for a specific platform and target", + ) -+ -+ 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", ++ make_host = subcommands.add_parser( ++ "make-host", ++ help="Run `make` for a specific platform and target", + ) -+ run.add_argument( -+ "--platform", -+ dest="platform", -+ choices=available_platforms, -+ default=available_platforms[0], -+ help=f"The platform to target (default: {available_platforms[0]})", ++ package = subcommands.add_parser( ++ "package", ++ help="Create a release package for the platform", + ) -+ 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." -+ ), ++ build = subcommands.add_parser( ++ "build", ++ help="Build all platform targets and create the XCframework", + ) -+ run.add_argument( -+ "-v", -+ "--verbose", -+ action="store_true", -+ help="Enable verbose output", ++ test = subcommands.add_parser( ++ "test", ++ help="Run the testbed for a specific platform", ++ ) ++ ci = subcommands.add_parser( ++ "ci", ++ help="Run build, package, and test", + ) + -+ 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 = [] ++ # 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", ++ ) + -+ context = parser.parse_args(testbed_args) ++ # 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'" ++ ), ++ ) + -+ 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], ++ # --cross-build-dir argument ++ for cmd in [ ++ clean, ++ configure_build, ++ make_build, ++ pythoninfo_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." ++ ), + ) -+ 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) ++ # --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, ++ "pythoninfo-build": pythoninfo_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__": @@ -2962,4264 +2803,5970 @@ index 00000000000..643034e9bca + # to the output of our subprocesses. + for stream in [sys.stdout, sys.stderr]: + stream.reconfigure(line_buffering=True) ++ + main() -diff --git a/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +diff --git a/Platforms/Apple/iOS/README.md b/Platforms/Apple/iOS/README.md new file mode 100644 -index 00000000000..1f9e3f23a02 +index 00000000000..faeeead1df0 --- /dev/null -+++ b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj -@@ -0,0 +1,563 @@ -+// !$*UTF8*$! -+{ -+ archiveVersion = 1; -+ classes = { -+ }; -+ objectVersion = 56; -+ objects = { ++++ b/Platforms/Apple/iOS/README.md +@@ -0,0 +1,339 @@ ++# Python on iOS README + -+/* Begin PBXBuildFile section */ -+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; }; -+ 71CB095279CCE565BAFB69C3 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D995ADFDE8B3EF5DDEB37874 /* SceneDelegate.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 */ ++**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).** + -+/* Begin PBXContainerItemProxy section */ -+ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { -+ isa = PBXContainerItemProxy; -+ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; -+ proxyType = 1; -+ remoteGlobalIDString = 607A66112B0EFA380010BFC8; -+ remoteInfo = iOSTestbed; -+ }; -+/* End PBXContainerItemProxy section */ ++This document provides a quick overview of some iOS specific features in the ++Python distribution. + -+/* 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 */ ++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. + -+/* 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 = ""; }; -+ 83DC5E71F9D64737365A3BE4 /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; -+ D995ADFDE8B3EF5DDEB37874 /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.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 */ ++## Compilers for building on iOS + -+/* 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 */ ++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. + -+/* 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 */, -+ 83DC5E71F9D64737365A3BE4 /* SceneDelegate.h */, -+ D995ADFDE8B3EF5DDEB37874 /* SceneDelegate.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 */ ++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. + -+/* 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 */ ++## Building Python on iOS + -+/* 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 */ ++### ABIs and Architectures + -+/* 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 */ ++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`). + -+/* 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 */ ++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. + -+/* Begin PBXSourcesBuildPhase section */ -+ 607A660E2B0EFA380010BFC8 /* Sources */ = { -+ isa = PBXSourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, -+ 71CB095279CCE565BAFB69C3 /* SceneDelegate.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 */ ++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). + -+/* Begin PBXTargetDependency section */ -+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { -+ isa = PBXTargetDependency; -+ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; -+ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; -+ }; -+/* End PBXTargetDependency section */ ++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. + -+/* Begin PBXVariantGroup section */ -+ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = { -+ isa = PBXVariantGroup; -+ children = ( -+ 607A66242B0EFA390010BFC8 /* Base */, -+ ); -+ name = LaunchScreen.storyboard; -+ sourceTree = ""; -+ }; -+/* End PBXVariantGroup section */ ++### Building a multi-architecture iOS XCframework + -+/* 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 = 15.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 = 15.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 = 15.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 = 15.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 = 15.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 = 15.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 */ ++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:: + -+/* 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/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme -new file mode 100644 -index 00000000000..3c330a4152b ---- /dev/null -+++ b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme -@@ -0,0 +1,97 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/Apple/testbed/iOSTestbed.xctestplan b/Apple/testbed/iOSTestbed.xctestplan -new file mode 100644 -index 00000000000..0c4ab9eb2ba ---- /dev/null -+++ b/Apple/testbed/iOSTestbed.xctestplan -@@ -0,0 +1,46 @@ -+{ -+ "configurations" : [ -+ { -+ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", -+ "name" : "Test Scheme Action", -+ "options" : { ++ python Platforms/Apple build iOS + -+ } -+ } -+ ], -+ "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/Apple/testbed/iOSTestbed/AppDelegate.h b/Apple/testbed/iOSTestbed/AppDelegate.h -new file mode 100644 -index 00000000000..f695b3b5efc ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/AppDelegate.h -@@ -0,0 +1,11 @@ -+// -+// AppDelegate.h -+// iOSTestbed -+// ++This will: + -+#import ++* 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. + -+@interface AppDelegate : UIResponder ++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 + -+@end -diff --git a/Apple/testbed/iOSTestbed/AppDelegate.m b/Apple/testbed/iOSTestbed/AppDelegate.m -new file mode 100644 -index 00000000000..fbe71bae23b ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/AppDelegate.m -@@ -0,0 +1,24 @@ -+// -+// AppDelegate.m -+// iOSTestbed -+// ++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. + -+#import "AppDelegate.h" -+#import "SceneDelegate.h" ++#### iOS specific arguments to configure + -+@interface AppDelegate () ++* `--enable-framework[=DIR]` + -+@end ++ 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. + -+@implementation AppDelegate ++ This argument *must* be provided when configuring iOS builds. iOS does not ++ support non-framework builds. + -+- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { -+ UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; -+ configuration.delegateClass = [SceneDelegate class]; -+ return configuration; -+} ++* `--with-framework-name=NAME` + -+- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { -+} ++ Specify the name for the Python framework; defaults to `Python`. + -+@end -diff --git a/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json -new file mode 100644 -index 00000000000..eb878970081 ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json -@@ -0,0 +1,11 @@ -+{ -+ "colors" : [ -+ { -+ "idiom" : "universal" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json -new file mode 100644 -index 00000000000..13613e3ee1a ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json -@@ -0,0 +1,13 @@ -+{ -+ "images" : [ -+ { -+ "idiom" : "universal", -+ "platform" : "ios", -+ "size" : "1024x1024" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json b/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json -new file mode 100644 -index 00000000000..73c00596a7f ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json -@@ -0,0 +1,6 @@ -+{ -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard b/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard -new file mode 100644 -index 00000000000..5daafe73a86 ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard -@@ -0,0 +1,9 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/Apple/testbed/iOSTestbed/SceneDelegate.h b/Apple/testbed/iOSTestbed/SceneDelegate.h -new file mode 100644 -index 00000000000..264aacadca1 ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/SceneDelegate.h -@@ -0,0 +1,12 @@ -+// -+// SceneDelegate.h -+// iOSTestbed -+// ++ > [!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. + -+#import ++#### Building Python for iOS + -+@interface SceneDelegate : UIResponder ++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. + -+@property (strong, nonatomic) UIWindow *window; ++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 ++``` + -+@end -diff --git a/Apple/testbed/iOSTestbed/SceneDelegate.m b/Apple/testbed/iOSTestbed/SceneDelegate.m -new file mode 100644 -index 00000000000..e82e447985d ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/SceneDelegate.m -@@ -0,0 +1,21 @@ -+// -+// SceneDelegate.m -+// iOSTestbed -+// ++In this invocation: + -+#import "SceneDelegate.h" ++* `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. + -+@interface SceneDelegate () ++ 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. + -+@end ++ 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.) + -+@implementation SceneDelegate ++ 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. + -+- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { -+ UIWindowScene *windowScene = (UIWindowScene *)scene; -+ self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; -+ self.window.rootViewController = [[UIViewController alloc] init]; -+ [self.window makeKeyAndVisible]; -+} ++* `--host` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: + -+@end -diff --git a/Apple/testbed/iOSTestbed/app/README b/Apple/testbed/iOSTestbed/app/README -new file mode 100644 -index 00000000000..46c0e8e2a29 ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/app/README -@@ -0,0 +1,7 @@ -+This folder can contain any Python application code. ++ - `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. + -+During the build, any binary modules found in this folder will be processed into -+Framework form. ++* `--build` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: + -+When the test suite runs, this folder will be on the PYTHONPATH, and will be the -+working directory for the test suite. -diff --git a/Apple/testbed/iOSTestbed/app_packages/README b/Apple/testbed/iOSTestbed/app_packages/README -new file mode 100644 -index 00000000000..02c2beccfbd ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/app_packages/README -@@ -0,0 +1,7 @@ -+This folder can be a target for installing any Python dependencies needed by the -+test suite. ++ - `arm64-apple-darwin` for Apple Silicon devices. ++ - `x86_64-apple-darwin` for Intel devices. + -+During the build, any binary modules found in this folder will be processed into -+Framework form. ++* `/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. + -+When the test suite runs, this folder will be on the PYTHONPATH. -diff --git a/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist b/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist -new file mode 100644 -index 00000000000..3a560656972 ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist -@@ -0,0 +1,62 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleDisplayName -+ ${PRODUCT_NAME} -+ CFBundleExecutable -+ ${EXECUTABLE_NAME} -+ CFBundleIdentifier -+ org.python.iOSTestbed -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleName -+ ${PRODUCT_NAME} -+ CFBundlePackageType -+ APPL -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ 1 -+ LSRequiresIPhoneOS -+ -+ UIRequiresFullScreen -+ -+ UILaunchStoryboardName -+ Launch Screen -+ UISupportedInterfaceOrientations -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ -+ UISupportedInterfaceOrientations~ipad -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationPortraitUpsideDown -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ -+ UIApplicationSceneManifest -+ -+ UIApplicationSupportsMultipleScenes -+ -+ UISceneConfigurations -+ -+ UIWindowSceneSessionRoleApplication -+ -+ -+ UISceneConfigurationName -+ Default Configuration -+ UISceneDelegateClassName -+ SceneDelegate -+ -+ -+ -+ -+ -+ -diff --git a/Apple/testbed/iOSTestbed/main.m b/Apple/testbed/iOSTestbed/main.m -new file mode 100644 -index 00000000000..e32bd78c9b4 ---- /dev/null -+++ b/Apple/testbed/iOSTestbed/main.m -@@ -0,0 +1,16 @@ -+// -+// main.m -+// iOSTestbed -+// ++* 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. + -+#import -+#import "AppDelegate.h" ++ 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. + -+int main(int argc, char * argv[]) { -+ NSString * appDelegateClassName; -+ @autoreleasepool { -+ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++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. + -+ return UIApplicationMain(argc, argv, nil, appDelegateClassName); -+ } -+} -diff --git a/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj b/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj -new file mode 100644 -index 00000000000..85e7047dfb7 ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj -@@ -0,0 +1,506 @@ -+// !$*UTF8*$! -+{ -+ archiveVersion = 1; -+ classes = { -+ }; -+ objectVersion = 77; -+ objects = { ++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. + -+/* Begin PBXBuildFile section */ -+ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; -+ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; -+ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+/* End PBXBuildFile section */ ++## Testing Python on iOS + -+/* Begin PBXContainerItemProxy section */ -+ EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */ = { -+ isa = PBXContainerItemProxy; -+ containerPortal = EE989E462DCD6E780036B268 /* Project object */; -+ proxyType = 1; -+ remoteGlobalIDString = EE989E4D2DCD6E780036B268; -+ remoteInfo = tvOSTestbed; -+ }; -+/* End PBXContainerItemProxy section */ ++### Testing a multi-architecture framework + -+/* Begin PBXCopyFilesBuildPhase section */ -+ EE7C8A212DCD70CD003206DB /* Embed Frameworks */ = { -+ isa = PBXCopyFilesBuildPhase; -+ buildActionMask = 2147483647; -+ dstPath = ""; -+ dstSubfolderSpec = 10; -+ files = ( -+ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */, -+ ); -+ name = "Embed Frameworks"; -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXCopyFilesBuildPhase section */ ++Once you have a built an XCframework, you can test that framework by running: + -+/* Begin PBXFileReference section */ -+ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = tvOSTestbed.xctestplan; sourceTree = ""; }; -+ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; -+ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; -+ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -+/* End PBXFileReference section */ ++ $ python Platforms/Apple test iOS + -+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ -+ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */ = { -+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; -+ membershipExceptions = ( -+ "tvOSTestbed-Info.plist", -+ ); -+ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; -+ }; -+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++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. + -+/* Begin PBXFileSystemSynchronizedRootGroup section */ -+ EE989E502DCD6E780036B268 /* tvOSTestbed */ = { -+ isa = PBXFileSystemSynchronizedRootGroup; -+ exceptions = ( -+ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */, -+ ); -+ explicitFolders = ( -+ app, -+ app_packages, -+ ); -+ path = tvOSTestbed; -+ sourceTree = ""; -+ }; -+ EE989E682DCD6E7A0036B268 /* TestbedTests */ = { -+ isa = PBXFileSystemSynchronizedRootGroup; -+ path = TestbedTests; -+ sourceTree = ""; -+ }; -+/* End PBXFileSystemSynchronizedRootGroup section */ ++If the test runner is executed on GitHub Actions, the `GITHUB_ACTIONS` ++environment variable will be exposed to the iOS process at runtime. + -+/* Begin PBXFrameworksBuildPhase section */ -+ EE989E4B2DCD6E780036B268 /* Frameworks */ = { -+ isa = PBXFrameworksBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ EE989E622DCD6E7A0036B268 /* Frameworks */ = { -+ isa = PBXFrameworksBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXFrameworksBuildPhase section */ ++### Testing a single-architecture framework + -+/* Begin PBXGroup section */ -+ EE989E452DCD6E780036B268 = { -+ isa = PBXGroup; -+ children = ( -+ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */, -+ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */, -+ EE989E502DCD6E780036B268 /* tvOSTestbed */, -+ EE989E682DCD6E7A0036B268 /* TestbedTests */, -+ EE989E4F2DCD6E780036B268 /* Products */, -+ ); -+ sourceTree = ""; -+ }; -+ EE989E4F2DCD6E780036B268 /* Products */ = { -+ isa = PBXGroup; -+ children = ( -+ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */, -+ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */, -+ ); -+ name = Products; -+ sourceTree = ""; -+ }; -+/* End PBXGroup section */ ++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. + -+/* Begin PBXNativeTarget section */ -+ EE989E4D2DCD6E780036B268 /* tvOSTestbed */ = { -+ isa = PBXNativeTarget; -+ buildConfigurationList = EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */; -+ buildPhases = ( -+ EE989E4A2DCD6E780036B268 /* Sources */, -+ EE989E4B2DCD6E780036B268 /* Frameworks */, -+ EE989E4C2DCD6E780036B268 /* Resources */, -+ EE7C8A222DCD70F4003206DB /* Process Python libraries */, -+ EE7C8A212DCD70CD003206DB /* Embed Frameworks */, -+ ); -+ buildRules = ( -+ ); -+ dependencies = ( -+ ); -+ fileSystemSynchronizedGroups = ( -+ EE989E502DCD6E780036B268 /* tvOSTestbed */, -+ ); -+ name = tvOSTestbed; -+ packageProductDependencies = ( -+ ); -+ productName = tvOSTestbed; -+ productReference = EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */; -+ productType = "com.apple.product-type.application"; -+ }; -+ EE989E642DCD6E7A0036B268 /* TestbedTests */ = { -+ isa = PBXNativeTarget; -+ buildConfigurationList = EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */; -+ buildPhases = ( -+ EE989E612DCD6E7A0036B268 /* Sources */, -+ EE989E622DCD6E7A0036B268 /* Frameworks */, -+ EE989E632DCD6E7A0036B268 /* Resources */, -+ ); -+ buildRules = ( -+ ); -+ dependencies = ( -+ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */, -+ ); -+ fileSystemSynchronizedGroups = ( -+ EE989E682DCD6E7A0036B268 /* TestbedTests */, -+ ); -+ name = TestbedTests; -+ packageProductDependencies = ( -+ ); -+ productName = TestbedTests; -+ productReference = EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */; -+ productType = "com.apple.product-type.bundle.unit-test"; -+ }; -+/* End PBXNativeTarget section */ ++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 ++``` + -+/* Begin PBXProject section */ -+ EE989E462DCD6E780036B268 /* Project object */ = { -+ isa = PBXProject; -+ attributes = { -+ BuildIndependentTargetsInParallel = 1; -+ LastUpgradeCheck = 1620; -+ TargetAttributes = { -+ EE989E4D2DCD6E780036B268 = { -+ CreatedOnToolsVersion = 16.2; -+ }; -+ EE989E642DCD6E7A0036B268 = { -+ CreatedOnToolsVersion = 16.2; -+ TestTargetID = EE989E4D2DCD6E780036B268; -+ }; -+ }; -+ }; -+ buildConfigurationList = EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */; -+ developmentRegion = en; -+ hasScannedForEncodings = 0; -+ knownRegions = ( -+ en, -+ Base, -+ ); -+ mainGroup = EE989E452DCD6E780036B268; -+ minimizedProjectReferenceProxies = 1; -+ preferredProjectObjectVersion = 77; -+ productRefGroup = EE989E4F2DCD6E780036B268 /* Products */; -+ projectDirPath = ""; -+ projectRoot = ""; -+ targets = ( -+ EE989E4D2DCD6E780036B268 /* tvOSTestbed */, -+ EE989E642DCD6E7A0036B268 /* TestbedTests */, -+ ); -+ }; -+/* End PBXProject section */ ++This will: + -+/* Begin PBXResourcesBuildPhase section */ -+ EE989E4C2DCD6E780036B268 /* Resources */ = { -+ isa = PBXResourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ EE989E632DCD6E7A0036B268 /* Resources */ = { -+ isa = PBXResourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXResourcesBuildPhase section */ ++* 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). + -+/* Begin PBXShellScriptBuildPhase section */ -+ EE7C8A222DCD70F4003206DB /* Process Python libraries */ = { -+ isa = PBXShellScriptBuildPhase; -+ alwaysOutOfDate = 1; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ inputFileListPaths = ( -+ ); -+ inputPaths = ( -+ ); -+ name = "Process Python libraries"; -+ outputFileListPaths = ( -+ ); -+ outputPaths = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ shellPath = /bin/sh; -+ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; -+ showEnvVarsInLog = 0; -+ }; -+/* End PBXShellScriptBuildPhase section */ ++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. + -+/* Begin PBXSourcesBuildPhase section */ -+ EE989E4A2DCD6E780036B268 /* Sources */ = { -+ isa = PBXSourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ EE989E612DCD6E7A0036B268 /* Sources */ = { -+ isa = PBXSourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXSourcesBuildPhase section */ ++### Debugging test failures + -+/* Begin PBXTargetDependency section */ -+ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */ = { -+ isa = PBXTargetDependency; -+ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; -+ targetProxy = EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */; -+ }; -+/* End PBXTargetDependency section */ ++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.`. + -+/* Begin XCBuildConfiguration section */ -+ EE989E772DCD6E7A0036B268 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; -+ CLANG_ANALYZER_NONNULL = YES; -+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; -+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; -+ CLANG_ENABLE_MODULES = YES; -+ CLANG_ENABLE_OBJC_ARC = YES; -+ CLANG_ENABLE_OBJC_WEAK = YES; -+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; -+ CLANG_WARN_BOOL_CONVERSION = YES; -+ CLANG_WARN_COMMA = YES; -+ CLANG_WARN_CONSTANT_CONVERSION = YES; -+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; -+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -+ CLANG_WARN_EMPTY_BODY = YES; -+ CLANG_WARN_ENUM_CONVERSION = YES; -+ CLANG_WARN_INFINITE_RECURSION = YES; -+ CLANG_WARN_INT_CONVERSION = YES; -+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; -+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; -+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; -+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; -+ CLANG_WARN_STRICT_PROTOTYPES = YES; -+ CLANG_WARN_SUSPICIOUS_MOVE = YES; -+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; -+ CLANG_WARN_UNREACHABLE_CODE = YES; -+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -+ COPY_PHASE_STRIP = NO; -+ DEBUG_INFORMATION_FORMAT = dwarf; -+ ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; -+ GCC_C_LANGUAGE_STANDARD = gnu17; -+ GCC_DYNAMIC_NO_PIC = NO; -+ GCC_NO_COMMON_BLOCKS = YES; -+ GCC_OPTIMIZATION_LEVEL = 0; -+ GCC_PREPROCESSOR_DEFINITIONS = ( -+ "DEBUG=1", -+ "$(inherited)", -+ ); -+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -+ GCC_WARN_UNDECLARED_SELECTOR = YES; -+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -+ GCC_WARN_UNUSED_FUNCTION = YES; -+ GCC_WARN_UNUSED_VARIABLE = YES; -+ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; -+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; -+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; -+ MTL_FAST_MATH = YES; -+ ONLY_ACTIVE_ARCH = YES; -+ SDKROOT = appletvos; -+ TVOS_DEPLOYMENT_TARGET = 18.2; -+ }; -+ name = Debug; -+ }; -+ EE989E782DCD6E7A0036B268 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; -+ CLANG_ANALYZER_NONNULL = YES; -+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; -+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; -+ CLANG_ENABLE_MODULES = YES; -+ CLANG_ENABLE_OBJC_ARC = YES; -+ CLANG_ENABLE_OBJC_WEAK = YES; -+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; -+ CLANG_WARN_BOOL_CONVERSION = YES; -+ CLANG_WARN_COMMA = YES; -+ CLANG_WARN_CONSTANT_CONVERSION = YES; -+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; -+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -+ CLANG_WARN_EMPTY_BODY = YES; -+ CLANG_WARN_ENUM_CONVERSION = YES; -+ CLANG_WARN_INFINITE_RECURSION = YES; -+ CLANG_WARN_INT_CONVERSION = YES; -+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; -+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; -+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; -+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; -+ CLANG_WARN_STRICT_PROTOTYPES = YES; -+ CLANG_WARN_SUSPICIOUS_MOVE = YES; -+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; -+ CLANG_WARN_UNREACHABLE_CODE = YES; -+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -+ COPY_PHASE_STRIP = NO; -+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; -+ ENABLE_NS_ASSERTIONS = NO; -+ ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = NO; -+ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; -+ GCC_C_LANGUAGE_STANDARD = gnu17; -+ GCC_NO_COMMON_BLOCKS = YES; -+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -+ GCC_WARN_UNDECLARED_SELECTOR = YES; -+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -+ GCC_WARN_UNUSED_FUNCTION = YES; -+ GCC_WARN_UNUSED_VARIABLE = YES; -+ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; -+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; -+ MTL_ENABLE_DEBUG_INFO = NO; -+ MTL_FAST_MATH = YES; -+ SDKROOT = appletvos; -+ TVOS_DEPLOYMENT_TARGET = 18.2; -+ VALIDATE_PRODUCT = YES; -+ }; -+ name = Release; -+ }; -+ EE989E7A2DCD6E7A0036B268 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; -+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ GENERATE_INFOPLIST_FILE = NO; -+ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; -+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; -+ INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; -+ LD_RUNPATH_SEARCH_PATHS = ( -+ "$(inherited)", -+ "@executable_path/Frameworks", -+ ); -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 3; -+ }; -+ name = Debug; -+ }; -+ EE989E7B2DCD6E7A0036B268 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; -+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ GENERATE_INFOPLIST_FILE = NO; -+ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; -+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; -+ INFOPLIST_KEY_UIMainStoryboardFile = Main; -+ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; -+ LD_RUNPATH_SEARCH_PATHS = ( -+ "$(inherited)", -+ "@executable_path/Frameworks", -+ ); -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 3; -+ }; -+ name = Release; -+ }; -+ EE989E7D2DCD6E7A0036B268 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ BUNDLE_LOADER = "$(TEST_HOST)"; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ GENERATE_INFOPLIST_FILE = YES; -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 3; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; -+ TVOS_DEPLOYMENT_TARGET = 18.2; -+ }; -+ name = Debug; -+ }; -+ EE989E7E2DCD6E7A0036B268 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ BUNDLE_LOADER = "$(TEST_HOST)"; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ GENERATE_INFOPLIST_FILE = YES; -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 3; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; -+ TVOS_DEPLOYMENT_TARGET = 18.2; -+ }; -+ name = Release; -+ }; -+/* End XCBuildConfiguration section */ ++You can generate your own standalone testbed instance by running: ++``` ++python cross-build/iOS/testbed clone my-testbed ++``` + -+/* Begin XCConfigurationList section */ -+ EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ EE989E772DCD6E7A0036B268 /* Debug */, -+ EE989E782DCD6E7A0036B268 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+ EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ EE989E7A2DCD6E7A0036B268 /* Debug */, -+ EE989E7B2DCD6E7A0036B268 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+ EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ EE989E7D2DCD6E7A0036B268 /* Debug */, -+ EE989E7E2DCD6E7A0036B268 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+/* End XCConfigurationList section */ -+ }; -+ rootObject = EE989E462DCD6E780036B268 /* Project object */; -+} -diff --git a/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme b/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme -new file mode 100644 -index 00000000000..c3f3f894a1f ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme -@@ -0,0 +1,97 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/Apple/testbed/tvOSTestbed.xctestplan b/Apple/testbed/tvOSTestbed.xctestplan -new file mode 100644 -index 00000000000..f996facc178 ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed.xctestplan -@@ -0,0 +1,46 @@ -+{ -+ "configurations" : [ -+ { -+ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", -+ "name" : "Test Scheme Action", -+ "options" : { ++In this invocation, `my-testbed` is the name of the folder for the new ++testbed clone. + -+ } -+ } -+ ], -+ "defaultOptions" : { -+ "commandLineArgumentEntries" : [ -+ { -+ "argument" : "test" -+ }, -+ { -+ "argument" : "-uall" -+ }, -+ { -+ "argument" : "--single-process" -+ }, -+ { -+ "argument" : "--rerun" -+ }, -+ { -+ "argument" : "-W" -+ } -+ ], -+ "targetForVariableExpansion" : { -+ "containerPath" : "container:tvOSTestbed.xcodeproj", -+ "identifier" : "607A66112B0EFA380010BFC8", -+ "name" : "tvOSTestbed" -+ } -+ }, -+ "testTargets" : [ -+ { -+ "parallelizable" : false, -+ "target" : { -+ "containerPath" : "container:tvOSTestbed.xcodeproj", -+ "identifier" : "EE989E642DCD6E7A0036B268", -+ "name" : "TestbedTests" -+ } -+ } -+ ], -+ "version" : 1 -+} -\ No newline at end of file -diff --git a/Apple/testbed/tvOSTestbed/AppDelegate.h b/Apple/testbed/tvOSTestbed/AppDelegate.h -new file mode 100644 -index 00000000000..112c9ed64b8 ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed/AppDelegate.h -@@ -0,0 +1,11 @@ -+// -+// AppDelegate.h -+// tvOSTestbed -+// ++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 ++``` + -+#import ++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`. + -+@interface AppDelegate : UIResponder ++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. + -+@end -diff --git a/Apple/testbed/tvOSTestbed/AppDelegate.m b/Apple/testbed/tvOSTestbed/AppDelegate.m -new file mode 100644 -index 00000000000..bd91fb2d7d6 ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed/AppDelegate.m -@@ -0,0 +1,19 @@ -+// -+// AppDelegate.m -+// tvOSTestbed -+// ++### Testing in Xcode + -+#import "AppDelegate.h" ++You can also open the testbed project in Xcode by running: ++``` ++open my-testbed/iOSTestbed.xcodeproj ++``` + -+@interface AppDelegate () ++This will allow you to use the full Xcode suite of tools for debugging. + -+@end ++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. + -+@implementation AppDelegate ++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 + -+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -+ return YES; -+} ++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). + -+@end -diff --git a/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard b/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard ++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..660ba53de4f +index 00000000000..26ef7a95de4 --- /dev/null -+++ b/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard -@@ -0,0 +1,24 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/Apple/testbed/tvOSTestbed/app/README b/Apple/testbed/tvOSTestbed/app/README -new file mode 100644 -index 00000000000..46c0e8e2a29 ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed/app/README -@@ -0,0 +1,7 @@ -+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/Apple/testbed/tvOSTestbed/app_packages/README b/Apple/testbed/tvOSTestbed/app_packages/README -new file mode 100644 -index 00000000000..02c2beccfbd ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed/app_packages/README -@@ -0,0 +1,7 @@ -+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/Apple/testbed/tvOSTestbed/main.m b/Apple/testbed/tvOSTestbed/main.m -new file mode 100644 -index 00000000000..d5808fbb933 ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed/main.m -@@ -0,0 +1,16 @@ -+// -+// main.m -+// tvOSTestbed -+// -+ -+#import -+#import "AppDelegate.h" -+ -+int main(int argc, char * argv[]) { -+ NSString * appDelegateClassName; -+ @autoreleasepool { -+ appDelegateClassName = NSStringFromClass([AppDelegate class]); -+ -+ return UIApplicationMain(argc, argv, nil, appDelegateClassName); -+ } -+} -diff --git a/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist -new file mode 100644 -index 00000000000..f08f6098999 ---- /dev/null -+++ b/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist -@@ -0,0 +1,52 @@ ++++ b/Platforms/Apple/iOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ + -+ -+ ++ ++ + + CFBundleDevelopmentRegion + en -+ CFBundleDisplayName -+ ${PRODUCT_NAME} + CFBundleExecutable -+ ${EXECUTABLE_NAME} ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library + CFBundleIdentifier -+ org.python.tvOSTestbed ++ @PYTHONFRAMEWORKIDENTIFIER@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName -+ ${PRODUCT_NAME} ++ Python + CFBundlePackageType -+ APPL ++ FMWK + CFBundleShortVersionString -+ 1.0 ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. + CFBundleSignature + ???? + CFBundleVersion -+ 1 -+ LSRequiresIPhoneOS -+ -+ UIRequiresFullScreen -+ -+ UILaunchStoryboardName -+ Launch Screen -+ UISupportedInterfaceOrientations -+ -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight -+ -+ UISupportedInterfaceOrientations~ipad ++ %VERSION% ++ CFBundleSupportedPlatforms + -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationPortraitUpsideDown -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight ++ iPhoneOS + -+ UIApplicationSceneManifest -+ -+ UIApplicationSupportsMultipleScenes -+ -+ UISceneConfigurations -+ -+ ++ MinimumOSVersion ++ @IPHONEOS_DEPLOYMENT_TARGET@ + + -diff --git a/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj b/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj +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..0fd5df716b1 +index 00000000000..4acff2c6051 --- /dev/null -+++ b/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj -@@ -0,0 +1,558 @@ -+// !$*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 */; }; -+ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; }; -+ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */; }; -+ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; }; -+ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; }; -+ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; -+ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; -+/* 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 = ( -+ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */, -+ ); -+ name = "Embed Frameworks"; -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { -+ isa = PBXCopyFilesBuildPhase; -+ buildActionMask = 2147483647; -+ dstPath = ""; -+ dstSubfolderSpec = 10; -+ files = ( -+ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */, -+ ); -+ name = "Embed Frameworks"; -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXCopyFilesBuildPhase section */ ++++ b/Platforms/Apple/iOS/Resources/pyconfig.h +@@ -0,0 +1,7 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif + -+/* Begin PBXFileReference section */ -+ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = visionOSTestbed.xctestplan; sourceTree = ""; }; -+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = visionOSTestbed.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 = ""; }; -+ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; -+ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -+ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestbedTests.m; sourceTree = ""; }; -+ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "visionOSTestbed-Info.plist"; sourceTree = ""; }; -+ 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; }; -+ 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; }; -+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; -+/* End PBXFileReference section */ ++#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 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ tvos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ xros-arm64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ xros ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ xros-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ xros ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ watchos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_32 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64_32 ++ ++ SupportedPlatform ++ watchos ++ ++ ++ CFBundlePackageType ++ XFWK ++ XCFrameworkFormatVersion ++ 1.0 ++ ++ +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 ++# ----- + -+/* Begin PBXFrameworksBuildPhase section */ -+ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { -+ isa = PBXFrameworksBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { -+ isa = PBXFrameworksBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXFrameworksBuildPhase section */ ++# 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 + -+/* Begin PBXGroup section */ -+ 607A66092B0EFA380010BFC8 = { -+ isa = PBXGroup; -+ children = ( -+ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */, -+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */, -+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, -+ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, -+ 607A66132B0EFA380010BFC8 /* Products */, -+ 607A664F2B0EFFE00010BFC8 /* Frameworks */, -+ ); -+ sourceTree = ""; -+ }; -+ 607A66132B0EFA380010BFC8 /* Products */ = { -+ isa = PBXGroup; -+ children = ( -+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */, -+ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */, -+ ); -+ name = Products; -+ sourceTree = ""; -+ }; -+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */ = { -+ isa = PBXGroup; -+ children = ( -+ 608619552CB7819B00F46182 /* app */, -+ 608619532CB77BA900F46182 /* app_packages */, -+ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */, -+ 607A66152B0EFA380010BFC8 /* AppDelegate.h */, -+ 607A66162B0EFA380010BFC8 /* AppDelegate.m */, -+ 607A66212B0EFA390010BFC8 /* Assets.xcassets */, -+ 607A66272B0EFA390010BFC8 /* main.m */, -+ ); -+ path = visionOSTestbed; -+ 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 /* visionOSTestbed */ = { -+ isa = PBXNativeTarget; -+ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */; -+ buildPhases = ( -+ 607A660E2B0EFA380010BFC8 /* Sources */, -+ 607A660F2B0EFA380010BFC8 /* Frameworks */, -+ 607A66102B0EFA380010BFC8 /* Resources */, -+ 607A66552B0F061D0010BFC8 /* Process Python libraries */, -+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, -+ ); -+ buildRules = ( -+ ); -+ dependencies = ( -+ ); -+ name = visionOSTestbed; -+ productName = iOSTestbed; -+ productReference = 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */; -+ productType = "com.apple.product-type.application"; -+ }; -+ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */ = { -+ isa = PBXNativeTarget; -+ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */; -+ buildPhases = ( -+ 607A66292B0EFA3A0010BFC8 /* Sources */, -+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, -+ 607A662B2B0EFA3A0010BFC8 /* Resources */, -+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, -+ ); -+ buildRules = ( -+ ); -+ dependencies = ( -+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, -+ ); -+ name = TestbedTests; -+ productName = iOSTestbedTests; -+ productReference = 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */; -+ productType = "com.apple.product-type.bundle.unit-test"; -+ }; -+/* End PBXNativeTarget section */ ++ 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 + -+/* 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 "visionOSTestbed" */; -+ compatibilityVersion = "Xcode 14.0"; -+ developmentRegion = en; -+ hasScannedForEncodings = 0; -+ knownRegions = ( -+ en, -+ Base, -+ ); -+ mainGroup = 607A66092B0EFA380010BFC8; -+ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; -+ projectDirPath = ""; -+ projectRoot = ""; -+ targets = ( -+ 607A66112B0EFA380010BFC8 /* visionOSTestbed */, -+ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */, -+ ); -+ }; -+/* End PBXProject section */ ++ # 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 ++} + -+/* Begin PBXResourcesBuildPhase section */ -+ 607A66102B0EFA380010BFC8 /* Resources */ = { -+ isa = PBXResourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 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 */ ++# 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 + -+/* 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\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; -+ showEnvVarsInLog = 0; -+ }; -+/* End PBXShellScriptBuildPhase section */ ++ # 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" + -+/* 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 */ ++ # 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 + -+/* Begin PBXTargetDependency section */ -+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { -+ isa = PBXTargetDependency; -+ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; -+ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; -+ }; -+/* End PBXTargetDependency section */ ++ 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" + -+/* Begin XCBuildConfiguration section */ -+ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; -+ CLANG_ANALYZER_NONNULL = YES; -+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; -+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; -+ CLANG_ENABLE_MODULES = YES; -+ CLANG_ENABLE_OBJC_ARC = YES; -+ CLANG_ENABLE_OBJC_WEAK = YES; -+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; -+ CLANG_WARN_BOOL_CONVERSION = YES; -+ CLANG_WARN_COMMA = YES; -+ CLANG_WARN_CONSTANT_CONVERSION = YES; -+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; -+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -+ CLANG_WARN_EMPTY_BODY = YES; -+ CLANG_WARN_ENUM_CONVERSION = YES; -+ CLANG_WARN_INFINITE_RECURSION = YES; -+ CLANG_WARN_INT_CONVERSION = YES; -+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; -+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; -+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; -+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; -+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; -+ CLANG_WARN_STRICT_PROTOTYPES = YES; -+ CLANG_WARN_SUSPICIOUS_MOVE = YES; -+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; -+ CLANG_WARN_UNREACHABLE_CODE = YES; -+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -+ COPY_PHASE_STRIP = NO; -+ DEBUG_INFORMATION_FORMAT = dwarf; -+ ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_TESTABILITY = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = YES; -+ GCC_C_LANGUAGE_STANDARD = gnu17; -+ GCC_DYNAMIC_NO_PIC = NO; -+ GCC_NO_COMMON_BLOCKS = YES; -+ GCC_OPTIMIZATION_LEVEL = 0; -+ GCC_PREPROCESSOR_DEFINITIONS = ( -+ "DEBUG=1", -+ "$(inherited)", -+ ); -+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -+ GCC_WARN_UNDECLARED_SELECTOR = YES; -+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -+ GCC_WARN_UNUSED_FUNCTION = YES; -+ GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; -+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; -+ MTL_FAST_MATH = YES; -+ ONLY_ACTIVE_ARCH = YES; -+ SDKROOT = xros; -+ }; -+ name = Debug; -+ }; -+ 607A66402B0EFA3A0010BFC8 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ ALWAYS_SEARCH_USER_PATHS = NO; -+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; -+ CLANG_ANALYZER_NONNULL = YES; -+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; -+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; -+ CLANG_ENABLE_MODULES = YES; -+ CLANG_ENABLE_OBJC_ARC = YES; -+ CLANG_ENABLE_OBJC_WEAK = YES; -+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; -+ CLANG_WARN_BOOL_CONVERSION = YES; -+ CLANG_WARN_COMMA = YES; -+ CLANG_WARN_CONSTANT_CONVERSION = YES; -+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; -+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -+ CLANG_WARN_EMPTY_BODY = YES; -+ CLANG_WARN_ENUM_CONVERSION = YES; -+ CLANG_WARN_INFINITE_RECURSION = YES; -+ CLANG_WARN_INT_CONVERSION = YES; -+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; -+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; -+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; -+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; -+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; -+ CLANG_WARN_STRICT_PROTOTYPES = YES; -+ CLANG_WARN_SUSPICIOUS_MOVE = YES; -+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; -+ CLANG_WARN_UNREACHABLE_CODE = YES; -+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -+ COPY_PHASE_STRIP = NO; -+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; -+ ENABLE_NS_ASSERTIONS = NO; -+ ENABLE_STRICT_OBJC_MSGSEND = YES; -+ ENABLE_USER_SCRIPT_SANDBOXING = YES; -+ GCC_C_LANGUAGE_STANDARD = gnu17; -+ GCC_NO_COMMON_BLOCKS = YES; -+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -+ GCC_WARN_UNDECLARED_SELECTOR = YES; -+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -+ GCC_WARN_UNUSED_FUNCTION = YES; -+ GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; -+ MTL_ENABLE_DEBUG_INFO = NO; -+ MTL_FAST_MATH = YES; -+ SDKROOT = xros; -+ 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 = "visionOSTestbed/visionOSTestbed-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"; -+ LD_RUNPATH_SEARCH_PATHS = ( -+ "$(inherited)", -+ "@executable_path/Frameworks", -+ ); -+ MARKETING_VERSION = 3.13.0a1; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 7; -+ XROS_DEPLOYMENT_TARGET = 2.0; -+ }; -+ 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 = "visionOSTestbed/visionOSTestbed-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"; -+ LD_RUNPATH_SEARCH_PATHS = ( -+ "$(inherited)", -+ "@executable_path/Frameworks", -+ ); -+ MARKETING_VERSION = 3.13.0a1; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 7; -+ XROS_DEPLOYMENT_TARGET = 2.0; -+ }; -+ name = Release; -+ }; -+ 607A66452B0EFA3A0010BFC8 /* Debug */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ BUNDLE_LOADER = "$(TEST_HOST)"; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = 3HEZE76D99; -+ GENERATE_INFOPLIST_FILE = YES; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 7; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; -+ }; -+ name = Debug; -+ }; -+ 607A66462B0EFA3A0010BFC8 /* Release */ = { -+ isa = XCBuildConfiguration; -+ buildSettings = { -+ BUNDLE_LOADER = "$(TEST_HOST)"; -+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; -+ CODE_SIGN_STYLE = Automatic; -+ CURRENT_PROJECT_VERSION = 1; -+ DEVELOPMENT_TEAM = 3HEZE76D99; -+ GENERATE_INFOPLIST_FILE = YES; -+ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; -+ MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; -+ PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 7; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; -+ }; -+ name = Release; -+ }; -+/* End XCBuildConfiguration section */ ++ # 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 + -+/* Begin XCConfigurationList section */ -+ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ 607A663F2B0EFA3A0010BFC8 /* Debug */, -+ 607A66402B0EFA3A0010BFC8 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ 607A66422B0EFA3A0010BFC8 /* Debug */, -+ 607A66432B0EFA3A0010BFC8 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { -+ isa = XCConfigurationList; -+ buildConfigurations = ( -+ 607A66452B0EFA3A0010BFC8 /* Debug */, -+ 607A66462B0EFA3A0010BFC8 /* Release */, -+ ); -+ defaultConfigurationIsVisible = 0; -+ defaultConfigurationName = Release; -+ }; -+/* End XCConfigurationList section */ -+ }; -+ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; ++ 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" +} -diff --git a/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme b/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 -@@ -0,0 +1,97 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/Apple/testbed/visionOSTestbed.xctestplan b/Apple/testbed/visionOSTestbed.xctestplan -new file mode 100644 -index 00000000000..73a2be8c22a ---- /dev/null -+++ b/Apple/testbed/visionOSTestbed.xctestplan -@@ -0,0 +1,46 @@ -+{ -+ "configurations" : [ -+ { -+ "id" : "C17FA044-0B70-48CA-AFF8-BC252081002F", -+ "name" : "Test Scheme Action", -+ "options" : { + -+ } -+ } -+ ], -+ "defaultOptions" : { -+ "commandLineArgumentEntries" : [ -+ { -+ "argument" : "test" -+ }, -+ { -+ "argument" : "-uall" -+ }, -+ { -+ "argument" : "--single-process" -+ }, -+ { -+ "argument" : "--rerun" -+ }, -+ { -+ "argument" : "-W" -+ } -+ ], -+ "targetForVariableExpansion" : { -+ "containerPath" : "container:visionOSTestbed.xcodeproj", -+ "identifier" : "607A66112B0EFA380010BFC8", -+ "name" : "visionOSTestbed" -+ } -+ }, -+ "testTargets" : [ -+ { -+ "parallelizable" : false, -+ "target" : { -+ "containerPath" : "container:visionOSTestbed.xcodeproj", -+ "identifier" : "607A662C2B0EFA3A0010BFC8", -+ "name" : "TestbedTests" -+ } -+ } -+ ], -+ "version" : 1 ++# 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 +} -\ No newline at end of file -diff --git a/Apple/testbed/visionOSTestbed/AppDelegate.h b/Apple/testbed/visionOSTestbed/AppDelegate.h -new file mode 100644 -index 00000000000..8fe7ec8e64e ---- /dev/null -+++ b/Apple/testbed/visionOSTestbed/AppDelegate.h -@@ -0,0 +1,11 @@ -+// -+// AppDelegate.h -+// visionOSTestbed -+// -+ -+#import -+ -+@interface AppDelegate : UIResponder + ++# 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 + -+@end -diff --git a/Apple/testbed/visionOSTestbed/AppDelegate.m b/Apple/testbed/visionOSTestbed/AppDelegate.m -new file mode 100644 -index 00000000000..b3ff14f7255 ---- /dev/null -+++ b/Apple/testbed/visionOSTestbed/AppDelegate.m -@@ -0,0 +1,19 @@ -+// -+// AppDelegate.m -+// visionOSTestbed -+// -+ -+#import "AppDelegate.h" -+ -+@interface AppDelegate () -+ -+@end -+ -+@implementation AppDelegate -+ ++ 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 + -+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -+ return YES; ++ for package_path in $@; do ++ echo "Installing $package_path extension modules ..." ++ process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path ++ done +} -+ -+@end -diff --git a/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +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..eb878970081 +index 00000000000..6f8c0bc2095 --- /dev/null -+++ b/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json -@@ -0,0 +1,11 @@ -+{ -+ "colors" : [ -+ { -+ "idiom" : "universal" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/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 -@@ -0,0 +1,13 @@ -+{ -+ "images" : [ -+ { -+ "idiom" : "universal", -+ "platform" : "ios", -+ "size" : "1024x1024" -+ } -+ ], -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json b/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json -new file mode 100644 -index 00000000000..73c00596a7f ---- /dev/null -+++ b/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json -@@ -0,0 +1,6 @@ -+{ -+ "info" : { -+ "author" : "xcode", -+ "version" : 1 -+ } -+} -diff --git a/Apple/testbed/visionOSTestbed/app/README b/Apple/testbed/visionOSTestbed/app/README -new file mode 100644 -index 00000000000..af22c685f87 ---- /dev/null -+++ b/Apple/testbed/visionOSTestbed/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 -+iOS 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/Apple/testbed/visionOSTestbed/app_packages/README b/Apple/testbed/visionOSTestbed/app_packages/README -new file mode 100644 -index 00000000000..42d7fdeb813 ---- /dev/null -+++ b/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. -+ -+During the build, any binary modules found in this folder will be processed into -+iOS Framework form. -+ -+When the test suite runs, this folder will be on the PYTHONPATH. -diff --git a/Apple/testbed/visionOSTestbed/main.m b/Apple/testbed/visionOSTestbed/main.m -new file mode 100644 -index 00000000000..2bb491f25c8 ---- /dev/null -+++ b/Apple/testbed/visionOSTestbed/main.m -@@ -0,0 +1,16 @@ -+// -+// main.m -+// visionOSTestbed -+// -+ -+#import -+#import "AppDelegate.h" -+ -+int main(int argc, char * argv[]) { -+ NSString * appDelegateClassName; -+ @autoreleasepool { -+ appDelegateClassName = NSStringFromClass([AppDelegate class]); -+ -+ return UIApplicationMain(argc, argv, nil, appDelegateClassName); -+ } -+} -diff --git a/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist b/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist -new file mode 100644 -index 00000000000..fce9298555d ---- /dev/null -+++ b/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist -@@ -0,0 +1,56 @@ ++++ b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en -+ CFBundleDisplayName -+ ${PRODUCT_NAME} + CFBundleExecutable -+ ${EXECUTABLE_NAME} ++ + CFBundleIdentifier -+ org.python.visionOSTestbed ++ + CFBundleInfoDictionaryVersion + 6.0 -+ CFBundleName -+ ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ 1 -+ TestArgs ++ CFBundleSupportedPlatforms + -+ test -+ -uall -+ --single-process -+ --rerun -+ -W ++ watchOS + -+ UIApplicationSceneManifest -+ -+ UIApplicationSupportsMultipleScenes -+ -+ UISceneConfigurations -+ -+ -+ UIRequiresFullScreen -+ -+ UISupportedInterfaceOrientations ++ 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 + -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight ++ XROS + -+ UISupportedInterfaceOrientations~ipad ++ CFBundleVersion ++ 1 ++ MinimumOSVersion ++ 2.0 ++ UIDeviceFamily + -+ UIInterfaceOrientationPortrait -+ UIInterfaceOrientationPortraitUpsideDown -+ UIInterfaceOrientationLandscapeLeft -+ UIInterfaceOrientationLandscapeRight ++ 7 + + + -diff --git a/Apple/tvOS/README.rst b/Apple/tvOS/README.rst +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..1f793252caf +index 00000000000..c1b076d12cd --- /dev/null -+++ b/Apple/tvOS/README.rst -@@ -0,0 +1,108 @@ -+===================== -+Python on tvOS README -+===================== -+ -+:Authors: -+ Russell Keith-Magee (2023-11) -+ -+This document provides a quick overview of some tvOS specific features in the -+Python distribution. ++++ b/Platforms/Apple/testbed/Python.xcframework/ios-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+Compilers for building on tvOS -+============================== ++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. + -+Building for tvOS requires the use of Apple's Xcode tooling. It is strongly -+recommended that you use the most recent stable release of Xcode, on the -+most recently released macOS. ++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. + -+tvOS specific arguments to configure -+=================================== ++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. + -+* ``--enable-framework[=DIR]`` ++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. + -+ This argument specifies the location where the Python.framework will -+ be installed. ++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. + -+* ``--with-framework-name=NAME`` ++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. + -+ Specify the name for the python framework, defaults to ``Python``. ++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..cc0d9224042 +--- /dev/null ++++ b/Platforms/Apple/testbed/TestbedTests/TestbedTests.m +@@ -0,0 +1,198 @@ ++#import ++#import + -+Building and using Python on tvOS -+================================= ++@interface TestbedTests : XCTestCase + -+ABIs and Architectures -+---------------------- ++@end + -+tvOS apps can be deployed on physical devices, and on the tvOS simulator. -+Although the API used on these devices is identical, the ABI is different - you -+need to link against different libraries for an tvOS device build -+(``appletvos``) or an tvOS simulator build (``appletvsimulator``). Apple uses -+the XCframework format to allow specifying a single dependency that supports -+multiple ABIs. An XCframework is a wrapper around multiple ABI-specific -+frameworks. ++@implementation TestbedTests + -+tvOS can also support different CPU architectures within each ABI. At present, -+there is only a single support ed architecture on physical devices - ARM64. -+However, the *simulator* supports 2 architectures - ARM64 (for running on Apple -+Silicon machines), and x86_64 (for running on older Intel-based machines.) + -+To support multiple CPU architectures on a single platform, Apple uses a "fat -+binary" format - a single physical file that contains support for multiple -+architectures. ++- (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; + -+How do I build Python for tvOS? -+------------------------------- ++ NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + -+The Python build system will build a ``Python.framework`` that supports a -+*single* ABI with a *single* architecture. If you want to use Python in an tvOS -+project, you need to: ++ // 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); + -+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. ++ 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]; ++ } + -+tvOS builds of Python *must* be constructed as framework builds. To support this, -+you must provide the ``--enable-framework`` flag when configuring the build. ++ // Generate an isolated Python configuration. ++ NSLog(@"Configuring isolated Python..."); ++ PyPreConfig_InitIsolatedConfig(&preconfig); ++ PyConfig_InitIsolatedConfig(&config); + -+The build also requires the use of cross-compilation. The commands for building -+Python for tvOS will look somethign like:: ++ // Configure the Python interpreter: ++ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. ++ // See https://docs.python.org/3/library/os.html#python-utf-8-mode. ++ preconfig.utf8_mode = 1; ++ // Don't buffer stdio. We want output to appears in the log immediately ++ config.buffered_stdio = 0; ++ // Don't write bytecode; we can't modify the app bundle ++ // after it has been signed. ++ config.write_bytecode = 0; ++ // Ensure that signal handlers are installed ++ config.install_signal_handlers = 1; ++ // Run the test module. ++ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:1] UTF8String], NULL); ++ // For debugging - enable verbose mode. ++ // config.verbose = 1; + -+ $ ./configure \ -+ --enable-framework=/path/to/install \ -+ --host=aarch64-apple-tvos \ -+ --build=aarch64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install ++ 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; ++ } + -+In this invocation: ++ // 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); + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++ // 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; ++ } + -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: ++ 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; ++ } + -+ - ``aarch64-apple-tvos`` for ARM64 tvOS devices. -+ - ``aarch64-apple-tvos-simulator`` for the tvOS simulator running on Apple -+ Silicon devices. -+ - ``x86_64-apple-tvos-simulator`` for the tvOS simulator running on Intel -+ devices. ++ 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; ++ } + -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: ++ // 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; ++ } + -+ - ``aarch64-apple-darwin`` for Apple Silicon devices. -+ - ``x86_64-apple-darwin`` for Intel devices. ++ 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/to/python.exe`` is the path to a Python binary on the machine that -+ will be running the compiler. This is needed because the Python compilation -+ process involves running some Python code. On a normal desktop build of -+ Python, you can compile a python interpreter and then use that interpreter to -+ run Python code. However, the binaries produced for tvOS won't run on macOS, so -+ you need to provide an external Python interpreter. This interpreter must be -+ the version as the Python that is being compiled. ++ 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); + -+Using a framework-based Python on tvOS -+====================================== -diff --git a/Apple/tvOS/Resources/Info.plist.in b/Apple/tvOS/Resources/Info.plist.in ++ 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..ab3050804b8 ---- /dev/null -+++ b/Apple/tvOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ Python -+ CFBundleGetInfoString -+ Python Runtime and Library -+ CFBundleIdentifier -+ @PYTHONFRAMEWORKIDENTIFIER@ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleName -+ Python -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ %VERSION% -+ CFBundleLongVersionString -+ %VERSION%, (c) 2001-2024 Python Software Foundation. -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ 1 -+ CFBundleSupportedPlatforms -+ -+ tvOS -+ -+ MinimumOSVersion -+ @TVOS_DEPLOYMENT_TARGET@ -+ -+ -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar b/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar -new file mode 100755 -index 00000000000..e302748a13c +index 00000000000..97afc03fa34 --- /dev/null -+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang -new file mode 100755 -index 00000000000..7fb6d3d901c ++++ b/Platforms/Apple/testbed/__main__.py +@@ -0,0 +1,480 @@ ++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": ++ # Any watchOS device will do; we'll look for "SE" devices. 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" watchOS version. ++ se_simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple Watch" ++ and "Apple Watch SE" in devicetype["name"] ++ ) ++ simulator = se_simulators[-1][1] ++ 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. However, we don't need test diagnostics - ++ # they're large, and the context they contain generally isn't helpful for ++ # CPython tests. ++ process = subprocess.Popen( ++ [ ++ "xcodebuild", ++ "test-without-building", ++ "-collect-test-diagnostics", ++ "never", ++ ] ++ + 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.") ++ ++ # xcodebuild doesn't guarantee that the CoreSimulatorService daemon is ++ # running prior to using a simulator, but calling `xcrun simctl list` does. ++ # Determining the default simulator that *would* be used is a cheap action; ++ # so use that as a way to ensure that the CoreSimulatorService daemon is ++ # running. ++ default_simulator = select_simulator_device(platform) ++ if simulator is None: ++ simulator = default_simulator ++ 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..a0dd6851dc1 +--- /dev/null ++++ b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,534 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 77; ++ objects = { ++ ++/* Begin PBXBuildFile section */ ++ 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, ); }; }; ++/* 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; }; ++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = iOSTestbed.xctestplan; sourceTree = ""; }; ++/* End PBXFileReference section */ ++ ++/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ 691FECCB8963969DDBED592E /* Exceptions for "iOSTestbed" folder in "iOSTestbed" target */ = { ++ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; ++ membershipExceptions = ( ++ "iOSTestbed-Info.plist", ++ ); ++ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; ++ }; ++/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ ++/* Begin PBXFileSystemSynchronizedRootGroup section */ ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ exceptions = ( ++ 691FECCB8963969DDBED592E /* Exceptions for "iOSTestbed" folder in "iOSTestbed" target */, ++ ); ++ explicitFolders = ( ++ app, ++ app_packages, ++ ); ++ path = iOSTestbed; ++ sourceTree = ""; ++ }; ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++/* End PBXFileSystemSynchronizedRootGroup 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 */, ++ ); ++ sourceTree = ""; ++ }; ++ 607A66132B0EFA380010BFC8 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */, ++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */, ++ ); ++ name = Products; ++ 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 = ( ++ ); ++ fileSystemSynchronizedGroups = ( ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */, ++ ); ++ name = iOSTestbed; ++ packageProductDependencies = ( ++ ); ++ 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 */, ++ ); ++ fileSystemSynchronizedGroups = ( ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, ++ ); ++ name = iOSTestbedTests; ++ packageProductDependencies = ( ++ ); ++ 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; ++ minimizedProjectReferenceProxies = 1; ++ preferredProjectObjectVersion = 77; ++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ 607A66112B0EFA380010BFC8 /* iOSTestbed */, ++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ ++ ++/* Begin PBXResourcesBuildPhase section */ ++ 607A66102B0EFA380010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ 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 = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66292B0EFA3A0010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ ++ ++/* Begin PBXTargetDependency section */ ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; ++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency 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 = 15.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 = 15.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 = 15.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 = 15.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 = 15.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 = 15.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/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..fbe71bae23b +--- /dev/null ++++ b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.m +@@ -0,0 +1,24 @@ ++// ++// AppDelegate.m ++// iOSTestbed ++// ++ ++#import "AppDelegate.h" ++#import "SceneDelegate.h" ++ ++@interface AppDelegate () ++ ++@end ++ ++@implementation AppDelegate ++ ++- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { ++ UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; ++ configuration.delegateClass = [SceneDelegate class]; ++ return configuration; ++} ++ ++- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { ++} ++ ++@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/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ -new file mode 100755 -index 00000000000..33bfb1367c3 ++++ 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/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp b/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp -new file mode 100755 -index 00000000000..641c1bc8d18 ++++ b/Platforms/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,9 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/iOSTestbed/SceneDelegate.h b/Platforms/Apple/testbed/iOSTestbed/SceneDelegate.h +new file mode 100644 +index 00000000000..264aacadca1 --- /dev/null -+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} -E "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar -new file mode 100755 -index 00000000000..87ef5015aae ++++ b/Platforms/Apple/testbed/iOSTestbed/SceneDelegate.h +@@ -0,0 +1,12 @@ ++// ++// SceneDelegate.h ++// iOSTestbed ++// ++ ++#import ++ ++@interface SceneDelegate : UIResponder ++ ++@property (strong, nonatomic) UIWindow *window; ++ ++@end +diff --git a/Platforms/Apple/testbed/iOSTestbed/SceneDelegate.m b/Platforms/Apple/testbed/iOSTestbed/SceneDelegate.m +new file mode 100644 +index 00000000000..e82e447985d +--- /dev/null ++++ b/Platforms/Apple/testbed/iOSTestbed/SceneDelegate.m +@@ -0,0 +1,21 @@ ++// ++// SceneDelegate.m ++// iOSTestbed ++// ++ ++#import "SceneDelegate.h" ++ ++@interface SceneDelegate () ++ ++@end ++ ++@implementation SceneDelegate ++ ++- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { ++ UIWindowScene *windowScene = (UIWindowScene *)scene; ++ self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; ++ self.window.rootViewController = [[UIViewController alloc] init]; ++ [self.window makeKeyAndVisible]; ++} ++ ++@end +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/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..3a560656972 +--- /dev/null ++++ b/Platforms/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist +@@ -0,0 +1,62 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.iOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ LSRequiresIPhoneOS ++ ++ UIRequiresFullScreen ++ ++ UILaunchStoryboardName ++ Launch Screen ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ UIWindowSceneSessionRoleApplication ++ ++ ++ UISceneConfigurationName ++ Default Configuration ++ UISceneDelegateClassName ++ SceneDelegate ++ ++ ++ ++ ++ ++ +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/Platforms/Apple/testbed/iOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// iOSTestbed ++// ++ ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +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/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,505 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 77; ++ objects = { ++ ++/* Begin PBXBuildFile section */ ++ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; ++ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; }; ++ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++/* End PBXBuildFile section */ ++ ++/* Begin PBXContainerItemProxy section */ ++ EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = EE989E462DCD6E780036B268 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = EE989E4D2DCD6E780036B268; ++ remoteInfo = tvOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ ++ ++/* Begin PBXCopyFilesBuildPhase section */ ++ EE7C8A212DCD70CD003206DB /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ EE7C8A202DCD70CD003206DB /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ ++ ++/* Begin PBXFileReference section */ ++ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = tvOSTestbed.xctestplan; sourceTree = ""; }; ++ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++/* End PBXFileReference section */ ++ ++/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */ = { ++ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; ++ membershipExceptions = ( ++ "tvOSTestbed-Info.plist", ++ ); ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ }; ++/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ ++/* Begin PBXFileSystemSynchronizedRootGroup section */ ++ EE989E502DCD6E780036B268 /* tvOSTestbed */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ exceptions = ( ++ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */, ++ ); ++ explicitFolders = ( ++ app, ++ app_packages, ++ ); ++ path = tvOSTestbed; ++ sourceTree = ""; ++ }; ++ EE989E682DCD6E7A0036B268 /* TestbedTests */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++/* End PBXFileSystemSynchronizedRootGroup section */ ++ ++/* Begin PBXFrameworksBuildPhase section */ ++ EE989E4B2DCD6E780036B268 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EE7C8A1F2DCD70CD003206DB /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E622DCD6E7A0036B268 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EE7C8A1E2DCD6FF3003206DB /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ ++ ++/* Begin PBXGroup section */ ++ EE989E452DCD6E780036B268 = { ++ isa = PBXGroup; ++ children = ( ++ 6077B3802E82A4BE00E3D6A3 /* tvOSTestbed.xctestplan */, ++ EE7C8A1C2DCD6FF3003206DB /* Python.xcframework */, ++ EE989E502DCD6E780036B268 /* tvOSTestbed */, ++ EE989E682DCD6E7A0036B268 /* TestbedTests */, ++ EE989E4F2DCD6E780036B268 /* Products */, ++ ); ++ sourceTree = ""; ++ }; ++ EE989E4F2DCD6E780036B268 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */, ++ EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ ++ ++/* Begin PBXNativeTarget section */ ++ EE989E4D2DCD6E780036B268 /* tvOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */; ++ buildPhases = ( ++ EE989E4A2DCD6E780036B268 /* Sources */, ++ EE989E4B2DCD6E780036B268 /* Frameworks */, ++ EE989E4C2DCD6E780036B268 /* Resources */, ++ EE7C8A222DCD70F4003206DB /* Process Python libraries */, ++ EE7C8A212DCD70CD003206DB /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ fileSystemSynchronizedGroups = ( ++ EE989E502DCD6E780036B268 /* tvOSTestbed */, ++ ); ++ name = tvOSTestbed; ++ packageProductDependencies = ( ++ ); ++ productName = tvOSTestbed; ++ productReference = EE989E4E2DCD6E780036B268 /* tvOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ EE989E642DCD6E7A0036B268 /* TestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildPhases = ( ++ EE989E612DCD6E7A0036B268 /* Sources */, ++ EE989E622DCD6E7A0036B268 /* Frameworks */, ++ EE989E632DCD6E7A0036B268 /* Resources */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */, ++ ); ++ fileSystemSynchronizedGroups = ( ++ EE989E682DCD6E7A0036B268 /* TestbedTests */, ++ ); ++ name = TestbedTests; ++ packageProductDependencies = ( ++ ); ++ productName = TestbedTests; ++ productReference = EE989E652DCD6E7A0036B268 /* TestbedTests.xctest */; ++ productType = "com.apple.product-type.bundle.unit-test"; ++ }; ++/* End PBXNativeTarget section */ ++ ++/* Begin PBXProject section */ ++ EE989E462DCD6E780036B268 /* Project object */ = { ++ isa = PBXProject; ++ attributes = { ++ BuildIndependentTargetsInParallel = 1; ++ LastUpgradeCheck = 1620; ++ TargetAttributes = { ++ EE989E4D2DCD6E780036B268 = { ++ CreatedOnToolsVersion = 16.2; ++ }; ++ EE989E642DCD6E7A0036B268 = { ++ CreatedOnToolsVersion = 16.2; ++ TestTargetID = EE989E4D2DCD6E780036B268; ++ }; ++ }; ++ }; ++ buildConfigurationList = EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = EE989E452DCD6E780036B268; ++ minimizedProjectReferenceProxies = 1; ++ preferredProjectObjectVersion = 77; ++ productRefGroup = EE989E4F2DCD6E780036B268 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ EE989E4D2DCD6E780036B268 /* tvOSTestbed */, ++ EE989E642DCD6E7A0036B268 /* TestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ ++ ++/* Begin PBXResourcesBuildPhase section */ ++ EE989E4C2DCD6E780036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E632DCD6E7A0036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ ++ ++/* Begin PBXShellScriptBuildPhase section */ ++ EE7C8A222DCD70F4003206DB /* Process Python libraries */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Process Python libraries"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ }; ++/* End PBXShellScriptBuildPhase section */ ++ ++/* Begin PBXSourcesBuildPhase section */ ++ EE989E4A2DCD6E780036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E612DCD6E7A0036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ ++ ++/* Begin PBXTargetDependency section */ ++ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ targetProxy = EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ ++ ++/* Begin XCBuildConfiguration section */ ++ EE989E772DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = appletvos; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Debug; ++ }; ++ EE989E782DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = appletvos; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ VALIDATE_PRODUCT = YES; ++ }; ++ name = Release; ++ }; ++ EE989E7A2DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 3; ++ }; ++ name = Debug; ++ }; ++ EE989E7B2DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "tvOSTestbed/tvOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.tvOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 3; ++ }; ++ name = Release; ++ }; ++ EE989E7D2DCD6E7A0036B268 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 3; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Debug; ++ }; ++ EE989E7E2DCD6E7A0036B268 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 3; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tvOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tvOSTestbed"; ++ TVOS_DEPLOYMENT_TARGET = 18.2; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ ++ ++/* Begin XCConfigurationList section */ ++ EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E772DCD6E7A0036B268 /* Debug */, ++ EE989E782DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7A2DCD6E7A0036B268 /* Debug */, ++ EE989E7B2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7D2DCD6E7A0036B268 /* Debug */, ++ EE989E7E2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = EE989E462DCD6E780036B268 /* Project object */; ++} +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/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang -new file mode 100755 -index 00000000000..c8719cb0318 ++++ 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/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ -new file mode 100755 -index 00000000000..e3f0e720f7f ++++ b/Platforms/Apple/testbed/tvOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", ++ "name" : "Test Scheme Action", ++ "options" : { ++ ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "tvOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "EE989E642DCD6E7A0036B268", ++ "name" : "TestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +\ No newline at end of file +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/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp -new file mode 100755 -index 00000000000..f9a37b72a61 ++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// tvOSTestbed ++// ++ ++#import ++ ++@interface AppDelegate : UIResponder ++ ++ ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..7501ec7b6ef --- /dev/null -+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip b/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip -new file mode 100755 -index 00000000000..a8cce95233e ++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m +@@ -0,0 +1,24 @@ ++// ++// AppDelegate.m ++// tvOSTestbed ++// ++ ++#import "AppDelegate.h" ++#import "SceneDelegate.h" ++ ++@interface AppDelegate () ++ ++@end ++ ++@implementation AppDelegate ++ ++- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { ++ UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; ++ configuration.delegateClass = [SceneDelegate class]; ++ return configuration; ++} ++ ++- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { ++} ++ ++@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/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch arm64 "$@" -diff --git a/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip b/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip -new file mode 100755 -index 00000000000..ee1d2b95ff1 ++++ b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,24 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h +new file mode 100644 +index 00000000000..264aacadca1 --- /dev/null -+++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk iphoneos${TVOS_SDK_VERSION} strip -arch arm64 "$@" -diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar -new file mode 100755 -index 00000000000..87ef5015aae ++++ b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.h +@@ -0,0 +1,12 @@ ++// ++// SceneDelegate.h ++// iOSTestbed ++// ++ ++#import ++ ++@interface SceneDelegate : UIResponder ++ ++@property (strong, nonatomic) UIWindow *window; ++ ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m +new file mode 100644 +index 00000000000..e82e447985d --- /dev/null -+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" -diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang -new file mode 100755 -index 00000000000..ea0cc26cbd9 ++++ b/Platforms/Apple/testbed/tvOSTestbed/SceneDelegate.m +@@ -0,0 +1,21 @@ ++// ++// SceneDelegate.m ++// iOSTestbed ++// ++ ++#import "SceneDelegate.h" ++ ++@interface SceneDelegate () ++ ++@end ++ ++@implementation SceneDelegate ++ ++- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { ++ UIWindowScene *windowScene = (UIWindowScene *)scene; ++ self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; ++ self.window.rootViewController = [[UIViewController alloc] init]; ++ [self.window makeKeyAndVisible]; ++} ++ ++@end +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/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ -new file mode 100755 -index 00000000000..f18f3603169 ++++ b/Platforms/Apple/testbed/tvOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. ++ ++During the build, any binary modules found in this folder will be processed into ++Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH, and will be the ++working directory for the test suite. +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/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp -new file mode 100755 -index 00000000000..b98054d1ce2 ++++ 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. ++ ++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/tvOSTestbed/main.m b/Platforms/Apple/testbed/tvOSTestbed/main.m +new file mode 100644 +index 00000000000..d5808fbb933 --- /dev/null -+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" -diff --git a/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip -new file mode 100755 -index 00000000000..f6a884b4aef ++++ b/Platforms/Apple/testbed/tvOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// tvOSTestbed ++// ++ ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +diff --git a/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +new file mode 100644 +index 00000000000..6d84447c031 --- /dev/null -+++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch x86_64 "$@" -diff --git a/Apple/tvOS/Resources/pyconfig.h b/Apple/tvOS/Resources/pyconfig.h ++++ b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +@@ -0,0 +1,62 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.tvOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ LSRequiresIPhoneOS ++ ++ UIRequiresFullScreen ++ ++ UILaunchStoryboardName ++ Launch Screen ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ UIWindowSceneSessionRoleApplication ++ ++ ++ UISceneDelegateClassName ++ SceneDelegate ++ UISceneConfigurationName ++ Default Configuration ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj new file mode 100644 -index 00000000000..4acff2c6051 +index 00000000000..d3ca84bd32c --- /dev/null -+++ b/Apple/tvOS/Resources/pyconfig.h -@@ -0,0 +1,7 @@ -+#ifdef __arm64__ -+#include "pyconfig-arm64.h" -+#endif ++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,550 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 77; ++ objects = { ++ ++/* Begin PBXBuildFile section */ ++ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; ++ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; ++/* 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 = ( ++ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ ++ ++/* Begin PBXFileReference section */ ++ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = visionOSTestbed.xctestplan; sourceTree = ""; }; ++ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = visionOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++/* End PBXFileReference section */ ++ ++/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ 4172389B57E3372C38B3DB88 /* Exceptions for "visionOSTestbed" folder in "visionOSTestbed" target */ = { ++ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; ++ membershipExceptions = ( ++ "visionOSTestbed-Info.plist", ++ ); ++ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; ++ }; ++/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ ++/* Begin PBXFileSystemSynchronizedRootGroup section */ ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ exceptions = ( ++ 4172389B57E3372C38B3DB88 /* Exceptions for "visionOSTestbed" folder in "visionOSTestbed" target */, ++ ); ++ explicitFolders = ( ++ app, ++ app_packages, ++ ); ++ path = visionOSTestbed; ++ sourceTree = ""; ++ }; ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++/* End PBXFileSystemSynchronizedRootGroup section */ ++ ++/* Begin PBXFrameworksBuildPhase section */ ++ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ ++ ++/* Begin PBXGroup section */ ++ 607A66092B0EFA380010BFC8 = { ++ isa = PBXGroup; ++ children = ( ++ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */, ++ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */, ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, ++ 607A66132B0EFA380010BFC8 /* Products */, ++ ); ++ sourceTree = ""; ++ }; ++ 607A66132B0EFA380010BFC8 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */, ++ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ ++ ++/* Begin PBXNativeTarget section */ ++ 607A66112B0EFA380010BFC8 /* visionOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */; ++ buildPhases = ( ++ 607A660E2B0EFA380010BFC8 /* Sources */, ++ 607A660F2B0EFA380010BFC8 /* Frameworks */, ++ 607A66102B0EFA380010BFC8 /* Resources */, ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */, ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ fileSystemSynchronizedGroups = ( ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, ++ ); ++ name = visionOSTestbed; ++ packageProductDependencies = ( ++ ); ++ productName = iOSTestbed; ++ productReference = 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildPhases = ( ++ 607A66292B0EFA3A0010BFC8 /* Sources */, ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, ++ 607A662B2B0EFA3A0010BFC8 /* Resources */, ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, ++ ); ++ fileSystemSynchronizedGroups = ( ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, ++ ); ++ name = TestbedTests; ++ packageProductDependencies = ( ++ ); ++ productName = iOSTestbedTests; ++ productReference = 607A662D2B0EFA3A0010BFC8 /* TestbedTests.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 "visionOSTestbed" */; ++ compatibilityVersion = "Xcode 14.0"; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = 607A66092B0EFA380010BFC8; ++ minimizedProjectReferenceProxies = 1; ++ preferredProjectObjectVersion = 77; ++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ 607A66112B0EFA380010BFC8 /* visionOSTestbed */, ++ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ ++ ++/* Begin PBXResourcesBuildPhase section */ ++ 607A66102B0EFA380010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ 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\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ showEnvVarsInLog = 0; ++ }; ++/* End PBXShellScriptBuildPhase section */ ++ ++/* Begin PBXSourcesBuildPhase section */ ++ 607A660E2B0EFA380010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66292B0EFA3A0010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ ++ ++/* Begin PBXTargetDependency section */ ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; ++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ ++ ++/* Begin XCBuildConfiguration section */ ++ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = xros; ++ }; ++ name = Debug; ++ }; ++ 607A66402B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = xros; ++ 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 = "visionOSTestbed/visionOSTestbed-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"; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 7; ++ XROS_DEPLOYMENT_TARGET = 2.0; ++ }; ++ 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 = "visionOSTestbed/visionOSTestbed-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"; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 7; ++ XROS_DEPLOYMENT_TARGET = 2.0; ++ }; ++ name = Release; ++ }; ++ 607A66452B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 7; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ }; ++ name = Debug; ++ }; ++ 607A66462B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 7; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ + -+#ifdef __x86_64__ -+#include "pyconfig-x86_64.h" -+#endif -diff --git a/Apple/visionOS/Resources/Info.plist.in b/Apple/visionOS/Resources/Info.plist.in ++/* Begin XCConfigurationList section */ ++ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A663F2B0EFA3A0010BFC8 /* Debug */, ++ 607A66402B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66422B0EFA3A0010BFC8 /* Debug */, ++ 607A66432B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66452B0EFA3A0010BFC8 /* Debug */, ++ 607A66462B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ 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..2679440da67 +index 00000000000..b8397ea7cf1 --- /dev/null -+++ b/Apple/visionOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ ++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme +@@ -0,0 +1,97 @@ + -+ -+ -+ -+ 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@ -+ -+ -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-ar b/Apple/visionOS/Resources/bin/arm64-apple-xros-ar -new file mode 100755 -index 00000000000..9fd78a205f3 ---- /dev/null -+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xros${XROS_SDK_VERSION} ar "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-clang b/Apple/visionOS/Resources/bin/arm64-apple-xros-clang -new file mode 100755 -index 00000000000..9a1a757cbd0 ---- /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} "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ -new file mode 100755 -index 00000000000..f64fcfc11cd ---- /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} "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp b/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp -new file mode 100755 -index 00000000000..d6492eff052 ---- /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 "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar -new file mode 100755 -index 00000000000..b202330fb5d ---- /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 "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang -new file mode 100755 -index 00000000000..87b0aba0751 ---- /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 "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ -new file mode 100755 -index 00000000000..c89d48f1cb8 ---- /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 "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp -new file mode 100755 -index 00000000000..ee11e018ae0 ---- /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 "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip b/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip -new file mode 100644 -index 00000000000..376aff61a23 ---- /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 "$@" -diff --git a/Apple/visionOS/Resources/bin/arm64-apple-xros-strip b/Apple/visionOS/Resources/bin/arm64-apple-xros-strip -new file mode 100644 -index 00000000000..2de3ba5d31c ---- /dev/null -+++ b/Apple/visionOS/Resources/bin/arm64-apple-xros-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk xros${XROS_SDK_VERSION} strip -arch arm64 "$@" -diff --git a/Apple/visionOS/Resources/pyconfig.h b/Apple/visionOS/Resources/pyconfig.h ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xctestplan b/Platforms/Apple/testbed/visionOSTestbed.xctestplan new file mode 100644 -index 00000000000..2a433078400 +index 00000000000..73a2be8c22a --- /dev/null -+++ b/Apple/visionOS/Resources/pyconfig.h -@@ -0,0 +1,3 @@ -+#ifdef __arm64__ -+#include "pyconfig-arm64.h" -+#endif -diff --git a/Apple/watchOS/README.rst b/Apple/watchOS/README.rst ++++ b/Platforms/Apple/testbed/visionOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "C17FA044-0B70-48CA-AFF8-BC252081002F", ++ "name" : "Test Scheme Action", ++ "options" : { ++ ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:visionOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "visionOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:visionOSTestbed.xcodeproj", ++ "identifier" : "607A662C2B0EFA3A0010BFC8", ++ "name" : "TestbedTests" ++ } ++ } ++ ], ++ "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..35221478452 +index 00000000000..8fe7ec8e64e --- /dev/null -+++ b/Apple/watchOS/README.rst -@@ -0,0 +1,108 @@ -+======================== -+Python on watchOS README -+======================== -+ -+:Authors: -+ Russell Keith-Magee (2023-11) ++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// visionOSTestbed ++// + -+This document provides a quick overview of some watchOS specific features in the -+Python distribution. ++#import + -+Compilers for building on watchOS -+================================= ++@interface AppDelegate : UIResponder + -+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 -+======================================= ++@end +diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..f46c65a3834 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m +@@ -0,0 +1,24 @@ ++// ++// AppDelegate.m ++// visionOSTestbed ++// + -+* ``--enable-framework[=DIR]`` ++#import "AppDelegate.h" ++#import "SceneDelegate.h" + -+ This argument specifies the location where the Python.framework will -+ be installed. ++@interface AppDelegate () + -+* ``--with-framework-name=NAME`` ++@end + -+ Specify the name for the python framework, defaults to ``Python``. ++@implementation AppDelegate + ++- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { ++ UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; ++ configuration.delegateClass = [SceneDelegate class]; ++ return configuration; ++} + -+Building and using Python on watchOS -+==================================== ++- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { ++} + -+ABIs and Architectures -+---------------------- ++@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/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +@@ -0,0 +1,11 @@ ++{ ++ "colors" : [ ++ { ++ "idiom" : "universal" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "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/Platforms/Apple/testbed/visionOSTestbed/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/visionOSTestbed/Assets.xcassets/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json +new file mode 100644 +index 00000000000..73c00596a7f +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json +@@ -0,0 +1,6 @@ ++{ ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h +new file mode 100644 +index 00000000000..264aacadca1 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.h +@@ -0,0 +1,12 @@ ++// ++// SceneDelegate.h ++// iOSTestbed ++// + -+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. ++#import + -+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.) ++@interface SceneDelegate : UIResponder + -+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. ++@property (strong, nonatomic) UIWindow *window; + -+How do I build Python for watchOS? -+------------------------------- ++@end +diff --git a/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m +new file mode 100644 +index 00000000000..e82e447985d +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/SceneDelegate.m +@@ -0,0 +1,21 @@ ++// ++// SceneDelegate.m ++// iOSTestbed ++// + -+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: ++#import "SceneDelegate.h" + -+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. ++@interface SceneDelegate () + -+watchOS builds of Python *must* be constructed as framework builds. To support this, -+you must provide the ``--enable-framework`` flag when configuring the build. ++@end + -+The build also requires the use of cross-compilation. The commands for building -+Python for watchOS will look somethign like:: ++@implementation SceneDelegate + -+ $ ./configure \ -+ --enable-framework=/path/to/install \ -+ --host=aarch64-apple-watchos \ -+ --build=aarch64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install ++- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { ++ UIWindowScene *windowScene = (UIWindowScene *)scene; ++ self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; ++ self.window.rootViewController = [[UIViewController alloc] init]; ++ [self.window makeKeyAndVisible]; ++} + -+In this invocation: ++@end +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/Platforms/Apple/testbed/visionOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++During the build, any binary modules found in this folder will be processed into ++iOS Framework form. + -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: ++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/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. + -+ - ``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. ++During the build, any binary modules found in this folder will be processed into ++iOS Framework form. + -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: ++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/Platforms/Apple/testbed/visionOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// visionOSTestbed ++// + -+ - ``aarch64-apple-darwin`` for Apple Silicon devices. -+ - ``x86_64-apple-darwin`` for Intel devices. ++#import ++#import "AppDelegate.h" + -+* ``/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. ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); + -+Using a framework-based Python on watchOS -+====================================== -diff --git a/Apple/watchOS/Resources/Info.plist.in b/Apple/watchOS/Resources/Info.plist.in ++ 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..e83ddfd2a43 +index 00000000000..11a8230d1d2 --- /dev/null -+++ b/Apple/watchOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ ++++ b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist +@@ -0,0 +1,66 @@ + -+ -+ ++ ++ + + CFBundleDevelopmentRegion + en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} + CFBundleExecutable -+ Python -+ CFBundleGetInfoString -+ Python Runtime and Library ++ ${EXECUTABLE_NAME} + CFBundleIdentifier -+ @PYTHONFRAMEWORKIDENTIFIER@ ++ org.python.visionOSTestbed + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName -+ Python ++ ${PRODUCT_NAME} + CFBundlePackageType -+ FMWK ++ APPL + CFBundleShortVersionString -+ %VERSION% -+ CFBundleLongVersionString -+ %VERSION%, (c) 2001-2023 Python Software Foundation. ++ 1.0 + CFBundleSignature + ???? + CFBundleVersion -+ %VERSION% -+ CFBundleSupportedPlatforms ++ 1 ++ TestArgs + -+ watchOS ++ test ++ -uall ++ --single-process ++ --rerun ++ -W ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ UIWindowSceneSessionRoleApplication ++ ++ ++ UISceneDelegateClassName ++ SceneDelegate ++ UISceneConfigurationName ++ Default Configuration ++ ++ ++ ++ ++ UIRequiresFullScreen ++ ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight + -+ MinimumOSVersion -+ @WATCHOS_DEPLOYMENT_TARGET@ + + -diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar -new file mode 100755 -index 00000000000..dda2b211bd5 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang -new file mode 100755 -index 00000000000..fe834d3efe4 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ -new file mode 100755 -index 00000000000..757f3a26d8f ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp -new file mode 100755 -index 00000000000..fdb57d9e010 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip -new file mode 100755 -index 00000000000..e28e3f7597a ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip b/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip -new file mode 100755 -index 00000000000..efe5a1260ad ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar -new file mode 100755 -index 00000000000..029f9a32073 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang -new file mode 100755 -index 00000000000..285036d4010 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ -new file mode 100755 -index 00000000000..c8f60ebec51 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" -diff --git a/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp -new file mode 100755 -index 00000000000..b411fc25aa4 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} -E "$@" -diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar -new file mode 100755 -index 00000000000..dda2b211bd5 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" -diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang -new file mode 100755 -index 00000000000..4776b9b5348 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ -new file mode 100755 -index 00000000000..e9b0c5f4b87 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" -diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp -new file mode 100755 -index 00000000000..d3b821c5f7f ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" -diff --git a/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip -new file mode 100755 -index 00000000000..105c78281f9 ---- /dev/null -+++ b/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip -@@ -0,0 +1,2 @@ -+#!/bin/sh -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch x86_64 "$@" -diff --git a/Apple/watchOS/Resources/pyconfig.h b/Apple/watchOS/Resources/pyconfig.h +diff --git a/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/project.pbxproj new file mode 100644 -index 00000000000..f842b987b2e +index 00000000000..a4c77addc35 --- /dev/null -+++ b/Apple/watchOS/Resources/pyconfig.h -@@ -0,0 +1,11 @@ -+#ifdef __arm64__ -+# ifdef __LP64__ -+#include "pyconfig-arm64.h" -+# else -+#include "pyconfig-arm64_32.h" -+# endif -+#endif ++++ b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,490 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 77; ++ objects = { ++ ++/* Begin PBXBuildFile section */ ++ 60F7C0012EB0000000A1B2C3 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60F7C0072EB0000000A1B2C3 /* Python.xcframework */; }; ++ 60F7C0022EB0000000A1B2C3 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60F7C0072EB0000000A1B2C3 /* Python.xcframework */; }; ++ 60F7C0032EB0000000A1B2C3 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 60F7C0072EB0000000A1B2C3 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++/* End PBXBuildFile section */ ++ ++/* Begin PBXContainerItemProxy section */ ++ 60F7C0042EB0000000A1B2C3 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = 60F7C0132EB0000000A1B2C3 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = 60F7C0112EB0000000A1B2C3; ++ remoteInfo = watchOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ + -+#ifdef __x86_64__ -+#include "pyconfig-x86_64.h" -+#endif -diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst -index 9921fd6114b..4ed18f4938c 100644 ---- a/Doc/using/ios.rst -+++ b/Doc/using/ios.rst -@@ -170,7 +170,7 @@ - To add Python to an iOS Xcode project: - - 1. Build or obtain a Python ``XCFramework``. See the instructions in -- :source:`iOS/README.rst` (in the CPython source distribution) for details on -+ :source:`Apple/iOS/README.md` (in the CPython source distribution) for details on - how to build a Python ``XCFramework``. At a minimum, you will need a build - that supports ``arm64-apple-ios``, plus one of either - ``arm64-apple-ios-simulator`` or ``x86_64-apple-ios-simulator``. -@@ -180,22 +180,19 @@ - of your project; however, you can use any other location that you want by - adjusting paths as needed. - --3. Drag the ``iOS/Resources/dylib-Info-template.plist`` file into your project, -- and ensure it is associated with the app target. -- --4. Add your application code as a folder in your Xcode project. In the -+3. Add your application code as a folder in your Xcode project. In the - following instructions, we'll assume that your user code is in a folder - named ``app`` in the root of your project; you can use any other location by - adjusting paths as needed. Ensure that this folder is associated with your - app target. - --5. Select the app target by selecting the root node of your Xcode project, then -+4. Select the app target by selecting the root node of your Xcode project, then - the target name in the sidebar that appears. - --6. In the "General" settings, under "Frameworks, Libraries and Embedded -+5. In the "General" settings, under "Frameworks, Libraries and Embedded - Content", add ``Python.xcframework``, with "Embed & Sign" selected. - --7. In the "Build Settings" tab, modify the following: -+6. In the "Build Settings" tab, modify the following: - - - Build Options - -@@ -211,86 +208,24 @@ - - * Quoted Include In Framework Header: No - --8. Add a build step that copies the Python standard library into your app. In -- the "Build Phases" tab, add a new "Run Script" build step *before* the -- "Embed Frameworks" step, but *after* the "Copy Bundle Resources" step. Name -- the step "Install Target Specific Python Standard Library", disable the -- "Based on dependency analysis" checkbox, and set the script content to: -+7. Add a build step that processes the Python standard library, and your own -+ Python binary dependencies. In the "Build Phases" tab, add a new "Run -+ Script" build step *before* the "Embed Frameworks" step, but *after* the -+ "Copy Bundle Resources" step. Name the step "Process Python libraries", -+ disable the "Based on dependency analysis" checkbox, and set the script -+ content to: - - .. code-block:: bash - -- set -e -- -- mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" -- if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then -- echo "Installing Python modules for iOS Simulator" -- rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" -- else -- echo "Installing Python modules for iOS Device" -- rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" -- fi -+ set -e -+ source $PROJECT_DIR/Python.xcframework/build/build_utils.sh -+ install_python Python.xcframework app - -- Note that the name of the simulator "slice" in the XCframework may be -- different, depending the CPU architectures your ``XCFramework`` supports. -+ If you have placed your XCframework somewhere other than the root of your -+ project, modify the path to the first argument. - --9. Add a second build step that processes the binary extension modules in the -- standard library into "Framework" format. Add a "Run Script" build step -- *directly after* the one you added in step 8, named "Prepare Python Binary -- Modules". It should also have "Based on dependency analysis" unchecked, with -- the following script content: -- -- .. code-block:: bash -- -- set -e -- -- install_dylib () { -- INSTALL_BASE=$1 -- FULL_EXT=$2 -- -- # The name of the extension file -- EXT=$(basename "$FULL_EXT") -- # The location of the extension file, relative to the bundle -- RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} -- # The path to the extension file, relative to the install base -- PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} -- # The full dotted name of the extension module, constructed from the file path. -- FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); -- # A bundle identifier; not actually used, but required by Xcode framework packaging -- FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") -- # The name of the framework folder. -- FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" -- -- # If the framework folder doesn't exist, create it. -- if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then -- echo "Creating framework for $RELATIVE_EXT" -- mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" -- cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" -- plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" -- plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" -- fi -- -- echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" -- mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" -- # Create a placeholder .fwork file where the .so was -- echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork -- # Create a back reference to the .so file location in the framework -- echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" -- } -- -- PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") -- echo "Install Python $PYTHON_VER standard library extension modules..." -- find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do -- install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" -- done -- -- # Clean up dylib template -- rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" -- -- echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." -- find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \; -- --10. Add Objective C code to initialize and use a Python interpreter in embedded -- mode. You should ensure that: -+8. Add Objective C code to initialize and use a Python interpreter in embedded -+ mode. You should ensure that: - - * UTF-8 mode (:c:member:`PyPreConfig.utf8_mode`) is *enabled*; - * Buffered stdio (:c:member:`PyConfig.buffered_stdio`) is *disabled*; -@@ -307,22 +242,19 @@ - Your app's bundle location can be determined using ``[[NSBundle mainBundle] - resourcePath]``. - --Steps 8, 9 and 10 of these instructions assume that you have a single folder of -+Steps 7 and 8 of these instructions assume that you have a single folder of - pure Python application code, named ``app``. If you have third-party binary - modules in your app, some additional steps will be required: - - * You need to ensure that any folders containing third-party binaries are -- either associated with the app target, or copied in as part of step 8. Step 8 -- should also purge any binaries that are not appropriate for the platform a -- specific build is targeting (i.e., delete any device binaries if you're -- building an app targeting the simulator). -- --* Any folders that contain third-party binaries must be processed into -- framework form by step 9. The invocation of ``install_dylib`` that processes -- the ``lib-dynload`` folder can be copied and adapted for this purpose. -+ either associated with the app target, or are explicitly copied as part of -+ step 7. Step 7 should also purge any binaries that are not appropriate for -+ the platform a specific build is targeting (i.e., delete any device binaries -+ if you're building an app targeting the simulator). - --* If you're using a separate folder for third-party packages, ensure that folder -- is included as part of the :envvar:`PYTHONPATH` configuration in step 10. -+* If you're using a separate folder for third-party packages, ensure that -+ folder is added to the end of the call to ``install_python`` in step 7, and -+ as part of the :envvar:`PYTHONPATH` configuration in step 8. - - * If any of the folders that contain third-party packages will contain ``.pth`` - files, you should add that folder as a *site directory* (using -@@ -332,25 +264,30 @@ - Testing a Python package - ------------------------ - --The CPython source tree contains :source:`a testbed project ` that -+The CPython source tree contains :source:`a testbed project ` that - is used to run the CPython test suite on the iOS simulator. This testbed can also - be used as a testbed project for running your Python library's test suite on iOS. - --After building or obtaining an iOS XCFramework (See :source:`iOS/README.rst` --for details), create a clone of the Python iOS testbed project by running: -+After building or obtaining an iOS XCFramework (see :source:`Apple/iOS/README.md` -+for details), create a clone of the Python iOS testbed project. If you used the -+``Apple`` build script to build the XCframework, you can run: ++/* Begin PBXCopyFilesBuildPhase section */ ++ 60F7C0052EB0000000A1B2C3 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ 60F7C0032EB0000000A1B2C3 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ ++ ++/* Begin PBXFileReference section */ ++ 60F7C0062EB0000000A1B2C3 /* watchOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = watchOSTestbed.xctestplan; sourceTree = ""; }; ++ 60F7C0072EB0000000A1B2C3 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ 60F7C0082EB0000000A1B2C3 /* watchOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = watchOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 60F7C0092EB0000000A1B2C3 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++/* End PBXFileReference section */ ++ ++/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ 60F7C00A2EB0000000A1B2C3 /* Exceptions for "watchOSTestbed" folder in "watchOSTestbed" target */ = { ++ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; ++ membershipExceptions = ( ++ "watchOSTestbed-Info.plist", ++ ); ++ target = 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */; ++ }; ++/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ ++/* Begin PBXFileSystemSynchronizedRootGroup section */ ++ 60F7C00B2EB0000000A1B2C3 /* watchOSTestbed */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ exceptions = ( ++ 60F7C00A2EB0000000A1B2C3 /* Exceptions for "watchOSTestbed" folder in "watchOSTestbed" target */, ++ ); ++ explicitFolders = ( ++ app, ++ app_packages, ++ ); ++ path = watchOSTestbed; ++ sourceTree = ""; ++ }; ++ 60F7C00C2EB0000000A1B2C3 /* TestbedTests */ = { ++ isa = PBXFileSystemSynchronizedRootGroup; ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++/* End PBXFileSystemSynchronizedRootGroup section */ ++ ++/* Begin PBXFrameworksBuildPhase section */ ++ 60F7C00D2EB0000000A1B2C3 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 60F7C0022EB0000000A1B2C3 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 60F7C00E2EB0000000A1B2C3 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 60F7C0012EB0000000A1B2C3 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ ++ ++/* Begin PBXGroup section */ ++ 60F7C00F2EB0000000A1B2C3 = { ++ isa = PBXGroup; ++ children = ( ++ 60F7C0062EB0000000A1B2C3 /* watchOSTestbed.xctestplan */, ++ 60F7C0072EB0000000A1B2C3 /* Python.xcframework */, ++ 60F7C00B2EB0000000A1B2C3 /* watchOSTestbed */, ++ 60F7C00C2EB0000000A1B2C3 /* TestbedTests */, ++ 60F7C0102EB0000000A1B2C3 /* Products */, ++ ); ++ sourceTree = ""; ++ }; ++ 60F7C0102EB0000000A1B2C3 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ 60F7C0082EB0000000A1B2C3 /* watchOSTestbed.app */, ++ 60F7C0092EB0000000A1B2C3 /* TestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ ++ ++/* Begin PBXNativeTarget section */ ++ 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 60F7C0212EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "watchOSTestbed" */; ++ buildPhases = ( ++ 60F7C0172EB0000000A1B2C3 /* Sources */, ++ 60F7C00D2EB0000000A1B2C3 /* Frameworks */, ++ 60F7C0142EB0000000A1B2C3 /* Resources */, ++ 60F7C0162EB0000000A1B2C3 /* Process Python libraries */, ++ 60F7C0052EB0000000A1B2C3 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ fileSystemSynchronizedGroups = ( ++ 60F7C00B2EB0000000A1B2C3 /* watchOSTestbed */, ++ ); ++ name = watchOSTestbed; ++ packageProductDependencies = ( ++ ); ++ productName = watchOSTestbed; ++ productReference = 60F7C0082EB0000000A1B2C3 /* watchOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ 60F7C0122EB0000000A1B2C3 /* TestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 60F7C0222EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildPhases = ( ++ 60F7C0182EB0000000A1B2C3 /* Sources */, ++ 60F7C00E2EB0000000A1B2C3 /* Frameworks */, ++ 60F7C0152EB0000000A1B2C3 /* Resources */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ 60F7C0192EB0000000A1B2C3 /* PBXTargetDependency */, ++ ); ++ fileSystemSynchronizedGroups = ( ++ 60F7C00C2EB0000000A1B2C3 /* TestbedTests */, ++ ); ++ name = TestbedTests; ++ packageProductDependencies = ( ++ ); ++ productName = TestbedTests; ++ productReference = 60F7C0092EB0000000A1B2C3 /* TestbedTests.xctest */; ++ productType = "com.apple.product-type.bundle.unit-test"; ++ }; ++/* End PBXNativeTarget section */ + -+.. code-block:: bash ++/* Begin PBXProject section */ ++ 60F7C0132EB0000000A1B2C3 /* Project object */ = { ++ isa = PBXProject; ++ attributes = { ++ BuildIndependentTargetsInParallel = 1; ++ LastUpgradeCheck = 1620; ++ TargetAttributes = { ++ 60F7C0112EB0000000A1B2C3 = { ++ CreatedOnToolsVersion = 16.2; ++ }; ++ 60F7C0122EB0000000A1B2C3 = { ++ CreatedOnToolsVersion = 16.2; ++ TestTargetID = 60F7C0112EB0000000A1B2C3; ++ }; ++ }; ++ }; ++ buildConfigurationList = 60F7C0202EB0000000A1B2C3 /* Build configuration list for PBXProject "watchOSTestbed" */; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = 60F7C00F2EB0000000A1B2C3; ++ minimizedProjectReferenceProxies = 1; ++ preferredProjectObjectVersion = 77; ++ productRefGroup = 60F7C0102EB0000000A1B2C3 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */, ++ 60F7C0122EB0000000A1B2C3 /* TestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ + -+ $ python cross-build/iOS/testbed clone --app --app app-testbed ++/* Begin PBXResourcesBuildPhase section */ ++ 60F7C0142EB0000000A1B2C3 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 60F7C0152EB0000000A1B2C3 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ + -+Or, if you've sourced your own XCframework, by running: - - .. code-block:: bash - -- $ python iOS/testbed clone --framework --app --app app-testbed -+ $ python Apple/testbed clone --platform iOS --framework --app --app app-testbed - --You will need to modify the ``iOS/testbed`` reference to point to that --directory in the CPython source tree; any folders specified with the ``--app`` --flag will be copied into the cloned testbed project. The resulting testbed will --be created in the ``app-testbed`` folder. In this example, the ``module1`` and --``module2`` would be importable modules at runtime. If your project has --additional dependencies, they can be installed into the --``app-testbed/iOSTestbed/app_packages`` folder (using ``pip install --target --app-testbed/iOSTestbed/app_packages`` or similar). -+Any folders specified with the ``--app`` flag will be copied into the cloned -+testbed project. The resulting testbed will be created in the ``app-testbed`` -+folder. In this example, the ``module1`` and ``module2`` would be importable -+modules at runtime. If your project has additional dependencies, they can be -+installed into the ``app-testbed/Testbed/app_packages`` folder (using ``pip -+install --target app-testbed/Testbed/app_packages`` or similar). - - You can then use the ``app-testbed`` folder to run the test suite for your app, - For example, if ``module1.tests`` was the entry point to your test suite, you -@@ -379,7 +316,7 @@ - arguments. - - The test plan also disables parallel testing, and specifies the use of the --``iOSTestbed.lldbinit`` file for providing configuration of the debugger. 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. - -@@ -389,7 +326,12 @@ - The only mechanism for distributing apps to third-party iOS devices is to - submit the app to the iOS App Store; apps submitted for distribution must pass - Apple's app review process. This process includes a set of automated validation --rules that inspect the submitted application bundle for problematic code. -+rules that inspect the submitted application bundle for problematic code. There -+are some steps that must be taken to ensure that your app will be able to pass -+these validation steps. ++/* Begin PBXShellScriptBuildPhase section */ ++ 60F7C0162EB0000000A1B2C3 /* Process Python libraries */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Process Python libraries"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ showEnvVarsInLog = 0; ++ }; ++/* End PBXShellScriptBuildPhase section */ + -+Incompatible code in the standard library -+----------------------------------------- - - The Python standard library contains some code that is known to violate these - automated rules. While these violations appear to be false positives, Apple's -@@ -400,3 +342,18 @@ - :source:`a patch file ` that will remove - all code that is known to cause issues with the App Store review process. This - patch is applied automatically when building for iOS. ++/* Begin PBXSourcesBuildPhase section */ ++ 60F7C0172EB0000000A1B2C3 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 60F7C0182EB0000000A1B2C3 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ + -+Privacy manifests -+----------------- ++/* Begin PBXTargetDependency section */ ++ 60F7C0192EB0000000A1B2C3 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = 60F7C0112EB0000000A1B2C3 /* watchOSTestbed */; ++ targetProxy = 60F7C0042EB0000000A1B2C3 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ + -+In April 2025, Apple introduced a requirement for `certain third-party -+libraries to provide a Privacy Manifest -+`__. -+As a result, if you have a binary module that uses one of the affected -+libraries, you must provide an ``.xcprivacy`` file for that library. -+OpenSSL is one library affected by this requirement, but there are others. ++/* Begin XCBuildConfiguration section */ ++ 60F7C01A2EB0000000A1B2C3 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = watchos; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; ++ }; ++ name = Debug; ++ }; ++ 60F7C01B2EB0000000A1B2C3 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)"; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = watchos; ++ VALIDATE_PRODUCT = YES; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; ++ }; ++ name = Release; ++ }; ++ 60F7C01C2EB0000000A1B2C3 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "watchOSTestbed/watchOSTestbed-Info.plist"; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.watchOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ TARGETED_DEVICE_FAMILY = 4; ++ }; ++ name = Debug; ++ }; ++ 60F7C01D2EB0000000A1B2C3 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = NO; ++ INFOPLIST_FILE = "watchOSTestbed/watchOSTestbed-Info.plist"; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.watchOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ TARGETED_DEVICE_FAMILY = 4; ++ }; ++ name = Release; ++ }; ++ 60F7C01E2EB0000000A1B2C3 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ TARGETED_DEVICE_FAMILY = 4; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/watchOSTestbed"; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; ++ }; ++ name = Debug; ++ }; ++ 60F7C01F2EB0000000A1B2C3 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ GENERATE_INFOPLIST_FILE = YES; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ TARGETED_DEVICE_FAMILY = 4; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/watchOSTestbed"; ++ WATCHOS_DEPLOYMENT_TARGET = 9.0; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ + -+If you produce a binary module named ``mymodule.so``, and use you the Xcode -+build script described in step 7 above, you can place a ``mymodule.xcprivacy`` -+file next to ``mymodule.so``, and the privacy manifest will be installed into -+the required location when the binary module is converted into a framework. -diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py -index c430f9faa47..e733003e965 100644 ---- a/Lib/ctypes/__init__.py -+++ b/Lib/ctypes/__init__.py -@@ -380,9 +380,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 43f10dedce8..7cbc6039fa2 100644 ---- a/Lib/ctypes/util.py -+++ b/Lib/ctypes/util.py -@@ -67,7 +67,7 @@ - return fname - return None - --elif os.name == "posix" and sys.platform 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, -diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py -index fcfe312747c..e02a897ea08 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) - -@@ -1806,7 +1806,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 8895177e326..9e1e0628671 100755 ---- a/Lib/platform.py -+++ b/Lib/platform.py -@@ -522,6 +522,78 @@ - return IOSVersionInfo(system, release, model, is_simulator) - - -+# A namedtuple for tvOS version information. -+TVOSVersionInfo = collections.namedtuple( -+ "TVOSVersionInfo", -+ ["system", "release", "model", "is_simulator"] -+) ++/* Begin XCConfigurationList section */ ++ 60F7C0202EB0000000A1B2C3 /* Build configuration list for PBXProject "watchOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 60F7C01A2EB0000000A1B2C3 /* Debug */, ++ 60F7C01B2EB0000000A1B2C3 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 60F7C0212EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "watchOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 60F7C01C2EB0000000A1B2C3 /* Debug */, ++ 60F7C01D2EB0000000A1B2C3 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 60F7C0222EB0000000A1B2C3 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 60F7C01E2EB0000000A1B2C3 /* Debug */, ++ 60F7C01F2EB0000000A1B2C3 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = 60F7C0132EB0000000A1B2C3 /* Project object */; ++} +diff --git a/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/xcshareddata/xcschemes/watchOSTestbed.xcscheme b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/xcshareddata/xcschemes/watchOSTestbed.xcscheme +new file mode 100644 +index 00000000000..96689416946 +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed.xcodeproj/xcshareddata/xcschemes/watchOSTestbed.xcscheme +@@ -0,0 +1,97 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/watchOSTestbed.xctestplan b/Platforms/Apple/testbed/watchOSTestbed.xctestplan +new file mode 100644 +index 00000000000..f83ff3a5595 +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "8E3B1C52-4F0A-4D7E-9B61-2C5A7E0D9F14", ++ "name" : "Test Scheme Action", ++ "options" : { + ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:watchOSTestbed.xcodeproj", ++ "identifier" : "60F7C0112EB0000000A1B2C3", ++ "name" : "watchOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:watchOSTestbed.xcodeproj", ++ "identifier" : "60F7C0122EB0000000A1B2C3", ++ "name" : "TestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +diff --git a/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.h +new file mode 100644 +index 00000000000..cc7359048ae +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// watchOSTestbed ++// + -+def tvos_ver(system="", release="", model="", is_simulator=False): -+ """Get tvOS version information, and return it as a namedtuple: -+ (system, release, model, is_simulator). ++#import + -+ 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) ++@interface AppDelegate : NSObject + -+ return TVOSVersionInfo(system, release, model, is_simulator) + ++@end +diff --git a/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..3390d2efc87 +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed/AppDelegate.m +@@ -0,0 +1,21 @@ ++// ++// AppDelegate.m ++// watchOSTestbed ++// + -+# A namedtuple for watchOS version information. -+WatchOSVersionInfo = collections.namedtuple( -+ "WatchOSVersionInfo", -+ ["system", "release", "model", "is_simulator"] -+) ++#import "AppDelegate.h" + ++@interface AppDelegate () + -+def watchos_ver(system="", release="", model="", is_simulator=False): -+ """Get watchOS version information, and return it as a namedtuple: -+ (system, release, model, is_simulator). ++@end + -+ 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) ++@implementation AppDelegate + -+ return WatchOSVersionInfo(system, release, model, is_simulator) ++// The testbed has no storyboard (Interface.plist), so WatchKit requires the ++// delegate to provide a root interface controller class, or the app crashes ++// on launch. ++- (Class)applicationRootInterfaceControllerClass { ++ return [WKInterfaceController class]; ++} + ++@end +diff --git a/Platforms/Apple/testbed/watchOSTestbed/app/README b/Platforms/Apple/testbed/watchOSTestbed/app/README +new file mode 100644 +index 00000000000..46c0e8e2a29 +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. + -+# A namedtuple for visionOS version information. -+VisionOSVersionInfo = collections.namedtuple( -+ "VisionOSVersionInfo", -+ ["system", "release", "model", "is_simulator"] -+) ++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/watchOSTestbed/app_packages/README b/Platforms/Apple/testbed/watchOSTestbed/app_packages/README +new file mode 100644 +index 00000000000..02c2beccfbd +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed/app_packages/README +@@ -0,0 +1,7 @@ ++This folder can be a target for installing any Python dependencies needed by the ++test suite. + -+def visionos_ver(system="", release="", model="", is_simulator=False): -+ """Get visionOS version information, and return it as a namedtuple: -+ (system, release, model, is_simulator). ++During the build, any binary modules found in this folder will be processed into ++Framework form. + -+ 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) ++When the test suite runs, this folder will be on the PYTHONPATH. +diff --git a/Platforms/Apple/testbed/watchOSTestbed/main.m b/Platforms/Apple/testbed/watchOSTestbed/main.m +new file mode 100644 +index 00000000000..b0e43f1a06f +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// watchOSTestbed ++// + -+ return VisionOSVersionInfo(system, release, model, is_simulator) ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return WKApplicationMain(argc, argv, appDelegateClassName); ++ } ++} +diff --git a/Platforms/Apple/testbed/watchOSTestbed/watchOSTestbed-Info.plist b/Platforms/Apple/testbed/watchOSTestbed/watchOSTestbed-Info.plist +new file mode 100644 +index 00000000000..4f25642a8e2 +--- /dev/null ++++ b/Platforms/Apple/testbed/watchOSTestbed/watchOSTestbed-Info.plist +@@ -0,0 +1,30 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.watchOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ WKApplication ++ ++ WKWatchOnly ++ ++ ++ +diff --git a/Platforms/Apple/tvOS/README.md b/Platforms/Apple/tvOS/README.md +new file mode 100644 +index 00000000000..24e1cb239ba +--- /dev/null ++++ b/Platforms/Apple/tvOS/README.md +@@ -0,0 +1,28 @@ ++# Python on tvOS README ++ ++This document provides a quick overview of some tvOS specific features in the ++Python distribution. ++ ++Python on tvOS is largely the same as Python on iOS; most of the instructions in ++the [iOS README](../iOS/README.md) can be used for tvOS builds, substituting: ++ ++- `tvOS` as the identifier for `Platforms/Apple` invocations; e.g., ++ - `python Platforms/Apple build tvOS` ++ - `python Platforms/Apple ci tvOS` ++ - `python Platforms/Apple test tvOS` ++ ++- tvOS resources are stored in the `Platforms/Apple/tvOS/Resources` directory. ++ ++- The tvOS SDK version is controlled with the `TVOS_SDK_VERSION` environment ++ variable. ++ ++- The minimum supported tvOS SDK version is controlled with the ++ `TVOS_DEPLOYMENT_TARGET` environment variable. ++ ++- When referencing platforms, the tvOS compiler triples are: ++ - `arm64-apple-tvos` for AppleTV devices ++ - `arm64-apple-tvos-simulator` for AppleTV simulators on ARM64 hardware ++ - `x86_64-apple-tvos-simulator` for AppleTV simulators on x86-64 hardware ++ ++- The tvOS testbed Xcode template project is stored in ++ `Platforms/Apple/testbed/tvOSTestbed`. +diff --git a/Platforms/Apple/tvOS/Resources/Info.plist.in b/Platforms/Apple/tvOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..b913504d69e +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ @PYTHONFRAMEWORK@ ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ @PYTHONFRAMEWORK@ ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ @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/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/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/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/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/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/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/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/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/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/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/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +diff --git a/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 + ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +diff --git a/Platforms/Apple/visionOS/README.md b/Platforms/Apple/visionOS/README.md +new file mode 100644 +index 00000000000..f6bdb2608e2 +--- /dev/null ++++ b/Platforms/Apple/visionOS/README.md +@@ -0,0 +1,28 @@ ++# Python on visionOS README + - 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 -@@ -721,7 +793,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 - -@@ -885,14 +957,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' ++This document provides a quick overview of some visionOS specific features in the ++Python distribution. + -+ def get_visionos(): -+ if sys.implementation._multiarch.endswith("simulator"): -+ return os.uname().machine -+ return 'arm64' ++Python on visionOS is largely the same as Python on iOS; most of the instructions in ++the [iOS README](../iOS/README.md) can be used for visionOS builds, substituting: + -+ def get_watchos(): -+ if sys.implementation._multiarch.endswith("simulator"): -+ return os.uname().machine -+ return 'arm64_32' ++- `visionOS` as the identifier for `Platforms/Apple` invocations; e.g., ++ - `python Platforms/Apple build visionOS` ++ - `python Platforms/Apple ci visionOS` ++ - `python Platforms/Apple test visionOS` + - def from_subprocess(): - """ - Fall back to `uname -p` -@@ -1052,9 +1140,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 '' -@@ -1344,6 +1438,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 041dca113a5..3dedc828524 100644 ---- a/Lib/site.py -+++ b/Lib/site.py -@@ -292,8 +292,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 e845f48a407..b64a0553973 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 43edebce347..dd6acc0fd9f 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 = { -@@ -117,7 +120,7 @@ - return env_base - - # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories -- if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}: -+ if sys.platform in {"emscripten", "ios", "tvos", "visionos", "vxworks", "wasi", "watchos"}: - return None - - def joinuser(*args): -@@ -688,6 +691,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 ca60e204cff..ab7aeba9e7d 100644 ---- a/Lib/test/datetimetester.py -+++ b/Lib/test/datetimetester.py -@@ -6897,9 +6897,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 abc525b80e0..1621ff9eecd 100644 ---- a/Lib/test/support/__init__.py -+++ b/Lib/test/support/__init__.py -@@ -66,6 +66,7 @@ - "force_not_colorized_test_class", - "make_clean_env", - "BrokenIter", -+ "reset_code", "on_github_actions" - ] - - -@@ -594,7 +595,7 @@ - - is_android = sys.platform == "android" - --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 +605,7 @@ - is_emscripten = sys.platform == "emscripten" - is_wasi = sys.platform == "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 ( -@@ -1348,6 +1349,8 @@ - _opcode.ENABLE_SPECIALIZATION, "requires specialization")(test) - - -+on_github_actions = "GITHUB_ACTIONS" in os.environ ++- visionOS resources are stored in the `Platforms/Apple/visionOS/Resources` ++ directory. + - #======================================================================= - # Check for the presence of docstrings. - -diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py -index 6fb2bc24fa3..88cb6691cd8 100644 ---- a/Lib/test/test__interpreters.py -+++ b/Lib/test/test__interpreters.py -@@ -695,6 +695,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_platform.py b/Lib/test/test_platform.py -index ed277276b51..c846a9bc5d7 100644 ---- a/Lib/test/test_platform.py -+++ b/Lib/test/test_platform.py -@@ -236,13 +236,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_socketserver.py b/Lib/test/test_socketserver.py -index 0f62f9eb200..2ca356606b2 100644 ---- a/Lib/test/test_socketserver.py -+++ b/Lib/test/test_socketserver.py -@@ -218,12 +218,16 @@ - self.dgram_examine) - - @requires_unix_sockets -+ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, -+ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") - def test_UnixDatagramServer(self): - self.run_server(socketserver.UnixDatagramServer, - socketserver.DatagramRequestHandler, - self.dgram_examine) - - @requires_unix_sockets -+ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, -+ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") - def test_ThreadingUnixDatagramServer(self): - self.run_server(socketserver.ThreadingUnixDatagramServer, - socketserver.DatagramRequestHandler, -diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py -index 135f116d46b..c756e8e0ba7 100644 ---- a/Lib/test/test_webbrowser.py -+++ b/Lib/test/test_webbrowser.py -@@ -252,7 +252,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 be033d707a9..8e47e6576fe 100755 ---- a/Lib/webbrowser.py -+++ b/Lib/webbrowser.py -@@ -500,7 +500,8 @@ - # OS X can use below Unix support (but we prefer using the OS X - # 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": -@@ -629,9 +630,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 8fdbbd83b4b..9938ec463c8 100644 ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -202,6 +202,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 ++- The visionOS SDK version is controlled with the `XROS_SDK_VERSION` environment ++ variable. ++ ++- The minimum supported visionOS SDK version is controlled with the ++ `XROS_DEPLOYMENT_TARGET` environment variable. ++ ++- When referencing platforms, the visionOS compiler triples are: ++ - `arm64-apple-visionOS` for Apple Vision devices ++ - `arm64-apple-visionOS-simulator` for Apple Vision simulators ++ ++- The visionOS testbed Xcode template project is stored in ++ `Platforms/Apple/testbed/visionOSTestbed`. +diff --git a/Platforms/Apple/visionOS/Resources/Info.plist.in b/Platforms/Apple/visionOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..c3dd63ced8d +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ @PYTHONFRAMEWORK@ ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ @PYTHONFRAMEWORK@ ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2023 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ %VERSION% ++ CFBundleSupportedPlatforms ++ ++ XROS ++ ++ MinimumOSVersion ++ @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/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++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/Platforms/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} "$@" +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/Platforms/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} "$@" +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/Platforms/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 "$@" +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/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++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/Platforms/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 "$@" +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/Platforms/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 "$@" +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/Platforms/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 "$@" +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/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip +@@ -0,0 +1,2 @@ ++#!/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/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++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/Platforms/Apple/visionOS/Resources/pyconfig.h +@@ -0,0 +1,3 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif +diff --git a/Platforms/Apple/watchOS/README.md b/Platforms/Apple/watchOS/README.md +new file mode 100644 +index 00000000000..44f23e327cb +--- /dev/null ++++ b/Platforms/Apple/watchOS/README.md +@@ -0,0 +1,29 @@ ++# Python on watchOS README + ++This document provides a quick overview of some watchOS specific features in the ++Python distribution. ++ ++Python on watchOS is largely the same as Python on iOS; most of the instructions in ++the [iOS README](../iOS/README.md) can be used for watchOS builds, substituting: ++ ++- `watchOS` as the identifier for `Platforms/Apple` invocations; e.g., ++ - `python Platforms/Apple build watchOS` ++ - `python Platforms/Apple ci watchOS` ++ - `python Platforms/Apple test watchOS` ++ ++- watchOS resources are stored in the `Platforms/Apple/watchOS/Resources` ++ directory ++ ++- The watchOS SDK version is controlled with the `WATCHOS_SDK_VERSION` environment ++ variable. ++ ++- The minimum supported watchOS SDK version is controlled with the ++ `WATCHOS_DEPLOYMENT_TARGET` environment variable. ++ ++- When referencing platforms, the watchOS compiler triples are: ++ - `arm64_32-apple-watchos` for Apple Watch devices ++ - `arm64-apple-watchos-simulator` for Apple Watch simulators on ARM64 hardware ++ - `x86_64-apple-watchos-simulator` for Apple Watch simulators on x86_64 hardware ++ ++- The watchOS testbed Xcode template project is stored in ++ `Platforms/Apple/testbed/watchOSTestbed`. +diff --git a/Platforms/Apple/watchOS/Resources/Info.plist.in b/Platforms/Apple/watchOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..b2f64710d70 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ @PYTHONFRAMEWORK@ ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ @PYTHONFRAMEWORK@ ++ 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 + - # Option to install to strip binaries - STRIPFLAG=-s - -@@ -2094,7 +2100,7 @@ - fi - - # Clone the testbed project into the XCFOLDER -- $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" -+ $(PYTHON_FOR_BUILD) $(srcdir)/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" - - # Run the testbed project - $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W -@@ -2798,6 +2804,9 @@ - $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) - sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist - $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) -+ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(LIBDIR) -+ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(LDVERSION).dylib" -+ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(VERSION).dylib" - $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) - for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ - $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ -@@ -2982,10 +2991,10 @@ - -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' - -rm -f Include/pydtrace_probes.h - -rm -f profile-gen-stamp -- -rm -rf iOS/testbed/Python.xcframework/ios-*/bin -- -rm -rf iOS/testbed/Python.xcframework/ios-*/lib -- -rm -rf iOS/testbed/Python.xcframework/ios-*/include -- -rm -rf iOS/testbed/Python.xcframework/ios-*/Python.framework -+ -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 - - .PHONY: profile-removal - profile-removal: -@@ -3019,7 +3028,7 @@ - config.cache config.log pyconfig.h Modules/config.c - -rm -rf build platform - -rm -rf $(PYTHONFRAMEWORKDIR) -- -rm -rf iOS/Frameworks -+ -rm -rf 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 ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif diff --git a/config.sub b/config.sub index 1bb6a05dc11..49febd56a37 100755 --- a/config.sub @@ -7243,7 +8790,7 @@ index 1bb6a05dc11..49febd56a37 100755 none--*) # None (no kernel, i.e. freestanding / bare metal), diff --git a/configure b/configure -index 73d317a659b..7c01263e25d 100755 +index 73d317a659b..2418e9be287 100755 --- a/configure +++ b/configure @@ -979,6 +979,10 @@ @@ -7257,7 +8804,7 @@ index 73d317a659b..7c01263e25d 100755 IPHONEOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET -@@ -4058,6 +4062,15 @@ +@@ -4058,6 +4062,18 @@ *-apple-ios*) ac_sys_system=iOS ;; @@ -7269,11 +8816,14 @@ index 73d317a659b..7c01263e25d 100755 + ;; + *-apple-watchos*) + ac_sys_system=watchOS ++ ;; ++ *-*-darwin*) ++ ac_sys_system=Darwin + ;; *-*-vxworks*) ac_sys_system=VxWorks ;; -@@ -4112,7 +4125,7 @@ +@@ -4112,7 +4128,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. # @@ -7282,7 +8832,7 @@ index 73d317a659b..7c01263e25d 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 -@@ -4127,6 +4140,17 @@ +@@ -4127,6 +4143,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 ;; @@ -7300,7 +8850,7 @@ index 73d317a659b..7c01263e25d 100755 *) esac fi -@@ -4135,6 +4159,17 @@ +@@ -4135,6 +4162,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 ;; @@ -7318,7 +8868,7 @@ index 73d317a659b..7c01263e25d 100755 *) esac fi -@@ -4143,6 +4178,17 @@ +@@ -4143,6 +4181,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 ;; @@ -7336,7 +8886,7 @@ index 73d317a659b..7c01263e25d 100755 *) esac fi -@@ -4151,6 +4197,17 @@ +@@ -4151,6 +4200,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++ ;; @@ -7354,21 +8904,21 @@ index 73d317a659b..7c01263e25d 100755 *) esac fi -@@ -4271,8 +4328,11 @@ +@@ -4271,8 +4331,11 @@ case $enableval in yes) case $ac_sys_system in - Darwin) enableval=/Library/Frameworks ;; - iOS) enableval=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 -@@ -4281,6 +4341,9 @@ +@@ -4281,6 +4344,9 @@ no) case $ac_sys_system in iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; @@ -7378,14 +8928,15 @@ index 73d317a659b..7c01263e25d 100755 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -4383,9 +4446,54 @@ +@@ -4383,9 +4449,54 @@ prefix=$PYTHONFRAMEWORKPREFIX PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" - RESSRCDIR=iOS/Resources -+ RESSRCDIR=Apple/iOS/Resources -+ -+ ac_config_files="$ac_config_files Apple/iOS/Resources/Info.plist" ++ RESSRCDIR=Platforms/Apple/iOS/Resources + +- ac_config_files="$ac_config_files iOS/Resources/Info.plist" ++ ac_config_files="$ac_config_files Platforms/Apple/iOS/Resources/Info.plist" + + ;; + tvOS) : @@ -7398,9 +8949,9 @@ index 73d317a659b..7c01263e25d 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) : @@ -7413,9 +8964,9 @@ index 73d317a659b..7c01263e25d 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) : @@ -7428,14 +8979,13 @@ index 73d317a659b..7c01263e25d 100755 + + prefix=$PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=Apple/watchOS/Resources - -- ac_config_files="$ac_config_files iOS/Resources/Info.plist" -+ ac_config_files="$ac_config_files Apple/watchOS/Resources/Info.plist" ++ RESSRCDIR=Platforms/Apple/watchOS/Resources ++ ++ ac_config_files="$ac_config_files Platforms/Apple/watchOS/Resources/Info.plist" ;; *) -@@ -4398,6 +4506,9 @@ +@@ -4398,6 +4509,9 @@ case $ac_sys_system in iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; @@ -7445,7 +8995,7 @@ index 73d317a659b..7c01263e25d 100755 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -4451,8 +4562,8 @@ +@@ -4451,8 +4565,8 @@ case "$withval" in yes) case $ac_sys_system in @@ -7456,7 +9006,7 @@ index 73d317a659b..7c01263e25d 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 ;; -@@ -4470,8 +4581,8 @@ +@@ -4470,8 +4584,8 @@ else $as_nop case $ac_sys_system in @@ -7467,7 +9017,7 @@ index 73d317a659b..7c01263e25d 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; } -@@ -4488,6 +4599,8 @@ +@@ -4488,6 +4602,8 @@ @@ -7476,7 +9026,7 @@ index 73d317a659b..7c01263e25d 100755 if test "$cross_compiling" = yes; then case "$host" in -@@ -4525,6 +4638,78 @@ +@@ -4525,6 +4641,78 @@ ;; esac ;; @@ -7539,7 +9089,7 @@ index 73d317a659b..7c01263e25d 100755 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking watchOS deployment target" >&5 +printf %s "checking watchOS deployment target... " >&6; } + WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} -+ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=9.0} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5 +printf "%s\n" "$WATCHOS_DEPLOYMENT_TARGET" >&6; } + @@ -7555,7 +9105,7 @@ index 73d317a659b..7c01263e25d 100755 *-*-vxworks*) _host_ident=$host_cpu ;; -@@ -4603,9 +4788,15 @@ +@@ -4603,9 +4791,15 @@ define_xopen_source=no;; Darwin/[12][0-9].*) define_xopen_source=no;; @@ -7572,7 +9122,7 @@ index 73d317a659b..7c01263e25d 100755 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -4668,7 +4859,14 @@ +@@ -4668,7 +4862,14 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' @@ -7588,7 +9138,7 @@ index 73d317a659b..7c01263e25d 100755 # checks for alternative programs -@@ -4714,6 +4912,26 @@ +@@ -4714,6 +4915,26 @@ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" ;; #( @@ -7615,7 +9165,7 @@ index 73d317a659b..7c01263e25d 100755 *) : ;; esac -@@ -7015,6 +7233,12 @@ +@@ -7015,6 +7236,12 @@ MULTIARCH="" ;; #( iOS) : MULTIARCH="" ;; #( @@ -7628,7 +9178,7 @@ index 73d317a659b..7c01263e25d 100755 FreeBSD*) : MULTIARCH="" ;; #( *) : -@@ -7035,7 +7259,7 @@ +@@ -7035,7 +7262,7 @@ printf "%s\n" "$MULTIARCH" >&6; } case $ac_sys_system in #( @@ -7637,7 +9187,7 @@ index 73d317a659b..7c01263e25d 100755 SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( *) : SOABI_PLATFORM=$PLATFORM_TRIPLET -@@ -7086,6 +7310,18 @@ +@@ -7086,6 +7313,18 @@ PY_SUPPORT_TIER=3 ;; #( aarch64-apple-ios*/clang) : PY_SUPPORT_TIER=3 ;; #( @@ -7656,7 +9206,7 @@ index 73d317a659b..7c01263e25d 100755 aarch64-*-linux-android/clang) : PY_SUPPORT_TIER=3 ;; #( x86_64-*-linux-android/clang) : -@@ -7559,7 +7795,7 @@ +@@ -7559,7 +7798,7 @@ case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; @@ -7665,7 +9215,7 @@ index 73d317a659b..7c01263e25d 100755 LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; -@@ -7625,7 +7861,7 @@ +@@ -7625,7 +7864,7 @@ BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; @@ -7674,7 +9224,7 @@ index 73d317a659b..7c01263e25d 100755 LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) -@@ -13074,7 +13310,7 @@ +@@ -13074,7 +13313,7 @@ BLDSHARED="$LDSHARED" fi ;; @@ -7683,7 +9233,7 @@ index 73d317a659b..7c01263e25d 100755 LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" -@@ -13207,7 +13443,7 @@ +@@ -13207,7 +13446,7 @@ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys @@ -7692,7 +9242,7 @@ index 73d317a659b..7c01263e25d 100755 LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -13231,7 +13467,7 @@ +@@ -13231,7 +13470,7 @@ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' fi LINKFORSHARED="$LINKFORSHARED" @@ -7701,7 +9251,7 @@ index 73d317a659b..7c01263e25d 100755 LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi ;; -@@ -14828,7 +15064,7 @@ +@@ -14828,7 +15067,7 @@ ctypes_malloc_closure=yes ;; #( @@ -7710,7 +9260,7 @@ index 73d317a659b..7c01263e25d 100755 ctypes_malloc_closure=yes ;; #( -@@ -18291,12 +18527,6 @@ +@@ -18291,12 +18530,6 @@ then : printf "%s\n" "#define HAVE_DUP 1" >>confdefs.h @@ -7723,7 +9273,7 @@ index 73d317a659b..7c01263e25d 100755 fi ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero" if test "x$ac_cv_func_explicit_bzero" = xyes -@@ -18357,18 +18587,6 @@ +@@ -18357,18 +18590,6 @@ then : printf "%s\n" "#define HAVE_FEXECVE 1" >>confdefs.h @@ -7742,7 +9292,7 @@ index 73d317a659b..7c01263e25d 100755 fi ac_fn_c_check_func "$LINENO" "fpathconf" "ac_cv_func_fpathconf" if test "x$ac_cv_func_fpathconf" = xyes -@@ -18795,24 +19013,6 @@ +@@ -18795,24 +19016,6 @@ then : printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h @@ -7767,7 +9317,7 @@ index 73d317a659b..7c01263e25d 100755 fi ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread" if test "x$ac_cv_func_pread" = xyes -@@ -19101,12 +19301,6 @@ +@@ -19101,12 +19304,6 @@ then : printf "%s\n" "#define HAVE_SIGACTION 1" >>confdefs.h @@ -7780,7 +9330,7 @@ index 73d317a659b..7c01263e25d 100755 fi ac_fn_c_check_func "$LINENO" "sigfillset" "ac_cv_func_sigfillset" if test "x$ac_cv_func_sigfillset" = xyes -@@ -19381,11 +19575,11 @@ +@@ -19381,11 +19578,11 @@ fi @@ -7794,7 +9344,7 @@ index 73d317a659b..7c01263e25d 100755 ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" if test "x$ac_cv_func_getentropy" = xyes then : -@@ -19409,7 +19603,7 @@ +@@ -19409,7 +19606,7 @@ # In addition, macOS introduced dup3 and pipe2 in macOS 27, late in the # lifetime of this version of Python. Rather than adding weaklinking # support for them, just continue to ignore them. @@ -7803,10 +9353,57 @@ index 73d317a659b..7c01263e25d 100755 ac_fn_c_check_func "$LINENO" "dup3" "ac_cv_func_dup3" if test "x$ac_cv_func_dup3" = xyes then : -@@ -19425,6 +19619,53 @@ +@@ -19425,6 +19622,100 @@ 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_fn_c_check_func "$LINENO" "execv" "ac_cv_func_execv" ++if test "x$ac_cv_func_execv" = xyes ++then : ++ printf "%s\n" "#define HAVE_EXECV 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "fork" "ac_cv_func_fork" ++if test "x$ac_cv_func_fork" = xyes ++then : ++ printf "%s\n" "#define HAVE_FORK 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "fork1" "ac_cv_func_fork1" ++if test "x$ac_cv_func_fork1" = xyes ++then : ++ printf "%s\n" "#define HAVE_FORK1 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn" ++if test "x$ac_cv_func_posix_spawn" = xyes ++then : ++ printf "%s\n" "#define HAVE_POSIX_SPAWN 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp" ++if test "x$ac_cv_func_posix_spawnp" = xyes ++then : ++ printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" ++if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes ++then : ++ printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "sigaltstack" "ac_cv_func_sigaltstack" ++if test "x$ac_cv_func_sigaltstack" = xyes ++then : ++ printf "%s\n" "#define HAVE_SIGALTSTACK 1" >>confdefs.h ++ ++fi ++ ++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_fn_c_check_func "$LINENO" "execv" "ac_cv_func_execv" @@ -7857,7 +9454,7 @@ index 73d317a659b..7c01263e25d 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} -@@ -22247,7 +22488,8 @@ +@@ -22247,7 +22538,8 @@ # check for openpty, login_tty, and forkpty @@ -7867,7 +9464,7 @@ index 73d317a659b..7c01263e25d 100755 for ac_func in openpty do : -@@ -22343,7 +22585,7 @@ +@@ -22343,7 +22635,7 @@ fi done @@ -7876,7 +9473,7 @@ index 73d317a659b..7c01263e25d 100755 printf %s "checking for library containing login_tty... " >&6; } if test ${ac_cv_search_login_tty+y} then : -@@ -22500,6 +22742,7 @@ +@@ -22500,6 +22792,7 @@ fi done @@ -7884,7 +9481,7 @@ index 73d317a659b..7c01263e25d 100755 # check for long file support functions ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64" -@@ -22746,10 +22989,10 @@ +@@ -22746,10 +23039,10 @@ done @@ -7897,7 +9494,7 @@ index 73d317a659b..7c01263e25d 100755 then for ac_func in clock_settime -@@ -23035,7 +23278,7 @@ +@@ -23035,7 +23328,7 @@ if test "$cross_compiling" = yes then : @@ -7906,18 +9503,18 @@ index 73d317a659b..7c01263e25d 100755 ac_cv_buggy_getaddrinfo="no" elif test "${enable_ipv6+set}" = set; then ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" -@@ -24984,8 +25227,8 @@ +@@ -24984,8 +25277,8 @@ LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" fi -# On iOS the shared libraries must be linked with the Python framework -if test "$ac_sys_system" = "iOS"; then +# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework -+if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "visionOS" -o $ac_sys_system = "watchOS"; then ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi -@@ -27976,7 +28219,7 @@ +@@ -27976,7 +28269,7 @@ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 printf "%s\n" "$as_me: checking for device files" >&6;} @@ -7926,7 +9523,7 @@ index 73d317a659b..7c01263e25d 100755 ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else -@@ -28409,7 +28652,7 @@ +@@ -28409,7 +28702,7 @@ with_ensurepip=no ;; #( WASI) : with_ensurepip=no ;; #( @@ -7935,7 +9532,7 @@ index 73d317a659b..7c01263e25d 100755 with_ensurepip=no ;; #( *) : with_ensurepip=upgrade -@@ -29428,7 +29671,7 @@ +@@ -29428,7 +29721,7 @@ ;; #( Darwin) : ;; #( @@ -7944,23 +9541,23 @@ index 73d317a659b..7c01263e25d 100755 -@@ -33192,7 +33435,10 @@ +@@ -33192,7 +33485,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" ;; - "iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES iOS/Resources/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" ;; ++ "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 4ef6de9dd6e..45a05334cb1 100644 +index 4ef6de9dd6e..fd79216f05a 100644 --- a/configure.ac +++ b/configure.ac -@@ -330,6 +330,15 @@ +@@ -330,6 +330,18 @@ *-apple-ios*) ac_sys_system=iOS ;; @@ -7972,11 +9569,14 @@ index 4ef6de9dd6e..45a05334cb1 100644 + ;; + *-apple-watchos*) + ac_sys_system=watchOS ++ ;; ++ *-*-darwin*) ++ ac_sys_system=Darwin + ;; *-*-vxworks*) ac_sys_system=VxWorks ;; -@@ -382,7 +391,7 @@ +@@ -382,7 +394,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. # @@ -7985,7 +9585,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 # which isn't a binary that exists, and isn't very convenient, as it contains the # iOS version. As the default cross-compiler name won't exist, configure falls # back to gcc, which *definitely* won't work. We're providing wrapper scripts for -@@ -397,6 +406,17 @@ +@@ -397,6 +409,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 ;; @@ -8003,7 +9603,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 *) esac fi -@@ -405,6 +425,17 @@ +@@ -405,6 +428,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 ;; @@ -8021,7 +9621,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 *) esac fi -@@ -413,6 +444,17 @@ +@@ -413,6 +447,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 ;; @@ -8039,7 +9639,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 *) esac fi -@@ -421,6 +463,17 @@ +@@ -421,6 +466,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++ ;; @@ -8057,21 +9657,21 @@ index 4ef6de9dd6e..45a05334cb1 100644 *) esac fi -@@ -535,8 +588,11 @@ +@@ -535,8 +591,11 @@ case $enableval in yes) case $ac_sys_system in - Darwin) enableval=/Library/Frameworks ;; - iOS) enableval=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 -@@ -545,6 +601,9 @@ +@@ -545,6 +604,9 @@ no) case $ac_sys_system in iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; @@ -8081,15 +9681,15 @@ index 4ef6de9dd6e..45a05334cb1 100644 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -643,9 +702,51 @@ +@@ -643,9 +705,51 @@ prefix=$PYTHONFRAMEWORKPREFIX PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" - RESSRCDIR=iOS/Resources -+ RESSRCDIR=Apple/iOS/Resources ++ RESSRCDIR=Platforms/Apple/iOS/Resources - AC_CONFIG_FILES([iOS/Resources/Info.plist]) -+ AC_CONFIG_FILES([Apple/iOS/Resources/Info.plist]) ++ AC_CONFIG_FILES([Platforms/Apple/iOS/Resources/Info.plist]) + ;; + tvOS) : + FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" @@ -8101,9 +9701,9 @@ index 4ef6de9dd6e..45a05334cb1 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" @@ -8115,9 +9715,9 @@ index 4ef6de9dd6e..45a05334cb1 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" @@ -8129,13 +9729,13 @@ index 4ef6de9dd6e..45a05334cb1 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]) -@@ -655,6 +756,9 @@ +@@ -655,6 +759,9 @@ ],[ case $ac_sys_system in iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; @@ -8145,7 +9745,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 *) PYTHONFRAMEWORK= PYTHONFRAMEWORKDIR=no-framework -@@ -707,8 +811,8 @@ +@@ -707,8 +814,8 @@ case "$withval" in yes) case $ac_sys_system in @@ -8156,7 +9756,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" ;; *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;; -@@ -722,8 +826,8 @@ +@@ -722,8 +829,8 @@ esac ],[ case $ac_sys_system in @@ -8167,7 +9767,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch" AC_MSG_RESULT([applying default app store compliance patch]) ;; -@@ -736,6 +840,8 @@ +@@ -736,6 +843,8 @@ ]) AC_SUBST([APP_STORE_COMPLIANCE_PATCH]) @@ -8176,7 +9776,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 AC_SUBST([_PYTHON_HOST_PLATFORM]) if test "$cross_compiling" = yes; then case "$host" in -@@ -771,6 +877,70 @@ +@@ -771,6 +880,70 @@ ;; esac ;; @@ -8232,7 +9832,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 + # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version + AC_MSG_CHECKING([watchOS deployment target]) + WATCHOS_DEPLOYMENT_TARGET=${_host_os:7} -+ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=4.0} ++ WATCHOS_DEPLOYMENT_TARGET=${WATCHOS_DEPLOYMENT_TARGET:=9.0} + AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET]) + + case "$host_cpu" in @@ -8247,7 +9847,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 *-*-vxworks*) _host_ident=$host_cpu ;; -@@ -848,9 +1018,15 @@ +@@ -848,9 +1021,15 @@ define_xopen_source=no;; Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) define_xopen_source=no;; @@ -8264,7 +9864,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -909,8 +1085,15 @@ +@@ -909,8 +1088,15 @@ CONFIGURE_MACOSX_DEPLOYMENT_TARGET= EXPORT_MACOSX_DEPLOYMENT_TARGET='#' @@ -8281,7 +9881,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 # checks for alternative programs -@@ -944,15 +1127,28 @@ +@@ -944,15 +1130,28 @@ ], ) @@ -8313,7 +9913,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 ], ) -@@ -1140,6 +1336,9 @@ +@@ -1140,6 +1339,9 @@ AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], [iOS], [MULTIARCH=""], @@ -8323,7 +9923,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 [FreeBSD*], [MULTIARCH=""], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] ) -@@ -1161,7 +1360,7 @@ +@@ -1161,7 +1363,7 @@ dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of dnl the PLATFORM_TRIPLET that will be used in binary module extensions. AS_CASE([$ac_sys_system], @@ -8332,7 +9932,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 [SOABI_PLATFORM=$PLATFORM_TRIPLET] ) -@@ -1188,15 +1387,21 @@ +@@ -1188,15 +1390,21 @@ [wasm32-unknown-wasip1/clang], [PY_SUPPORT_TIER=2], dnl WebAssembly System Interface preview1, clang [x86_64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], dnl Linux on AMD64, any vendor, glibc, clang @@ -8363,7 +9963,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 [PY_SUPPORT_TIER=0] ) -@@ -1529,7 +1734,7 @@ +@@ -1529,7 +1737,7 @@ case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; @@ -8372,7 +9972,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) AC_MSG_ERROR([Unknown platform for framework build]);; -@@ -1594,7 +1799,7 @@ +@@ -1594,7 +1802,7 @@ BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; @@ -8381,7 +9981,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) -@@ -3516,7 +3721,7 @@ +@@ -3516,7 +3724,7 @@ BLDSHARED="$LDSHARED" fi ;; @@ -8390,7 +9990,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' BLDSHARED="$LDSHARED" -@@ -3640,7 +3845,7 @@ +@@ -3640,7 +3848,7 @@ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys @@ -8399,7 +9999,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -3664,7 +3869,7 @@ +@@ -3664,7 +3872,7 @@ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' fi LINKFORSHARED="$LINKFORSHARED" @@ -8408,7 +10008,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi ;; -@@ -4137,7 +4342,7 @@ +@@ -4137,7 +4345,7 @@ dnl when do we need USING_APPLE_OS_LIBFFI? ctypes_malloc_closure=yes ], @@ -8417,7 +10017,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 ctypes_malloc_closure=yes ], [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] -@@ -5248,9 +5453,9 @@ +@@ -5248,9 +5456,9 @@ # checks for library functions AC_CHECK_FUNCS([ \ accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \ @@ -8429,7 +10029,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin getlogin_r \ getpeername getpgid getpid getppid getpriority _getpty \ -@@ -5258,15 +5463,14 @@ +@@ -5258,15 +5466,14 @@ getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ @@ -8447,7 +10047,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ sysconf sysctlbyname tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ -@@ -5281,20 +5485,28 @@ +@@ -5281,20 +5488,36 @@ AC_CHECK_FUNCS([lchmod]) fi @@ -8468,6 +10068,14 @@ index 4ef6de9dd6e..45a05334cb1 100644 AC_CHECK_FUNCS([dup3 pipe2]) 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 ++ +# 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([ \ @@ -8479,7 +10087,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 AC_CHECK_DECL([dirfd], [AC_DEFINE([HAVE_DIRFD], [1], [Define if you have the 'dirfd' function or macro.])], -@@ -5555,20 +5767,22 @@ +@@ -5555,20 +5778,22 @@ [@%:@include ]) # check for openpty, login_tty, and forkpty @@ -8516,7 +10124,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 # check for long file support functions AC_CHECK_FUNCS([fseek64 fseeko fstatvfs ftell64 ftello statvfs]) -@@ -5607,10 +5821,10 @@ +@@ -5607,10 +5832,10 @@ ]) ]) @@ -8529,7 +10137,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 then AC_CHECK_FUNCS([clock_settime], [], [ AC_CHECK_LIB([rt], [clock_settime], [ -@@ -5768,7 +5982,7 @@ +@@ -5768,7 +5993,7 @@ [ac_cv_buggy_getaddrinfo=no], [ac_cv_buggy_getaddrinfo=yes], [ @@ -8538,18 +10146,18 @@ index 4ef6de9dd6e..45a05334cb1 100644 ac_cv_buggy_getaddrinfo="no" elif test "${enable_ipv6+set}" = set; then ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" -@@ -6362,8 +6576,8 @@ +@@ -6362,8 +6587,8 @@ LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" fi -# On iOS the shared libraries must be linked with the Python framework -if test "$ac_sys_system" = "iOS"; then +# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework -+if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "visionOS" -o $ac_sys_system = "watchOS"; then ++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi -@@ -7039,7 +7253,7 @@ +@@ -7039,7 +7264,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. @@ -8558,7 +10166,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no else -@@ -7296,7 +7510,7 @@ +@@ -7296,7 +7521,7 @@ AS_CASE([$ac_sys_system], [Emscripten], [with_ensurepip=no], [WASI], [with_ensurepip=no], @@ -8567,7 +10175,7 @@ index 4ef6de9dd6e..45a05334cb1 100644 [with_ensurepip=upgrade] ) ]) -@@ -7704,7 +7918,7 @@ +@@ -7704,7 +7929,7 @@ [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])], dnl The _scproxy module is available on macOS [Darwin], [],