diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ecbb02f7..c35bb42d 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,7 +4,6 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
- # Check for updates on Sunday, 8PM UTC
- interval: "weekly"
- day: "sunday"
- time: "20:00"
+ # Check for updates on the first Sunday of every month, 8PM UTC
+ interval: "cron"
+ cronjob: "0 20 * * sun#1"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 6befbc46..e9ba60f8 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -56,7 +56,7 @@ jobs:
XZ_VERSION: ${{ steps.extract.outputs.XZ_VERSION }}
steps:
- - uses: actions/checkout@v4.1.7
+ - uses: actions/checkout@v6
- name: Extract config variables
id: extract
@@ -84,28 +84,27 @@ jobs:
echo "XZ_VERSION=${XZ_VERSION}" | tee -a ${GITHUB_OUTPUT}
build:
- runs-on: macOS-latest
+ runs-on: macOS-15
needs: [ config ]
strategy:
fail-fast: false
matrix:
- target: ['macOS', 'iOS', 'tvOS', 'watchOS']
- include:
- - briefcase-run-args:
- - run-tests: false
-
- - target: macOS
- run-tests: true
-
- - target: iOS
- briefcase-run-args: ' -d "iPhone SE (3rd generation)"'
- run-tests: true
+ platform: ['macOS', 'iOS', 'tvOS', 'watchOS']
steps:
- - uses: actions/checkout@v4.1.7
+ - uses: actions/checkout@v6
+
+ - name: Set up Xcode
+ # GitHub recommends explicitly selecting the desired Xcode version:
+ # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140
+ # This became a necessity as a result of
+ # https://github.com/actions/runner-images/issues/12541 and
+ # https://github.com/actions/runner-images/issues/12751.
+ run: |
+ sudo xcode-select --switch /Applications/Xcode_16.4.app
- name: Set up Python
- uses: actions/setup-python@v5.4.0
+ uses: actions/setup-python@v6.1.0
with:
# Appending -dev ensures that we can always build the dev release.
# It's a no-op for versions that have been published.
@@ -114,31 +113,193 @@ jobs:
# It's an edge case, but when a new alpha is released, we need to use it ASAP.
check-latest: true
- - name: Build ${{ matrix.target }}
+ - name: Build ${{ matrix.platform }}
run: |
- # Do the build for the requested target.
- make ${{ matrix.target }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }}
+ # Do the build for the requested platform.
+ make ${{ matrix.platform }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }}
- name: Upload build artefacts
- uses: actions/upload-artifact@v4.6.1
+ uses: actions/upload-artifact@v6.0.0
+ with:
+ name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+
+ briefcase-testbed:
+ name: Briefcase testbed (${{ matrix.platform }})
+ runs-on: macOS-15
+ needs: [ config, build ]
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: ["macOS", "iOS"]
+ include:
+ - briefcase-run-args:
+
+ - platform: iOS
+ briefcase-run-args: ' -d "iPhone 16e::iOS 18.5"'
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set up Xcode
+ # GitHub recommends explicitly selecting the desired Xcode version:
+ # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140
+ # This became a necessity as a result of
+ # https://github.com/actions/runner-images/issues/12541 and
+ # https://github.com/actions/runner-images/issues/12751.
+ run: |
+ sudo xcode-select --switch /Applications/Xcode_16.4.app
+
+ - name: Get build artifact
+ uses: actions/download-artifact@v7.0.0
with:
- name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
- path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ path: dist
+ merge-multiple: true
- - uses: actions/checkout@v4.1.7
- if: matrix.run-tests
+ - name: Set up Python
+ uses: actions/setup-python@v6.1.0
+ with:
+ # Appending -dev ensures that we can always build the dev release.
+ # It's a no-op for versions that have been published.
+ python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev
+ # Ensure that we *always* use the latest build, not a cached version.
+ # It's an edge case, but when a new alpha is released, we need to use it ASAP.
+ check-latest: true
+
+ - uses: actions/checkout@v6
with:
repository: beeware/Python-support-testbed
path: Python-support-testbed
- name: Install dependencies
- if: matrix.run-tests
run: |
# Use the development version of Briefcase
python -m pip install git+https://github.com/beeware/briefcase.git
- name: Run support testbed check
- if: matrix.run-tests
- timeout-minutes: 10
+ timeout-minutes: 15
working-directory: Python-support-testbed
- run: briefcase run ${{ matrix.target }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\'
+ run: briefcase run ${{ matrix.platform }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\'
+
+ cpython-testbed:
+ name: CPython testbed (${{ matrix.platform }})
+ runs-on: macOS-15
+ needs: [ config, build ]
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: ["iOS", "tvOS"]
+ include:
+ - platform: "iOS"
+ testbed-args: '--simulator "iPhone 16e,arch=arm64,OS=18.5"'
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Get build artifact
+ uses: actions/download-artifact@v7.0.0
+ with:
+ pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ path: dist
+ merge-multiple: true
+
+ - name: Set up Xcode
+ # GitHub recommends explicitly selecting the desired Xcode version:
+ # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140
+ # This became a necessity as a result of
+ # https://github.com/actions/runner-images/issues/12541 and
+ # https://github.com/actions/runner-images/issues/12751.
+ run: |
+ sudo xcode-select --switch /Applications/Xcode_16.4.app
+
+ - name: Set up Python
+ uses: actions/setup-python@v6.1.0
+ with:
+ # Appending -dev ensures that we can always build the dev release.
+ # It's a no-op for versions that have been published.
+ python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev
+ # Ensure that we *always* use the latest build, not a cached version.
+ # It's an edge case, but when a new alpha is released, we need to use it ASAP.
+ check-latest: true
+
+ - name: Unpack support package
+ run: |
+ mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}
+ cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}
+ tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+
+ - name: Run CPython testbed
+ timeout-minutes: 15
+ working-directory: support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}
+ run: |
+ # Run a representative subset of CPython core tests:
+ # - test_builtin as a test of core language tools
+ # - test_grammar as a test of core language features
+ # - test_os as a test of system library calls
+ # - test_bz2 as a simple test of third party libraries
+ # - test_ctypes as a test of FFI
+ python -m testbed run --verbose ${{ matrix.testbed-args }} -- test --single-process --rerun -W test_builtin test_grammar test_os test_bz2 test_ctypes
+
+ crossenv-test:
+ name: Cross-platform env test (${{ matrix.multiarch }})
+ runs-on: macOS-latest
+ needs: [ config, build ]
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: iOS
+ slice: ios-arm64_x86_64-simulator
+ multiarch: arm64-iphonesimulator
+ - platform: iOS
+ slice: ios-arm64_x86_64-simulator
+ multiarch: x86_64-iphonesimulator
+ - platform: iOS
+ slice: ios-arm64
+ multiarch: arm64-iphoneos
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Get build artifact
+ uses: actions/download-artifact@v7.0.0
+ with:
+ pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ path: dist
+ merge-multiple: true
+
+ - name: Set up Python
+ uses: actions/setup-python@v6.1.0
+ with:
+ # Appending -dev ensures that we can always build the dev release.
+ # It's a no-op for versions that have been published.
+ python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev
+ # Ensure that we *always* use the latest build, not a cached version.
+ # It's an edge case, but when a new alpha is released, we need to use it ASAP.
+ check-latest: true
+
+ - name: Unpack support package
+ run: |
+ mkdir -p support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}
+ cd support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}
+ tar zxvf ../../../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+
+ - name: Run cross-platform environment test
+ env:
+ PYTHON_CROSS_PLATFORM: ${{ matrix.platform }}
+ PYTHON_CROSS_SLICE: ${{ matrix.slice }}
+ PYTHON_CROSS_MULTIARCH: ${{ matrix.multiarch }}
+ run: |
+ # Create and activate a native virtual environment
+ python${{ needs.config.outputs.PYTHON_VER }} -m venv cross-venv
+ source cross-venv/bin/activate
+
+ # Install pytest
+ python -m pip install pytest
+
+ # Convert venv into cross-venv
+ python support/${{ needs.config.outputs.PYTHON_VER }}/${{ matrix.platform }}/Python.xcframework/${{ matrix.slice }}/platform-config/${{ matrix.multiarch }}/make_cross_venv.py cross-venv
+
+ # Run the test suite
+ python -m pytest tests
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 5d2ce753..8ba1300f 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -8,10 +8,10 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up Python environment
- uses: actions/setup-python@v5.4.0
+ uses: actions/setup-python@v6.1.0
with:
python-version: "3.X"
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 61da403c..1b65e9fd 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -40,14 +40,14 @@ jobs:
needs: [ config, ci ]
steps:
- name: Get build artifacts
- uses: actions/download-artifact@v4.1.9
+ uses: actions/download-artifact@v7.0.0
with:
pattern: Python-*
path: dist
merge-multiple: true
- name: Create Release
- uses: ncipollo/release-action@v1.16.0
+ uses: ncipollo/release-action@v1.20.0
with:
name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }}
tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }}
diff --git a/Makefile b/Makefile
index dfc85651..bcb37e0a 100644
--- a/Makefile
+++ b/Makefile
@@ -18,19 +18,19 @@ 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.12.8
-PYTHON_PKG_VERSION=$(PYTHON_VERSION)
+PYTHON_VERSION=3.12.12
+PYTHON_PKG_VERSION=3.12.10
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+")
PYTHON_VER=$(basename $(PYTHON_VERSION))
# The binary releases of dependencies, published at:
# https://github.com/beeware/cpython-apple-source-deps/releases
-BZIP2_VERSION=1.0.8-1
-LIBFFI_VERSION=3.4.7-1
-MPDECIMAL_VERSION=4.0.0-1
-OPENSSL_VERSION=3.0.16-1
-XZ_VERSION=5.6.4-1
+BZIP2_VERSION=1.0.8-2
+LIBFFI_VERSION=3.4.7-2
+MPDECIMAL_VERSION=4.0.0-2
+OPENSSL_VERSION=3.0.18-1
+XZ_VERSION=5.6.4-2
# Supported OS
OS_LIST=macOS iOS tvOS watchOS
@@ -39,18 +39,26 @@ CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar
# macOS targets
TARGETS-macOS=macosx.x86_64 macosx.arm64
+TRIPLE_OS-macOS=macos
+PLATFORM_NAME-macOS=macOS
VERSION_MIN-macOS=11.0
# iOS targets
TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64
+TRIPLE_OS-iOS=ios
+PLATFORM_NAME-iOS=iOS
VERSION_MIN-iOS=13.0
# tvOS targets
TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64
+TRIPLE_OS-tvOS=tvos
+PLATFORM_NAME-tvOS=tvOS
VERSION_MIN-tvOS=12.0
# watchOS targets
TARGETS-watchOS=watchsimulator.x86_64 watchsimulator.arm64 watchos.arm64_32
+TRIPLE_OS-watchOS=watchos
+PLATFORM_NAME-watchOS=watchOS
VERSION_MIN-watchOS=4.0
# The architecture of the machine doing the build
@@ -80,13 +88,13 @@ update-patch:
# comparing between the current state of the 3.X branch against the v3.X.Y
# tag associated with the release being built. This allows you to
# maintain a branch that contains custom patches against the default Python.
- # The patch archived in this respository is based on github.com/freakboy3742/cpython
+ # The patch archived in this repository is based on github.com/freakboy3742/cpython
# Requires patchutils (installable via `brew install patchutils`); this
# also means we need to re-introduce homebrew to the path for the filterdiff
# call
if [ -z "$(PYTHON_REPO_DIR)" ]; then echo "\n\nPYTHON_REPO_DIR must be set to the root of your Python github checkout\n\n"; fi
cd $(PYTHON_REPO_DIR) && \
- git diff -D v$(PYTHON_VERSION) $(PYTHON_VER)-patched \
+ git diff --no-renames -D v$(PYTHON_VERSION) $(PYTHON_VER)-patched \
| PATH="/usr/local/bin:/opt/homebrew/bin:$(PATH)" filterdiff \
-X $(PROJECT_DIR)/patch/Python/diff.exclude -p 1 --clean \
> $(PROJECT_DIR)/patch/Python/Python.patch
@@ -128,10 +136,10 @@ ARCH-$(target)=$$(subst .,,$$(suffix $(target)))
ifneq ($(os),macOS)
ifeq ($$(findstring simulator,$$(SDK-$(target))),)
-TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os))
+TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(TRIPLE_OS-$(os))$$(VERSION_MIN-$(os))
IS_SIMULATOR-$(target)=False
else
-TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))$$(VERSION_MIN-$(os))-simulator
+TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(TRIPLE_OS-$(os))$$(VERSION_MIN-$(os))-simulator
IS_SIMULATOR-$(target)=True
endif
endif
@@ -278,7 +286,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))/$(os)/Resources/bin/*
+ chmod 755 $$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin/*
# Touch the configure script to ensure that Make identifies it as up to date.
touch $$(PYTHON_SRCDIR-$(target))/configure
@@ -286,7 +294,7 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \
$$(PYTHON_SRCDIR-$(target))/configure
# Configure target Python
cd $$(PYTHON_SRCDIR-$(target)) && \
- PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \
+ PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \
./configure \
LIBLZMA_CFLAGS="-I$$(XZ_INSTALL-$(target))/include" \
LIBLZMA_LIBS="-L$$(XZ_INSTALL-$(target))/lib -llzma" \
@@ -307,14 +315,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))/$(os)/Resources/bin:$(PATH)" \
+ PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \
make 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))/$(os)/Resources/bin:$(PATH)" \
+ PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Apple/$(os)/Resources/bin:$(PATH)" \
make install \
2>&1 | tee -a ../python-$(PYTHON_VERSION).install.log
@@ -323,7 +331,7 @@ $$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe
$$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)):
- @echo ">>> Create cross-plaform config for $(target)"
+ @echo ">>> Create cross-platform config for $(target)"
mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(target))
# Create the cross-platform site definition
echo "import _cross_$$(ARCH-$(target))_$$(SDK-$(target)); import _cross_venv;" \
@@ -397,15 +405,13 @@ define build-sdk
sdk=$1
os=$2
-OS_LOWER-$(sdk)=$(shell echo $(os) | tr '[:upper:]' '[:lower:]')
-
SDK_TARGETS-$(sdk)=$$(filter $(sdk).%,$$(TARGETS-$(os)))
SDK_ARCHES-$(sdk)=$$(sort $$(subst .,,$$(suffix $$(SDK_TARGETS-$(sdk)))))
ifeq ($$(findstring simulator,$(sdk)),)
-SDK_SLICE-$(sdk)=$$(OS_LOWER-$(sdk))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g")
+SDK_SLICE-$(sdk)=$$(TRIPLE_OS-$(os))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g")
else
-SDK_SLICE-$(sdk)=$$(OS_LOWER-$(sdk))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g")-simulator
+SDK_SLICE-$(sdk)=$$(TRIPLE_OS-$(os))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g")-simulator
endif
# Expand the build-target macro for target on this OS
@@ -426,7 +432,6 @@ PYTHON_INSTALL_VERSION-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Versions/$(PYTHON_VER)
PYTHON_LIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/Python
PYTHON_INCLUDE-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/include/python$(PYTHON_VER)
PYTHON_MODULEMAP-$(sdk)=$$(PYTHON_INCLUDE-$(sdk))/module.modulemap
-PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/lib/python$(PYTHON_VER)
else
# Non-macOS builds need to be merged on a per-SDK basis. The merge covers:
@@ -441,7 +446,6 @@ PYTHON_FRAMEWORK-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/Python.framework
PYTHON_LIB-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Python
PYTHON_BIN-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/bin
PYTHON_INCLUDE-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Headers
-PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/lib/python$(PYTHON_VER)
PYTHON_PLATFORM_CONFIG-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/platform-config
$$(PYTHON_LIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_LIB-$$(target)))
@@ -484,34 +488,31 @@ $$(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))))/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h
+ cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/Apple/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h
-$$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT: $$(PYTHON_LIB-$(sdk)) $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target)))
+$$(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)))
@echo ">>> Build Python stdlib for the $(sdk) SDK"
- mkdir -p $$(PYTHON_STDLIB-$(sdk))/lib-dynload
- # Copy stdlib from the first target associated with the $(sdk) SDK
- cp -r $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/ $$(PYTHON_STDLIB-$(sdk))
+ mkdir -p $$(PYTHON_INSTALL-$(sdk))/lib
- # Delete the single-SDK parts of the standard library
- rm -rf \
- $$(PYTHON_STDLIB-$(sdk))/_sysconfigdata__*.py \
- $$(PYTHON_STDLIB-$(sdk))/config-* \
- $$(PYTHON_STDLIB-$(sdk))/lib-dynload/*
+ # Create arch-specific stdlib directories
+ $$(foreach target,$$(SDK_TARGETS-$(sdk)),mkdir -p $$(PYTHON_INSTALL-$(sdk))/lib-$$(ARCH-$$(target))/python$(PYTHON_VER); )
+ $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfigdata_* $$(PYTHON_INSTALL-$(sdk))/lib-$$(ARCH-$$(target))/python$(PYTHON_VER)/; )
+ $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp -r $$(PYTHON_STDLIB-$$(target))/lib-dynload $$(PYTHON_INSTALL-$(sdk))/lib-$$(ARCH-$$(target))/python$(PYTHON_VER)/; )
- # Copy the individual _sysconfigdata modules into names that include the architecture
- $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfigdata_* $$(PYTHON_STDLIB-$(sdk))/; )
+ # Copy in known-required xcprivacy files.
+ # Libraries linking OpenSSL must provide a privacy manifest. The one in this repository
+ # has been sourced from https://github.com/openssl/openssl/blob/openssl-3.0/os-dep/Apple/PrivacyInfo.xcprivacy
+ $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$$(target))/lib-dynload/_hashlib.xcprivacy; )
+ $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$$(target))/lib-dynload/_ssl.xcprivacy; )
# Copy the platform site folders for each architecture
mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(sdk))
$$(foreach target,$$(SDK_TARGETS-$(sdk)),cp -r $$(PYTHON_PLATFORM_CONFIG-$$(target)) $$(PYTHON_PLATFORM_CONFIG-$(sdk)); )
- # Merge the binary modules from each target in the $(sdk) SDK into a single binary
- $$(foreach module,$$(wildcard $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/lib-dynload/*),lipo -create -output $$(PYTHON_STDLIB-$(sdk))/lib-dynload/$$(notdir $$(module)) $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_STDLIB-$$(target))/lib-dynload/$$(notdir $$(module))); )
-
endif
-$(sdk): $$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT
+$(sdk): $$(PYTHON_PLATFORM_CONFIG-$(sdk))/sitecustomize.py
###########################################################################
# SDK: Debug
@@ -528,8 +529,7 @@ vars-$(sdk):
@echo "PYTHON_LIB-$(sdk): $$(PYTHON_LIB-$(sdk))"
@echo "PYTHON_BIN-$(sdk): $$(PYTHON_BIN-$(sdk))"
@echo "PYTHON_INCLUDE-$(sdk): $$(PYTHON_INCLUDE-$(sdk))"
- @echo "PYTHON_STDLIB-$(sdk): $$(PYTHON_STDLIB-$(sdk))"
-
+ @echo "PYTHON_PLATFORM_CONFIG-$(sdk): $$(PYTHON_PLATFORM_CONFIG-$(sdk))"
@echo
endef # build-sdk
@@ -634,22 +634,40 @@ dist/Python-$(PYTHON_VER)-macOS-support.$(BUILD_NUMBER).tar.gz: \
else
$$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \
- $$(foreach sdk,$$(SDKS-$(os)),$$(PYTHON_STDLIB-$$(sdk))/LICENSE.TXT)
+ $$(foreach sdk,$$(SDKS-$(os)),$$(PYTHON_PLATFORM_CONFIG-$$(sdk))/sitecustomize.py)
@echo ">>> Create Python.XCFramework on $(os)"
mkdir -p $$(dir $$(PYTHON_XCFRAMEWORK-$(os)))
xcodebuild -create-xcframework \
-output $$(PYTHON_XCFRAMEWORK-$(os)) $$(foreach sdk,$$(SDKS-$(os)),-framework $$(PYTHON_FRAMEWORK-$$(sdk))) \
2>&1 | tee -a support/$(PYTHON_VER)/python-$(os).xcframework.log
+ @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
+
+ @echo ">>> Install stdlib for $(os)"
+ mkdir -p $$(PYTHON_XCFRAMEWORK-$(os))/lib
+ cp -r $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/ $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)
+
+ # Delete the single-SDK parts of the standard library
+ rm -rf \
+ $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)/_sysconfigdata__*.py \
+ $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)/config-* \
+ $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)/lib-dynload
+
@echo ">>> Install PYTHONHOME for $(os)"
$$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/include $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); )
$$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/bin $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); )
- $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/lib $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); )
+ $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/lib* $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); )
$$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/platform-config $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); )
-ifeq ($(os),iOS)
+ # 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),$(os))
@echo ">>> Clone testbed project for $(os)"
- $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/iOS/testbed clone --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed
+ $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Apple/testbed clone --platform $(os) --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed
endif
@echo ">>> Create VERSIONS file for $(os)"
diff --git a/patch/Python/OpenSSL.xcprivacy b/patch/Python/OpenSSL.xcprivacy
new file mode 100644
index 00000000..95780a09
--- /dev/null
+++ b/patch/Python/OpenSSL.xcprivacy
@@ -0,0 +1,23 @@
+
+
+
+
+ NSPrivacyAccessedAPITypes
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryFileTimestamp
+ NSPrivacyAccessedAPITypeReasons
+
+ C617.1
+
+
+
+ NSPrivacyCollectedDataTypes
+
+ NSPrivacyTrackingDomains
+
+ NSPrivacyTracking
+
+
+
diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch
index 06990a0a..f94e49f7 100644
--- a/patch/Python/Python.patch
+++ b/patch/Python/Python.patch
@@ -1,10414 +1,12903 @@
+diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
+index ad91fe68a33..25dffd4d899 100644
+--- a/.pre-commit-config.yaml
++++ b/.pre-commit-config.yaml
+@@ -2,6 +2,10 @@
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.9.1
+ hooks:
++ - id: ruff
++ name: Run Ruff (lint) on Apple/
++ args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml]
++ files: ^Apple/
+ - id: ruff
+ name: Run Ruff (lint) on Doc/
+ args: [--exit-non-zero-on-fix]
+@@ -14,6 +18,10 @@
+ name: Run Ruff (lint) on Argument Clinic
+ args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml]
+ files: ^Tools/clinic/|Lib/test/test_clinic.py
++ - id: ruff-format
++ name: Run Ruff (format) on Apple/
++ args: [--config=Apple/.ruff.toml]
++ files: ^Apple
+ - id: ruff-format
+ name: Run Ruff (format) on Doc/
+ args: [--check]
--- /dev/null
-+++ b/Doc/includes/wasm-ios-notavail.rst
-@@ -0,0 +1,8 @@
-+.. include for modules that don't work on WASM or iOS
++++ b/.ruff.toml
+@@ -0,0 +1,12 @@
++# Default settings for Ruff in CPython
+
-+.. availability:: not WASI, not iOS.
++# PYTHON_FOR_REGEN
++target-version = "py310"
+
-+ This module does not work or is not available on WebAssembly platforms, or
-+ on iOS. See :ref:`wasm-availability` for more information on WASM
-+ availability; see :ref:`iOS-availability` for more information on iOS
-+ availability.
-diff --git a/Doc/includes/wasm-notavail.rst b/Doc/includes/wasm-notavail.rst
-index e680e1f9b43..c1b79d2a4a0 100644
---- a/Doc/includes/wasm-notavail.rst
-+++ b/Doc/includes/wasm-notavail.rst
-@@ -1,7 +1,6 @@
- .. include for modules that don't work on WASM
-
--.. availability:: not Emscripten, not WASI.
-+.. availability:: not WASI.
-
-- This module does not work or is not available on WebAssembly platforms
-- ``wasm32-emscripten`` and ``wasm32-wasi``. See
-+ This module does not work or is not available on WebAssembly. See
- :ref:`wasm-availability` for more information.
-diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst
-index 2ebda3d3396..91ea6150fb1 100644
---- a/Doc/library/curses.rst
-+++ b/Doc/library/curses.rst
-@@ -21,6 +21,8 @@
- designed to match the API of ncurses, an open-source curses library hosted on
- Linux and the BSD variants of Unix.
-
-+.. include:: ../includes/wasm-ios-notavail.rst
++# PEP 8
++line-length = 79
+
- .. note::
-
- Whenever the documentation mentions a *character* it can be specified
-diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst
-index 500e831908f..7f7d650bf5d 100644
---- a/Doc/library/dbm.rst
-+++ b/Doc/library/dbm.rst
-@@ -14,6 +14,7 @@
- is a `third party interface `_ to
- the Oracle Berkeley DB.
-
-+.. include:: ../includes/wasm-ios-notavail.rst
-
- .. exception:: error
-
-@@ -398,4 +399,3 @@
- .. method:: dumbdbm.close()
-
- Close the database.
--
-diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst
-index 3726028492a..518a2940edc 100644
---- a/Doc/library/ensurepip.rst
-+++ b/Doc/library/ensurepip.rst
-@@ -38,7 +38,7 @@
- :pep:`453`: Explicit bootstrapping of pip in Python installations
- The original rationale and specification for this module.
-
--.. include:: ../includes/wasm-notavail.rst
-+.. include:: ../includes/wasm-ios-notavail.rst
-
- Command line interface
- ----------------------
-diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst
-index d23a105cd5b..1faef54c116 100644
---- a/Doc/library/fcntl.rst
-+++ b/Doc/library/fcntl.rst
-@@ -18,7 +18,7 @@
- See the :manpage:`fcntl(2)` and :manpage:`ioctl(2)` Unix manual pages
- for full details.
-
--.. availability:: Unix, not Emscripten, not WASI.
-+.. availability:: Unix, not WASI.
-
- All functions in this module take a file descriptor *fd* as their first
- argument. This can be an integer file descriptor, such as returned by
-diff --git a/Doc/library/grp.rst b/Doc/library/grp.rst
-index 57a77d51a02..f1157e189a3 100644
---- a/Doc/library/grp.rst
-+++ b/Doc/library/grp.rst
-@@ -10,7 +10,7 @@
- This module provides access to the Unix group database. It is available on all
- Unix versions.
-
--.. availability:: Unix, not Emscripten, not WASI.
-+.. availability:: Unix, not WASI, not iOS.
-
- Group database entries are reported as a tuple-like object, whose attributes
- correspond to the members of the ``group`` structure (Attribute field below, see
-diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
-index e4e09b096f7..200bd41c3ec 100644
---- a/Doc/library/importlib.rst
-+++ b/Doc/library/importlib.rst
-@@ -1215,6 +1215,69 @@
- and how the module's :attr:`~module.__file__` is populated.
-
-
-+.. class:: AppleFrameworkLoader(name, path)
++# Enable automatic fixes by default.
++# To override this, use ``fix = false`` in a subdirectory's config file
++# or ``--no-fix`` on the command line.
++fix = true
+--- /dev/null
++++ b/Apple/.ruff.toml
+@@ -0,0 +1,22 @@
++extend = "../.ruff.toml" # Inherit the project-wide settings
++
++[format]
++preview = true
++docstring-code-format = true
++
++[lint]
++select = [
++ "C4", # flake8-comprehensions
++ "E", # pycodestyle
++ "F", # pyflakes
++ "I", # isort
++ "ISC", # flake8-implicit-str-concat
++ "LOG", # flake8-logging
++ "PGH", # pygrep-hooks
++ "PT", # flake8-pytest-style
++ "PYI", # flake8-pyi
++ "RUF100", # Ban unused `# noqa` comments
++ # "UP", # pyupgrade
++ "W", # pycodestyle
++ "YTT", # flake8-2020
++]
+--- /dev/null
++++ b/Apple/__main__.py
+@@ -0,0 +1,1110 @@
++#!/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
+
-+ A specialization of :class:`importlib.machinery.ExtensionFileLoader` that
-+ is able to load extension modules in Framework format.
++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
++
++CROSS_BUILD_DIR = PYTHON_DIR / "cross-build"
++
++HOSTS: dict[str, dict[str, dict[str, str]]] = {
++ # Structure of this data:
++ # * Platform identifier
++ # * an XCframework slice that must exist for that platform
++ # * a host triple: the multiarch spec for that host
++ "iOS": {
++ "ios-arm64": {
++ "arm64-apple-ios": "arm64-iphoneos",
++ },
++ "ios-arm64_x86_64-simulator": {
++ "arm64-apple-ios-simulator": "arm64-iphonesimulator",
++ "x86_64-apple-ios-simulator": "x86_64-iphonesimulator",
++ },
++ },
++ "tvOS": {
++ "tvos-arm64": {
++ "arm64-apple-tvos": "arm64-appletvos",
++ },
++ "tvos-arm64_x86_64-simulator": {
++ "arm64-apple-tvos-simulator": "arm64-appletvsimulator",
++ "x86_64-apple-tvos-simulator": "x86_64-appletvsimulator",
++ },
++ },
++ "visionOS": {
++ "xros-arm64": {
++ "arm64-apple-xros": "arm64-xros",
++ },
++ "xros-arm64-simulator": {
++ "arm64-apple-xros-simulator": "arm64-xrsimulator",
++ },
++ },
++ "watchOS": {
++ "watchos-arm64_32": {
++ "arm64_32-apple-watchos": "arm64_32-watchos",
++ },
++ "watchos-arm64_x86_64-simulator": {
++ "arm64-apple-watchos-simulator": "arm64-watchsimulator",
++ "x86_64-apple-watchos-simulator": "x86_64-watchsimulator",
++ },
++ },
++}
+
-+ For compatibility with the iOS App Store, *all* binary modules in an iOS app
-+ must be dynamic libraries, contained in a framework with appropriate
-+ metadata, stored in the ``Frameworks`` folder of the packaged app. There can
-+ be only a single binary per framework, and there can be no executable binary
-+ material outside the Frameworks folder.
+
-+ To accomodate this requirement, when running on iOS, extension module
-+ binaries are *not* packaged as ``.so`` files on ``sys.path``, but as
-+ individual standalone frameworks. To discover those frameworks, this loader
-+ is be registered against the ``.fwork`` file extension, with a ``.fwork``
-+ file acting as a placeholder in the original location of the binary on
-+ ``sys.path``. The ``.fwork`` file contains the path of the actual binary in
-+ the ``Frameworks`` folder, relative to the app bundle. To allow for
-+ resolving a framework-packaged binary back to the original location, the
-+ framework is expected to contain a ``.origin`` file that contains the
-+ location of the ``.fwork`` file, relative to the app bundle.
++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
+
-+ For example, consider the case of an import ``from foo.bar import _whiz``,
-+ where ``_whiz`` is implemented with the binary module
-+ ``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location
-+ registered on ``sys.path``, relative to the application bundle. This module
-+ *must* be distributed as
-+ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` (creating the framework
-+ name from the full import path of the module), with an ``Info.plist`` file
-+ in the ``.framework`` directory identifying the binary as a framework. The
-+ ``foo.bar._whiz`` module would be represented in the original location with
-+ a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing the path
-+ ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also contain
-+ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing the
-+ path to the ``.fwork`` file.
+
-+ When a module is loaded with this loader, the ``__file__`` for the module
-+ will report as the location of the ``.fwork`` file. This allows code to use
-+ the ``__file__`` of a module as an anchor for file system traveral.
-+ However, the spec origin will reference the location of the *actual* binary
-+ in the ``.framework`` folder.
++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 Xcode project building the app is responsible for converting any ``.so``
-+ files from wherever they exist in the ``PYTHONPATH`` into frameworks in the
-+ ``Frameworks`` folder (including stripping extensions from the module file,
-+ the addition of framework metadata, and signing the resulting framework),
-+ and creating the ``.fwork`` and ``.origin`` files. This will usually be done
-+ with a build step in the Xcode project; see the iOS documentation for
-+ details on how to construct this build step.
++ Optionally logs the executed command to the console.
++ """
++ kwargs.setdefault("check", True)
++ if env is None:
++ env = os.environ.copy()
+
-+ .. versionadded:: 3.13
++ if host:
++ host_env = apple_env(host)
++ print_env(host_env)
++ env.update(host_env)
+
-+ .. availability:: iOS.
++ if log:
++ print(">", join_command(command))
++ return subprocess.run(command, env=env, **kwargs)
+
-+ .. attribute:: name
+
-+ Name of the module the loader supports.
++def join_command(args: str | Path | ArgsT) -> str:
++ """Format a command so it can be copied into a shell.
+
-+ .. attribute:: path
++ Similar to `shlex.join`, but also accepts arguments which are Paths, or a
++ single string/Path outside of a list.
++ """
++ if isinstance(args, (str, Path)):
++ return str(args)
++ else:
++ return shlex.join(map(str, args))
++
++
++def print_env(env: EnvironmentT) -> None:
++ """Format the environment so it can be pasted into a shell."""
++ for key, value in sorted(env.items()):
++ print(f"export {key}={shlex.quote(value)}")
++
++
++def platform_for_host(host):
++ """Determine the platform for a given host triple."""
++ for plat, slices in HOSTS.items():
++ for _, candidates in slices.items():
++ for candidate in candidates:
++ if candidate == host:
++ return plat
++ raise KeyError(host)
++
++
++def apple_env(host: str) -> EnvironmentT:
++ """Construct an Apple development environment for the given host."""
++ env = {
++ "PATH": ":".join([
++ str(PYTHON_DIR / f"Apple/{platform_for_host(host)}/Resources/bin"),
++ str(subdir(host) / "prefix"),
++ "/usr/bin",
++ "/bin",
++ "/usr/sbin",
++ "/sbin",
++ "/Library/Apple/usr/bin",
++ ]),
++ }
+
-+ Path to the ``.fwork`` file for the extension module.
++ return env
+
+
- :mod:`importlib.util` -- Utility code for importers
- ---------------------------------------------------
-
-diff --git a/Doc/library/intro.rst b/Doc/library/intro.rst
-index 5a4c9b8b16a..ffc8939d211 100644
---- a/Doc/library/intro.rst
-+++ b/Doc/library/intro.rst
-@@ -58,7 +58,7 @@
- operating system.
-
- * If not separately noted, all functions that claim "Availability: Unix" are
-- supported on macOS, which builds on a Unix core.
-+ supported on macOS and iOS, both of which build on a Unix core.
-
- * If an availability note contains both a minimum Kernel version and a minimum
- libc version, then both conditions must hold. For example a feature with note
-@@ -119,3 +119,44 @@
- .. _wasmtime: https://wasmtime.dev/
- .. _Pyodide: https://pyodide.org/
- .. _PyScript: https://pyscript.net/
++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)
+
-+.. _iOS-availability:
+
-+iOS
-+---
++def all_host_triples(platform: str) -> list[str]:
++ """Return all host triples for the given platform.
+
-+iOS is, in most respects, a POSIX operating system. File I/O, socket handling,
-+and threading all behave as they would on any POSIX operating system. However,
-+there are several major differences between iOS and other POSIX systems.
++ 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 = "all") -> None:
++ """The implementation of the "clean" command."""
++ # 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}"
++ )
+
-+* iOS can only use Python in "embedded" mode. There is no Python REPL, and no
-+ ability to execute binaries that are part of the normal Python developer
-+ experience, such as :program:`pip`. To add Python code to your iOS app, you must use
-+ the :ref:`Python embedding API ` to add a Python interpreter to an
-+ iOS app created with Xcode. See the :ref:`iOS usage guide ` for
-+ more details.
++ return binary
+
-+* An iOS app cannot use any form of subprocessing, background processing, or
-+ inter-process communication. If an iOS app attempts to create a subprocess,
-+ the process creating the subprocess will either lock up, or crash. An iOS app
-+ has no visibility of other applications that are running, nor any ability to
-+ communicate with other running applications, outside of the iOS-specific APIs
-+ that exist for this purpose.
+
-+* iOS apps have limited access to modify system resources (such as the system
-+ clock). These resources will often be *readable*, but attempts to modify
-+ those resources will usually fail.
++@contextmanager
++def group(text: str):
++ """A context manager that outputs a log marker around a section of a build.
+
-+* iOS apps have a limited concept of console input and output. ``stdout`` and
-+ ``stderr`` *exist*, and content written to ``stdout`` and ``stderr`` will be
-+ visible in logs when running in Xcode, but this content *won't* be recorded
-+ in the system log. If a user who has installed your app provides their app
-+ logs as a diagnostic aid, they will not include any detail written to
-+ ``stdout`` or ``stderr``.
++ 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)))
+
-+ iOS apps have no concept of ``stdin`` at all. While iOS apps can have a
-+ keyboard, this is a software feature, not something that is attached to
-+ ``stdin``.
++ yield
+
-+ As a result, Python library that involve console manipulation (such as
-+ :mod:`curses` and :mod:`readline`) are not available on iOS.
-diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
-index 3fec86080c5..0a8e21510c4 100644
---- a/Doc/library/multiprocessing.rst
-+++ b/Doc/library/multiprocessing.rst
-@@ -8,7 +8,7 @@
-
- --------------
-
--.. include:: ../includes/wasm-notavail.rst
-+.. include:: ../includes/wasm-ios-notavail.rst
-
- Introduction
- ------------
-diff --git a/Doc/library/os.rst b/Doc/library/os.rst
-index 18e58249a81..01d24bd7861 100644
---- a/Doc/library/os.rst
-+++ b/Doc/library/os.rst
-@@ -34,12 +34,13 @@
-
- * On VxWorks, os.popen, os.fork, os.execv and os.spawn*p* are not supported.
-
--* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, large
-- parts of the :mod:`os` module are not available or behave differently. API
-- related to processes (e.g. :func:`~os.fork`, :func:`~os.execve`), signals
-- (e.g. :func:`~os.kill`, :func:`~os.wait`), and resources
-- (e.g. :func:`~os.nice`) are not available. Others like :func:`~os.getuid`
-- and :func:`~os.getpid` are emulated or stubs.
-+* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, and on
-+ iOS, large parts of the :mod:`os` module are not available or behave
-+ differently. API related to processes (e.g. :func:`~os.fork`,
-+ :func:`~os.execve`) and resources (e.g. :func:`~os.nice`) are not available.
-+ Others like :func:`~os.getuid` and :func:`~os.getpid` are emulated or stubs.
-+ WebAssembly platforms also lack support for signals (e.g. :func:`~os.kill`,
-+ :func:`~os.wait`).
-
-
- .. note::
-@@ -784,6 +785,11 @@
- :func:`socket.gethostname` or even
- ``socket.gethostbyaddr(socket.gethostname())``.
-
-+ On macOS, iOS and Android, this returns the *kernel* name and version (i.e.,
-+ ``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android). :func:`platform.uname()`
-+ can be used to get the user-facing operating system name and version on iOS and
-+ Android.
++ if "GITHUB_ACTIONS" in os.environ:
++ print("::endgroup::")
++ else:
++ print()
+
- .. availability:: Unix.
-
- .. versionchanged:: 3.3
-@@ -3998,7 +4004,7 @@
-
- .. audit-event:: os.exec path,args,env os.execl
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
-
- .. versionchanged:: 3.3
- Added support for specifying *path* as an open file descriptor
-@@ -4201,7 +4207,7 @@
- for technical details of why we're surfacing this longstanding
- platform compatibility problem to developers.
-
-- .. availability:: POSIX, not Emscripten, not WASI.
-+ .. availability:: POSIX, not Emscripten, not WASI, not iOS.
-
-
- .. function:: forkpty()
-@@ -4228,7 +4234,7 @@
- threads, this now raises a :exc:`DeprecationWarning`. See the
- longer explanation on :func:`os.fork`.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: kill(pid, sig, /)
-@@ -4251,7 +4257,7 @@
-
- .. audit-event:: os.kill pid,sig os.kill
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
-
- .. versionchanged:: 3.2
- Added Windows support.
-@@ -4267,7 +4273,7 @@
-
- .. audit-event:: os.killpg pgid,sig os.killpg
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: nice(increment, /)
-@@ -4304,7 +4310,7 @@
- Lock program segments into memory. The value of *op* (defined in
- ````) determines which segments are locked.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: popen(cmd, mode='r', buffering=-1)
-@@ -4336,7 +4342,7 @@
- documentation for more powerful ways to manage and communicate with
- subprocesses.
-
-- .. availability:: not Emscripten, not WASI.
-+ .. availability:: not Emscripten, not WASI, not iOS.
-
- .. note::
- The :ref:`Python UTF-8 Mode ` affects encodings used
-@@ -4431,7 +4437,7 @@
-
- .. versionadded:: 3.8
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \
- setpgroup=None, resetids=False, setsid=False, setsigmask=(), \
-@@ -4447,7 +4453,7 @@
-
- .. versionadded:: 3.8
-
-- .. availability:: POSIX, not Emscripten, not WASI.
-+ .. availability:: POSIX, not Emscripten, not WASI, not iOS.
-
- See :func:`posix_spawn` documentation.
-
-@@ -4480,7 +4486,7 @@
-
- There is no way to unregister a function.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. versionadded:: 3.7
-
-@@ -4549,7 +4555,7 @@
-
- .. audit-event:: os.spawn mode,path,args,env os.spawnl
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
-
- :func:`spawnlp`, :func:`spawnlpe`, :func:`spawnvp`
- and :func:`spawnvpe` are not available on Windows. :func:`spawnle` and
-@@ -4673,7 +4679,7 @@
-
- .. audit-event:: os.system command os.system
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
-
-
- .. function:: times()
-@@ -4717,7 +4723,7 @@
- :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
- exit code.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. seealso::
-
-@@ -4751,7 +4757,10 @@
- Otherwise, if there are no matching children
- that could be waited for, :exc:`ChildProcessError` is raised.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
-+ .. note::
-+ This function is not available on macOS.
-
- .. note::
- This function is not available on macOS.
-@@ -4792,7 +4801,7 @@
- :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
- exit code.
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
-
- .. versionchanged:: 3.5
- If the system call is interrupted and the signal handler does not raise an
-@@ -4812,7 +4821,7 @@
- :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
- exitcode.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: wait4(pid, options)
-@@ -4826,7 +4835,7 @@
- :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
- exitcode.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. data:: P_PID
-@@ -4843,7 +4852,7 @@
- * :data:`!P_PIDFD` - wait for the child identified by the file descriptor
- *id* (a process file descriptor created with :func:`pidfd_open`).
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. note:: :data:`!P_PIDFD` is only available on Linux >= 5.4.
-
-@@ -4858,7 +4867,7 @@
- :func:`waitid` causes child processes to be reported if they have been
- continued from a job control stop since they were last reported.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. data:: WEXITED
-@@ -4869,7 +4878,7 @@
- The other ``wait*`` functions always report children that have terminated,
- so this option is not available for them.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. versionadded:: 3.3
-
-@@ -4881,7 +4890,7 @@
-
- This option is not available for the other ``wait*`` functions.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. versionadded:: 3.3
-
-@@ -4894,7 +4903,7 @@
-
- This option is not available for :func:`waitid`.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. data:: WNOHANG
-@@ -4903,7 +4912,7 @@
- :func:`waitid` to return right away if no child process status is available
- immediately.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. data:: WNOWAIT
-@@ -4913,7 +4922,7 @@
-
- This option is not available for the other ``wait*`` functions.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. data:: CLD_EXITED
-@@ -4926,7 +4935,7 @@
- These are the possible values for :attr:`!si_code` in the result returned by
- :func:`waitid`.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. versionadded:: 3.3
-
-@@ -4961,7 +4970,7 @@
- :func:`WIFEXITED`, :func:`WEXITSTATUS`, :func:`WIFSIGNALED`,
- :func:`WTERMSIG`, :func:`WIFSTOPPED`, :func:`WSTOPSIG` functions.
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
-
- .. versionadded:: 3.9
-
-@@ -4977,7 +4986,7 @@
-
- This function should be employed only if :func:`WIFSIGNALED` is true.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: WIFCONTINUED(status)
-@@ -4988,7 +4997,7 @@
-
- See :data:`WCONTINUED` option.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: WIFSTOPPED(status)
-@@ -5000,14 +5009,14 @@
- done using :data:`WUNTRACED` option or when the process is being traced (see
- :manpage:`ptrace(2)`).
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
- .. function:: WIFSIGNALED(status)
-
- Return ``True`` if the process was terminated by a signal, otherwise return
- ``False``.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: WIFEXITED(status)
-@@ -5016,7 +5025,7 @@
- by calling ``exit()`` or ``_exit()``, or by returning from ``main()``;
- otherwise return ``False``.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: WEXITSTATUS(status)
-@@ -5025,7 +5034,7 @@
-
- This function should be employed only if :func:`WIFEXITED` is true.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: WSTOPSIG(status)
-@@ -5034,7 +5043,7 @@
-
- This function should be employed only if :func:`WIFSTOPPED` is true.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- .. function:: WTERMSIG(status)
-@@ -5043,7 +5052,7 @@
-
- This function should be employed only if :func:`WIFSIGNALED` is true.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not Emscripten, not WASI, not iOS.
-
-
- Interface to the scheduler
-diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst
-index 2f5bf53bc5c..0cc5e532711 100644
---- a/Doc/library/platform.rst
-+++ b/Doc/library/platform.rst
-@@ -148,6 +148,9 @@
- Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``,
- ``'Windows'``. An empty string is returned if the value cannot be determined.
-
-+ On iOS and Android, this returns the user-facing OS name (i.e, ``'iOS``,
-+ ``'iPadOS'`` or ``'Android'``). To obtain the kernel name (``'Darwin'`` or
-+ ``'Linux'``), use :func:`os.uname()`.
-
- .. function:: system_alias(system, release, version)
-
-@@ -161,6 +164,8 @@
- Returns the system's release version, e.g. ``'#3 on degas'``. An empty string is
- returned if the value cannot be determined.
-
-+ On iOS and Android, this is the user-facing OS version. To obtain the
-+ Darwin or Linux kernel version, use :func:`os.uname()`.
-
- .. function:: uname()
-
-@@ -234,7 +239,6 @@
- macOS Platform
- --------------
-
--
- .. function:: mac_ver(release='', versioninfo=('','',''), machine='')
-
- Get macOS version information and return it as tuple ``(release, versioninfo,
-@@ -244,6 +248,24 @@
- Entries which cannot be determined are set to ``''``. All tuple entries are
- strings.
-
-+iOS Platform
-+------------
++@contextmanager
++def cwd(subdir: Path):
++ """A context manager that sets the current working directory."""
++ orig = os.getcwd()
++ os.chdir(subdir)
++ yield
++ os.chdir(orig)
+
-+.. function:: ios_ver(system='', release='', model='', is_simulator=False)
+
-+ Get iOS version information and return it as a
-+ :func:`~collections.namedtuple` with the following attributes:
++def configure_build_python(context: argparse.Namespace) -> None:
++ """The implementation of the "configure-build" command."""
++ if context.clean:
++ clean(context, "build")
+
-+ * ``system`` is the OS name; either ``'iOS'`` or ``'iPadOS'``.
-+ * ``release`` is the iOS version number as a string (e.g., ``'17.2'``).
-+ * ``model`` is the device model identifier; this will be a string like
-+ ``'iPhone13,2'`` for a physical device, or ``'iPhone'`` on a simulator.
-+ * ``is_simulator`` is a boolean describing if the app is running on a
-+ simulator or a physical device.
++ with (
++ group("Configuring build Python"),
++ cwd(subdir("build", create=True)),
++ ):
++ command = [relpath(PYTHON_DIR / "configure")]
++ if context.args:
++ command.extend(context.args)
++ run(command)
+
-+ Entries which cannot be determined are set to the defaults given as
-+ parameters.
+
-
- Unix Platforms
- --------------
-diff --git a/Doc/library/pwd.rst b/Doc/library/pwd.rst
-index 98ca174d9e3..d71d7212cfd 100644
---- a/Doc/library/pwd.rst
-+++ b/Doc/library/pwd.rst
-@@ -10,7 +10,7 @@
- This module provides access to the Unix user account and password database. It
- is available on all Unix versions.
-
--.. availability:: Unix, not Emscripten, not WASI.
-+.. availability:: Unix, not WASI, not iOS.
-
- Password database entries are reported as a tuple-like object, whose attributes
- correspond to the members of the ``passwd`` structure (Attribute field below,
-diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst
-index f02aec8a6a8..b6486576872 100644
---- a/Doc/library/readline.rst
-+++ b/Doc/library/readline.rst
-@@ -24,6 +24,8 @@
- allowable constructs of that file, and the capabilities of the
- Readline library in general.
-
-+.. include:: ../includes/wasm-ios-notavail.rst
++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())])
+
- .. note::
-
- The underlying Readline library API may be implemented by
-diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst
-index 02009d82104..0515d205bbc 100644
---- a/Doc/library/resource.rst
-+++ b/Doc/library/resource.rst
-@@ -13,7 +13,7 @@
- This module provides basic mechanisms for measuring and controlling system
- resources utilized by a program.
-
--.. availability:: Unix, not Emscripten, not WASI.
-+.. availability:: Unix, not WASI.
-
- Symbolic constants are used to specify particular system resources and to
- request usage information about either the current process or its children.
-diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst
-index 641a6c021c1..79c4948e99e 100644
---- a/Doc/library/signal.rst
-+++ b/Doc/library/signal.rst
-@@ -26,9 +26,9 @@
- underlying implementation), with the exception of the handler for
- :const:`SIGCHLD`, which follows the underlying implementation.
-
--On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, signals
--are emulated and therefore behave differently. Several functions and signals
--are not available on these platforms.
-+On WebAssembly platforms, signals are emulated and therefore behave
-+differently. Several functions and signals are not available on these
-+platforms.
-
- Execution of Python signal handlers
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
-index dd4c2f8317d..e11a06f056f 100644
---- a/Doc/library/socket.rst
-+++ b/Doc/library/socket.rst
-@@ -1244,7 +1244,7 @@
- buffer. Raises :exc:`OverflowError` if *length* is outside the
- permissible range of values.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not WASI.
-
- Most Unix platforms.
-
-@@ -1267,7 +1267,7 @@
- amount of ancillary data that can be received, since additional
- data may be able to fit into the padding area.
-
-- .. availability:: Unix, not Emscripten, not WASI.
-+ .. availability:: Unix, not WASI.
-
- most Unix platforms.
-
-@@ -1307,7 +1307,7 @@
- (index int, name string) tuples.
- :exc:`OSError` if the system call fails.
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not WASI.
-
- .. versionadded:: 3.3
-
-@@ -1334,7 +1334,7 @@
- interface name.
- :exc:`OSError` if no interface with the given name exists.
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not WASI.
-
- .. versionadded:: 3.3
-
-@@ -1351,7 +1351,7 @@
- interface index number.
- :exc:`OSError` if no interface with the given index exists.
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not WASI.
-
- .. versionadded:: 3.3
-
-@@ -1368,7 +1368,7 @@
- The *fds* parameter is a sequence of file descriptors.
- Consult :meth:`~socket.sendmsg` for the documentation of these parameters.
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not WASI.
-
- Unix platforms supporting :meth:`~socket.sendmsg`
- and :const:`SCM_RIGHTS` mechanism.
-@@ -1382,7 +1382,7 @@
- Return ``(msg, list(fds), flags, addr)``.
- Consult :meth:`~socket.recvmsg` for the documentation of these parameters.
-
-- .. availability:: Unix, Windows, not Emscripten, not WASI.
-+ .. availability:: Unix, Windows, not WASI.
-
- Unix platforms supporting :meth:`~socket.sendmsg`
- and :const:`SCM_RIGHTS` mechanism.
-diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
-index 755ff4c6f0f..b03db8f3e0a 100644
---- a/Doc/library/subprocess.rst
-+++ b/Doc/library/subprocess.rst
-@@ -25,7 +25,7 @@
-
- :pep:`324` -- PEP proposing the subprocess module
-
--.. include:: ../includes/wasm-notavail.rst
-+.. include:: ../includes/wasm-ios-notavail.rst
-
- Using the :mod:`subprocess` Module
- ----------------------------------
-diff --git a/Doc/library/syslog.rst b/Doc/library/syslog.rst
-index 79b808ab63c..332b58413d3 100644
---- a/Doc/library/syslog.rst
-+++ b/Doc/library/syslog.rst
-@@ -11,7 +11,7 @@
- Refer to the Unix manual pages for a detailed description of the ``syslog``
- facility.
-
--.. availability:: Unix, not Emscripten, not WASI.
-+.. availability:: Unix, not WASI, not iOS.
-
- This module wraps the system ``syslog`` family of routines. A pure Python
- library that can speak to a syslog server is available in the
-diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst
-index b32b4af1aa8..69daa381013 100644
---- a/Doc/library/urllib.parse.rst
-+++ b/Doc/library/urllib.parse.rst
-@@ -22,11 +22,19 @@
-
- The module has been designed to match the internet RFC on Relative Uniform
- Resource Locators. It supports the following URL schemes: ``file``, ``ftp``,
--``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``mailto``, ``mms``,
-+``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``itms-services``, ``mailto``, ``mms``,
- ``news``, ``nntp``, ``prospero``, ``rsync``, ``rtsp``, ``rtsps``, ``rtspu``,
- ``sftp``, ``shttp``, ``sip``, ``sips``, ``snews``, ``svn``, ``svn+ssh``,
- ``telnet``, ``wais``, ``ws``, ``wss``.
-
-+.. impl-detail::
+
-+ The inclusion of the ``itms-services`` URL scheme can prevent an app from
-+ passing Apple's App Store review process for the macOS and iOS App Stores.
-+ Handling for the ``itms-services`` scheme is always removed on iOS; on
-+ macOS, it *may* be removed if CPython has been built with the
-+ :option:`--with-app-store-compliance` option.
++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])
+
- The :mod:`urllib.parse` module defines functions that fall into two broad
- categories: URL parsing and URL quoting. These are covered in detail in
- the following sections.
-diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
-index 9eb0e9d191c..cc403f1bb7b 100644
---- a/Doc/library/venv.rst
-+++ b/Doc/library/venv.rst
-@@ -56,7 +56,7 @@
- `Python Packaging User Guide: Creating and using virtual environments
- `__
-
--.. include:: ../includes/wasm-notavail.rst
-+.. include:: ../includes/wasm-ios-notavail.rst
-
- Creating virtual environments
- -----------------------------
-diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst
-index c34b2170f8f..2fed2e817e8 100644
---- a/Doc/library/webbrowser.rst
-+++ b/Doc/library/webbrowser.rst
-@@ -33,6 +33,13 @@
- browsers are not available on Unix, the controlling process will launch a new
- browser and wait.
-
-+On iOS, the :envvar:`BROWSER` environment variable, as well as any arguments
-+controlling autoraise, browser preference, and new tab/window creation will be
-+ignored. Web pages will *always* be opened in the user's preferred browser, in
-+a new tab, with the browser being brought to the foreground. The use of the
-+:mod:`webbrowser` module on iOS requires the :mod:`ctypes` module. If
-+:mod:`ctypes` isn't available, calls to :func:`.open` will fail.
++ raise KeyError(host)
+
- The script :program:`webbrowser` can be used as a command-line interface for the
- module. It accepts a URL as the argument. It accepts the following optional
- parameters: ``-n`` opens the URL in a new browser window, if possible;
-@@ -154,6 +161,8 @@
- +------------------------+-----------------------------------------+-------+
- | ``'chromium-browser'`` | :class:`Chromium('chromium-browser')` | |
- +------------------------+-----------------------------------------+-------+
-+| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) |
-++------------------------+-----------------------------------------+-------+
-
- Notes:
-
-@@ -168,7 +177,11 @@
- Only on Windows platforms.
-
- (3)
-- Only on macOS platform.
-+ Only on macOS.
+
-+(4)
-+ Only on iOS.
++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
+
-
- .. versionadded:: 3.3
- Support for Chrome/Chromium has been added.
-@@ -181,6 +194,9 @@
- .. deprecated-removed:: 3.11 3.13
- :class:`MacOSX` is deprecated, use :class:`MacOSXOSAScript` instead.
-
-+.. versionchanged:: 3.13
-+ Support for iOS has been added.
++ raise KeyError(host)
+
- Here are some simple examples::
-
- url = 'https://docs.python.org/'
-diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py
-index 615c7f1f30f..6ecf395298a 100644
---- a/Doc/tools/extensions/pyspecific.py
-+++ b/Doc/tools/extensions/pyspecific.py
-@@ -107,6 +107,80 @@
- return [pnode]
-
-
-+# Support for documenting platform availability
-+
-+class Availability(SphinxDirective):
-+
-+ has_content = True
-+ required_arguments = 1
-+ optional_arguments = 0
-+ final_argument_whitespace = True
-+
-+ # known platform, libc, and threading implementations
-+ known_platforms = frozenset({
-+ "AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD",
-+ "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", "Unix", "VxWorks",
-+ "WASI", "Windows", "macOS", "iOS",
-+ # libc
-+ "BSD libc", "glibc", "musl",
-+ # POSIX platforms with pthreads
-+ "pthreads",
-+ })
-+
-+ def run(self):
-+ availability_ref = ':ref:`Availability `: '
-+ avail_nodes, avail_msgs = self.state.inline_text(
-+ availability_ref + self.arguments[0],
-+ self.lineno)
-+ pnode = nodes.paragraph(availability_ref + self.arguments[0],
-+ '', *avail_nodes, *avail_msgs)
-+ self.set_source_info(pnode)
-+ cnode = nodes.container("", pnode, classes=["availability"])
-+ self.set_source_info(cnode)
-+ if self.content:
-+ self.state.nested_parse(self.content, self.content_offset, cnode)
-+ self.parse_platforms()
-+
-+ return [cnode]
-+
-+ def parse_platforms(self):
-+ """Parse platform information from arguments
-+
-+ Arguments is a comma-separated string of platforms. A platform may
-+ be prefixed with "not " to indicate that a feature is not available.
-+
-+ Example::
-+
-+ .. availability:: Windows, Linux >= 4.2, not WASI
-+
-+ Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not
-+ parsed into separate tokens.
-+ """
-+ platforms = {}
-+ for arg in self.arguments[0].rstrip(".").split(","):
-+ arg = arg.strip()
-+ platform, _, version = arg.partition(" >= ")
-+ if platform.startswith("not "):
-+ version = False
-+ platform = platform[4:]
-+ elif not version:
-+ version = True
-+ platforms[platform] = version
-+
-+ unknown = set(platforms).difference(self.known_platforms)
-+ if unknown:
-+ cls = type(self)
-+ logger = logging.getLogger(cls.__qualname__)
-+ logger.warning(
-+ f"Unknown platform(s) or syntax '{' '.join(sorted(unknown))}' "
-+ f"in '.. availability:: {self.arguments[0]}', see "
-+ f"{__file__}:{cls.__qualname__}.known_platforms for a set "
-+ "known platforms."
-+ )
+
-+ return platforms
++def unpack_deps(
++ platform: str,
++ host: str,
++ prefix_dir: Path,
++ cache_dir: Path,
++) -> None:
++ """Unpack binary dependencies into a provided directory.
+
++ Downloads binaries if they aren't already present. Downloads will be stored
++ in provided cache directory.
+
- # Support for documenting decorators
-
- class PyDecoratorMixin(object):
-diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
-index fed1d1e2c75..857df610f87 100644
---- a/Doc/using/configure.rst
-+++ b/Doc/using/configure.rst
-@@ -638,7 +638,7 @@
- macOS Options
- -------------
-
--See ``Mac/README.rst``.
-+See :source:`Mac/README.rst`.
-
- .. option:: --enable-universalsdk
- .. option:: --enable-universalsdk=SDKDIR
-@@ -673,6 +673,31 @@
- Specify the name for the python framework on macOS only valid when
- :option:`--enable-framework` is set (default: ``Python``).
-
-+.. option:: --with-app-store-compliance
-+.. option:: --with-app-store-compliance=PATCH-FILE
++ 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.18-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)
+
-+ The Python standard library contains strings that are known to trigger
-+ automated inspection tool errors when submitted for distribution by
-+ the macOS and iOS App Stores. If enabled, this option will apply the list of
-+ patches that are known to correct app store compliance. A custom patch
-+ file can also be specified. This option is disabled by default.
++ # Dynamic libraries will be preferentially linked over static;
++ # On iOS, ensure that no dylibs are available in the prefix folder.
++ if platform == "iOS":
++ for dylib in prefix_dir.glob("**/*.dylib"):
++ dylib.unlink()
+
-+ .. versionadded:: 3.13
+
-+iOS Options
-+-----------
++def download(url: str, target_dir: Path) -> Path:
++ """Download the specified URL into the given directory.
+
-+See :source:`iOS/README.rst`.
++ :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
+
-+.. option:: --enable-framework=INSTALLDIR
+
-+ Create a Python.framework. Unlike macOS, the *INSTALLDIR* argument
-+ specifying the installation path is mandatory.
++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
+
-+.. option:: --with-framework-name=FRAMEWORK
++ if context.clean:
++ clean(context, host)
+
-+ Specify the name for the framework (default: ``Python``).
++ host_dir = subdir(host, create=True)
++ prefix_dir = host_dir / "prefix"
+
-
- Cross Compiling Options
- -----------------------
-diff --git a/Doc/using/index.rst b/Doc/using/index.rst
-index e1a3111f36a..f55a12f1ab8 100644
---- a/Doc/using/index.rst
-+++ b/Doc/using/index.rst
-@@ -18,4 +18,5 @@
- configure.rst
- windows.rst
- mac.rst
-+ ios.rst
- editors.rst
---- /dev/null
-+++ b/Doc/using/ios.rst
-@@ -0,0 +1,386 @@
-+.. _using-ios:
++ with group(f"Downloading dependencies ({host})"):
++ if not prefix_dir.exists():
++ prefix_dir.mkdir()
++ unpack_deps(context.platform, host, prefix_dir, context.cache_dir)
++ else:
++ print("Dependencies already installed")
+
-+===================
-+Using Python on iOS
-+===================
++ 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-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_LDFLAGS=-L{prefix_dir}/lib -lmpdec",
++ ]
+
-+:Authors:
-+ Russell Keith-Magee (2024-03)
++ if context.args:
++ command.extend(context.args)
++ run(command, host=host)
+
-+Python on iOS is unlike Python on desktop platforms. On a desktop platform,
-+Python is generally installed as a system resource that can be used by any user
-+of that computer. Users then interact with Python by running a :program:`python`
-+executable and entering commands at an interactive prompt, or by running a
-+Python script.
+
-+On iOS, there is no concept of installing as a system resource. The only unit
-+of software distribution is an "app". There is also no console where you could
-+run a :program:`python` executable, or interact with a Python REPL.
++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
+
-+As a result, the only way you can use Python on iOS is in embedded mode - that
-+is, by writing a native iOS application, and embedding a Python interpreter
-+using ``libPython``, and invoking Python code using the :ref:`Python embedding
-+API `. The full Python interpreter, the standard library, and all
-+your Python code is then packaged as a standalone bundle that can be
-+distributed via the iOS App Store.
++ with (
++ group(f"Compiling host Python ({host})"),
++ cwd(subdir(host)),
++ ):
++ run(["make"], host=host)
++ run(["make", "install"], host=host)
+
-+If you're looking to experiment for the first time with writing an iOS app in
-+Python, projects such as `BeeWare `__ and `Kivy
-+`__ will provide a much more approachable user experience.
-+These projects manage the complexities associated with getting an iOS project
-+running, so you only need to deal with the Python code itself.
+
-+Python at runtime on iOS
-+========================
++def framework_path(host_triple: str, multiarch: str) -> Path:
++ """The path to a built single-architecture framework product.
+
-+iOS version compatibility
-+-------------------------
++ :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}"
++ )
+
-+The minimum supported iOS version is specified at compile time, using the
-+:option:`--host` option to ``configure``. By default, when compiled for iOS,
-+Python will be compiled with a minimum supported iOS version of 13.0. To use a
-+different miniumum iOS version, provide the version number as part of the
-+:option:`!--host` argument - for example,
-+``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 simulator build
-+with a deployment target of 15.4.
+
-+Platform identification
-+-----------------------
++def package_version(prefix_path: Path) -> str:
++ """Extract the Python version being built from patchlevel.h."""
++ for path in prefix_path.glob("**/patchlevel.h"):
++ text = path.read_text(encoding="utf-8")
++ if match := re.search(
++ r'\n\s*#define\s+PY_VERSION\s+"(.+)"\s*\n', text
++ ):
++ version = match[1]
++ # If not building against a tagged commit, add a timestamp to the
++ # version. Follow the PyPA version number rules, as this will make
++ # it easier to process with other tools. The version will have a
++ # `+` suffix once any official release has been made; a freshly
++ # forked main branch will have a version of 3.X.0a0.
++ if version.endswith("a0"):
++ version += "+"
++ if version.endswith("+"):
++ version += datetime.now(timezone.utc).strftime("%Y%m%d.%H%M%S")
++
++ return version
++
++ sys.exit("Unable to determine Python version being packaged.")
++
++
++def lib_platform_files(dirname, names):
++ """A file filter that ignores platform-specific files in lib."""
++ path = Path(dirname)
++ if (
++ path.parts[-3] == "lib"
++ and path.parts[-2].startswith("python")
++ and path.parts[-1] == "lib-dynload"
++ ):
++ return names
++ elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
++ ignored_names = {
++ name
++ for name in names
++ if (
++ name.startswith("_sysconfigdata_")
++ or name.startswith("_sysconfig_vars_")
++ or name == "build-details.json"
++ )
++ }
++ elif path.parts[-1] == "lib":
++ ignored_names = {
++ name
++ for name in names
++ if name.startswith("libpython") and name.endswith(".dylib")
++ }
++ else:
++ ignored_names = set()
+
-+When executing on iOS, ``sys.platform`` will report as ``ios``. This value will
-+be returned on an iPhone or iPad, regardless of whether the app is running on
-+the simulator or a physical device.
++ return ignored_names
+
-+Information about the specific runtime environment, including the iOS version,
-+device model, and whether the device is a simulator, can be obtained using
-+:func:`platform.ios_ver()`. :func:`platform.system()` will report ``iOS`` or
-+``iPadOS``, depending on the device.
+
-+:func:`os.uname()` reports kernel-level details; it will report a name of
-+``Darwin``.
++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()
+
-+Standard library availability
-+-----------------------------
+
-+The Python standard library has some notable omissions and restrictions on
-+iOS. See the :ref:`API availability guide for iOS ` for
-+details.
++def create_xcframework(platform: str) -> str:
++ """Build an XCframework from the component parts for the platform.
+
-+Binary extension modules
-+------------------------
++ :return: The version number of the Python version that was packaged.
++ """
++ package_path = CROSS_BUILD_DIR / platform
++ try:
++ package_path.mkdir()
++ except FileExistsError:
++ raise RuntimeError(
++ f"{platform} XCframework already exists; do you need to run "
++ "with --clean?"
++ ) from None
++
++ frameworks = []
++ # Merge Frameworks for each component SDK. If there's only one architecture
++ # for the SDK, we can use the compiled Python.framework as-is. However, if
++ # there's more than architecture, we need to merge the individual built
++ # frameworks into a merged "fat" framework.
++ for slice_name, slice_parts in HOSTS[platform].items():
++ # Some parts are the same across all slices, so we use can any of the
++ # host frameworks as the source for the merged version. Use the first
++ # one on the list, as it's as representative as any other.
++ first_host_triple, first_multiarch = next(iter(slice_parts.items()))
++ first_framework = (
++ framework_path(first_host_triple, first_multiarch)
++ / "Python.framework"
++ )
+
-+One notable difference about iOS as a platform is that App Store distribution
-+imposes hard requirements on the packaging of an application. One of these
-+requirements governs how binary extension modules are distributed.
++ if len(slice_parts) == 1:
++ # The first framework is the only framework, so copy it.
++ print(f"Copying framework for {slice_name}...")
++ frameworks.append(first_framework)
++ else:
++ print(f"Merging framework for {slice_name}...")
++ slice_path = CROSS_BUILD_DIR / slice_name
++ slice_framework = slice_path / "Python.framework"
++ slice_framework.mkdir(exist_ok=True, parents=True)
++
++ # Copy the Info.plist
++ shutil.copy(
++ first_framework / "Info.plist",
++ slice_framework / "Info.plist",
++ )
+
-+The iOS App Store requires that *all* binary modules in an iOS app must be
-+dynamic libraries, contained in a framework with appropriate metadata, stored
-+in the ``Frameworks`` folder of the packaged app. There can be only a single
-+binary per framework, and there can be no executable binary material outside
-+the ``Frameworks`` folder.
++ # Copy the headers
++ shutil.copytree(
++ first_framework / "Headers",
++ slice_framework / "Headers",
++ )
+
-+This conflicts with the usual Python approach for distributing binaries, which
-+allows a binary extension module to be loaded from any location on
-+``sys.path``. To ensure compliance with App Store policies, an iOS project must
-+post-process any Python packages, converting ``.so`` binary modules into
-+individual standalone frameworks with appropriate metadata and signing. For
-+details on how to perform this post-processing, see the guide for :ref:`adding
-+Python to your project `.
++ # 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()
++ ]
++ )
+
-+To help Python discover binaries in their new location, the original ``.so``
-+file on ``sys.path`` is replaced with a ``.fwork`` file. This file is a text
-+file containing the location of the framework binary, relative to the app
-+bundle. To allow the framework to resolve back to the original location, the
-+framework must contain a ``.origin`` file that contains the location of the
-+``.fwork`` file, relative to the app bundle.
++ # Add this merged slice to the list to be added to the XCframework
++ frameworks.append(slice_framework)
+
-+For example, consider the case of an import ``from foo.bar import _whiz``,
-+where ``_whiz`` is implemented with the binary module
-+``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location
-+registered on ``sys.path``, relative to the application bundle. This module
-+*must* be distributed as ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz``
-+(creating the framework name from the full import path of the module), with an
-+``Info.plist`` file in the ``.framework`` directory identifying the binary as a
-+framework. The ``foo.bar._whiz`` module would be represented in the original
-+location with a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing
-+the path ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also
-+contain ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing
-+the path to the ``.fwork`` file.
++ print()
++ print("Build XCframework...")
++ cmd = [
++ "xcodebuild",
++ "-create-xcframework",
++ "-output",
++ package_path / "Python.xcframework",
++ ]
++ for framework in frameworks:
++ cmd.extend(["-framework", framework])
++
++ run(cmd)
++
++ # Extract the package version from the merged framework
++ version = package_version(package_path / "Python.xcframework")
++ version_tag = ".".join(version.split(".")[:2])
++
++ # On non-macOS platforms, each framework in XCframework only contains the
++ # headers, libPython, plus an Info.plist. Other resources like the standard
++ # library and binary shims aren't allowed to live in framework; they need
++ # to be copied in separately.
++ print()
++ print("Copy additional resources...")
++ has_common_stdlib = False
++ for slice_name, slice_parts in HOSTS[platform].items():
++ # Some parts are the same across all slices, so we can any of the
++ # host frameworks as the source for the merged version.
++ first_host_triple, first_multiarch = next(iter(slice_parts.items()))
++ first_path = framework_path(first_host_triple, first_multiarch)
++ first_framework = first_path / "Python.framework"
++
++ slice_path = package_path / f"Python.xcframework/{slice_name}"
++ slice_framework = slice_path / "Python.framework"
++
++ # Copy the binary helpers
++ print(f" - {slice_name} binaries")
++ shutil.copytree(first_path / "bin", slice_path / "bin")
++
++ # Copy the include path (a symlink to the framework headers)
++ print(f" - {slice_name} include files")
++ shutil.copytree(
++ first_path / "include",
++ slice_path / "include",
++ symlinks=True,
++ )
+
-+When running on iOS, the Python interpreter will install an
-+:class:`~importlib.machinery.AppleFrameworkLoader` that is able to read and
-+import ``.fwork`` files. Once imported, the ``__file__`` attribute of the
-+binary module will report as the location of the ``.fwork`` file. However, the
-+:class:`~importlib.machinery.ModuleSpec` for the loaded module will report the
-+``origin`` as the location of the binary in the framework folder.
++ # Copy in the cross-architecture pyconfig.h
++ shutil.copy(
++ PYTHON_DIR / f"Apple/{platform}/Resources/pyconfig.h",
++ slice_framework / "Headers/pyconfig.h",
++ )
+
-+Compiler stub binaries
-+----------------------
++ print(f" - {slice_name} shared library")
++ # Create a simlink for the fat library
++ shared_lib = slice_path / f"lib/libpython{version_tag}.dylib"
++ shared_lib.parent.mkdir()
++ shared_lib.symlink_to("../Python.framework/Python")
++
++ print(f" - {slice_name} architecture-specific files")
++ for host_triple, multiarch in slice_parts.items():
++ print(f" - {multiarch} standard library")
++ arch, _ = multiarch.split("-", 1)
++
++ if not has_common_stdlib:
++ print(" - using this architecture as the common stdlib")
++ shutil.copytree(
++ framework_path(host_triple, multiarch) / "lib",
++ package_path / "Python.xcframework/lib",
++ ignore=lib_platform_files,
++ symlinks=True,
++ )
++ has_common_stdlib = True
+
-+Xcode doesn't expose explicit compilers for iOS; instead, it uses an ``xcrun``
-+script that resolves to a full compiler path (e.g., ``xcrun --sdk iphoneos
-+clang`` to get the ``clang`` for an iPhone device). However, using this script
-+poses two problems:
++ shutil.copytree(
++ framework_path(host_triple, multiarch) / "lib",
++ slice_path / f"lib-{arch}",
++ ignore=lib_non_platform_files,
++ symlinks=True,
++ )
+
-+* The output of ``xcrun`` includes paths that are machine specific, resulting
-+ in a sysconfig module that cannot be shared between users; and
++ # 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",
++ )
+
-+* It results in ``CC``/``CPP``/``LD``/``AR`` definitions that include spaces.
-+ There is a lot of C ecosystem tooling that assumes that you can split a
-+ command line at the first space to get the path to the compiler executable;
-+ this isn't the case when using ``xcrun``.
++ # 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",
++ )
+
-+To avoid these problems, Python provided stubs for these tools. These stubs are
-+shell script wrappers around the underingly ``xcrun`` tools, distributed in a
-+``bin`` folder distributed alongside the compiled iOS framework. These scripts
-+are relocatable, and will always resolve to the appropriate local system paths.
-+By including these scripts in the bin folder that accompanies a framework, the
-+contents of the ``sysconfig`` module becomes useful for end-users to compile
-+their own modules. When compiling third-party Python modules for iOS, you
-+should ensure these stub binaries are on your path.
++ print(" - build tools")
++ shutil.copytree(
++ PYTHON_DIR / "Apple/testbed/Python.xcframework/build",
++ package_path / "Python.xcframework/build",
++ )
+
-+Installing Python on iOS
-+========================
++ return version
+
-+Tools for building iOS apps
-+---------------------------
+
-+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.
++def package(context: argparse.Namespace) -> None:
++ """The implementation of the "package" command."""
++ if context.clean:
++ clean(context, "package")
+
-+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 from the Platforms tab of the Xcode Settings panel.
++ with group("Building package"):
++ # Create an XCframework
++ version = create_xcframework(context.platform)
+
-+.. _adding-ios:
++ # 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"
++ )
+
-+Adding Python to an iOS project
-+-------------------------------
++ 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.")
+
-+Python can be added to any iOS project, using either Swift or Objective C. The
-+following examples will use Objective C; if you are using Swift, you may find a
-+library like `PythonKit `__ to be
-+helpful.
+
-+To add Python to an iOS Xcode project:
++def build(context: argparse.Namespace, host: str | None = None) -> None:
++ """The implementation of the "build" command."""
++ if host is None:
++ host = context.host
+
-+1. Build or obtain a Python ``XCFramework``. See the instructions in
-+ :source:`iOS/README.rst` (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``.
++ if context.clean:
++ clean(context, host)
+
-+2. Drag the ``XCframework`` into your iOS project. In the following
-+ instructions, we'll assume you've dropped the ``XCframework`` into the root
-+ of your project; however, you can use any other location that you want by
-+ adjusting paths as needed.
++ if host in {"all", "build"}:
++ for step in [
++ configure_build_python,
++ make_build_python,
++ ]:
++ step(context)
+
-+3. Drag the ``iOS/Resources/dylib-Info-template.plist`` file into your project,
-+ and ensure it is associated with the app target.
++ if host == "build":
++ hosts = []
++ elif host in {"all", "hosts"}:
++ hosts = all_host_triples(context.platform)
++ else:
++ hosts = [host]
+
-+4. 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.
++ for step_host in hosts:
++ for step in [
++ configure_host_python,
++ make_host_python,
++ ]:
++ step(context, host=step_host)
+
-+5. Select the app target by selecting the root node of your Xcode project, then
-+ the target name in the sidebar that appears.
++ if host in {"all", "hosts"}:
++ package(context)
+
-+6. 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:
++def test(context: argparse.Namespace, host: str | None = None) -> None:
++ """The implementation of the "test" command."""
++ if host is None:
++ host = context.host
+
-+ - Build Options
++ if context.clean:
++ clean(context, "test")
+
-+ * User Script Sandboxing: No
-+ * Enable Testability: Yes
++ 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]
+
-+ - Search Paths
++ 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)}"
++ )
+
-+ * Framework Search Paths: ``$(PROJECT_DIR)``
-+ * Header Search Paths: ``"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"``
++ run([
++ sys.executable,
++ "Apple/testbed",
++ "clone",
++ "--platform",
++ context.platform,
++ "--framework",
++ framework_path,
++ testbed_dir,
++ ])
++
++ run(
++ [
++ sys.executable,
++ testbed_dir,
++ "run",
++ "--verbose",
++ ]
++ + (
++ ["--simulator", str(context.simulator)]
++ if context.simulator
++ else []
++ )
++ + [
++ "--",
++ "test",
++ # f"--{context.ci_mode}-ci",
++ "-uall",
++ "--rerun",
++ "--single-process",
++ "-W",
++ # 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",
++ ]
++ )
+
-+ - Apple Clang - Warnings - All languages
+
-+ * Quoted Include In Framework Header: No
++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
+
-+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:
++ raise KeyError(platform_name)
+
-+ .. code-block:: bash
+
-+ set -e
++def ci(context: argparse.Namespace) -> None:
++ """The implementation of the "ci" command.
+
-+ 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
++ 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.
+
-+ Note that the name of the simulator "slice" in the XCframework may be
-+ different, depending the CPU architectures your ``XCFramework`` supports.
++ 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)
+
-+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
++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)
+
-+ 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"
-+ }
++ clean = subcommands.add_parser(
++ "clean",
++ help="Delete all build directories",
++ )
+
-+ 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
++ configure_build = subcommands.add_parser(
++ "configure-build", help="Run `configure` for the build Python"
++ )
++ subcommands.add_parser(
++ "make-build", help="Run `make` for the build Python"
++ )
++ configure_host = subcommands.add_parser(
++ "configure-host",
++ help="Run `configure` for a specific platform and target",
++ )
++ make_host = subcommands.add_parser(
++ "make-host",
++ help="Run `make` for a specific platform and target",
++ )
++ package = subcommands.add_parser(
++ "package",
++ help="Create a release package for the platform",
++ )
++ build = subcommands.add_parser(
++ "build",
++ help="Build all platform targets and create the XCframework",
++ )
++ test = subcommands.add_parser(
++ "test",
++ help="Run the testbed for a specific platform",
++ )
++ ci = subcommands.add_parser(
++ "ci",
++ help="Run build, package, and test",
++ )
+
-+ # Clean up dylib template
-+ rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist"
++ # 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",
++ )
+
-+ 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 "{}" \;
++ # 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'"
++ ),
++ )
+
-+10. Add Objective C code to initialize and use a Python interpreter in embedded
-+ mode. You should ensure that:
++ # --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",
++ )
+
-+ * UTF-8 mode (:c:member:`PyPreConfig.utf8_mode`) is *enabled*;
-+ * Buffered stdio (:c:member:`PyConfig.buffered_stdio`) is *disabled*;
-+ * Writing bytecode (:c:member:`PyConfig.write_bytecode`) is *disabled*;
-+ * Signal handlers (:c:member:`PyConfig.install_signal_handlers`) are *enabled*;
-+ * ``PYTHONHOME`` for the interpreter is configured to point at the
-+ ``python`` subfolder of your app's bundle; and
-+ * The ``PYTHONPATH`` for the interpreter includes:
++ # --cache-dir option
++ for cmd in [configure_host, build, ci]:
++ cmd.add_argument(
++ "--cache-dir",
++ default="./cross-build/downloads",
++ help="The directory to store cached downloads.",
++ )
+
-+ - the ``python/lib/python3.X`` subfolder of your app's bundle,
-+ - the ``python/lib/python3.X/lib-dynload`` subfolder of your app's bundle, and
-+ - the ``app`` subfolder of your app's bundle
++ # --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",
++ )
+
-+ Your app's bundle location can be determined using ``[[NSBundle mainBundle]
-+ resourcePath]``.
++ for subcommand in [configure_build, configure_host, build, ci]:
++ subcommand.add_argument(
++ "args", nargs="*", help="Extra arguments to pass to `configure`"
++ )
+
-+Steps 8, 9 and 10 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:
++ return parser.parse_args()
+
-+* 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 targetting (i.e., delete any device binaries if you're
-+ building app 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.
++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")
+
-+* If you're using a separate folder for third-party packages, ensure that folder
-+ is included as part of the ``PYTHONPATH`` configuration in step 10.
++ # shlex uses single quotes, so we surround the command with double quotes.
++ print(
++ f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}'
++ )
+
-+Testing a Python package
-+------------------------
+
-+The CPython source tree contains :source:`a testbed project ` that
-+is used to run the CPython test suite on the iOS simulator. This testbed can also
-+be used as a testbed project for running your Python library's test suite on iOS.
++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()
++ dispatch: dict[str, Callable] = {
++ "clean": clean,
++ "configure-build": configure_build_python,
++ "make-build": make_build_python,
++ "configure-host": configure_host_python,
++ "make-host": make_host_python,
++ "package": package,
++ "build": build,
++ "test": test,
++ "ci": ci,
++ }
+
-+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:
++ 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)
+
-+.. code-block:: bash
+
-+ $ python iOS/testbed clone --framework --app --app app-testbed
++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)
+
-+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).
++ main()
+--- /dev/null
++++ b/Apple/iOS/README.md
+@@ -0,0 +1,339 @@
++# Python on iOS README
+
-+You can then use the ``app-testbed`` folder to run the test suite for your app,
-+For example, if ``module1.tests`` was the entry point to your test suite, you
-+could run:
++**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).**
+
-+.. code-block:: bash
++This document provides a quick overview of some iOS specific features in the
++Python distribution.
+
-+ $ python app-testbed run -- module1.tests
++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.
+
-+This is the equivalent of running ``python -m module1.tests`` on a desktop
-+Python build. Any arguments after the ``--`` will be passed to the testbed as
-+if they were arguments to ``python -m`` on a desktop machine.
++## Compilers for building on iOS
+
-+You can also open the testbed project in Xcode by running:
++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.
+
-+.. code-block:: bash
++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.
+
-+ $ open app-testbed/iOSTestbed.xcodeproj
++## Building Python on iOS
+
-+This will allow you to use the full Xcode suite of tools for debugging.
++### ABIs and Architectures
+
-+App Store Compliance
-+====================
++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`).
+
-+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.
++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.
+
-+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
-+review rules cannot be challenged; so, it is necessary to modify the Python
-+standard library for an app to pass App Store review.
++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).
+
-+The Python source tree contains
-+: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.
-diff --git a/Doc/using/mac.rst b/Doc/using/mac.rst
-index 8b67652d1df..2dfac075843 100644
---- a/Doc/using/mac.rst
-+++ b/Doc/using/mac.rst
-@@ -188,6 +188,28 @@
- * `PyInstaller `__: A cross-platform packaging tool that creates
- a single file or folder as a distributable artifact.
-
-+App Store Compliance
-+--------------------
++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.
+
-+Apps submitted for distribution through the macOS App Store 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.
++### Building a multi-architecture iOS XCframework
+
-+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
-+review rules cannot be challenged. Therefore, it is necessary to modify the
-+Python standard library for an app to pass App Store review.
++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::
+
-+The Python source tree contains
-+: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 CPython is configured with the
-+:option:`--with-app-store-compliance` option.
++ python Apple build iOS
+
-+This patch is not normally required to use CPython on a Mac; nor is it required
-+if you are distributing an app *outside* the macOS App Store. It is *only*
-+required if you are using the macOS App Store as a distribution channel.
++This will:
+
- Other Resources
- ===============
-
---- /dev/null
-+++ b/Lib/_apple_support.py
-@@ -0,0 +1,66 @@
-+import io
-+import sys
++* 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.
+
-+def init_streams(log_write, stdout_level, stderr_level):
-+ # Redirect stdout and stderr to the Apple system log. This method is
-+ # invoked by init_apple_streams() (initconfig.c) if config->use_system_logger
-+ # is enabled.
-+ sys.stdout = SystemLog(log_write, stdout_level, errors=sys.stderr.errors)
-+ sys.stderr = SystemLog(log_write, stderr_level, errors=sys.stderr.errors)
++### 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.
+
-+class SystemLog(io.TextIOWrapper):
-+ def __init__(self, log_write, level, **kwargs):
-+ kwargs.setdefault("encoding", "UTF-8")
-+ kwargs.setdefault("line_buffering", True)
-+ super().__init__(LogStream(log_write, level), **kwargs)
++#### iOS specific arguments to configure
+
-+ def __repr__(self):
-+ return f""
++* `--enable-framework[=DIR]`
+
-+ def write(self, s):
-+ if not isinstance(s, str):
-+ raise TypeError(
-+ f"write() argument must be str, not {type(s).__name__}")
++ 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.
+
-+ # In case `s` is a str subclass that writes itself to stdout or stderr
-+ # when we call its methods, convert it to an actual str.
-+ s = str.__str__(s)
++ This argument *must* be provided when configuring iOS builds. iOS does not
++ support non-framework builds.
+
-+ # We want to emit one log message per line, so split
-+ # the string before sending it to the superclass.
-+ for line in s.splitlines(keepends=True):
-+ super().write(line)
++* `--with-framework-name=NAME`
+
-+ return len(s)
++ Specify the name for the Python framework; defaults to `Python`.
+
++ > [!NOTE]
++ > Unless you know what you're doing, changing the name of the Python
++ > framework on iOS is not advised. If you use this option, you won't be able
++ > to run the `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.
+
-+class LogStream(io.RawIOBase):
-+ def __init__(self, log_write, level):
-+ self.log_write = log_write
-+ self.level = level
++#### Building Python for iOS
+
-+ def __repr__(self):
-+ return f""
++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.
+
-+ def writable(self):
-+ return True
++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
++```
+
-+ def write(self, b):
-+ if type(b) is not bytes:
-+ try:
-+ b = bytes(memoryview(b))
-+ except TypeError:
-+ raise TypeError(
-+ f"write() argument must be bytes-like, not {type(b).__name__}"
-+ ) from None
++In this invocation:
+
-+ # Writing an empty string to the stream should have no effect.
-+ if b:
-+ # Encode null bytes using "modified UTF-8" to avoid truncating the
-+ # message. This should not affect the return value, as the caller
-+ # may be expecting it to match the length of the input.
-+ self.log_write(self.level, b.replace(b"\x00", b"\xc0\x80"))
++* `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.
+
-+ return len(b)
---- /dev/null
-+++ b/Lib/_ios_support.py
-@@ -0,0 +1,71 @@
-+import sys
-+try:
-+ from ctypes import cdll, c_void_p, c_char_p, util
-+except ImportError:
-+ # ctypes is an optional module. If it's not present, we're limited in what
-+ # we can tell about the system, but we don't want to prevent the module
-+ # from working.
-+ print("ctypes isn't available; iOS system calls will not be available")
-+ objc = None
-+else:
-+ # ctypes is available. Load the ObjC library, and wrap the objc_getClass,
-+ # sel_registerName methods
-+ lib = util.find_library("objc")
-+ if lib is None:
-+ # Failed to load the objc library
-+ raise RuntimeError("ObjC runtime library couldn't be loaded")
++ 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.
+
-+ objc = cdll.LoadLibrary(lib)
-+ objc.objc_getClass.restype = c_void_p
-+ objc.objc_getClass.argtypes = [c_char_p]
-+ objc.sel_registerName.restype = c_void_p
-+ objc.sel_registerName.argtypes = [c_char_p]
++ These scripts will, by default, use the currently active Xcode installation.
++ If you want to use a different Xcode installation, you can use
++ `xcode-select` to set a new default Xcode globally, or you can use the
++ `DEVELOPER_DIR` environment variable to specify an Xcode install. The
++ scripts will use the default `iphoneos`/`iphonesimulator` SDK version for
++ the select Xcode install; if you want to use a different SDK, you can set the
++ `IOS_SDK_VERSION` environment variable. (e.g, setting
++ `IOS_SDK_VERSION=17.1` would cause the scripts to use the `iphoneos17.1`
++ and `iphonesimulator17.1` SDKs, regardless of the Xcode default.)
+
++ The path has also been cleared of any user customizations. A common source of
++ bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS
++ build. Resetting the path to a known "bare bones" value is the easiest way to
++ avoid these problems.
+
-+def get_platform_ios():
-+ # Determine if this is a simulator using the multiarch value
-+ is_simulator = sys.implementation._multiarch.endswith("simulator")
++* `--host` is the architecture and ABI that you want to build, in GNU compiler
++ triple format. This will be one of:
+
-+ # We can't use ctypes; abort
-+ if not objc:
-+ return None
++ - `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.
+
-+ # Most of the methods return ObjC objects
-+ objc.objc_msgSend.restype = c_void_p
-+ # All the methods used have no arguments.
-+ objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
++* `--build` is the GNU compiler triple for the machine that will be running
++ the compiler. This is one of:
+
-+ # Equivalent of:
-+ # device = [UIDevice currentDevice]
-+ UIDevice = objc.objc_getClass(b"UIDevice")
-+ SEL_currentDevice = objc.sel_registerName(b"currentDevice")
-+ device = objc.objc_msgSend(UIDevice, SEL_currentDevice)
++ - `arm64-apple-darwin` for Apple Silicon devices.
++ - `x86_64-apple-darwin` for Intel devices.
+
-+ # Equivalent of:
-+ # device_systemVersion = [device systemVersion]
-+ SEL_systemVersion = objc.sel_registerName(b"systemVersion")
-+ device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion)
++* `/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.
+
-+ # Equivalent of:
-+ # device_systemName = [device systemName]
-+ SEL_systemName = objc.sel_registerName(b"systemName")
-+ device_systemName = objc.objc_msgSend(device, SEL_systemName)
++* 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.
+
-+ # Equivalent of:
-+ # device_model = [device model]
-+ SEL_model = objc.sel_registerName(b"model")
-+ device_model = objc.objc_msgSend(device, SEL_model)
++ 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.
+
-+ # UTF8String returns a const char*;
-+ SEL_UTF8String = objc.sel_registerName(b"UTF8String")
-+ objc.objc_msgSend.restype = c_char_p
++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.
+
-+ # Equivalent of:
-+ # system = [device_systemName UTF8String]
-+ # release = [device_systemVersion UTF8String]
-+ # model = [device_model UTF8String]
-+ system = objc.objc_msgSend(device_systemName, SEL_UTF8String).decode()
-+ release = objc.objc_msgSend(device_systemVersion, SEL_UTF8String).decode()
-+ model = objc.objc_msgSend(device_model, SEL_UTF8String).decode()
++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.
+
-+ return system, release, model, is_simulator
-diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
-index 6cedee74236..0e88cffc74f 100644
---- a/Lib/ctypes/__init__.py
-+++ b/Lib/ctypes/__init__.py
-@@ -346,6 +346,17 @@
- winmode=None):
- if name:
- name = _os.fspath(name)
++## Testing Python on iOS
+
-+ # 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 name.endswith(".fwork"):
-+ with open(name) as f:
-+ name = _os.path.join(
-+ _os.path.dirname(_sys.executable),
-+ f.read().strip()
-+ )
++### Testing a multi-architecture framework
+
- self._name = name
- flags = self._func_flags_
- if use_errno:
-diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
-index c550883e7c7..12d7428fe9a 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 == "darwin":
-+elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "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 9b8a8dfc5aa..7a4fad2f746 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'
-+_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos'
- _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY
- + _CASE_INSENSITIVE_PLATFORMS_STR_KEY)
-
-@@ -1698,6 +1698,46 @@
- return f'FileFinder({self.path!r})'
-
-
-+class AppleFrameworkLoader(ExtensionFileLoader):
-+ """A loader for modules that have been packaged as frameworks for
-+ compatibility with Apple's iOS App Store policies.
-+ """
-+ def create_module(self, spec):
-+ # If the ModuleSpec has been created by the FileFinder, it will have
-+ # been created with an origin pointing to the .fwork file. We need to
-+ # redirect this to the location in the Frameworks folder, using the
-+ # content of the .fwork file.
-+ if spec.origin.endswith(".fwork"):
-+ with _io.FileIO(spec.origin, 'r') as file:
-+ framework_binary = file.read().decode().strip()
-+ bundle_path = _path_split(sys.executable)[0]
-+ spec.origin = _path_join(bundle_path, framework_binary)
++Once you have a built an XCframework, you can test that framework by running:
+
-+ # If the loader is created based on the spec for a loaded module, the
-+ # path will be pointing at the Framework location. If this occurs,
-+ # get the original .fwork location to use as the module's __file__.
-+ if self.path.endswith(".fwork"):
-+ path = self.path
-+ else:
-+ with _io.FileIO(self.path + ".origin", 'r') as file:
-+ origin = file.read().decode().strip()
-+ bundle_path = _path_split(sys.executable)[0]
-+ path = _path_join(bundle_path, origin)
++ $ python Apple test iOS
+
-+ module = _bootstrap._call_with_frames_removed(_imp.create_dynamic, spec)
++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.
+
-+ _bootstrap._verbose_message(
-+ "Apple framework extension module {!r} loaded from {!r} (path {!r})",
-+ spec.name,
-+ spec.origin,
-+ path,
-+ )
++If the test runner is executed on GitHub Actions, the `GITHUB_ACTIONS`
++environment variable will be exposed to the iOS process at runtime.
+
-+ # Ensure that the __file__ points at the .fwork location
-+ module.__file__ = path
++### Testing a single-architecture framework
+
-+ return module
++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.
+
- # Import setup ###############################################################
-
- def _fix_up_module(ns, name, pathname, cpathname=None):
-@@ -1730,10 +1770,17 @@
-
- Each item is a tuple (loader, suffixes).
- """
-- extensions = ExtensionFileLoader, _imp.extension_suffixes()
-+ if sys.platform in {"ios", "tvos", "watchos"}:
-+ extension_loaders = [(AppleFrameworkLoader, [
-+ suffix.replace(".so", ".fwork")
-+ for suffix in _imp.extension_suffixes()
-+ ])]
-+ else:
-+ extension_loaders = []
-+ extension_loaders.append((ExtensionFileLoader, _imp.extension_suffixes()))
- source = SourceFileLoader, SOURCE_SUFFIXES
- bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES
-- return [extensions, source, bytecode]
-+ return extension_loaders + [source, bytecode]
-
-
- def _set_bootstrap_module(_bootstrap_module):
-diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
-index b56fa94eb9c..37fef357fe2 100644
---- a/Lib/importlib/abc.py
-+++ b/Lib/importlib/abc.py
-@@ -180,7 +180,11 @@
- else:
- return self.source_to_code(source, path)
-
--_register(ExecutionLoader, machinery.ExtensionFileLoader)
-+_register(
-+ ExecutionLoader,
-+ machinery.ExtensionFileLoader,
-+ machinery.AppleFrameworkLoader,
-+)
-
-
- class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader):
-diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py
-index d9a19a13f7b..fbd30b159fb 100644
---- a/Lib/importlib/machinery.py
-+++ b/Lib/importlib/machinery.py
-@@ -12,6 +12,7 @@
- from ._bootstrap_external import SourceFileLoader
- from ._bootstrap_external import SourcelessFileLoader
- from ._bootstrap_external import ExtensionFileLoader
-+from ._bootstrap_external import AppleFrameworkLoader
- from ._bootstrap_external import NamespaceLoader
-
-
-diff --git a/Lib/inspect.py b/Lib/inspect.py
-index b630cb28359..bd1a99af211 100644
---- a/Lib/inspect.py
-+++ b/Lib/inspect.py
-@@ -961,6 +961,10 @@
- elif any(filename.endswith(s) for s in
- importlib.machinery.EXTENSION_SUFFIXES):
- return None
-+ elif filename.endswith(".fwork"):
-+ # Apple mobile framework markers are another type of non-source file
-+ return None
++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
++```
+
- # return a filename found in the linecache even if it doesn't exist on disk
- if filename in linecache.cache:
- return filename
-@@ -991,6 +995,7 @@
- return object
- if hasattr(object, '__module__'):
- return sys.modules.get(object.__module__)
++This will:
+
- # Try the filename to modulename cache
- if _filename is not None and _filename in modulesbyfile:
- return sys.modules.get(modulesbyfile[_filename])
-@@ -1084,7 +1089,7 @@
- # Allow filenames in form of "" to pass through.
- # `doctest` monkeypatches `linecache` module to enable
- # inspection, so let `linecache.getlines` to be called.
-- if not (file.startswith('<') and file.endswith('>')):
-+ if (not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'):
- raise OSError('source code not available')
-
- module = getmodule(object, file)
-diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py
-index a0a020f9eeb..ac478ee7f51 100644
---- a/Lib/modulefinder.py
-+++ b/Lib/modulefinder.py
-@@ -72,7 +72,12 @@
- if isinstance(spec.loader, importlib.machinery.SourceFileLoader):
- kind = _PY_SOURCE
-
-- elif isinstance(spec.loader, importlib.machinery.ExtensionFileLoader):
-+ elif isinstance(
-+ spec.loader, (
-+ importlib.machinery.ExtensionFileLoader,
-+ importlib.machinery.AppleFrameworkLoader,
-+ )
-+ ):
- kind = _C_EXTENSION
-
- elif isinstance(spec.loader, importlib.machinery.SourcelessFileLoader):
-diff --git a/Lib/platform.py b/Lib/platform.py
-index c5b60480369..c99f08899e4 100755
---- a/Lib/platform.py
-+++ b/Lib/platform.py
-@@ -497,6 +497,78 @@
- # If that also doesn't work return the default values
- return release, versioninfo, machine
-
-+
-+# A namedtuple for iOS version information.
-+IOSVersionInfo = collections.namedtuple(
-+ "IOSVersionInfo",
-+ ["system", "release", "model", "is_simulator"]
-+)
-+
-+
-+def ios_ver(system="", release="", model="", is_simulator=False):
-+ """Get iOS version information, and return it as a namedtuple:
-+ (system, release, model, is_simulator).
-+
-+ If values can't be determined, they are set to values provided as
-+ parameters.
-+ """
-+ if sys.platform == "ios":
-+ import _ios_support
-+ result = _ios_support.get_platform_ios()
-+ if result is not None:
-+ return IOSVersionInfo(*result)
-+
-+ return IOSVersionInfo(system, release, model, is_simulator)
++* Build an iOS framework for your chosen architecture;
++* Finalize the single-platform framework;
++* Make a clean copy of the testbed project;
++* Install the Python iOS framework into the copy of the testbed project; and
++* Run the test suite on an "entry-level device" simulator (i.e., an iPhone SE,
++ iPhone 16e, or a similar).
+
++On success, the test suite will exit and report successful completion of the
++test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15
++minutes to run; a couple of extra minutes is required to compile the testbed
++project, and then boot and prepare the iOS simulator.
+
-+# A namedtuple for tvOS version information.
-+TVOSVersionInfo = collections.namedtuple(
-+ "TVOSVersionInfo",
-+ ["system", "release", "model", "is_simulator"]
-+)
++### Debugging test failures
+
++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.`.
+
-+def tvos_ver(system="", release="", model="", is_simulator=False):
-+ """Get tvOS version information, and return it as a namedtuple:
-+ (system, release, model, is_simulator).
++You can generate your own standalone testbed instance by running:
++```
++python cross-build/iOS/testbed clone my-testbed
++```
+
-+ 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)
++In this invocation, `my-testbed` is the name of the folder for the new
++testbed clone.
+
-+ return TVOSVersionInfo(system, release, model, is_simulator)
++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
++```
+
++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`.
+
-+# A namedtuple for watchOS version information.
-+WatchOSVersionInfo = collections.namedtuple(
-+ "WatchOSVersionInfo",
-+ ["system", "release", "model", "is_simulator"]
-+)
++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.
+
-+def watchos_ver(system="", release="", model="", is_simulator=False):
-+ """Get watchOS version information, and return it as a namedtuple:
-+ (system, release, model, is_simulator).
++### Testing in Xcode
+
-+ 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)
++You can also open the testbed project in Xcode by running:
++```
++open my-testbed/iOSTestbed.xcodeproj
++```
+
-+ return WatchOSVersionInfo(system, release, model, is_simulator)
++This will allow you to use the full Xcode suite of tools for debugging.
+
++The arguments used to run the test suite are defined as part of the test plan.
++To modify the test plan, select the test plan node of the project tree (it
++should be the first child of the root node), and select the "Configurations"
++tab. Modify the "Arguments Passed On Launch" value to change the testing
++arguments.
+
- def _java_getprop(name, default):
-
- from java.lang import System
-@@ -612,7 +684,7 @@
- if cleaned == platform:
- break
- platform = cleaned
-- while platform[-1] == '-':
-+ while platform and platform[-1] == '-':
- platform = platform[:-1]
-
- return platform
-@@ -653,7 +725,7 @@
- default in case the command should fail.
-
- """
-- if sys.platform in ('dos', 'win32', 'win16'):
-+ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}:
- # XXX Others too ?
- return default
-
-@@ -815,6 +887,25 @@
- csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
- return 'Alpha' if cpu_number >= 128 else 'VAX'
-
-+ # On the iOS/tvOS/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'
++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.
+
-+ def get_tvos():
-+ if sys.implementation._multiarch.endswith("simulator"):
-+ return os.uname().machine
-+ return 'arm64'
++### Testing on an iOS device
+
-+ def get_watchos():
-+ if sys.implementation._multiarch.endswith("simulator"):
-+ return os.uname().machine
-+ return 'arm64_32'
++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 from_subprocess():
- """
- Fall back to `uname -p`
-@@ -969,6 +1060,14 @@
- system = 'Windows'
- release = 'Vista'
-
-+ # 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 == 'watchos':
-+ system, release, _, _ = watchos_ver()
++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.
+--- /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@
++
++
+--- /dev/null
++++ b/Apple/iOS/Resources/bin/arm64-apple-ios-ar
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@"
+--- /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} "$@"
+--- /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} "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /dev/null
++++ b/Apple/iOS/Resources/pyconfig.h
+@@ -0,0 +1,7 @@
++#ifdef __arm64__
++#include "pyconfig-arm64.h"
++#endif
+
- vals = system, node, release, version, machine
- # Replace 'unknown' values with the more portable ''
- _uname_cache = uname_result(*map(_unknown_as_blank, vals))
-@@ -1248,11 +1347,18 @@
- system, release, version = system_alias(system, release, version)
-
- if system == 'Darwin':
-- # macOS (darwin kernel)
-- macos_release = mac_ver()[0]
-- if macos_release:
-- system = 'macOS'
-- release = macos_release
-+ # 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 == "watchos":
-+ system, release, _, _ = watchos_ver()
-+ else:
-+ macos_release = mac_ver()[0]
-+ if macos_release:
-+ system = 'macOS'
-+ release = macos_release
-
- if system == 'Windows':
- # MS platforms
-diff --git a/Lib/site.py b/Lib/site.py
-index aed254ad504..ecbb2f57b3c 100644
---- a/Lib/site.py
-+++ b/Lib/site.py
-@@ -287,8 +287,8 @@
- if env_base:
- return env_base
-
-- # Emscripten, VxWorks, and WASI have no home directories
-- if sys.platform in {"emscripten", "vxworks", "wasi"}:
-+ # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
-+ if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
- return None
-
- def joinuser(*args):
-diff --git a/Lib/subprocess.py b/Lib/subprocess.py
-index 1d17ae3608a..34dfa0019a5 100644
---- a/Lib/subprocess.py
-+++ b/Lib/subprocess.py
-@@ -74,8 +74,8 @@
- else:
- _mswindows = True
-
--# wasm32-emscripten and wasm32-wasi do not support processes
--_can_fork_exec = sys.platform not in {"emscripten", "wasi"}
-+# some platforms do not support subprocesses
-+_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"}
-
- if _mswindows:
- import _winapi
-@@ -103,18 +103,22 @@
- if _can_fork_exec:
- from _posixsubprocess import fork_exec as _fork_exec
- # used in methods that are called by __del__
-- _waitpid = os.waitpid
-- _waitstatus_to_exitcode = os.waitstatus_to_exitcode
-- _WIFSTOPPED = os.WIFSTOPPED
-- _WSTOPSIG = os.WSTOPSIG
-- _WNOHANG = os.WNOHANG
-+ class _del_safe:
-+ waitpid = os.waitpid
-+ waitstatus_to_exitcode = os.waitstatus_to_exitcode
-+ WIFSTOPPED = os.WIFSTOPPED
-+ WSTOPSIG = os.WSTOPSIG
-+ WNOHANG = os.WNOHANG
-+ ECHILD = errno.ECHILD
- else:
-- _fork_exec = None
-- _waitpid = None
-- _waitstatus_to_exitcode = None
-- _WIFSTOPPED = None
-- _WSTOPSIG = None
-- _WNOHANG = None
-+ class _del_safe:
-+ waitpid = None
-+ waitstatus_to_exitcode = None
-+ WIFSTOPPED = None
-+ WSTOPSIG = None
-+ WNOHANG = None
-+ ECHILD = errno.ECHILD
-+
- import select
- import selectors
-
-@@ -1958,20 +1962,16 @@
- raise child_exception_type(err_msg)
-
-
-- def _handle_exitstatus(self, sts,
-- _waitstatus_to_exitcode=_waitstatus_to_exitcode,
-- _WIFSTOPPED=_WIFSTOPPED,
-- _WSTOPSIG=_WSTOPSIG):
-+ def _handle_exitstatus(self, sts, _del_safe=_del_safe):
- """All callers to this function MUST hold self._waitpid_lock."""
- # This method is called (indirectly) by __del__, so it cannot
- # refer to anything outside of its local scope.
-- if _WIFSTOPPED(sts):
-- self.returncode = -_WSTOPSIG(sts)
-+ if _del_safe.WIFSTOPPED(sts):
-+ self.returncode = -_del_safe.WSTOPSIG(sts)
- else:
-- self.returncode = _waitstatus_to_exitcode(sts)
-+ self.returncode = _del_safe.waitstatus_to_exitcode(sts)
-
-- def _internal_poll(self, _deadstate=None, _waitpid=_waitpid,
-- _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD):
-+ def _internal_poll(self, _deadstate=None, _del_safe=_del_safe):
- """Check if child process has terminated. Returns returncode
- attribute.
-
-@@ -1987,13 +1987,13 @@
- try:
- if self.returncode is not None:
- return self.returncode # Another thread waited.
-- pid, sts = _waitpid(self.pid, _WNOHANG)
-+ pid, sts = _del_safe.waitpid(self.pid, _del_safe.WNOHANG)
- if pid == self.pid:
- self._handle_exitstatus(sts)
- except OSError as e:
- if _deadstate is not None:
- self.returncode = _deadstate
-- elif e.errno == _ECHILD:
-+ elif e.errno == _del_safe.ECHILD:
- # This happens if SIGCLD is set to be ignored or
- # waiting for child processes has otherwise been
- # disabled for our process. This child is dead, we
-diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
-index 517b13acaf6..eb4bf0d50a8 100644
---- a/Lib/sysconfig.py
-+++ b/Lib/sysconfig.py
-@@ -21,6 +21,7 @@
-
- # Keys for get_config_var() that are never converted to Python integers.
- _ALWAYS_STR = {
-+ 'IPHONEOS_DEPLOYMENT_TARGET',
- 'MACOSX_DEPLOYMENT_TARGET',
- }
-
-@@ -57,6 +58,7 @@
- 'scripts': '{base}/Scripts',
- 'data': '{base}',
- },
-+
- # Downstream distributors can overwrite the default install scheme.
- # This is done to support downstream modifications where distributors change
- # the installation layout (eg. different site-packages directory).
-@@ -112,8 +114,8 @@
- if env_base:
- return env_base
-
-- # Emscripten, VxWorks, and WASI have no home directories
-- if sys.platform in {"emscripten", "vxworks", "wasi"}:
-+ # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
-+ if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
- return None
-
- def joinuser(*args):
-@@ -292,6 +294,7 @@
- 'home': 'posix_home',
- 'user': 'osx_framework_user',
- }
-+
- return {
- 'prefix': 'posix_prefix',
- 'home': 'posix_home',
-@@ -823,10 +826,23 @@
- if m:
- release = m.group()
- elif osname[:6] == "darwin":
-- import _osx_support
-- osname, release, machine = _osx_support.get_platform_osx(
-- get_config_vars(),
-- osname, release, machine)
-+ if sys.platform == "ios":
-+ 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", "9.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
-+ else:
-+ import _osx_support
-+ osname, release, machine = _osx_support.get_platform_osx(
-+ get_config_vars(),
-+ osname, release, machine)
-
- return f"{osname}-{release}-{machine}"
-
-diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
-index 6efeaad8126..e9b0df085d0 100644
---- a/Lib/test/pythoninfo.py
-+++ b/Lib/test/pythoninfo.py
-@@ -287,6 +287,7 @@
- "HOMEDRIVE",
- "HOMEPATH",
- "IDLESTARTUP",
-+ "IPHONEOS_DEPLOYMENT_TARGET",
- "LANG",
- "LDFLAGS",
- "LDSHARED",
-diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
-index 20f38fd36a8..1f64710c9c2 100644
---- a/Lib/test/support/os_helper.py
-+++ b/Lib/test/support/os_helper.py
-@@ -22,8 +22,8 @@
-
- # TESTFN_UNICODE is a non-ascii filename
- TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f"
--if sys.platform == 'darwin':
-- # In Mac OS X's VFS API file names are, by definition, canonically
-+if support.is_apple:
-+ # On Apple's VFS API file names are, by definition, canonically
- # decomposed Unicode, encoded using UTF-8. See QA1173:
- # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
- import unicodedata
-@@ -48,8 +48,8 @@
- 'encoding (%s). Unicode filename tests may not be effective'
- % (TESTFN_UNENCODABLE, sys.getfilesystemencoding()))
- TESTFN_UNENCODABLE = None
--# macOS and Emscripten deny unencodable filenames (invalid utf-8)
--elif sys.platform not in {'darwin', 'emscripten', 'wasi'}:
-+# Apple and Emscripten deny unencodable filenames (invalid utf-8)
-+elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}:
- try:
- # ascii and utf-8 cannot encode the byte 0xff
- b'\xff'.decode(sys.getfilesystemencoding())
-@@ -615,7 +615,8 @@
- if hasattr(os, 'sysconf'):
- try:
- MAXFD = os.sysconf("SC_OPEN_MAX")
-- except OSError:
-+ except (OSError, ValueError):
-+ # gh-118201: ValueError is raised intermittently on iOS
- pass
-
- old_modes = None
++#ifdef __x86_64__
++#include "pyconfig-x86_64.h"
++#endif
--- /dev/null
-+++ b/Lib/test/test_apple.py
-@@ -0,0 +1,155 @@
-+import unittest
-+from _apple_support import SystemLog
-+from test.support import is_apple_mobile
-+from unittest.mock import Mock, call
-+
-+if not is_apple_mobile:
-+ raise unittest.SkipTest("iOS-specific")
-+
-+
-+# Test redirection of stdout and stderr to the Apple system log.
-+class TestAppleSystemLogOutput(unittest.TestCase):
-+ maxDiff = None
-+
-+ def assert_writes(self, output):
-+ self.assertEqual(
-+ self.log_write.mock_calls,
-+ [
-+ call(self.log_level, line)
-+ for line in output
-+ ]
-+ )
-+
-+ self.log_write.reset_mock()
-+
-+ def setUp(self):
-+ self.log_write = Mock()
-+ self.log_level = 42
-+ self.log = SystemLog(self.log_write, self.log_level, errors="replace")
++++ b/Apple/testbed/Python.xcframework/Info.plist
+@@ -0,0 +1,106 @@
++
++
++
++
++ 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
++ 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
++
++
+--- /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
++
++
+--- /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
++
++
+--- /dev/null
++++ b/Apple/testbed/Python.xcframework/build/utils.sh
+@@ -0,0 +1,174 @@
++# Utility methods for use in an Xcode project.
++#
++# An iOS XCframework cannot include any content other than the library binary
++# and relevant metadata. However, Python requires a standard library at runtime.
++# Therefore, it is necessary to add a build step to an Xcode app target that
++# processes the standard library and puts the content into the final app.
++#
++# In general, these tools will be invoked after bundle resources have been
++# copied into the app, but before framework embedding (and signing).
++#
++# The following is an example script, assuming that:
++# * Python.xcframework is in the root of the project
++# * There is an `app` folder that contains the app code
++# * There is an `app_packages` folder that contains installed Python packages.
++# -----
++# set -e
++# source $PROJECT_DIR/Python.xcframework/build/build_utils.sh
++# install_python Python.xcframework app app_packages
++# -----
++
++# Copy the standard library from the XCframework into the app bundle.
++#
++# Accepts one argument:
++# 1. The path, relative to the root of the Xcode project, where the Python
++# XCframework can be found.
++install_stdlib() {
++ PYTHON_XCFRAMEWORK_PATH=$1
++
++ mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib"
++ if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then
++ echo "Installing Python modules for iOS Simulator"
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/ios-arm64-simulator" ]; then
++ SLICE_FOLDER="ios-arm64-simulator"
++ else
++ SLICE_FOLDER="ios-arm64_x86_64-simulator"
++ fi
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then
++ echo "Installing Python modules for iOS Device"
++ SLICE_FOLDER="ios-arm64"
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then
++ echo "Installing Python modules for tvOS Simulator"
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then
++ SLICE_FOLDER="tvos-arm64-simulator"
++ else
++ SLICE_FOLDER="tvos-arm64_x86_64-simulator"
++ fi
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then
++ echo "Installing Python modules for tvOS Device"
++ SLICE_FOLDER="tvos-arm64"
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then
++ echo "Installing Python modules for watchOS Simulator"
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then
++ SLICE_FOLDER="watchos-arm64-simulator"
++ else
++ SLICE_FOLDER="watchos-arm64_x86_64-simulator"
++ fi
++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then
++ echo "Installing Python modules for watchOS Device"
++ SLICE_FOLDER="watchos-arm64"
++ else
++ echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME"
++ exit 1
++ fi
+
-+ def test_repr(self):
-+ self.assertEqual(repr(self.log), "")
-+ self.assertEqual(repr(self.log.buffer), "")
++ # 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.
++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then
++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/"
++ rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/"
++ else
++ # A single-arch framework will have a libpython symlink; that can't be included at runtime
++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib'
++ fi
++}
+
-+ def test_log_config(self):
-+ self.assertIs(self.log.writable(), True)
-+ self.assertIs(self.log.readable(), False)
++# Convert a single .so library into a framework that iOS can load.
++#
++# Accepts three arguments:
++# 1. The path, relative to the root of the Xcode project, where the Python
++# XCframework can be found.
++# 2. The base path, relative to the installed location in the app bundle, that
++# needs to be processed. Any .so file found in this path (or a subdirectory
++# of it) will be processed.
++# 2. The full path to a single .so file to process. This path should include
++# the base path.
++install_dylib () {
++ PYTHON_XCFRAMEWORK_PATH=$1
++ INSTALL_BASE=$2
++ FULL_EXT=$3
++
++ # The name of the extension file
++ EXT=$(basename "$FULL_EXT")
++ # The name and location of the module
++ MODULE_PATH=$(dirname "$FULL_EXT")
++ MODULE_NAME=$(echo $EXT | cut -d "." -f 1)
++ # The location of the extension file, relative to the bundle
++ RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/}
++ # The path to the extension file, relative to the install base
++ PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}
++ # The full dotted name of the extension module, constructed from the file path.
++ FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" ".");
++ # A bundle identifier; not actually used, but required by Xcode framework packaging
++ FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-")
++ # The name of the framework folder.
++ FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework"
++
++ # If the framework folder doesn't exist, create it.
++ if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then
++ echo "Creating framework for $RELATIVE_EXT"
++ mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER"
++ cp "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/build/$PLATFORM_FAMILY_NAME-dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
++ plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
++ plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
++ fi
+
-+ self.assertEqual("UTF-8", self.log.encoding)
-+ self.assertEqual("replace", self.log.errors)
++ 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
+
-+ self.assertIs(self.log.line_buffering, True)
-+ self.assertIs(self.log.write_through, False)
++ 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"
++}
+
-+ def test_empty_str(self):
-+ self.log.write("")
-+ self.log.flush()
++# 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
++}
+
-+ self.assert_writes([])
++# The entry point for post-processing a Python XCframework.
++#
++# Accepts 1 or more arguments:
++# 1. The path, relative to the root of the Xcode project, where the Python
++# XCframework can be found. If the XCframework is in the root of the project,
++# 2+. The path of a package, relative to the root of the packaged app, that contains
++# library content that should be processed for binary libraries.
++install_python() {
++ PYTHON_XCFRAMEWORK_PATH=$1
++ shift
++
++ install_stdlib $PYTHON_XCFRAMEWORK_PATH
++ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib")
++ echo "Install Python $PYTHON_VER standard library extension modules..."
++ process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload
++
++ for package_path in $@; do
++ echo "Installing $package_path extension modules ..."
++ process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path
++ done
++}
+--- /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
++
++
+--- /dev/null
++++ b/Apple/testbed/Python.xcframework/ios-arm64/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
+
-+ def test_simple_str(self):
-+ self.log.write("hello world\n")
++It should be used as a target for `--enable-framework` when compiling an iOS on-device
++build for testing purposes.
+--- /dev/null
++++ b/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
+
-+ self.assert_writes([b"hello world\n"])
++It should be used as a target for `--enable-framework` when compiling an iOS simulator
++build for testing purposes (either x86_64 or ARM64).
+--- /dev/null
++++ b/Apple/testbed/Python.xcframework/tvos-arm64/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
+
-+ def test_buffered_str(self):
-+ self.log.write("h")
-+ self.log.write("ello")
-+ self.log.write(" ")
-+ self.log.write("world\n")
-+ self.log.write("goodbye.")
-+ self.log.flush()
++It should be used as a target for `--enable-framework` when compiling a tvOS
++on-device build for testing purposes.
+--- /dev/null
++++ b/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
+
-+ self.assert_writes([b"hello world\n", b"goodbye."])
++It should be used as a target for `--enable-framework` when compiling a tvOS
++simulator build for testing purposes (either x86_64 or ARM64).
+--- /dev/null
++++ b/Apple/testbed/Python.xcframework/watchos-arm64_32/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
+
-+ def test_manual_flush(self):
-+ self.log.write("Hello")
++It should be used as a target for `--enable-framework` when compiling a watchOS on-device
++build for testing purposes.
+--- /dev/null
++++ b/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README
+@@ -0,0 +1,4 @@
++This directory is intentionally empty.
+
-+ self.assert_writes([])
++It should be used as a target for `--enable-framework` when compiling a watchOS
++simulator build for testing purposes (either x86_64 or ARM64).
+--- /dev/null
++++ b/Apple/testbed/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
+--- /dev/null
++++ b/Apple/testbed/TestbedTests/TestbedTests.m
+@@ -0,0 +1,198 @@
++#import
++#import
+
-+ self.log.write(" world\nHere for a while...\nGoodbye")
-+ self.assert_writes([b"Hello world\n", b"Here for a while...\n"])
++@interface TestbedTests : XCTestCase
+
-+ self.log.write(" world\nHello again")
-+ self.assert_writes([b"Goodbye world\n"])
++@end
+
-+ self.log.flush()
-+ self.assert_writes([b"Hello again"])
++@implementation TestbedTests
+
-+ def test_non_ascii(self):
-+ # Spanish
-+ self.log.write("ol\u00e9\n")
-+ self.assert_writes([b"ol\xc3\xa9\n"])
+
-+ # Chinese
-+ self.log.write("\u4e2d\u6587\n")
-+ self.assert_writes([b"\xe4\xb8\xad\xe6\x96\x87\n"])
++- (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;
+
-+ # Printing Non-BMP emoji
-+ self.log.write("\U0001f600\n")
-+ self.assert_writes([b"\xf0\x9f\x98\x80\n"])
++ NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
+
-+ # Non-encodable surrogates are replaced
-+ self.log.write("\ud800\udc00\n")
-+ self.assert_writes([b"??\n"])
++ // 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);
+
-+ def test_modified_null(self):
-+ # Null characters are logged using "modified UTF-8".
-+ self.log.write("\u0000\n")
-+ self.assert_writes([b"\xc0\x80\n"])
-+ self.log.write("a\u0000\n")
-+ self.assert_writes([b"a\xc0\x80\n"])
-+ self.log.write("\u0000b\n")
-+ self.assert_writes([b"\xc0\x80b\n"])
-+ self.log.write("a\u0000b\n")
-+ self.assert_writes([b"a\xc0\x80b\n"])
++ 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];
++ }
+
-+ def test_nonstandard_str(self):
-+ # String subclasses are accepted, but they should be converted
-+ # to a standard str without calling any of their methods.
-+ class CustomStr(str):
-+ def splitlines(self, *args, **kwargs):
-+ raise AssertionError()
++ // Generate an isolated Python configuration.
++ NSLog(@"Configuring isolated Python...");
++ PyPreConfig_InitIsolatedConfig(&preconfig);
++ PyConfig_InitIsolatedConfig(&config);
+
-+ def __len__(self):
-+ raise AssertionError()
++ // 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;
+
-+ def __str__(self):
-+ raise AssertionError()
++ 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;
++ }
+
-+ self.log.write(CustomStr("custom\n"))
-+ self.assert_writes([b"custom\n"])
++ // 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 test_non_str(self):
-+ # Non-string classes are not accepted.
-+ for obj in [b"", b"hello", None, 42]:
-+ with self.subTest(obj=obj):
-+ with self.assertRaisesRegex(
-+ TypeError,
-+ fr"write\(\) argument must be str, not "
-+ fr"{type(obj).__name__}"
-+ ):
-+ self.log.write(obj)
++ // 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;
++ }
+
-+ def test_byteslike_in_buffer(self):
-+ # The underlying buffer *can* accept bytes-like objects
-+ self.log.buffer.write(bytearray(b"hello"))
-+ self.log.flush()
++ 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;
++ }
+
-+ self.log.buffer.write(b"")
-+ self.log.flush()
++ 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;
++ }
+
-+ self.log.buffer.write(b"goodbye")
-+ self.log.flush()
++ // 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;
++ }
+
-+ self.assert_writes([b"hello", b"goodbye"])
++ 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;
++ }
+
-+ def test_non_byteslike_in_buffer(self):
-+ for obj in ["hello", None, 42]:
-+ with self.subTest(obj=obj):
-+ with self.assertRaisesRegex(
-+ TypeError,
-+ fr"write\(\) argument must be bytes-like, not "
-+ fr"{type(obj).__name__}"
-+ ):
-+ self.log.buffer.write(obj)
-diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
-index abf425f5ef0..ffcde82b63e 100644
---- a/Lib/test/test_asyncio/test_events.py
-+++ b/Lib/test/test_asyncio/test_events.py
-@@ -1894,6 +1894,7 @@
- else:
- self.assertEqual(-signal.SIGKILL, returncode)
-
-+ @support.requires_subprocess()
- def test_subprocess_exec(self):
- prog = os.path.join(os.path.dirname(__file__), 'echo.py')
-
-@@ -1915,6 +1916,7 @@
- self.check_killed(proto.returncode)
- self.assertEqual(b'Python The Winner', proto.data[1])
-
-+ @support.requires_subprocess()
- def test_subprocess_interactive(self):
- prog = os.path.join(os.path.dirname(__file__), 'echo.py')
-
-@@ -1942,6 +1944,7 @@
- self.loop.run_until_complete(proto.completed)
- self.check_killed(proto.returncode)
-
-+ @support.requires_subprocess()
- def test_subprocess_shell(self):
- connect = self.loop.subprocess_shell(
- functools.partial(MySubprocessProtocol, self.loop),
-@@ -1958,6 +1961,7 @@
- self.assertEqual(proto.data[2], b'')
- transp.close()
-
-+ @support.requires_subprocess()
- def test_subprocess_exitcode(self):
- connect = self.loop.subprocess_shell(
- functools.partial(MySubprocessProtocol, self.loop),
-@@ -1969,6 +1973,7 @@
- self.assertEqual(7, proto.returncode)
- transp.close()
-
-+ @support.requires_subprocess()
- def test_subprocess_close_after_finish(self):
- connect = self.loop.subprocess_shell(
- functools.partial(MySubprocessProtocol, self.loop),
-@@ -1983,6 +1988,7 @@
- self.assertEqual(7, proto.returncode)
- self.assertIsNone(transp.close())
-
-+ @support.requires_subprocess()
- def test_subprocess_kill(self):
- prog = os.path.join(os.path.dirname(__file__), 'echo.py')
-
-@@ -1999,6 +2005,7 @@
- self.check_killed(proto.returncode)
- transp.close()
-
-+ @support.requires_subprocess()
- def test_subprocess_terminate(self):
- prog = os.path.join(os.path.dirname(__file__), 'echo.py')
-
-@@ -2016,6 +2023,7 @@
- transp.close()
-
- @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP")
-+ @support.requires_subprocess()
- def test_subprocess_send_signal(self):
- # bpo-31034: Make sure that we get the default signal handler (killing
- # the process). The parent process may have decided to ignore SIGHUP,
-@@ -2040,6 +2048,7 @@
- finally:
- signal.signal(signal.SIGHUP, old_handler)
-
-+ @support.requires_subprocess()
- def test_subprocess_stderr(self):
- prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
-
-@@ -2061,6 +2070,7 @@
- self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2])
- self.assertEqual(0, proto.returncode)
-
-+ @support.requires_subprocess()
- def test_subprocess_stderr_redirect_to_stdout(self):
- prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
-
-@@ -2086,6 +2096,7 @@
- transp.close()
- self.assertEqual(0, proto.returncode)
-
-+ @support.requires_subprocess()
- def test_subprocess_close_client_stream(self):
- prog = os.path.join(os.path.dirname(__file__), 'echo3.py')
-
-@@ -2120,6 +2131,7 @@
- self.loop.run_until_complete(proto.completed)
- self.check_killed(proto.returncode)
-
-+ @support.requires_subprocess()
- def test_subprocess_wait_no_same_group(self):
- # start the new process in a new session
- connect = self.loop.subprocess_shell(
-@@ -2132,6 +2144,7 @@
- self.assertEqual(7, proto.returncode)
- transp.close()
-
-+ @support.requires_subprocess()
- def test_subprocess_exec_invalid_args(self):
- async def connect(**kwds):
- await self.loop.subprocess_exec(
-@@ -2145,6 +2158,7 @@
- with self.assertRaises(ValueError):
- self.loop.run_until_complete(connect(shell=True))
-
-+ @support.requires_subprocess()
- def test_subprocess_shell_invalid_args(self):
-
- async def connect(cmd=None, **kwds):
-diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py
-index 686fef8377f..4f59c31dfe4 100644
---- a/Lib/test/test_asyncio/test_streams.py
-+++ b/Lib/test/test_asyncio/test_streams.py
-@@ -10,7 +10,6 @@
- import unittest
- from unittest import mock
- import warnings
--from test.support import socket_helper
- try:
- import ssl
- except ImportError:
-@@ -18,6 +17,7 @@
-
- import asyncio
- from test.test_asyncio import utils as test_utils
-+from test.support import requires_subprocess, socket_helper
-
-
- def tearDownModule():
-@@ -770,6 +770,7 @@
- self.assertEqual(msg2, b"hello world 2!\n")
-
- @unittest.skipIf(sys.platform == 'win32', "Don't have pipes")
-+ @requires_subprocess()
- def test_read_all_from_pipe_reader(self):
- # See asyncio issue 168. This test is derived from the example
- # subprocess_attach_read_pipe.py, but we configure the
-diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py
-index 859d2932c33..ed43895fd68 100644
---- a/Lib/test/test_asyncio/test_subprocess.py
-+++ b/Lib/test/test_asyncio/test_subprocess.py
-@@ -47,6 +47,7 @@
- self._proc.pid = -1
-
-
-+@support.requires_subprocess()
- class SubprocessTransportTests(test_utils.TestCase):
- def setUp(self):
- super().setUp()
-@@ -110,6 +111,7 @@
- transport.close()
-
-
-+@support.requires_subprocess()
- class SubprocessMixin:
-
- def test_stdin_stdout(self):
-diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py
-index 35c924a0cd6..9452213c685 100644
---- a/Lib/test/test_asyncio/test_unix_events.py
-+++ b/Lib/test/test_asyncio/test_unix_events.py
-@@ -1873,7 +1873,7 @@
- wsock.close()
-
-
--@unittest.skipUnless(hasattr(os, 'fork'), 'requires os.fork()')
-+@support.requires_fork()
- class TestFork(unittest.IsolatedAsyncioTestCase):
-
- async def test_fork_not_share_event_loop(self):
-diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
-index 98c74a44e4c..40284774a58 100644
---- a/Lib/test/test_capi/test_misc.py
-+++ b/Lib/test/test_capi/test_misc.py
-@@ -1979,6 +1979,13 @@
- self.addCleanup(os.close, r)
- self.addCleanup(os.close, w)
-
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if support.is_apple_mobile:
-+ loader = "AppleFrameworkLoader"
-+ else:
-+ loader = "ExtensionFileLoader"
++ 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);
+
- script = textwrap.dedent(f"""
- import importlib.machinery
- import importlib.util
-@@ -1986,7 +1993,7 @@
-
- fullname = '_test_module_state_shared'
- origin = importlib.util.find_spec('_testmultiphase').origin
-- loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
-+ loader = importlib.machinery.{loader}(fullname, origin)
- spec = importlib.util.spec_from_loader(fullname, loader)
- module = importlib.util.module_from_spec(spec)
- attr_id = str(id(module.Error)).encode()
-@@ -2160,7 +2167,12 @@
- def setUp(self):
- fullname = '_testmultiphase_meth_state_access' # XXX
- origin = importlib.util.find_spec('_testmultiphase').origin
-- loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if support.is_apple_mobile:
-+ loader = importlib.machinery.AppleFrameworkLoader(fullname, origin)
-+ else:
-+ loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
- spec = importlib.util.spec_from_loader(fullname, loader)
- module = importlib.util.module_from_spec(spec)
- loader.exec_module(module)
-diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
-index 1b588826010..74879ba2a9f 100644
---- a/Lib/test/test_cmd_line_script.py
-+++ b/Lib/test/test_cmd_line_script.py
-@@ -14,8 +14,7 @@
-
- import textwrap
- from test import support
--from test.support import import_helper
--from test.support import os_helper
-+from test.support import import_helper, is_apple, os_helper
- from test.support.script_helper import (
- make_pkg, make_script, make_zip_pkg, make_zip_script,
- assert_python_ok, assert_python_failure, spawn_python, kill_python)
-@@ -555,12 +554,17 @@
- self.assertTrue(text[3].startswith('NameError'))
-
- def test_non_ascii(self):
-- # Mac OS X denies the creation of a file with an invalid UTF-8 name.
-+ # Apple platforms deny the creation of a file with an invalid UTF-8 name.
- # Windows allows creating a name with an arbitrary bytes name, but
- # Python cannot a undecodable bytes argument to a subprocess.
-- # WASI does not permit invalid UTF-8 names.
-- if (os_helper.TESTFN_UNDECODABLE
-- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')):
-+ # Emscripten/WASI does not permit invalid UTF-8 names.
-+ if (
-+ os_helper.TESTFN_UNDECODABLE
-+ and sys.platform not in {
-+ "win32", "emscripten", "wasi"
-+ }
-+ and not is_apple
-+ ):
- name = os.fsdecode(os_helper.TESTFN_UNDECODABLE)
- elif os_helper.TESTFN_NONASCII:
- name = os_helper.TESTFN_NONASCII
-diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py
-index 6e4a4b7caff..d1357ea7fcd 100644
---- a/Lib/test/test_concurrent_futures/test_thread_pool.py
-+++ b/Lib/test/test_concurrent_futures/test_thread_pool.py
-@@ -49,6 +49,7 @@
- self.assertEqual(len(executor._threads), 1)
- executor.shutdown(wait=True)
-
-+ @support.requires_fork()
- @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork')
- @support.requires_resource('cpu')
- def test_hang_global_shutdown_lock(self):
-diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py
-index 203dd6fe57d..6d734d05245 100644
---- a/Lib/test/test_fcntl.py
-+++ b/Lib/test/test_fcntl.py
-@@ -6,7 +6,9 @@
- import struct
- import sys
- import unittest
--from test.support import verbose, cpython_only, get_pagesize
-+from test.support import (
-+ cpython_only, get_pagesize, is_apple, requires_subprocess, verbose
++ 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
+--- /dev/null
++++ b/Apple/testbed/__main__.py
+@@ -0,0 +1,456 @@
++import argparse
++import json
++import os
++import re
++import shlex
++import shutil
++import subprocess
++import sys
++from pathlib import Path
++
++TEST_SLICES = {
++ "iOS": "ios-arm64_x86_64-simulator",
++ "tvOS": "tvos-arm64_x86_64-simulator",
++ "visionOS": "xros-arm64-simulator",
++ "watchOS": "watchos-arm64_x86_64-simulator",
++}
++
++DECODE_ARGS = ("UTF-8", "backslashreplace")
++
++# The system log prefixes each line:
++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ...
++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ...
++
++LOG_PREFIX_REGEX = re.compile(
++ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD
++ r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ
++ r"\s+.*Testbed\[\d+:\w+\]" # Process/thread ID
+)
- from test.support.import_helper import import_module
- from test.support.os_helper import TESTFN, unlink
-
-@@ -56,8 +58,10 @@
- else:
- start_len = "qq"
-
-- if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd'))
-- or sys.platform == 'darwin'):
-+ if (
-+ sys.platform.startswith(('netbsd', 'freebsd', 'openbsd'))
-+ or is_apple
-+ ):
- if struct.calcsize('l') == 8:
- off_t = 'l'
- pid_t = 'i'
-@@ -157,6 +161,7 @@
- self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH)
-
- @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
-+ @requires_subprocess()
- def test_lockf_exclusive(self):
- self.f = open(TESTFN, 'wb+')
- cmd = fcntl.LOCK_EX | fcntl.LOCK_NB
-@@ -169,6 +174,7 @@
- self.assertEqual(p.exitcode, 0)
-
- @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
-+ @requires_subprocess()
- def test_lockf_share(self):
- self.f = open(TESTFN, 'wb+')
- cmd = fcntl.LOCK_SH | fcntl.LOCK_NB
-diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
-index 4c4a4498d6f..bed0e6d973b 100644
---- a/Lib/test/test_ftplib.py
-+++ b/Lib/test/test_ftplib.py
-@@ -18,6 +18,7 @@
-
- from unittest import TestCase, skipUnless
- from test import support
-+from test.support import requires_subprocess
- from test.support import threading_helper
- from test.support import socket_helper
- from test.support import warnings_helper
-@@ -900,6 +901,7 @@
-
-
- @skipUnless(ssl, "SSL not available")
-+@requires_subprocess()
- class TestTLS_FTPClassMixin(TestFTPClass):
- """Repeat TestFTPClass tests starting the TLS layer for both control
- and data connections first.
-@@ -916,6 +918,7 @@
-
-
- @skipUnless(ssl, "SSL not available")
-+@requires_subprocess()
- class TestTLS_FTPClass(TestCase):
- """Specific TLS_FTP class tests."""
-
-diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
-index 81bb5bb288e..dddf5e8cd93 100644
---- a/Lib/test/test_gc.py
-+++ b/Lib/test/test_gc.py
-@@ -1188,6 +1188,7 @@
- self.assertEqual(len(gc.garbage), 0)
-
-
-+ @requires_subprocess()
- @unittest.skipIf(BUILD_WITH_NDEBUG,
- 'built with -NDEBUG')
- def test_refcount_errors(self):
-diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
-index 3eefb722b81..3a92a65e10f 100644
---- a/Lib/test/test_genericpath.py
-+++ b/Lib/test/test_genericpath.py
-@@ -7,9 +7,9 @@
- import sys
- import unittest
- import warnings
--from test.support import is_emscripten
--from test.support import os_helper
--from test.support import warnings_helper
-+from test.support import (
-+ is_apple, is_emscripten, os_helper, warnings_helper
-+)
- from test.support.script_helper import assert_python_ok
- from test.support.os_helper import FakePath
-
-@@ -492,12 +492,16 @@
- self.assertIsInstance(abspath(path), str)
-
- def test_nonascii_abspath(self):
-- if (os_helper.TESTFN_UNDECODABLE
-- # macOS and Emscripten deny the creation of a directory with an
-- # invalid UTF-8 name. Windows allows creating a directory with an
-- # arbitrary bytes name, but fails to enter this directory
-- # (when the bytes name is used).
-- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')):
-+ if (
-+ os_helper.TESTFN_UNDECODABLE
-+ # Apple platforms and Emscripten/WASI deny the creation of a
-+ # directory with an invalid UTF-8 name. Windows allows creating a
-+ # directory with an arbitrary bytes name, but fails to enter this
-+ # directory (when the bytes name is used).
-+ and sys.platform not in {
-+ "win32", "emscripten", "wasi"
-+ } and not is_apple
-+ ):
- name = os_helper.TESTFN_UNDECODABLE
- elif os_helper.TESTFN_NONASCII:
- name = os_helper.TESTFN_NONASCII
-diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
-index 88d06fe04fb..1dc38dba3d3 100644
---- a/Lib/test/test_httpservers.py
-+++ b/Lib/test/test_httpservers.py
-@@ -31,8 +31,9 @@
-
- import unittest
- from test import support
--from test.support import os_helper
--from test.support import threading_helper
-+from test.support import (
-+ is_apple, os_helper, requires_subprocess, threading_helper
-+)
-
- support.requires_working_socket(module=True)
-
-@@ -411,8 +412,8 @@
- reader.close()
- return body
-
-- @unittest.skipIf(sys.platform == 'darwin',
-- 'undecodable name cannot always be decoded on macOS')
-+ @unittest.skipIf(is_apple,
-+ 'undecodable name cannot always be decoded on Apple platforms')
- @unittest.skipIf(sys.platform == 'win32',
- 'undecodable name cannot be decoded on win32')
- @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE,
-@@ -423,11 +424,11 @@
- with open(os.path.join(self.tempdir, filename), 'wb') as f:
- f.write(os_helper.TESTFN_UNDECODABLE)
- response = self.request(self.base_url + '/')
-- if sys.platform == 'darwin':
-- # On Mac OS the HFS+ filesystem replaces bytes that aren't valid
-- # UTF-8 into a percent-encoded value.
-+ if is_apple:
-+ # On Apple platforms the HFS+ filesystem replaces bytes that
-+ # aren't valid UTF-8 into a percent-encoded value.
- for name in os.listdir(self.tempdir):
-- if name != 'test': # Ignore a filename created in setUp().
-+ if name != 'test': # Ignore a filename created in setUp().
- filename = name
- break
- body = self.check_status_and_reason(response, HTTPStatus.OK)
-@@ -698,6 +699,7 @@
-
- @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
- "This test can't be run reliably as root (issue #13308).")
-+@requires_subprocess()
- class CGIHTTPServerTestCase(BaseTestCase):
- class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
- def run_cgi(self):
-diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
-index 81b2f33a840..3b63c49fa89 100644
---- a/Lib/test/test_import/__init__.py
-+++ b/Lib/test/test_import/__init__.py
-@@ -5,7 +5,11 @@
- import importlib.util
- from importlib._bootstrap_external import _get_sourcefile
- from importlib.machinery import (
-- BuiltinImporter, ExtensionFileLoader, FrozenImporter, SourceFileLoader,
-+ AppleFrameworkLoader,
-+ BuiltinImporter,
-+ ExtensionFileLoader,
-+ FrozenImporter,
-+ SourceFileLoader,
- )
- import marshal
- import os
-@@ -26,7 +30,7 @@
-
- from test.support import os_helper
- from test.support import (
-- STDLIB_DIR, swap_attr, swap_item, cpython_only, is_emscripten,
-+ STDLIB_DIR, swap_attr, swap_item, cpython_only, is_apple_mobile, is_emscripten,
- is_wasi, run_in_subinterp, run_in_subinterp_with_config)
- from test.support.import_helper import (
- forget, make_legacy_pyc, unlink, unload, ready_to_import,
-@@ -63,6 +67,7 @@
- MODULE_KINDS = {
- BuiltinImporter: 'built-in',
- ExtensionFileLoader: 'extension',
-+ AppleFrameworkLoader: 'framework extension',
- FrozenImporter: 'frozen',
- SourceFileLoader: 'pure Python',
- }
-@@ -88,7 +93,12 @@
- assert module.__spec__.origin == 'built-in', module.__spec__
-
- def require_extension(module, *, skip=False):
-- _require_loader(module, ExtensionFileLoader, skip)
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if is_apple_mobile:
-+ _require_loader(module, AppleFrameworkLoader, skip)
++
++
++# Select a simulator device to use.
++def select_simulator_device(platform):
++ # List the testing simulators, in JSON format
++ raw_json = subprocess.check_output(["xcrun", "simctl", "list", "-j"])
++ json_data = json.loads(raw_json)
++
++ if platform == "iOS":
++ # Any iOS device will do; we'll look for "SE" devices - but the name
++ # isn't consistent over time. Older Xcode versions will use "iPhone SE
++ # (Nth generation)"; As of 2025, they've started using "iPhone 16e".
++ #
++ # When Xcode is updated after a new release, new devices will be
++ # available and old ones will be dropped from the set available on the
++ # latest iOS version. Select the one with the highest minimum runtime
++ # version - this is an indicator of the "newest" released device, which
++ # should always be supported on the "most recent" iOS version.
++ se_simulators = sorted(
++ (devicetype["minRuntimeVersion"], devicetype["name"])
++ for devicetype in json_data["devicetypes"]
++ if devicetype["productFamily"] == "iPhone"
++ and (
++ (
++ "iPhone " in devicetype["name"]
++ and devicetype["name"].endswith("e")
++ )
++ or "iPhone SE " in devicetype["name"]
++ )
++ )
++ simulator = se_simulators[-1][1]
++ elif platform == "tvOS":
++ # Find the most recent tvOS release.
++ simulators = sorted(
++ (devicetype["minRuntimeVersion"], devicetype["name"])
++ for devicetype in json_data["devicetypes"]
++ if devicetype["productFamily"] == "Apple TV"
++ )
++ simulator = simulators[-1][1]
++ elif platform == "visionOS":
++ # Find the most recent visionOS release.
++ simulators = sorted(
++ (devicetype["minRuntimeVersion"], devicetype["name"])
++ for devicetype in json_data["devicetypes"]
++ if devicetype["productFamily"] == "Apple Vision"
++ )
++ simulator = simulators[-1][1]
++ elif platform == "watchOS":
++ raise NotImplementedError("Don't know how to launch watchOS (yet)")
+ else:
-+ _require_loader(module, ExtensionFileLoader, skip)
-
- def require_frozen(module, *, skip=True):
- module = _require_loader(module, FrozenImporter, skip)
-@@ -131,7 +141,8 @@
- # it to its nominal state.
- sys.modules.pop('_testsinglephase', None)
- _orig._clear_globals()
-- _testinternalcapi.clear_extension('_testsinglephase', _orig.__file__)
-+ origin = _orig.__spec__.origin
-+ _testinternalcapi.clear_extension('_testsinglephase', origin)
- import _testsinglephase
-
-
-@@ -354,10 +365,14 @@
- from _testcapi import i_dont_exist
- self.assertEqual(cm.exception.name, '_testcapi')
- if hasattr(_testcapi, "__file__"):
-- self.assertEqual(cm.exception.path, _testcapi.__file__)
-+ # The path on the exception is strictly the spec origin, not the
-+ # module's __file__. For most cases, these are the same; but on
-+ # iOS, the Framework relocation process results in the exception
-+ # being raised from the spec location.
-+ self.assertEqual(cm.exception.path, _testcapi.__spec__.origin)
- self.assertRegex(
- str(cm.exception),
-- r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|pyd)\)"
-+ r"cannot import name 'i_dont_exist' from '_testcapi' \(.*(\.(so|pyd))?\)"
- )
- else:
- self.assertEqual(
-@@ -1719,6 +1734,14 @@
- os.set_blocking(r, False)
- return (r, w)
-
-+ def create_extension_loader(self, modname, filename):
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if is_apple_mobile:
-+ return AppleFrameworkLoader(modname, filename)
-+ else:
-+ return ExtensionFileLoader(modname, filename)
++ raise ValueError(f"Unknown platform {platform}")
+
- def import_script(self, name, fd, filename=None, check_override=None):
- override_text = ''
- if check_override is not None:
-@@ -1727,12 +1750,19 @@
- _imp._override_multi_interp_extensions_check({check_override})
- '''
- if filename:
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if is_apple_mobile:
-+ loader = "AppleFrameworkLoader"
-+ else:
-+ loader = "ExtensionFileLoader"
++ return simulator
+
- return textwrap.dedent(f'''
- from importlib.util import spec_from_loader, module_from_spec
-- from importlib.machinery import ExtensionFileLoader
-+ from importlib.machinery import {loader}
- import os, sys
- {override_text}
-- loader = ExtensionFileLoader({name!r}, {filename!r})
-+ loader = {loader}({name!r}, {filename!r})
- spec = spec_from_loader({name!r}, loader)
- try:
- module = module_from_spec(spec)
-@@ -1913,7 +1943,7 @@
- def test_multi_init_extension_non_isolated_compat(self):
- modname = '_test_non_isolated'
- filename = _testmultiphase.__file__
-- loader = ExtensionFileLoader(modname, filename)
-+ loader = self.create_extension_loader(modname, filename)
- spec = importlib.util.spec_from_loader(modname, loader)
- module = importlib.util.module_from_spec(spec)
- loader.exec_module(module)
-@@ -1931,7 +1961,7 @@
- def test_multi_init_extension_per_interpreter_gil_compat(self):
- modname = '_test_shared_gil_only'
- filename = _testmultiphase.__file__
-- loader = ExtensionFileLoader(modname, filename)
-+ loader = self.create_extension_loader(modname, filename)
- spec = importlib.util.spec_from_loader(modname, loader)
- module = importlib.util.module_from_spec(spec)
- loader.exec_module(module)
-@@ -2061,10 +2091,25 @@
- @classmethod
- def setUpClass(cls):
- spec = importlib.util.find_spec(cls.NAME)
-- from importlib.machinery import ExtensionFileLoader
-- cls.FILE = spec.origin
- cls.LOADER = type(spec.loader)
-- assert cls.LOADER is ExtensionFileLoader
+
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader, and we need to differentiate between the
-+ # spec.origin and the original file location.
-+ if is_apple_mobile:
-+ assert cls.LOADER is AppleFrameworkLoader
-+
-+ cls.ORIGIN = spec.origin
-+ with open(spec.origin + ".origin", "r") as f:
-+ cls.FILE = os.path.join(
-+ os.path.dirname(sys.executable),
-+ f.read().strip()
-+ )
-+ else:
-+ assert cls.LOADER is ExtensionFileLoader
++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"]
+
-+ cls.ORIGIN = spec.origin
-+ cls.FILE = spec.origin
-
- # Start fresh.
- cls.clean_up()
-@@ -2080,14 +2125,15 @@
- @classmethod
- def clean_up(cls):
- name = cls.NAME
-- filename = cls.FILE
- if name in sys.modules:
- if hasattr(sys.modules[name], '_clear_globals'):
-- assert sys.modules[name].__file__ == filename
-+ assert sys.modules[name].__file__ == cls.FILE, \
-+ f"{sys.modules[name].__file__} != {cls.FILE}"
++ print("Building test project...")
++ subprocess.run(
++ ["xcodebuild", "build-for-testing"] + args + verbosity_args,
++ check=True,
++ )
+
- sys.modules[name]._clear_globals()
- del sys.modules[name]
- # Clear all internally cached data for the extension.
-- _testinternalcapi.clear_extension(name, filename)
-+ _testinternalcapi.clear_extension(name, cls.ORIGIN)
-
- #########################
- # helpers
-@@ -2095,7 +2141,7 @@
- def add_module_cleanup(self, name):
- def clean_up():
- # Clear all internally cached data for the extension.
-- _testinternalcapi.clear_extension(name, self.FILE)
-+ _testinternalcapi.clear_extension(name, self.ORIGIN)
- self.addCleanup(clean_up)
-
- def _load_dynamic(self, name, path):
-@@ -2118,7 +2164,7 @@
- except AttributeError:
- already_loaded = self.already_loaded = {}
- assert name not in already_loaded
-- mod = self._load_dynamic(name, self.FILE)
-+ mod = self._load_dynamic(name, self.ORIGIN)
- self.assertNotIn(mod, already_loaded.values())
- already_loaded[name] = mod
- return types.SimpleNamespace(
-@@ -2130,7 +2176,7 @@
- def re_load(self, name, mod):
- assert sys.modules[name] is mod
- assert mod.__dict__ == mod.__dict__
-- reloaded = self._load_dynamic(name, self.FILE)
-+ reloaded = self._load_dynamic(name, self.ORIGIN)
- return types.SimpleNamespace(
- name=name,
- module=reloaded,
-@@ -2150,7 +2196,7 @@
- name = {self.NAME!r}
- if name in sys.modules:
- sys.modules.pop(name)._clear_globals()
-- _testinternalcapi.clear_extension(name, {self.FILE!r})
-+ _testinternalcapi.clear_extension(name, {self.ORIGIN!r})
- '''))
- _interpreters.destroy(interpid)
- self.addCleanup(clean_up)
-@@ -2167,7 +2213,7 @@
- postcleanup = f'''
- {import_}
- mod._clear_globals()
-- _testinternalcapi.clear_extension(name, {self.FILE!r})
-+ _testinternalcapi.clear_extension(name, {self.ORIGIN!r})
- '''
-
- try:
-@@ -2205,7 +2251,7 @@
- # mod.__name__ might not match, but the spec will.
- self.assertEqual(mod.__spec__.name, loaded.name)
- self.assertEqual(mod.__file__, self.FILE)
-- self.assertEqual(mod.__spec__.origin, self.FILE)
-+ self.assertEqual(mod.__spec__.origin, self.ORIGIN)
- if not isolated:
- self.assertTrue(issubclass(mod.error, Exception))
- self.assertEqual(mod.int_const, 1969)
-@@ -2599,7 +2645,7 @@
- # First, load in the main interpreter but then completely clear it.
- loaded_main = self.load(self.NAME)
- loaded_main.module._clear_globals()
-- _testinternalcapi.clear_extension(self.NAME, self.FILE)
-+ _testinternalcapi.clear_extension(self.NAME, self.ORIGIN)
-
- # At this point:
- # * alive in 0 interpreters
-diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py
-index 3de120958fd..cdc8884d668 100644
---- a/Lib/test/test_importlib/extension/test_finder.py
-+++ b/Lib/test/test_importlib/extension/test_finder.py
-@@ -1,3 +1,4 @@
-+from test.support import is_apple_mobile
- from test.test_importlib import abc, util
-
- machinery = util.import_importlib('importlib.machinery')
-@@ -19,9 +20,27 @@
- )
-
- def find_spec(self, fullname):
-- importer = self.machinery.FileFinder(util.EXTENSIONS.path,
-- (self.machinery.ExtensionFileLoader,
-- self.machinery.EXTENSION_SUFFIXES))
-+ if is_apple_mobile:
-+ # Apple mobile platforms require a specialist loader that uses
-+ # .fwork files as placeholders for the true `.so` files.
-+ loaders = [
-+ (
-+ self.machinery.AppleFrameworkLoader,
-+ [
-+ ext.replace(".so", ".fwork")
-+ for ext in self.machinery.EXTENSION_SUFFIXES
-+ ]
-+ )
-+ ]
-+ else:
-+ loaders = [
-+ (
-+ self.machinery.ExtensionFileLoader,
-+ self.machinery.EXTENSION_SUFFIXES
-+ )
-+ ]
++ # Any environment variable prefixed with TEST_RUNNER_ is exposed into the
++ # test runner environment. There are some variables (like those identifying
++ # CI platforms) that can be useful to have access to.
++ test_env = os.environ.copy()
++ if "GITHUB_ACTIONS" in os.environ:
++ test_env["TEST_RUNNER_GITHUB_ACTIONS"] = os.environ["GITHUB_ACTIONS"]
++
++ print("Running test project...")
++ # Test execution *can't* be run -quiet; verbose mode
++ # is how we see the output of the test output.
++ process = subprocess.Popen(
++ ["xcodebuild", "test-without-building"] + args,
++ stdout=subprocess.PIPE,
++ stderr=subprocess.STDOUT,
++ env=test_env,
++ )
++ while line := (process.stdout.readline()).decode(*DECODE_ARGS):
++ # Strip the timestamp/process prefix from each log line
++ line = LOG_PREFIX_REGEX.sub("", line)
++ sys.stdout.write(line)
++ sys.stdout.flush()
+
-+ importer = self.machinery.FileFinder(util.EXTENSIONS.path, *loaders)
-
- return importer.find_spec(fullname)
-
-diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py
-index 12f9e43d123..7e8c9c8184a 100644
---- a/Lib/test/test_importlib/extension/test_loader.py
-+++ b/Lib/test/test_importlib/extension/test_loader.py
-@@ -1,3 +1,4 @@
-+from test.support import is_apple_mobile
- from warnings import catch_warnings
- from test.test_importlib import abc, util
-
-@@ -25,8 +26,15 @@
- raise unittest.SkipTest(
- f"{util.EXTENSIONS.name} is a builtin module"
- )
-- self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
-- util.EXTENSIONS.file_path)
++ status = process.wait(timeout=5)
++ exit(status)
+
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if is_apple_mobile:
-+ self.LoaderClass = self.machinery.AppleFrameworkLoader
-+ else:
-+ self.LoaderClass = self.machinery.ExtensionFileLoader
+
-+ self.loader = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
-
- def load_module(self, fullname):
- with warnings.catch_warnings():
-@@ -34,13 +42,11 @@
- return self.loader.load_module(fullname)
-
- def test_equality(self):
-- other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
-- util.EXTENSIONS.file_path)
-+ other = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
- self.assertEqual(self.loader, other)
-
- def test_inequality(self):
-- other = self.machinery.ExtensionFileLoader('_' + util.EXTENSIONS.name,
-- util.EXTENSIONS.file_path)
-+ other = self.LoaderClass('_' + util.EXTENSIONS.name, util.EXTENSIONS.file_path)
- self.assertNotEqual(self.loader, other)
-
- def test_load_module_API(self):
-@@ -60,8 +66,7 @@
- ('__package__', '')]:
- self.assertEqual(getattr(module, attr), value)
- self.assertIn(util.EXTENSIONS.name, sys.modules)
-- self.assertIsInstance(module.__loader__,
-- self.machinery.ExtensionFileLoader)
-+ self.assertIsInstance(module.__loader__, self.LoaderClass)
-
- # No extension module as __init__ available for testing.
- test_package = None
-@@ -88,7 +93,7 @@
- self.assertFalse(self.loader.is_package(util.EXTENSIONS.name))
- for suffix in self.machinery.EXTENSION_SUFFIXES:
- path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
-- loader = self.machinery.ExtensionFileLoader('pkg', path)
-+ loader = self.LoaderClass('pkg', path)
- self.assertTrue(loader.is_package('pkg'))
-
-
-@@ -103,6 +108,14 @@
- def setUp(self):
- if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
- raise unittest.SkipTest("Requires dynamic loading support.")
++def copy(src, tgt):
++ """An all-purpose copy.
+
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if is_apple_mobile:
-+ self.LoaderClass = self.machinery.AppleFrameworkLoader
-+ else:
-+ self.LoaderClass = self.machinery.ExtensionFileLoader
++ 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)
+
- self.name = '_testsinglephase'
- if self.name in sys.builtin_module_names:
- raise unittest.SkipTest(
-@@ -111,8 +124,8 @@
- finder = self.machinery.FileFinder(None)
- self.spec = importlib.util.find_spec(self.name)
- assert self.spec
-- self.loader = self.machinery.ExtensionFileLoader(
-- self.name, self.spec.origin)
+
-+ self.loader = self.LoaderClass(self.name, self.spec.origin)
-
- def load_module(self):
- with warnings.catch_warnings():
-@@ -122,7 +135,7 @@
- def load_module_by_name(self, fullname):
- # Load a module from the test extension by name.
- origin = self.spec.origin
-- loader = self.machinery.ExtensionFileLoader(fullname, origin)
-+ loader = self.LoaderClass(fullname, origin)
- spec = importlib.util.spec_from_loader(fullname, loader)
- module = importlib.util.module_from_spec(spec)
- loader.exec_module(module)
-@@ -139,8 +152,7 @@
- with self.assertRaises(AttributeError):
- module.__path__
- self.assertIs(module, sys.modules[self.name])
-- self.assertIsInstance(module.__loader__,
-- self.machinery.ExtensionFileLoader)
-+ self.assertIsInstance(module.__loader__, self.LoaderClass)
-
- # No extension module as __init__ available for testing.
- test_package = None
-@@ -184,6 +196,14 @@
- def setUp(self):
- if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
- raise unittest.SkipTest("Requires dynamic loading support.")
++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)
+
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if is_apple_mobile:
-+ self.LoaderClass = self.machinery.AppleFrameworkLoader
++ 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:
-+ self.LoaderClass = self.machinery.ExtensionFileLoader
++ 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)
+
- self.name = '_testmultiphase'
- if self.name in sys.builtin_module_names:
- raise unittest.SkipTest(
-@@ -192,8 +212,7 @@
- finder = self.machinery.FileFinder(None)
- self.spec = importlib.util.find_spec(self.name)
- assert self.spec
-- self.loader = self.machinery.ExtensionFileLoader(
-- self.name, self.spec.origin)
-+ self.loader = self.LoaderClass(self.name, self.spec.origin)
-
- def load_module(self):
- # Load the module from the test extension.
-@@ -204,7 +223,7 @@
- def load_module_by_name(self, fullname):
- # Load a module from the test extension by name.
- origin = self.spec.origin
-- loader = self.machinery.ExtensionFileLoader(fullname, origin)
-+ loader = self.LoaderClass(fullname, origin)
- spec = importlib.util.spec_from_loader(fullname, loader)
- module = importlib.util.module_from_spec(spec)
- loader.exec_module(module)
-@@ -230,8 +249,7 @@
- with self.assertRaises(AttributeError):
- module.__path__
- self.assertIs(module, sys.modules[self.name])
-- self.assertIsInstance(module.__loader__,
-- self.machinery.ExtensionFileLoader)
-+ self.assertIsInstance(module.__loader__, self.LoaderClass)
-
- def test_functionality(self):
- # Test basic functionality of stuff defined in an extension module.
-diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
-index 553e2087421..e23949aaa22 100644
---- a/Lib/test/test_importlib/test_util.py
-+++ b/Lib/test/test_importlib/test_util.py
-@@ -703,13 +703,20 @@
-
- @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
- def test_incomplete_multi_phase_init_module(self):
-+ # Apple extensions must be distributed as frameworks. This requires
-+ # a specialist loader.
-+ if support.is_apple_mobile:
-+ loader = "AppleFrameworkLoader"
++ 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:
-+ loader = "ExtensionFileLoader"
++ print(" Using pre-existing Python framework.")
+
- prescript = textwrap.dedent(f'''
- from importlib.util import spec_from_loader, module_from_spec
-- from importlib.machinery import ExtensionFileLoader
-+ from importlib.machinery import {loader}
-
- name = '_test_shared_gil_only'
- filename = {_testmultiphase.__file__!r}
-- loader = ExtensionFileLoader(name, filename)
-+ loader = {loader}(name, filename)
- spec = spec_from_loader(name, loader)
-
- ''')
-diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py
-index a900cc1dddf..89272484009 100644
---- a/Lib/test/test_importlib/util.py
-+++ b/Lib/test/test_importlib/util.py
-@@ -8,6 +8,7 @@
- import os.path
- from test import support
- from test.support import import_helper
-+from test.support import is_apple_mobile
- from test.support import os_helper
- import unittest
- import sys
-@@ -43,6 +44,11 @@
- global EXTENSIONS
- for path in sys.path:
- for ext in machinery.EXTENSION_SUFFIXES:
-+ # Apple mobile platforms mechanically load .so files,
-+ # but the findable files are labelled .fwork
-+ if is_apple_mobile:
-+ ext = ext.replace(".so", ".fwork")
++ 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")
+
- filename = EXTENSIONS.name + ext
- file_path = os.path.join(path, filename)
- if os.path.exists(file_path):
-diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
-index 0cd9e721b20..ffa58230425 100644
---- a/Lib/test/test_interpreters.py
-+++ b/Lib/test/test_interpreters.py
-@@ -752,6 +752,7 @@
-
- class FinalizationTests(TestBase):
-
-+ @support.requires_subprocess()
- def test_gh_109793(self):
- import subprocess
- argv = [sys.executable, '-c', '''if True:
-diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
-index d85040a3083..9d634acfd3e 100644
---- a/Lib/test/test_io.py
-+++ b/Lib/test/test_io.py
-@@ -39,11 +39,9 @@
- from test import support
- from test.support.script_helper import (
- assert_python_ok, assert_python_failure, run_python_until_end)
--from test.support import import_helper
--from test.support import os_helper
--from test.support import threading_helper
--from test.support import warnings_helper
--from test.support import skip_if_sanitizer
-+from test.support import (
-+ import_helper, is_apple, os_helper, skip_if_sanitizer, threading_helper, warnings_helper
-+)
- from test.support.os_helper import FakePath
-
- import codecs
-@@ -631,10 +629,10 @@
- self.read_ops(f, True)
-
- def test_large_file_ops(self):
-- # On Windows and Mac OSX this test consumes large resources; It takes
-- # a long time to build the >2 GiB file and takes >2 GiB of disk space
-- # therefore the resource must be enabled to run this test.
-- if sys.platform[:3] == 'win' or sys.platform == 'darwin':
-+ # On Windows and Apple platforms this test consumes large resources; It
-+ # takes a long time to build the >2 GiB file and takes >2 GiB of disk
-+ # space therefore the resource must be enabled to run this test.
-+ if sys.platform[:3] == 'win' or is_apple:
- support.requires(
- 'largefile',
- 'test requires %s bytes and a long time to run' % self.LARGE)
-diff --git a/Lib/test/test_lib2to3/test_parser.py b/Lib/test/test_lib2to3/test_parser.py
-index 2c798b181fd..e12ed1e9389 100644
---- a/Lib/test/test_lib2to3/test_parser.py
-+++ b/Lib/test/test_lib2to3/test_parser.py
-@@ -62,9 +62,7 @@
- shutil.rmtree(tmpdir)
-
- @unittest.skipIf(sys.executable is None, 'sys.executable required')
-- @unittest.skipIf(
-- sys.platform in {'emscripten', 'wasi'}, 'requires working subprocess'
-- )
-+ @test.support.requires_subprocess()
- def test_load_grammar_from_subprocess(self):
- tmpdir = tempfile.mkdtemp()
- tmpsubdir = os.path.join(tmpdir, 'subdir')
-diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py
-index 3d9d6d5d0ac..9c759170450 100644
---- a/Lib/test/test_marshal.py
-+++ b/Lib/test/test_marshal.py
-@@ -1,5 +1,5 @@
- from test import support
--from test.support import os_helper, requires_debug_ranges
-+from test.support import is_apple_mobile, os_helper, requires_debug_ranges
- from test.support.script_helper import assert_python_ok
- import array
- import io
-@@ -260,7 +260,7 @@
- #if os.name == 'nt' and support.Py_DEBUG:
- if os.name == 'nt':
- MAX_MARSHAL_STACK_DEPTH = 1000
-- elif sys.platform == 'wasi':
-+ elif sys.platform == 'wasi' or is_apple_mobile:
- MAX_MARSHAL_STACK_DEPTH = 1500
- else:
- MAX_MARSHAL_STACK_DEPTH = 2000
-diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
-index 1867e8c957f..f75e40940e4 100644
---- a/Lib/test/test_mmap.py
-+++ b/Lib/test/test_mmap.py
-@@ -1,5 +1,5 @@
- from test.support import (
-- requires, _2G, _4G, gc_collect, cpython_only, is_emscripten
-+ requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple,
- )
- from test.support.import_helper import import_module
- from test.support.os_helper import TESTFN, unlink
-@@ -1009,7 +1009,7 @@
- unlink(TESTFN)
-
- def _make_test_file(self, num_zeroes, tail):
-- if sys.platform[:3] == 'win' or sys.platform == 'darwin':
-+ if sys.platform[:3] == 'win' or is_apple:
- requires('largefile',
- 'test requires %s bytes and a long time to run' % str(0x180000000))
- f = open(TESTFN, 'w+b')
-diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
-index 1d6d92e0235..90afbf1d14a 100644
---- a/Lib/test/test_os.py
-+++ b/Lib/test/test_os.py
-@@ -2372,6 +2372,7 @@
- support.is_emscripten or support.is_wasi,
- "musl libc issue on Emscripten/WASI, bpo-46390"
- )
-+ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS")
- def test_fpathconf(self):
- self.check(os.pathconf, "PC_NAME_MAX")
- self.check(os.fpathconf, "PC_NAME_MAX")
-@@ -3946,6 +3947,7 @@
- self.assertGreaterEqual(size.columns, 0)
- self.assertGreaterEqual(size.lines, 0)
-
-+ @support.requires_subprocess()
- def test_stty_match(self):
- """Check if stty returns the same results
-
-diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
-index b62a9e38977..39b9f7b178b 100644
---- a/Lib/test/test_platform.py
-+++ b/Lib/test/test_platform.py
-@@ -10,6 +10,14 @@
- from test import support
- from test.support import os_helper
-
-+try:
-+ # Some of the iOS tests need ctypes to operate.
-+ # Confirm that the ctypes module is available
-+ # is available.
-+ import _ctypes
-+except ImportError:
-+ _ctypes = None
++ print(f"Successfully cloned testbed: {target.resolve()}")
+
- FEDORA_OS_RELEASE = """\
- NAME=Fedora
- VERSION="32 (Thirty Two)"
-@@ -219,6 +227,30 @@
- self.assertEqual(res[-1], res.processor)
- self.assertEqual(len(res), 6)
-
-+ if os.name == "posix":
-+ uname = os.uname()
-+ self.assertEqual(res.node, uname.nodename)
-+ self.assertEqual(res.version, uname.version)
-+ self.assertEqual(res.machine, uname.machine)
+
-+ if sys.platform == "android":
-+ self.assertEqual(res.system, "Android")
-+ self.assertEqual(res.release, platform.android_ver().release)
-+ elif sys.platform == "ios":
-+ # 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)
-+ else:
-+ self.assertEqual(res.system, "")
-+ self.assertEqual(res.release, "")
-+ else:
-+ self.assertEqual(res.system, uname.sysname)
-+ self.assertEqual(res.release, uname.release)
++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
++ ]
+
- @unittest.skipUnless(sys.platform.startswith('win'), "windows only test")
- def test_uname_win32_without_wmi(self):
- def raises_oserror(*a):
-@@ -405,6 +437,56 @@
- # parent
- support.wait_process(pid, exitcode=0)
-
-+ def test_ios_ver(self):
-+ result = platform.ios_ver()
++ with test_plan_path.open("w", encoding="utf-8") as f:
++ json.dump(test_plan, f, indent=2)
+
-+ # ios_ver is only fully available on iOS where ctypes is available.
-+ if sys.platform == "ios" and _ctypes:
-+ system, release, model, is_simulator = result
-+ # Result is a namedtuple
-+ self.assertEqual(result.system, system)
-+ self.assertEqual(result.release, release)
-+ self.assertEqual(result.model, model)
-+ self.assertEqual(result.is_simulator, is_simulator)
+
-+ # We can't assert specific values without reproducing the logic of
-+ # ios_ver(), so we check that the values are broadly what we expect.
++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.")
+
-+ # System is either iOS or iPadOS, depending on the test device
-+ self.assertIn(system, {"iOS", "iPadOS"})
++ if simulator is None:
++ simulator = select_simulator_device(platform)
++ print(f"Running test on {simulator}")
+
-+ # Release is a numeric version specifier with at least 2 parts
-+ parts = release.split(".")
-+ self.assertGreaterEqual(len(parts), 2)
-+ self.assertTrue(all(part.isdigit() for part in parts))
++ xcode_test(
++ location,
++ platform=platform,
++ simulator=simulator,
++ verbose=verbose,
++ )
+
-+ # If this is a simulator, we get a high level device descriptor
-+ # with no identifying model number. If this is a physical device,
-+ # we get a model descriptor like "iPhone13,1"
-+ if is_simulator:
-+ self.assertIn(model, {"iPhone", "iPad"})
-+ else:
-+ self.assertTrue(
-+ (model.startswith("iPhone") or model.startswith("iPad"))
-+ and "," in model
++
++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)
+
-+ self.assertEqual(type(is_simulator), bool)
++ run_testbed(
++ platform=context.platform,
++ simulator=context.simulator,
++ verbose=context.verbose,
++ args=test_args,
++ )
+ else:
-+ # On non-iOS platforms, calling ios_ver doesn't fail; you get
-+ # default values
-+ self.assertEqual(result.system, "")
-+ self.assertEqual(result.release, "")
-+ self.assertEqual(result.model, "")
-+ self.assertFalse(result.is_simulator)
++ 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)
+
-+ # Check the fallback values can be overridden by arguments
-+ override = platform.ios_ver("Foo", "Bar", "Whiz", True)
-+ self.assertEqual(override.system, "Foo")
-+ self.assertEqual(override.release, "Bar")
-+ self.assertEqual(override.model, "Whiz")
-+ self.assertTrue(override.is_simulator)
+
- @unittest.skipIf(support.is_emscripten, "Does not apply to Emscripten")
- def test_libc_ver(self):
- # check that libc_ver(executable) doesn't raise an exception
-@@ -500,7 +582,8 @@
- 'root:xnu-4570.71.2~1/RELEASE_X86_64'),
- 'x86_64', 'i386')
- arch = ('64bit', '')
-- with mock.patch.object(platform, 'uname', return_value=uname), \
-+ with mock.patch.object(sys, "platform", "darwin"), \
-+ mock.patch.object(platform, 'uname', return_value=uname), \
- mock.patch.object(platform, 'architecture', return_value=arch):
- for mac_ver, expected_terse, expected in [
- # darwin: mac_ver() returns empty strings
-diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
-index e225b8919d1..5d8920ddd60 100644
---- a/Lib/test/test_posix.py
-+++ b/Lib/test/test_posix.py
-@@ -1,7 +1,7 @@
- "Test posix functions"
-
- from test import support
--from test.support import import_helper
-+from test.support import is_apple
- from test.support import os_helper
- from test.support import warnings_helper
- from test.support.script_helper import assert_python_ok
-@@ -568,6 +568,7 @@
-
- @unittest.skipUnless(hasattr(posix, 'confstr'),
- 'test needs posix.confstr()')
-+ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS")
- def test_confstr(self):
- self.assertRaises(ValueError, posix.confstr, "CS_garbage")
- self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True)
-@@ -796,9 +797,10 @@
- check_stat(uid, gid)
- self.assertRaises(OSError, chown_func, first_param, 0, -1)
- check_stat(uid, gid)
-- if 0 not in os.getgroups():
-- self.assertRaises(OSError, chown_func, first_param, -1, 0)
-- check_stat(uid, gid)
-+ if hasattr(os, 'getgroups'):
-+ if 0 not in os.getgroups():
-+ self.assertRaises(OSError, chown_func, first_param, -1, 0)
-+ check_stat(uid, gid)
- # test illegal types
- for t in str, float:
- self.assertRaises(TypeError, chown_func, first_param, t(uid), gid)
-@@ -1264,8 +1266,8 @@
- self.assertIsInstance(lo, int)
- self.assertIsInstance(hi, int)
- self.assertGreaterEqual(hi, lo)
-- # OSX evidently just returns 15 without checking the argument.
-- if sys.platform != "darwin":
-+ # Apple plaforms return 15 without checking the argument.
-+ if not is_apple:
- self.assertRaises(OSError, posix.sched_get_priority_min, -23)
- self.assertRaises(OSError, posix.sched_get_priority_max, -23)
-
-@@ -2058,11 +2060,13 @@
-
-
- @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
-+@support.requires_subprocess()
- class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin):
- spawn_func = getattr(posix, 'posix_spawn', None)
-
-
- @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp")
-+@support.requires_subprocess()
- class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin):
- spawn_func = getattr(posix, 'posix_spawnp', None)
-
-diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py
-index 51e3a46d0df..3f2bac0155f 100644
---- a/Lib/test/test_pty.py
-+++ b/Lib/test/test_pty.py
-@@ -1,12 +1,17 @@
--from test.support import verbose, reap_children
--from test.support.os_helper import TESTFN, unlink
-+import sys
-+import unittest
-+from test.support import (
-+ is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose
-+)
- from test.support.import_helper import import_module
-+from test.support.os_helper import TESTFN, unlink
-
--# Skip these tests if termios or fcntl are not available
-+# Skip these tests if termios is not available
- import_module('termios')
--# fcntl is a proxy for not being one of the wasm32 platforms even though we
--# don't use this module... a proper check for what crashes those is needed.
--import_module("fcntl")
++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()
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj
+@@ -0,0 +1,557 @@
++// !$*UTF8*$!
++{
++ archiveVersion = 1;
++ classes = {
++ };
++ objectVersion = 56;
++ objects = {
+
-+# Skip tests on WASM platforms, plus iOS/tvOS/watchOS
-+if is_apple_mobile or is_emscripten or is_wasi:
-+ raise unittest.SkipTest(f"pty tests not required on {sys.platform}")
-
- import errno
- import os
-@@ -17,7 +22,6 @@
- import signal
- import socket
- import io # readline
--import unittest
- import warnings
-
- TEST_STRING_1 = b"I wish to buy a fish license.\n"
-diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py
-index 31757205ca3..6b88b121580 100644
---- a/Lib/test/test_selectors.py
-+++ b/Lib/test/test_selectors.py
-@@ -6,8 +6,7 @@
- import socket
- import sys
- from test import support
--from test.support import os_helper
--from test.support import socket_helper
-+from test.support import is_apple, os_helper, socket_helper
- from time import sleep
- import unittest
- import unittest.mock
-@@ -520,7 +519,7 @@
- try:
- fds = s.select()
- except OSError as e:
-- if e.errno == errno.EINVAL and sys.platform == 'darwin':
-+ if e.errno == errno.EINVAL and is_apple:
- # unexplainable errors on macOS don't need to fail the test
- self.skipTest("Invalid argument error calling poll()")
- raise
-diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
-index c553ea09e07..d4b1763a5af 100644
---- a/Lib/test/test_shutil.py
-+++ b/Lib/test/test_shutil.py
-@@ -2223,6 +2223,7 @@
- check_chown(dirname, uid, gid)
-
-
-+@support.requires_subprocess()
- class TestWhich(BaseTest, unittest.TestCase):
-
- def setUp(self):
-@@ -3318,6 +3319,7 @@
- self.assertGreaterEqual(size.lines, 0)
-
- @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty")
-+ @support.requires_subprocess()
- @unittest.skipUnless(hasattr(os, 'get_terminal_size'),
- 'need os.get_terminal_size()')
- def test_stty_match(self):
-diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
-index a45527d7315..8101322347e 100644
---- a/Lib/test/test_signal.py
-+++ b/Lib/test/test_signal.py
-@@ -13,9 +13,10 @@
- import time
- import unittest
- from test import support
--from test.support import os_helper
-+from test.support import (
-+ is_apple, is_apple_mobile, os_helper, threading_helper
-+)
- from test.support.script_helper import assert_python_ok, spawn_python
--from test.support import threading_helper
- try:
- import _testcapi
- except ImportError:
-@@ -834,7 +835,7 @@
- self.assertEqual(self.hndl_called, True)
-
- # Issue 3864, unknown if this affects earlier versions of freebsd also
-- @unittest.skipIf(sys.platform in ('netbsd5',),
-+ @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile,
- 'itimer not reliable (does not mix well with threading) on some BSDs.')
- def test_itimer_virtual(self):
- self.itimer = signal.ITIMER_VIRTUAL
-@@ -1346,7 +1347,7 @@
- # Python handler
- self.assertEqual(len(sigs), N, "Some signals were lost")
-
-- @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)")
-+ @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)")
- @unittest.skipUnless(hasattr(signal, "SIGUSR1"),
- "test needs SIGUSR1")
- @threading_helper.requires_working_threading()
-diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
-index b40ad28f7a7..1584269ec77 100644
---- a/Lib/test/test_socket.py
-+++ b/Lib/test/test_socket.py
-@@ -3,6 +3,7 @@
- from test.support import os_helper
- from test.support import socket_helper
- from test.support import threading_helper
-+from test.support import is_apple
-
- import _thread as thread
- import array
-@@ -1179,8 +1180,11 @@
- # Find one service that exists, then check all the related interfaces.
- # I've ordered this by protocols that have both a tcp and udp
- # protocol, at least for modern Linuxes.
-- if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd'))
-- or sys.platform in ('linux', 'darwin')):
-+ if (
-+ sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd'))
-+ or sys.platform == 'linux'
-+ or is_apple
-+ ):
- # avoid the 'echo' service on this platform, as there is an
- # assumption breaking non-standard port/protocol entry
- services = ('daytime', 'qotd', 'domain')
-@@ -3691,7 +3695,7 @@
- def _testFDPassCMSG_LEN(self):
- self.createAndSendFDs(1)
-
-- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
-+ @unittest.skipIf(is_apple, "skipping, see issue #12958")
- @unittest.skipIf(AIX, "skipping, see issue #22397")
- @requireAttrs(socket, "CMSG_SPACE")
- def testFDPassSeparate(self):
-@@ -3702,7 +3706,7 @@
- maxcmsgs=2)
-
- @testFDPassSeparate.client_skip
-- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
-+ @unittest.skipIf(is_apple, "skipping, see issue #12958")
- @unittest.skipIf(AIX, "skipping, see issue #22397")
- def _testFDPassSeparate(self):
- fd0, fd1 = self.newFDs(2)
-@@ -3715,7 +3719,7 @@
- array.array("i", [fd1]))]),
- len(MSG))
-
-- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
-+ @unittest.skipIf(is_apple, "skipping, see issue #12958")
- @unittest.skipIf(AIX, "skipping, see issue #22397")
- @requireAttrs(socket, "CMSG_SPACE")
- def testFDPassSeparateMinSpace(self):
-@@ -3729,7 +3733,7 @@
- maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC)
-
- @testFDPassSeparateMinSpace.client_skip
-- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
-+ @unittest.skipIf(is_apple, "skipping, see issue #12958")
- @unittest.skipIf(AIX, "skipping, see issue #22397")
- def _testFDPassSeparateMinSpace(self):
- fd0, fd1 = self.newFDs(2)
-@@ -3753,7 +3757,7 @@
- nbytes = self.sendmsgToServer([msg])
- self.assertEqual(nbytes, len(msg))
-
-- @unittest.skipIf(sys.platform == "darwin", "see issue #24725")
-+ @unittest.skipIf(is_apple, "skipping, see issue #12958")
- def testFDPassEmpty(self):
- # Try to pass an empty FD array. Can receive either no array
- # or an empty array.
-diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py
-index f0b99b13f68..f10b5209eb7 100644
---- a/Lib/test/test_sqlite3/test_dbapi.py
-+++ b/Lib/test/test_sqlite3/test_dbapi.py
-@@ -32,7 +32,7 @@
-
- from test.support import (
- SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess,
-- is_emscripten, is_wasi
-+ is_apple, is_emscripten, is_wasi
- )
- from test.support import threading_helper
- from _testcapi import INT_MAX, ULLONG_MAX
-@@ -679,7 +679,7 @@
- cx.execute(self._sql)
-
- @unittest.skipIf(sys.platform == "win32", "skipped on Windows")
-- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS")
-+ @unittest.skipIf(is_apple, "skipped on Apple platforms")
- @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI")
- @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths")
- def test_open_with_undecodable_path(self):
-@@ -725,7 +725,7 @@
- cx.execute(self._sql)
-
- @unittest.skipIf(sys.platform == "win32", "skipped on Windows")
-- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS")
-+ @unittest.skipIf(is_apple, "skipped on Apple platforms")
- @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI")
- @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths")
- def test_open_undecodable_uri(self):
-diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py
-index c77fec3d39d..ca55d429aec 100644
---- a/Lib/test/test_stat.py
-+++ b/Lib/test/test_stat.py
-@@ -2,8 +2,7 @@
- import os
- import socket
- import sys
--from test.support import os_helper
--from test.support import socket_helper
-+from test.support import is_apple, os_helper, socket_helper
- from test.support.import_helper import import_fresh_module
- from test.support.os_helper import TESTFN
-
-diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py
-index 35985b34a42..fb4bc7fce9a 100644
---- a/Lib/test/test_sys_settrace.py
-+++ b/Lib/test/test_sys_settrace.py
-@@ -7,7 +7,7 @@
- import gc
- from functools import wraps
- import asyncio
--from test.support import import_helper
-+from test.support import import_helper, requires_subprocess
- import contextlib
- import warnings
-
-diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
-index 3468d0ce022..7c79ee659bc 100644
---- a/Lib/test/test_sysconfig.py
-+++ b/Lib/test/test_sysconfig.py
-@@ -8,7 +8,11 @@
- from copy import copy
-
- from test.support import (
-- captured_stdout, PythonSymlink, requires_subprocess, is_wasi
-+ captured_stdout,
-+ is_apple_mobile,
-+ is_wasi,
-+ PythonSymlink,
-+ requires_subprocess,
- )
- from test.support.import_helper import import_module
- from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink,
-@@ -348,6 +352,8 @@
- # XXX more platforms to tests here
-
- @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
-+ @unittest.skipIf(is_apple_mobile,
-+ f"{sys.platform} doesn't distribute header files in the runtime environment")
- def test_get_config_h_filename(self):
- config_h = sysconfig.get_config_h_filename()
- self.assertTrue(os.path.isfile(config_h), config_h)
-@@ -457,6 +463,8 @@
- self.assertEqual(my_platform, test_platform)
-
- @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
-+ @unittest.skipIf(is_apple_mobile,
-+ f"{sys.platform} doesn't include config folder at runtime")
- def test_srcdir(self):
- # See Issues #15322, #15364.
- srcdir = sysconfig.get_config_var('srcdir')
-@@ -591,6 +599,8 @@
- @unittest.skipIf(sys.platform.startswith('win'),
- 'Test is not Windows compatible')
- @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
-+ @unittest.skipIf(is_apple_mobile,
-+ f"{sys.platform} doesn't include config folder at runtime")
- def test_get_makefile_filename(self):
- makefile = sysconfig.get_makefile_filename()
- self.assertTrue(os.path.isfile(makefile), makefile)
-diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py
-index 47619c8807b..25c16e3a0b7 100644
---- a/Lib/test/test_unicode_file_functions.py
-+++ b/Lib/test/test_unicode_file_functions.py
-@@ -5,7 +5,7 @@
- import unittest
- import warnings
- from unicodedata import normalize
--from test.support import os_helper
-+from test.support import is_apple, os_helper
- from test import support
-
-
-@@ -23,13 +23,13 @@
- '10_\u1fee\u1ffd',
- ]
-
--# Mac OS X decomposes Unicode names, using Normal Form D.
-+# Apple platforms decompose Unicode names, using Normal Form D.
- # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
- # "However, most volume formats do not follow the exact specification for
- # these normal forms. For example, HFS Plus uses a variant of Normal Form D
- # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through
- # U+2FAFF are not decomposed."
--if sys.platform != 'darwin':
-+if not is_apple:
- filenames.extend([
- # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different
- '11_\u0385\u03d3\u03d4',
-@@ -119,11 +119,11 @@
- os.stat(name)
- self._apply_failure(os.listdir, name, self._listdir_failure)
-
-- # Skip the test on darwin, because darwin does normalize the filename to
-+ # Skip the test on Apple platforms, because they don't normalize the filename to
- # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC,
- # NFKD in Python is useless, because darwin will normalize it later and so
- # open(), os.stat(), etc. don't raise any exception.
-- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X')
-+ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms')
- @unittest.skipIf(
- support.is_emscripten or support.is_wasi,
- "test fails on Emscripten/WASI when host platform is macOS."
-@@ -142,10 +142,10 @@
- self._apply_failure(os.remove, name)
- self._apply_failure(os.listdir, name)
-
-- # Skip the test on darwin, because darwin uses a normalization different
-+ # Skip the test on Apple platforms, because they use a normalization different
- # than Python NFD normalization: filenames are different even if we use
- # Python NFD normalization.
-- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X')
-+ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms')
- def test_listdir(self):
- sf0 = set(self.files)
- with warnings.catch_warnings():
-diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
-index 12b1053aa23..95b815f17ed 100644
---- a/Lib/test/test_urllib2.py
-+++ b/Lib/test/test_urllib2.py
-@@ -1,6 +1,7 @@
- import unittest
- from test import support
- from test.support import os_helper
-+from test.support import requires_subprocess
- from test.support import warnings_helper
- from test import test_urllib
- from unittest import mock
-@@ -995,6 +996,7 @@
-
- file_obj.close()
-
-+ @requires_subprocess()
- def test_http_body_pipe(self):
- # A file reading from a pipe.
- # A pipe cannot be seek'ed. There is no way to determine the
-diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
-index 8254a701e09..c90e864939a 100644
---- a/Lib/test/test_venv.py
-+++ b/Lib/test/test_venv.py
-@@ -20,8 +20,8 @@
- import shlex
- from test.support import (captured_stdout, captured_stderr,
- skip_if_broken_multiprocessing_synchronize, verbose,
-- requires_subprocess, is_emscripten, is_wasi,
-- requires_venv_with_pip, TEST_HOME_DIR,
-+ requires_subprocess, is_apple_mobile, is_emscripten,
-+ is_wasi, requires_venv_with_pip, TEST_HOME_DIR,
- requires_resource, copy_python_src_ignore)
- from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree,
- TESTFN, FakePath)
-@@ -41,8 +41,10 @@
- or sys._base_executable != sys.executable,
- 'cannot run venv.create from within a venv on this platform')
-
--if is_emscripten or is_wasi:
-- raise unittest.SkipTest("venv is not available on Emscripten/WASI.")
-+# Skip tests on WASM platforms, plus iOS/tvOS/watchOS
-+if is_apple_mobile or is_emscripten or is_wasi:
-+ raise unittest.SkipTest(f"venv tests not required on {sys.platform}")
-+
-
- @requires_subprocess()
- def check_output(cmd, encoding=None):
-diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
-index 2d695bc8831..4a6586fb1dd 100644
---- a/Lib/test/test_webbrowser.py
-+++ b/Lib/test/test_webbrowser.py
-@@ -5,11 +5,14 @@
- import subprocess
- from unittest import mock
- from test import support
-+from test.support import is_apple_mobile
- from test.support import import_helper
- from test.support import os_helper
-+from test.support import requires_subprocess
-+from test.support import threading_helper
-
--if not support.has_subprocess_support:
-- raise unittest.SkipTest("test webserver requires subprocess")
-+# The webbrowser module uses threading locks
-+threading_helper.requires_working_threading(module=True)
-
- URL = 'https://www.example.com'
- CMD_NAME = 'test'
-@@ -24,6 +27,7 @@
- return 0
-
-
-+@requires_subprocess()
- class CommandTestMixin:
-
- def _test(self, meth, *, args=[URL], kw={}, options, arguments):
-@@ -219,6 +223,73 @@
- arguments=['openURL({},new-tab)'.format(URL)])
-
-
-+@unittest.skipUnless(sys.platform == "ios", "Test only applicable to iOS")
-+class IOSBrowserTest(unittest.TestCase):
-+ def _obj_ref(self, *args):
-+ # Construct a string representation of the arguments that can be used
-+ # as a proxy for object instance references
-+ return "|".join(str(a) for a in args)
-+
-+ @unittest.skipIf(getattr(webbrowser, "objc", None) is None,
-+ "iOS Webbrowser tests require ctypes")
-+ def setUp(self):
-+ # Intercept the the objc library. Wrap the calls to get the
-+ # references to classes and selectors to return strings, and
-+ # wrap msgSend to return stringified object references
-+ self.orig_objc = webbrowser.objc
-+
-+ webbrowser.objc = mock.Mock()
-+ webbrowser.objc.objc_getClass = lambda cls: f"C#{cls.decode()}"
-+ webbrowser.objc.sel_registerName = lambda sel: f"S#{sel.decode()}"
-+ webbrowser.objc.objc_msgSend.side_effect = self._obj_ref
-+
-+ def tearDown(self):
-+ webbrowser.objc = self.orig_objc
++/* Begin PBXBuildFile section */
++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; };
++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; };
++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; };
++ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; };
++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */; };
++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; };
++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; };
++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
++ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; };
++ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; };
++/* End PBXBuildFile section */
+
-+ def _test(self, meth, **kwargs):
-+ # The browser always gets focus, there's no concept of separate browser
-+ # windows, and there's no API-level control over creating a new tab.
-+ # Therefore, all calls to webbrowser are effectively the same.
-+ getattr(webbrowser, meth)(URL, **kwargs)
++/* Begin PBXContainerItemProxy section */
++ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = {
++ isa = PBXContainerItemProxy;
++ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */;
++ proxyType = 1;
++ remoteGlobalIDString = 607A66112B0EFA380010BFC8;
++ remoteInfo = iOSTestbed;
++ };
++/* End PBXContainerItemProxy section */
+
-+ # The ObjC String version of the URL is created with UTF-8 encoding
-+ url_string_args = [
-+ "C#NSString",
-+ "S#stringWithCString:encoding:",
-+ b'https://www.example.com',
-+ 4,
-+ ]
-+ # The NSURL version of the URL is created from that string
-+ url_obj_args = [
-+ "C#NSURL",
-+ "S#URLWithString:",
-+ self._obj_ref(*url_string_args),
-+ ]
-+ # The openURL call is invoked on the shared application
-+ shared_app_args = ["C#UIApplication", "S#sharedApplication"]
++/* 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 */
+
-+ # Verify that the last call is the one that opens the URL.
-+ webbrowser.objc.objc_msgSend.assert_called_with(
-+ self._obj_ref(*shared_app_args),
-+ "S#openURL:options:completionHandler:",
-+ self._obj_ref(*url_obj_args),
-+ None,
-+ None
-+ )
++/* Begin PBXFileReference section */
++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; };
++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
++ 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
++ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestbedTests.m; sourceTree = ""; };
++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; };
++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = ""; };
++ 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; };
++ 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; };
++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = iOSTestbed.xctestplan; sourceTree = ""; };
++/* End PBXFileReference section */
+
-+ def test_open(self):
-+ self._test('open')
++/* 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 */
+
-+ def test_open_with_autoraise_false(self):
-+ self._test('open', autoraise=False)
++/* Begin PBXGroup section */
++ 607A66092B0EFA380010BFC8 = {
++ isa = PBXGroup;
++ children = (
++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */,
++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */,
++ 607A66142B0EFA380010BFC8 /* iOSTestbed */,
++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */,
++ 607A66132B0EFA380010BFC8 /* Products */,
++ 607A664F2B0EFFE00010BFC8 /* Frameworks */,
++ );
++ sourceTree = "";
++ };
++ 607A66132B0EFA380010BFC8 /* Products */ = {
++ isa = PBXGroup;
++ children = (
++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */,
++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */,
++ );
++ name = Products;
++ sourceTree = "";
++ };
++ 607A66142B0EFA380010BFC8 /* iOSTestbed */ = {
++ isa = PBXGroup;
++ children = (
++ 608619552CB7819B00F46182 /* app */,
++ 608619532CB77BA900F46182 /* app_packages */,
++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */,
++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */,
++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */,
++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */,
++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */,
++ 607A66272B0EFA390010BFC8 /* main.m */,
++ );
++ path = iOSTestbed;
++ sourceTree = "";
++ };
++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = {
++ isa = PBXGroup;
++ children = (
++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */,
++ );
++ path = TestbedTests;
++ sourceTree = "";
++ };
++ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = {
++ isa = PBXGroup;
++ children = (
++ );
++ name = Frameworks;
++ sourceTree = "";
++ };
++/* End PBXGroup section */
+
-+ def test_open_new(self):
-+ self._test('open_new')
++/* 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 */
+
-+ def test_open_new_tab(self):
-+ self._test('open_new_tab')
++/* Begin PBXProject section */
++ 607A660A2B0EFA380010BFC8 /* Project object */ = {
++ isa = PBXProject;
++ attributes = {
++ BuildIndependentTargetsInParallel = 1;
++ LastUpgradeCheck = 1500;
++ TargetAttributes = {
++ 607A66112B0EFA380010BFC8 = {
++ CreatedOnToolsVersion = 15.0.1;
++ };
++ 607A662C2B0EFA3A0010BFC8 = {
++ CreatedOnToolsVersion = 15.0.1;
++ TestTargetID = 607A66112B0EFA380010BFC8;
++ };
++ };
++ };
++ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */;
++ compatibilityVersion = "Xcode 14.0";
++ developmentRegion = en;
++ hasScannedForEncodings = 0;
++ knownRegions = (
++ en,
++ Base,
++ );
++ mainGroup = 607A66092B0EFA380010BFC8;
++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */;
++ projectDirPath = "";
++ projectRoot = "";
++ targets = (
++ 607A66112B0EFA380010BFC8 /* iOSTestbed */,
++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */,
++ );
++ };
++/* End PBXProject section */
+
++/* Begin PBXResourcesBuildPhase section */
++ 607A66102B0EFA380010BFC8 /* Resources */ = {
++ isa = PBXResourcesBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */,
++ 608619562CB7819B00F46182 /* app in Resources */,
++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */,
++ 608619542CB77BA900F46182 /* app_packages in Resources */,
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++ 607A662B2B0EFA3A0010BFC8 /* Resources */ = {
++ isa = PBXResourcesBuildPhase;
++ buildActionMask = 2147483647;
++ files = (
++ );
++ runOnlyForDeploymentPostprocessing = 0;
++ };
++/* End PBXResourcesBuildPhase section */
+
- class BrowserRegistrationTest(unittest.TestCase):
-
- def setUp(self):
-@@ -302,6 +373,10 @@
- webbrowser.register(name, None, webbrowser.GenericBrowser(name))
- webbrowser.get(sys.executable)
-
-+ @unittest.skipIf(
-+ is_apple_mobile,
-+ "Apple mobile doesn't allow modifying browser with environment"
-+ )
- def test_environment(self):
- webbrowser = import_helper.import_fresh_module('webbrowser')
- try:
-@@ -313,6 +388,10 @@
- webbrowser = import_helper.import_fresh_module('webbrowser')
- webbrowser.get()
-
-+ @unittest.skipIf(
-+ is_apple_mobile,
-+ "Apple mobile doesn't allow modifying browser with environment"
-+ )
- def test_environment_preferred(self):
- webbrowser = import_helper.import_fresh_module('webbrowser')
- try:
-diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
-index 13b9e85f9e1..a6792fa8d56 100755
---- a/Lib/webbrowser.py
-+++ b/Lib/webbrowser.py
-@@ -476,6 +476,9 @@
- # OS X can use below Unix support (but we prefer using the OS X
- # specific stuff)
-
-+ if sys.platform == "ios":
-+ register("iosbrowser", None, IOSBrowser(), preferred=True)
-+
- if sys.platform == "serenityos":
- # SerenityOS webbrowser, simply called "Browser".
- register("Browser", None, BackgroundBrowser("Browser"))
-@@ -656,6 +659,70 @@
- rc = osapipe.close()
- return not rc
-
-+#
-+# Platform support for iOS
-+#
-+if sys.platform == "ios":
-+ from _ios_support import objc
-+ if objc:
-+ # If objc exists, we know ctypes is also importable.
-+ from ctypes import c_void_p, c_char_p, c_ulong
-+
-+ class IOSBrowser(BaseBrowser):
-+ def open(self, url, new=0, autoraise=True):
-+ sys.audit("webbrowser.open", url)
-+ # If ctypes isn't available, we can't open a browser
-+ if objc is None:
-+ return False
-+
-+ # All the messages in this call return object references.
-+ objc.objc_msgSend.restype = c_void_p
-+
-+ # This is the equivalent of:
-+ # NSString url_string =
-+ # [NSString stringWithCString:url.encode("utf-8")
-+ # encoding:NSUTF8StringEncoding];
-+ NSString = objc.objc_getClass(b"NSString")
-+ constructor = objc.sel_registerName(b"stringWithCString:encoding:")
-+ objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_char_p, c_ulong]
-+ url_string = objc.objc_msgSend(
-+ NSString,
-+ constructor,
-+ url.encode("utf-8"),
-+ 4, # NSUTF8StringEncoding = 4
-+ )
-+
-+ # Create an NSURL object representing the URL
-+ # This is the equivalent of:
-+ # NSURL *nsurl = [NSURL URLWithString:url];
-+ NSURL = objc.objc_getClass(b"NSURL")
-+ urlWithString_ = objc.sel_registerName(b"URLWithString:")
-+ objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_void_p]
-+ ns_url = objc.objc_msgSend(NSURL, urlWithString_, url_string)
-+
-+ # Get the shared UIApplication instance
-+ # This code is the equivalent of:
-+ # UIApplication shared_app = [UIApplication sharedApplication]
-+ UIApplication = objc.objc_getClass(b"UIApplication")
-+ sharedApplication = objc.sel_registerName(b"sharedApplication")
-+ objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
-+ shared_app = objc.objc_msgSend(UIApplication, sharedApplication)
-+
-+ # Open the URL on the shared application
-+ # This code is the equivalent of:
-+ # [shared_app openURL:ns_url
-+ # options:NIL
-+ # completionHandler:NIL];
-+ openURL_ = objc.sel_registerName(b"openURL:options:completionHandler:")
-+ objc.objc_msgSend.argtypes = [
-+ c_void_p, c_void_p, c_void_p, c_void_p, c_void_p
-+ ]
-+ # Method returns void
-+ objc.objc_msgSend.restype = None
-+ objc.objc_msgSend(shared_app, openURL_, ns_url, None, None)
-+
-+ return True
-+
-
- def main():
- import getopt
---- /dev/null
-+++ b/Mac/Resources/app-store-compliance.patch
-@@ -0,0 +1,29 @@
-+diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
-+index d6c83a75c1c..19ed4e01091 100644
-+--- a/Lib/test/test_urlparse.py
-++++ b/Lib/test/test_urlparse.py
-+@@ -237,11 +237,6 @@ def test_roundtrips(self):
-+ '','',''),
-+ ('git+ssh', 'git@github.com','/user/project.git',
-+ '', '')),
-+- ('itms-services://?action=download-manifest&url=https://example.com/app',
-+- ('itms-services', '', '', '',
-+- 'action=download-manifest&url=https://example.com/app', ''),
-+- ('itms-services', '', '',
-+- 'action=download-manifest&url=https://example.com/app', '')),
-+ ('+scheme:path/to/file',
-+ ('', '', '+scheme:path/to/file', '', '', ''),
-+ ('', '', '+scheme:path/to/file', '', '')),
-+diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
-+index 8f724f907d4..148caf742c9 100644
-+--- a/Lib/urllib/parse.py
-++++ b/Lib/urllib/parse.py
-+@@ -59,7 +59,7 @@
-+ 'imap', 'wais', 'file', 'mms', 'https', 'shttp',
-+ 'snews', 'prospero', 'rtsp', 'rtsps', 'rtspu', 'rsync',
-+ 'svn', 'svn+ssh', 'sftp', 'nfs', 'git', 'git+ssh',
-+- 'ws', 'wss', 'itms-services']
-++ 'ws', 'wss']
++/* 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 */
+
-+ uses_params = ['', 'ftp', 'hdl', 'prospero', 'http', 'imap',
-+ 'https', 'shttp', 'rtsp', 'rtsps', 'rtspu', 'sip',
-diff --git a/Makefile.pre.in b/Makefile.pre.in
-index 083f4c750a0..aaf8c86d460 100644
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -178,18 +178,29 @@
- EXE= @EXEEXT@
- BUILDEXE= @BUILDEXEEXT@
-
-+# Name of the patch file to apply for app store compliance
-+APP_STORE_COMPLIANCE_PATCH=@APP_STORE_COMPLIANCE_PATCH@
++/* 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 */
+
- # Short name and location for Mac OS X Python framework
- UNIVERSALSDK=@UNIVERSALSDK@
- PYTHONFRAMEWORK= @PYTHONFRAMEWORK@
- PYTHONFRAMEWORKDIR= @PYTHONFRAMEWORKDIR@
- PYTHONFRAMEWORKPREFIX= @PYTHONFRAMEWORKPREFIX@
- PYTHONFRAMEWORKINSTALLDIR= @PYTHONFRAMEWORKINSTALLDIR@
--# Deployment target selected during configure, to be checked
-+PYTHONFRAMEWORKINSTALLNAMEPREFIX= @PYTHONFRAMEWORKINSTALLNAMEPREFIX@
-+RESSRCDIR= @RESSRCDIR@
-+# macOS deployment target selected during configure, to be checked
- # by distutils. The export statement is needed to ensure that the
- # deployment target is active during build.
- MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@
- @EXPORT_MACOSX_DEPLOYMENT_TARGET@export MACOSX_DEPLOYMENT_TARGET
-
-+# iOS Deployment target selected during configure. Unlike macOS, the iOS
-+# deployment target is controlled using `-mios-version-min` arguments added to
-+# CFLAGS and LDFLAGS by the configure script. This variable is not used during
-+# the build, and is only listed here so it will be included in sysconfigdata.
-+IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@
++/* Begin PBXTargetDependency section */
++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = {
++ isa = PBXTargetDependency;
++ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */;
++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */;
++ };
++/* End PBXTargetDependency section */
+
- # Option to install to strip binaries
- STRIPFLAG=-s
-
-@@ -614,7 +625,7 @@
- .PHONY: all
-
- .PHONY: build_all
--build_all: check-clean-src $(BUILDPYTHON) platform sharedmods \
-+build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sharedmods \
- gdbhooks Programs/_testembed scripts checksharedmods rundsymutil
-
- .PHONY: build_wasm
-@@ -637,6 +648,16 @@
- exit 1; \
- fi
-
-+# Check that the app store compliance patch can be applied (if configured).
-+# This is checked as a dry-run against the original library sources;
-+# the patch will be actually applied during the install phase.
-+.PHONY: check-app-store-compliance
-+check-app-store-compliance:
-+ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \
-+ patch --dry-run --quiet --force --strip 1 --directory "$(abs_srcdir)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)"; \
-+ echo "App store compliance patch can be applied."; \
-+ fi
++/* Begin PBXVariantGroup section */
++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = {
++ isa = PBXVariantGroup;
++ children = (
++ 607A66242B0EFA390010BFC8 /* Base */,
++ );
++ name = LaunchScreen.storyboard;
++ sourceTree = "";
++ };
++/* End PBXVariantGroup section */
+
- # Profile generation build must start from a clean tree.
- profile-clean-stamp:
- $(MAKE) clean
-@@ -826,7 +847,7 @@
- $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^
-
- libpython$(LDVERSION).dylib: $(LIBRARY_OBJS)
-- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \
-+ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \
-
-
- libpython$(VERSION).sl: $(LIBRARY_OBJS)
-@@ -851,14 +872,13 @@
- # This rule is here for OPENSTEP/Rhapsody/MacOSX. It builds a temporary
- # minimal framework (not including the Lib directory and such) in the current
- # directory.
--RESSRCDIR=Mac/Resources/framework
- $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \
- $(LIBRARY) \
- $(RESSRCDIR)/Info.plist
- $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)
- $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \
-- -all_load $(LIBRARY) -Wl,-single_module \
-- -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK) \
-+ -all_load $(LIBRARY) \
-+ -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \
- -compatibility_version $(VERSION) \
- -current_version $(VERSION) \
- -framework CoreFoundation $(LIBS);
-@@ -870,6 +890,21 @@
- $(LN) -fsn Versions/Current/$(PYTHONFRAMEWORK) $(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)
- $(LN) -fsn Versions/Current/Resources $(PYTHONFRAMEWORKDIR)/Resources
-
-+# This rule is for iOS, which requires an annoyingly just slighly different
-+# format for frameworks to macOS. It *doesn't* use a versioned framework, and
-+# the Info.plist must be in the root of the framework.
-+$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK): \
-+ $(LIBRARY) \
-+ $(RESSRCDIR)/Info.plist
-+ $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)
-+ $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \
-+ -all_load $(LIBRARY) \
-+ -install_name $(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \
-+ -compatibility_version $(VERSION) \
-+ -current_version $(VERSION) \
-+ -framework CoreFoundation $(LIBS);
-+ $(INSTALL_DATA) $(RESSRCDIR)/Info.plist $(PYTHONFRAMEWORKDIR)/Info.plist
-+
- # This rule builds the Cygwin Python DLL and import library if configured
- # for a shared core library; otherwise, this rule is a noop.
- $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS)
-@@ -1857,6 +1892,36 @@
- $(RUNSHARED) /usr/libexec/oah/translate \
- ./$(BUILDPYTHON) -E -m test -j 0 -u all $(TESTOPTS)
-
-+# Run the test suite on the iOS simulator. Must be run on a macOS machine with
-+# a full Xcode install that has an iPhone SE (3rd edition) simulator available.
-+# This must be run *after* a `make install` has completed the build. The
-+# `--with-framework-name` argument *cannot* be used when configuring the build.
-+XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s)
-+.PHONY: testios
-+testios:
-+ @if test "$(MACHDEP)" != "ios"; then \
-+ echo "Cannot run the iOS testbed for a non-iOS build."; \
-+ exit 1;\
-+ fi
-+ @if test "$(findstring -iphonesimulator,$(MULTIARCH))" != "-iphonesimulator"; then \
-+ echo "Cannot run the iOS testbed for non-simulator builds."; \
-+ exit 1;\
-+ fi
-+ @if test $(PYTHONFRAMEWORK) != "Python"; then \
-+ echo "Cannot run the iOS testbed with a non-default framework name."; \
-+ exit 1;\
-+ fi
-+ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \
-+ echo "Cannot find a finalized iOS Python.framework. Have you run 'make install' to finalize the framework build?"; \
-+ exit 1;\
-+ fi
++/* Begin XCBuildConfiguration section */
++ 607A663F2B0EFA3A0010BFC8 /* Debug */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ALWAYS_SEARCH_USER_PATHS = NO;
++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
++ CLANG_ANALYZER_NONNULL = YES;
++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
++ CLANG_ENABLE_MODULES = YES;
++ CLANG_ENABLE_OBJC_ARC = YES;
++ CLANG_ENABLE_OBJC_WEAK = YES;
++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
++ CLANG_WARN_BOOL_CONVERSION = YES;
++ CLANG_WARN_COMMA = YES;
++ CLANG_WARN_CONSTANT_CONVERSION = YES;
++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
++ CLANG_WARN_EMPTY_BODY = YES;
++ CLANG_WARN_ENUM_CONVERSION = YES;
++ CLANG_WARN_INFINITE_RECURSION = YES;
++ CLANG_WARN_INT_CONVERSION = YES;
++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
++ CLANG_WARN_STRICT_PROTOTYPES = YES;
++ CLANG_WARN_SUSPICIOUS_MOVE = YES;
++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
++ CLANG_WARN_UNREACHABLE_CODE = YES;
++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
++ COPY_PHASE_STRIP = NO;
++ DEBUG_INFORMATION_FORMAT = dwarf;
++ ENABLE_STRICT_OBJC_MSGSEND = YES;
++ ENABLE_TESTABILITY = YES;
++ ENABLE_USER_SCRIPT_SANDBOXING = YES;
++ GCC_C_LANGUAGE_STANDARD = gnu17;
++ GCC_DYNAMIC_NO_PIC = NO;
++ GCC_NO_COMMON_BLOCKS = YES;
++ GCC_OPTIMIZATION_LEVEL = 0;
++ GCC_PREPROCESSOR_DEFINITIONS = (
++ "DEBUG=1",
++ "$(inherited)",
++ );
++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
++ GCC_WARN_UNDECLARED_SELECTOR = YES;
++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
++ GCC_WARN_UNUSED_FUNCTION = YES;
++ GCC_WARN_UNUSED_VARIABLE = YES;
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
++ MTL_FAST_MATH = YES;
++ ONLY_ACTIVE_ARCH = YES;
++ SDKROOT = iphoneos;
++ };
++ name = Debug;
++ };
++ 607A66402B0EFA3A0010BFC8 /* Release */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ALWAYS_SEARCH_USER_PATHS = NO;
++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
++ CLANG_ANALYZER_NONNULL = YES;
++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
++ CLANG_ENABLE_MODULES = YES;
++ CLANG_ENABLE_OBJC_ARC = YES;
++ CLANG_ENABLE_OBJC_WEAK = YES;
++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
++ CLANG_WARN_BOOL_CONVERSION = YES;
++ CLANG_WARN_COMMA = YES;
++ CLANG_WARN_CONSTANT_CONVERSION = YES;
++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
++ CLANG_WARN_EMPTY_BODY = YES;
++ CLANG_WARN_ENUM_CONVERSION = YES;
++ CLANG_WARN_INFINITE_RECURSION = YES;
++ CLANG_WARN_INT_CONVERSION = YES;
++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
++ CLANG_WARN_STRICT_PROTOTYPES = YES;
++ CLANG_WARN_SUSPICIOUS_MOVE = YES;
++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
++ CLANG_WARN_UNREACHABLE_CODE = YES;
++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
++ COPY_PHASE_STRIP = NO;
++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
++ ENABLE_NS_ASSERTIONS = NO;
++ ENABLE_STRICT_OBJC_MSGSEND = YES;
++ ENABLE_USER_SCRIPT_SANDBOXING = YES;
++ GCC_C_LANGUAGE_STANDARD = gnu17;
++ GCC_NO_COMMON_BLOCKS = YES;
++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
++ GCC_WARN_UNDECLARED_SELECTOR = YES;
++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
++ GCC_WARN_UNUSED_FUNCTION = YES;
++ GCC_WARN_UNUSED_VARIABLE = YES;
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
++ MTL_ENABLE_DEBUG_INFO = NO;
++ MTL_FAST_MATH = YES;
++ SDKROOT = iphoneos;
++ VALIDATE_PRODUCT = YES;
++ };
++ name = Release;
++ };
++ 607A66422B0EFA3A0010BFC8 /* Debug */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = "";
++ ENABLE_USER_SCRIPT_SANDBOXING = NO;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist";
++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
++ INFOPLIST_KEY_UIMainStoryboardFile = Main;
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LD_RUNPATH_SEARCH_PATHS = (
++ "$(inherited)",
++ "@executable_path/Frameworks",
++ );
++ MARKETING_VERSION = 3.13.0a1;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = YES;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ };
++ name = Debug;
++ };
++ 607A66432B0EFA3A0010BFC8 /* Release */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = "";
++ ENABLE_TESTABILITY = YES;
++ ENABLE_USER_SCRIPT_SANDBOXING = NO;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist";
++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
++ INFOPLIST_KEY_UIMainStoryboardFile = Main;
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ LD_RUNPATH_SEARCH_PATHS = (
++ "$(inherited)",
++ "@executable_path/Frameworks",
++ );
++ MARKETING_VERSION = 3.13.0a1;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = YES;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ };
++ name = Release;
++ };
++ 607A66452B0EFA3A0010BFC8 /* Debug */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ BUNDLE_LOADER = "$(TEST_HOST)";
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = 3HEZE76D99;
++ GENERATE_INFOPLIST_FILE = YES;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ MARKETING_VERSION = 1.0;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = NO;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed";
++ };
++ name = Debug;
++ };
++ 607A66462B0EFA3A0010BFC8 /* Release */ = {
++ isa = XCBuildConfiguration;
++ buildSettings = {
++ BUNDLE_LOADER = "$(TEST_HOST)";
++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
++ CODE_SIGN_STYLE = Automatic;
++ CURRENT_PROJECT_VERSION = 1;
++ DEVELOPMENT_TEAM = 3HEZE76D99;
++ GENERATE_INFOPLIST_FILE = YES;
++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\"";
++ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
++ MARKETING_VERSION = 1.0;
++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests;
++ PRODUCT_NAME = "$(TARGET_NAME)";
++ SWIFT_EMIT_LOC_STRINGS = NO;
++ TARGETED_DEVICE_FAMILY = "1,2";
++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed";
++ };
++ name = Release;
++ };
++/* End XCBuildConfiguration section */
+
-+ # Clone the testbed project into the XCFOLDER
-+ $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)"
++/* 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 */;
++}
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme
+@@ -0,0 +1,97 @@
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed.xctestplan
+@@ -0,0 +1,46 @@
++{
++ "configurations" : [
++ {
++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5",
++ "name" : "Test Scheme Action",
++ "options" : {
+
-+ # Run the testbed project
-+ $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W
++ }
++ }
++ ],
++ "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
++}
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed/AppDelegate.h
+@@ -0,0 +1,11 @@
++//
++// AppDelegate.h
++// iOSTestbed
++//
+
- # Like testall, but with only one pass and without multiple processes.
- # Run an optional script to include information about the build environment.
- .PHONY: buildbottest
-@@ -1900,7 +1965,7 @@
- # which can lead to two parallel `./python setup.py build` processes that
- # step on each others toes.
- .PHONY: install
--install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKINSTALLLAST@
-+install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@
- if test "x$(ENSUREPIP)" != "xno" ; then \
- case $(ENSUREPIP) in \
- upgrade) ensurepip="--upgrade" ;; \
-@@ -2334,6 +2399,14 @@
- $(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py \
- $(DESTDIR)$(LIBDEST); \
- $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
-+ @ # If app store compliance has been configured, apply the patch to the
-+ @ # installed library code. The patch has been previously validated against
-+ @ # the original source tree, so we can ignore any errors that are raised
-+ @ # due to files that are missing because of --disable-test-modules etc.
-+ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \
-+ echo "Applying app store compliance patch"; \
-+ patch --force --reject-file "$(abs_builddir)/app-store-compliance.rej" --strip 2 --directory "$(DESTDIR)$(LIBDEST)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)" || true ; \
-+ fi
- @ # Build PYC files for the 3 optimization levels (0, 1, 2)
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
- $(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \
-@@ -2510,10 +2583,11 @@
- # only have to cater for the structural bits of the framework.
-
- .PHONY: frameworkinstallframework
--frameworkinstallframework: frameworkinstallstructure install frameworkinstallmaclib
-+frameworkinstallframework: @FRAMEWORKINSTALLFIRST@ install frameworkinstallmaclib
-
--.PHONY: frameworkinstallstructure
--frameworkinstallstructure: $(LDLIBRARY)
-+# macOS uses a versioned frameworks structure that includes a full install
-+.PHONY: frameworkinstallversionedstructure
-+frameworkinstallversionedstructure: $(LDLIBRARY)
- @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \
- echo Not configured with --enable-framework; \
- exit 1; \
-@@ -2534,6 +2608,27 @@
- $(LN) -fsn Versions/Current/Resources $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Resources
- $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY)
-
-+# iOS/tvOS/watchOS uses a non-versioned framework with Info.plist in the
-+# framework root, no .lproj data, and only stub compilation assistance binaries
-+.PHONY: frameworkinstallunversionedstructure
-+frameworkinstallunversionedstructure: $(LDLIBRARY)
-+ @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \
-+ echo Not configured with --enable-framework; \
-+ exit 1; \
-+ else true; \
-+ fi
-+ if test -d $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; then \
-+ echo "Clearing stale header symlink directory"; \
-+ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; \
-+ fi
-+ $(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)$(BINDIR)
-+ for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \
-+ $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \
-+ done
++#import
+
- # This installs Mac/Lib into the framework
- # Install a number of symlinks to keep software that expects a normal unix
- # install (which includes python-config) happy.
-@@ -2574,6 +2669,19 @@
- frameworkinstallextras:
- cd Mac && $(MAKE) installextras DESTDIR="$(DESTDIR)"
-
-+# On iOS, bin/lib can't live inside the framework; include needs to be called
-+# "Headers", but *must* be in the framework, and *not* include the `python3.X`
-+# subdirectory. The install has put these folders in the same folder as
-+# Python.framework; Move the headers to their final framework-compatible home.
-+.PHONY: frameworkinstallmobileheaders
-+frameworkinstallmobileheaders: frameworkinstallunversionedstructure inclinstall
-+ if test -d $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; then \
-+ echo "Removing old framework headers"; \
-+ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \
-+ fi
-+ mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers"
-+ $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)"
++@interface AppDelegate : UIResponder
+
- # Build the toplevel Makefile
- Makefile.pre: $(srcdir)/Makefile.pre.in config.status
- CONFIG_FILES=Makefile.pre CONFIG_HEADERS= ./config.status
-@@ -2684,6 +2792,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
-
- .PHONY: profile-removal
- profile-removal:
-@@ -2709,6 +2821,8 @@
- config.cache config.log pyconfig.h Modules/config.c
- -rm -rf build platform
- -rm -rf $(PYTHONFRAMEWORKDIR)
-+ -rm -rf iOS/Frameworks
-+ -rm -rf iOSTestbed.*
- -rm -f python-config.py python-config
-
- # Make things extra clean, before making a distribution:
-diff --git a/Modules/getpath.c b/Modules/getpath.c
-index 0a310000751..83a2bc469ae 100644
---- a/Modules/getpath.c
-+++ b/Modules/getpath.c
-@@ -15,6 +15,7 @@
- #endif
-
- #ifdef __APPLE__
-+# include "TargetConditionals.h"
- # include
- #endif
-
-@@ -759,7 +760,7 @@
- return winmodule_to_dict(dict, key, PyWin_DLLhModule);
- }
- #endif
--#elif defined(WITH_NEXT_FRAMEWORK)
-+#elif defined(WITH_NEXT_FRAMEWORK) && !defined(TARGET_OS_IPHONE)
- static char modPath[MAXPATHLEN + 1];
- static int modPathInitialized = -1;
- if (modPathInitialized < 0) {
-@@ -953,4 +954,3 @@
-
- return _PyStatus_OK();
- }
--
-diff --git a/Python/marshal.c b/Python/marshal.c
-index 3fc3f890422..892debe38dc 100644
---- a/Python/marshal.c
-+++ b/Python/marshal.c
-@@ -16,6 +16,10 @@
- #include "marshal.h" // Py_MARSHAL_VERSION
- #include "pycore_pystate.h" // _PyInterpreterState_GET()
-
-+#ifdef __APPLE__
-+# include "TargetConditionals.h"
-+#endif /* __APPLE__ */
+
- /*[clinic input]
- module marshal
- [clinic start generated code]*/
-@@ -35,11 +39,14 @@
- * #if defined(MS_WINDOWS) && defined(_DEBUG)
- */
- #if defined(MS_WINDOWS)
--#define MAX_MARSHAL_STACK_DEPTH 1000
-+# define MAX_MARSHAL_STACK_DEPTH 1000
- #elif defined(__wasi__)
--#define MAX_MARSHAL_STACK_DEPTH 1500
-+# define MAX_MARSHAL_STACK_DEPTH 1500
-+// TARGET_OS_IPHONE covers any non-macOS Apple platform.
-+#elif defined(__APPLE__) && TARGET_OS_IPHONE
-+# define MAX_MARSHAL_STACK_DEPTH 1500
- #else
--#define MAX_MARSHAL_STACK_DEPTH 2000
-+# define MAX_MARSHAL_STACK_DEPTH 2000
- #endif
-
- #define TYPE_NULL '0'
-diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
-index e9c1a0d72d6..811ebcb14ce 100644
---- a/Python/pylifecycle.c
-+++ b/Python/pylifecycle.c
-@@ -34,7 +34,21 @@
- #include // getenv()
-
- #if defined(__APPLE__)
--#include
-+# include
-+# include
-+# include
-+// The os_log unified logging APIs were introduced in macOS 10.12, iOS 10.0,
-+// tvOS 10.0, and watchOS 3.0; we enable the use of the system logger
-+// automatically on non-macOS platforms.
-+# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
-+# define USE_APPLE_SYSTEM_LOG 1
-+# else
-+# define USE_APPLE_SYSTEM_LOG 0
-+# endif
++@end
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed/AppDelegate.m
+@@ -0,0 +1,19 @@
++//
++// AppDelegate.m
++// iOSTestbed
++//
+
-+# if USE_APPLE_SYSTEM_LOG
-+# include
-+# endif
- #endif
-
- #ifdef HAVE_SIGNAL_H
-@@ -66,6 +80,9 @@
- static PyStatus init_import_site(void);
- static PyStatus init_set_builtins_open(void);
- static PyStatus init_sys_streams(PyThreadState *tstate);
-+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG
-+static PyStatus init_apple_streams(PyThreadState *tstate);
-+#endif
- static void wait_for_thread_shutdown(PyThreadState *tstate);
- static void call_ll_exitfuncs(_PyRuntimeState *runtime);
-
-@@ -1177,6 +1194,17 @@
- return status;
- }
-
-+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG
-+ status = init_apple_streams(tstate);
-+ if (_PyStatus_EXCEPTION(status)) {
-+ return status;
-+ }
-+#endif
++#import "AppDelegate.h"
+
-+#ifdef Py_DEBUG
-+ run_presite(tstate);
-+#endif
++@interface AppDelegate ()
+
- status = add_main_module(interp);
- if (_PyStatus_EXCEPTION(status)) {
- return status;
-@@ -2620,6 +2648,69 @@
- return res;
- }
-
-+#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG
++@end
+
-+static PyObject *
-+apple_log_write_impl(PyObject *self, PyObject *args)
++@implementation AppDelegate
++
++
++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
++ return YES;
++}
++
++@end
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
+@@ -0,0 +1,11 @@
+{
-+ int logtype = 0;
-+ const char *text = NULL;
-+ if (!PyArg_ParseTuple(args, "iy", &logtype, &text)) {
-+ return NULL;
++ "colors" : [
++ {
++ "idiom" : "universal"
+ }
-+
-+ // Pass the user-provided text through explicit %s formatting
-+ // to avoid % literals being interpreted as a formatting directive.
-+ os_log_with_type(OS_LOG_DEFAULT, logtype, "%s", text);
-+ Py_RETURN_NONE;
++ ],
++ "info" : {
++ "author" : "xcode",
++ "version" : 1
++ }
++}
+--- /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
++ }
++}
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json
+@@ -0,0 +1,6 @@
++{
++ "info" : {
++ "author" : "xcode",
++ "version" : 1
++ }
+}
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard
+@@ -0,0 +1,9 @@
++
++
++
++
++
++
++
++
++
+--- /dev/null
++++ b/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.
+
-+static PyMethodDef apple_log_write_method = {
-+ "apple_log_write", apple_log_write_impl, METH_VARARGS
-+};
++When the test suite runs, this folder will be on the PYTHONPATH, and will be the
++working directory for the test suite.
+--- /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.
+
++During the build, any binary modules found in this folder will be processed into
++Framework form.
+
-+static PyStatus
-+init_apple_streams(PyThreadState *tstate)
-+{
-+ PyStatus status = _PyStatus_OK();
-+ PyObject *_apple_support = NULL;
-+ PyObject *apple_log_write = NULL;
-+ PyObject *result = NULL;
++When the test suite runs, this folder will be on the PYTHONPATH.
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist
+@@ -0,0 +1,52 @@
++
++
++
++
++ CFBundleDevelopmentRegion
++ en
++ CFBundleDisplayName
++ ${PRODUCT_NAME}
++ CFBundleExecutable
++ ${EXECUTABLE_NAME}
++ CFBundleIdentifier
++ org.python.iOSTestbed
++ CFBundleInfoDictionaryVersion
++ 6.0
++ CFBundleName
++ ${PRODUCT_NAME}
++ CFBundlePackageType
++ APPL
++ CFBundleShortVersionString
++ 1.0
++ CFBundleSignature
++ ????
++ CFBundleVersion
++ 1
++ LSRequiresIPhoneOS
++
++ UIRequiresFullScreen
++
++ UILaunchStoryboardName
++ Launch Screen
++ UISupportedInterfaceOrientations
++
++ UIInterfaceOrientationPortrait
++ UIInterfaceOrientationLandscapeLeft
++ UIInterfaceOrientationLandscapeRight
++
++ UISupportedInterfaceOrientations~ipad
++
++ UIInterfaceOrientationPortrait
++ UIInterfaceOrientationPortraitUpsideDown
++ UIInterfaceOrientationLandscapeLeft
++ UIInterfaceOrientationLandscapeRight
++
++ UIApplicationSceneManifest
++
++ UIApplicationSupportsMultipleScenes
++
++ UISceneConfigurations
++
++
++
++
+--- /dev/null
++++ b/Apple/testbed/iOSTestbed/main.m
+@@ -0,0 +1,16 @@
++//
++// main.m
++// iOSTestbed
++//
+
-+ _apple_support = PyImport_ImportModule("_apple_support");
-+ if (_apple_support == NULL) {
-+ goto error;
-+ }
++#import
++#import "AppDelegate.h"
+
-+ apple_log_write = PyCFunction_New(&apple_log_write_method, NULL);
-+ if (apple_log_write == NULL) {
-+ goto error;
-+ }
++int main(int argc, char * argv[]) {
++ NSString * appDelegateClassName;
++ @autoreleasepool {
++ appDelegateClassName = NSStringFromClass([AppDelegate class]);
+
-+ // Initialize the logging streams, sending stdout -> Default; stderr -> Error
-+ result = PyObject_CallMethod(
-+ _apple_support, "init_streams", "Oii",
-+ apple_log_write, OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_ERROR);
-+ if (result == NULL) {
-+ goto error;
++ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+ }
-+ goto done;
++}
+--- /dev/null
++++ b/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj
+@@ -0,0 +1,506 @@
++// !$*UTF8*$!
++{
++ archiveVersion = 1;
++ classes = {
++ };
++ objectVersion = 77;
++ objects = {
+
-+error:
-+ _PyErr_Print(tstate);
-+ status = _PyStatus_ERR("failed to initialize Apple log streams");
++/* 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 */
+
-+done:
-+ Py_XDECREF(result);
-+ Py_XDECREF(apple_log_write);
-+ Py_XDECREF(_apple_support);
-+ return status;
-+}
++/* Begin PBXContainerItemProxy section */
++ EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */ = {
++ isa = PBXContainerItemProxy;
++ containerPortal = EE989E462DCD6E780036B268 /* Project object */;
++ proxyType = 1;
++ remoteGlobalIDString = EE989E4D2DCD6E780036B268;
++ remoteInfo = tvOSTestbed;
++ };
++/* End PBXContainerItemProxy section */
+
-+#endif // __APPLE__ && USE_APPLE_SYSTEM_LOG
++/* 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";
++ showEnvVarsInLog = 0;
++ };
++/* 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 */;
++}
+--- /dev/null
++++ b/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme
+@@ -0,0 +1,97 @@
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
+--- /dev/null
++++ b/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
+--- /dev/null
++++ b/Apple/testbed/tvOSTestbed/AppDelegate.h
+@@ -0,0 +1,11 @@
++//
++// AppDelegate.h
++// tvOSTestbed
++//
++
++#import
++
++@interface AppDelegate : UIResponder
++
++
++@end
+--- /dev/null
++++ b/Apple/testbed/tvOSTestbed/AppDelegate.m
+@@ -0,0 +1,19 @@
++//
++// AppDelegate.m
++// tvOSTestbed
++//
++
++#import "AppDelegate.h"
++
++@interface AppDelegate ()
++
++@end
++
++@implementation AppDelegate
++
++
++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
++ return YES;
++}
++
++@end
+--- /dev/null
++++ b/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard
+@@ -0,0 +1,24 @@
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
+--- /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.
+--- /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.
+--- /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);
++ }
++}
+--- /dev/null
++++ b/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist
+@@ -0,0 +1,52 @@
++
++
++
++
++ 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
++
++
++
++
+--- /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.
++
++Compilers for building on tvOS
++==============================
++
++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
++===================================
++
++* ``--enable-framework[=DIR]``
++
++ This argument specifies the location where the Python.framework will
++ be installed.
++
++* ``--with-framework-name=NAME``
++
++ Specify the name for the python framework, defaults to ``Python``.
++
++
++Building and using Python on tvOS
++=================================
++
++ABIs and Architectures
++----------------------
++
++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.
++
++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.
++
++How do I build Python for tvOS?
++-------------------------------
++
++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:
++
++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.
++
++tvOS 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 tvOS will look somethign like::
++
++ $ ./configure \
++ --enable-framework=/path/to/install \
++ --host=aarch64-apple-tvos \
++ --build=aarch64-apple-darwin \
++ --with-build-python=/path/to/python.exe
++ $ make
++ $ make install
++
++In this invocation:
++
++* ``/path/to/install`` is the location where the final Python.framework will be
++ output.
++
++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
++ triple format. This will be one of:
++
++ - ``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.
++
++* ``--build`` is the GNU compiler triple for the machine that will be running
++ the compiler. This is one of:
++
++ - ``aarch64-apple-darwin`` for Apple Silicon devices.
++ - ``x86_64-apple-darwin`` for Intel devices.
++
++* ``/path/to/python.exe`` is the path to a Python binary on the machine that
++ will be running the compiler. This is needed because the Python compilation
++ process involves running some Python code. On a normal desktop build of
++ Python, you can compile a python interpreter and then use that interpreter to
++ run Python code. However, the binaries produced for 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.
++
++Using a framework-based Python on tvOS
++======================================
+--- /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@
++
++
+--- /dev/null
++++ b/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@"
+--- /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} "$@"
+--- /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} "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /dev/null
++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
+--- /dev/null
++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
+--- /dev/null
++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@"
+--- /dev/null
++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+--- /dev/null
++++ b/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch x86_64 "$@"
+--- /dev/null
++++ b/Apple/tvOS/Resources/pyconfig.h
+@@ -0,0 +1,7 @@
++#ifdef __arm64__
++#include "pyconfig-arm64.h"
++#endif
++
++#ifdef __x86_64__
++#include "pyconfig-x86_64.h"
++#endif
+--- /dev/null
++++ b/Apple/watchOS/README.rst
+@@ -0,0 +1,108 @@
++========================
++Python on watchOS README
++========================
++
++:Authors:
++ Russell Keith-Magee (2023-11)
++
++This document provides a quick overview of some watchOS specific features in the
++Python distribution.
++
++Compilers for building on watchOS
++=================================
++
++Building for watchOS requires the use of Apple's Xcode tooling. It is strongly
++recommended that you use the most recent stable release of Xcode, on the
++most recently released macOS.
++
++watchOS specific arguments to configure
++=======================================
++
++* ``--enable-framework[=DIR]``
++
++ This argument specifies the location where the Python.framework will
++ be installed.
++
++* ``--with-framework-name=NAME``
++
++ Specify the name for the python framework, defaults to ``Python``.
++
++
++Building and using Python on watchOS
++====================================
++
++ABIs and Architectures
++----------------------
++
++watchOS apps can be deployed on physical devices, and on the watchOS simulator.
++Although the API used on these devices is identical, the ABI is different - you
++need to link against different libraries for an watchOS device build
++(``watchos``) or an watchOS simulator build (``watchsimulator``). Apple uses the
++XCframework format to allow specifying a single dependency that supports
++multiple ABIs. An XCframework is a wrapper around multiple ABI-specific
++frameworks.
++
++watchOS can also support different CPU architectures within each ABI. At present,
++there is only a single support ed architecture on physical devices - ARM64.
++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple
++Silicon machines), and x86_64 (for running on older Intel-based machines.)
++
++To support multiple CPU architectures on a single platform, Apple uses a "fat
++binary" format - a single physical file that contains support for multiple
++architectures.
++
++How do I build Python for watchOS?
++-------------------------------
++
++The Python build system will build a ``Python.framework`` that supports a
++*single* ABI with a *single* architecture. If you want to use Python in an watchOS
++project, you need to:
++
++1. Produce multiple ``Python.framework`` builds, one for each ABI and architecture;
++2. Merge the binaries for each architecture on a given ABI into a single "fat" binary;
++3. Merge the "fat" frameworks for each ABI into a single XCframework.
++
++watchOS builds of Python *must* be constructed as framework builds. To support this,
++you must provide the ``--enable-framework`` flag when configuring the build.
++
++The build also requires the use of cross-compilation. The commands for building
++Python for watchOS will look somethign like::
++
++ $ ./configure \
++ --enable-framework=/path/to/install \
++ --host=aarch64-apple-watchos \
++ --build=aarch64-apple-darwin \
++ --with-build-python=/path/to/python.exe
++ $ make
++ $ make install
++
++In this invocation:
++
++* ``/path/to/install`` is the location where the final Python.framework will be
++ output.
++
++* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
++ triple format. This will be one of:
++
++ - ``arm64_32-apple-watchos`` for ARM64-32 watchOS devices.
++ - ``aarch64-apple-watchos-simulator`` for the watchOS simulator running on Apple
++ Silicon devices.
++ - ``x86_64-apple-watchos-simulator`` for the watchOS simulator running on Intel
++ devices.
++
++* ``--build`` is the GNU compiler triple for the machine that will be running
++ the compiler. This is one of:
++
++ - ``aarch64-apple-darwin`` for Apple Silicon devices.
++ - ``x86_64-apple-darwin`` for Intel devices.
++
++* ``/path/to/python.exe`` is the path to a Python binary on the machine that
++ will be running the compiler. This is needed because the Python compilation
++ process involves running some Python code. On a normal desktop build of
++ Python, you can compile a python interpreter and then use that interpreter to
++ run Python code. However, the binaries produced for watchOS won't run on macOS, so
++ you need to provide an external Python interpreter. This interpreter must be
++ the version as the Python that is being compiled.
++
++Using a framework-based Python on watchOS
++======================================
+--- /dev/null
++++ b/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@
++
++
+--- /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 "$@"
+--- /dev/null
++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
+--- /dev/null
++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@"
+--- /dev/null
++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp
+@@ -0,0 +1,2 @@
++#!/bin/bash
++xcrun --sdk watchsimulator clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@"
+--- /dev/null
++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch arm64 "$@"
+--- /dev/null
++++ b/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk watchos${WATCHOS_SDK_VERSION} strip -arch arm64 "$@"
+--- /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 "$@"
+--- /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} "$@"
+--- /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} "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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 "$@"
+--- /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
++
++#ifdef __x86_64__
++#include "pyconfig-x86_64.h"
++#endif
+--- /dev/null
++++ b/Doc/includes/wasm-ios-notavail.rst
+@@ -0,0 +1,8 @@
++.. include for modules that don't work on WASM or iOS
++
++.. availability:: not WASI, not iOS.
++
++ This module does not work or is not available on WebAssembly platforms, or
++ on iOS. See :ref:`wasm-availability` for more information on WASM
++ availability; see :ref:`iOS-availability` for more information on iOS
++ availability.
+diff --git a/Doc/includes/wasm-notavail.rst b/Doc/includes/wasm-notavail.rst
+index e680e1f9b43..c1b79d2a4a0 100644
+--- a/Doc/includes/wasm-notavail.rst
++++ b/Doc/includes/wasm-notavail.rst
+@@ -1,7 +1,6 @@
+ .. include for modules that don't work on WASM
+
+-.. availability:: not Emscripten, not WASI.
++.. availability:: not WASI.
+
+- This module does not work or is not available on WebAssembly platforms
+- ``wasm32-emscripten`` and ``wasm32-wasi``. See
++ This module does not work or is not available on WebAssembly. See
+ :ref:`wasm-availability` for more information.
+diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst
+index 2ebda3d3396..91ea6150fb1 100644
+--- a/Doc/library/curses.rst
++++ b/Doc/library/curses.rst
+@@ -21,6 +21,8 @@
+ designed to match the API of ncurses, an open-source curses library hosted on
+ Linux and the BSD variants of Unix.
+
++.. include:: ../includes/wasm-ios-notavail.rst
++
+ .. note::
+
+ Whenever the documentation mentions a *character* it can be specified
+diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst
+index 53f186952ce..68a8c30aa99 100644
+--- a/Doc/library/dbm.rst
++++ b/Doc/library/dbm.rst
+@@ -14,6 +14,7 @@
+ is a `third party interface `_ to
+ the Oracle Berkeley DB.
+
++.. include:: ../includes/wasm-ios-notavail.rst
+
+ .. exception:: error
+
+@@ -398,4 +399,3 @@
+ .. method:: dumbdbm.close()
+
+ Close the database.
+-
+diff --git a/Doc/library/ensurepip.rst b/Doc/library/ensurepip.rst
+index 10773ee5f76..692ba9eb6a9 100644
+--- a/Doc/library/ensurepip.rst
++++ b/Doc/library/ensurepip.rst
+@@ -38,7 +38,7 @@
+ :pep:`453`: Explicit bootstrapping of pip in Python installations
+ The original rationale and specification for this module.
+
+-.. include:: ../includes/wasm-notavail.rst
++.. include:: ../includes/wasm-ios-notavail.rst
+
+ Command line interface
+ ----------------------
+diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst
+index d23a105cd5b..1faef54c116 100644
+--- a/Doc/library/fcntl.rst
++++ b/Doc/library/fcntl.rst
+@@ -18,7 +18,7 @@
+ See the :manpage:`fcntl(2)` and :manpage:`ioctl(2)` Unix manual pages
+ for full details.
+
+-.. availability:: Unix, not Emscripten, not WASI.
++.. availability:: Unix, not WASI.
+
+ All functions in this module take a file descriptor *fd* as their first
+ argument. This can be an integer file descriptor, such as returned by
+diff --git a/Doc/library/grp.rst b/Doc/library/grp.rst
+index 57a77d51a02..f1157e189a3 100644
+--- a/Doc/library/grp.rst
++++ b/Doc/library/grp.rst
+@@ -10,7 +10,7 @@
+ This module provides access to the Unix group database. It is available on all
+ Unix versions.
+
+-.. availability:: Unix, not Emscripten, not WASI.
++.. availability:: Unix, not WASI, not iOS.
+
+ Group database entries are reported as a tuple-like object, whose attributes
+ correspond to the members of the ``group`` structure (Attribute field below, see
+diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
+index a9d7665ac20..6611bd5018c 100644
+--- a/Doc/library/importlib.rst
++++ b/Doc/library/importlib.rst
+@@ -1233,6 +1233,69 @@
+ and how the module's :attr:`~module.__file__` is populated.
+
+
++.. class:: AppleFrameworkLoader(name, path)
++
++ A specialization of :class:`importlib.machinery.ExtensionFileLoader` that
++ is able to load extension modules in Framework format.
++
++ For compatibility with the iOS App Store, *all* binary modules in an iOS app
++ must be dynamic libraries, contained in a framework with appropriate
++ metadata, stored in the ``Frameworks`` folder of the packaged app. There can
++ be only a single binary per framework, and there can be no executable binary
++ material outside the Frameworks folder.
++
++ To accomodate this requirement, when running on iOS, extension module
++ binaries are *not* packaged as ``.so`` files on ``sys.path``, but as
++ individual standalone frameworks. To discover those frameworks, this loader
++ is be registered against the ``.fwork`` file extension, with a ``.fwork``
++ file acting as a placeholder in the original location of the binary on
++ ``sys.path``. The ``.fwork`` file contains the path of the actual binary in
++ the ``Frameworks`` folder, relative to the app bundle. To allow for
++ resolving a framework-packaged binary back to the original location, the
++ framework is expected to contain a ``.origin`` file that contains the
++ location of the ``.fwork`` file, relative to the app bundle.
++
++ For example, consider the case of an import ``from foo.bar import _whiz``,
++ where ``_whiz`` is implemented with the binary module
++ ``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location
++ registered on ``sys.path``, relative to the application bundle. This module
++ *must* be distributed as
++ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` (creating the framework
++ name from the full import path of the module), with an ``Info.plist`` file
++ in the ``.framework`` directory identifying the binary as a framework. The
++ ``foo.bar._whiz`` module would be represented in the original location with
++ a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing the path
++ ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also contain
++ ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing the
++ path to the ``.fwork`` file.
++
++ When a module is loaded with this loader, the ``__file__`` for the module
++ will report as the location of the ``.fwork`` file. This allows code to use
++ the ``__file__`` of a module as an anchor for file system traveral.
++ However, the spec origin will reference the location of the *actual* binary
++ in the ``.framework`` folder.
++
++ The Xcode project building the app is responsible for converting any ``.so``
++ files from wherever they exist in the ``PYTHONPATH`` into frameworks in the
++ ``Frameworks`` folder (including stripping extensions from the module file,
++ the addition of framework metadata, and signing the resulting framework),
++ and creating the ``.fwork`` and ``.origin`` files. This will usually be done
++ with a build step in the Xcode project; see the iOS documentation for
++ details on how to construct this build step.
++
++ .. versionadded:: 3.13
++
++ .. availability:: iOS.
++
++ .. attribute:: name
++
++ Name of the module the loader supports.
++
++ .. attribute:: path
++
++ Path to the ``.fwork`` file for the extension module.
++
++
+ :mod:`importlib.util` -- Utility code for importers
+ ---------------------------------------------------
- static void
- _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
-diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h
-index 1b1a1bdee78..a37f531984e 100644
---- a/Python/stdlib_module_names.h
-+++ b/Python/stdlib_module_names.h
-@@ -5,6 +5,7 @@
- "__future__",
- "_abc",
- "_aix_support",
-+"_apple_support",
- "_ast",
- "_asyncio",
- "_bisect",
-@@ -39,6 +40,7 @@
- "_heapq",
- "_imp",
- "_io",
-+"_ios_support",
- "_json",
- "_locale",
- "_lsprof",
-diff --git a/config.sub b/config.sub
-index d74fb6deac9..1bb6a05dc11 100755
---- a/config.sub
-+++ b/config.sub
-@@ -1,14 +1,15 @@
- #! /bin/sh
- # Configuration validation subroutine script.
--# Copyright 1992-2021 Free Software Foundation, Inc.
-+# Copyright 1992-2024 Free Software Foundation, Inc.
-
- # shellcheck disable=SC2006,SC2268 # see below for rationale
-
--timestamp='2021-08-14'
-+# Patched 2024-02-03 to include support for arm64_32 and iOS/tvOS/watchOS simulators
-+timestamp='2024-01-01'
-
- # This file is free software; you can redistribute it and/or modify it
- # under the terms of the GNU General Public License as published by
--# the Free Software Foundation; either version 3 of the License, or
-+# the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful, but
-@@ -76,13 +77,13 @@
- version="\
- GNU config.sub ($timestamp)
+diff --git a/Doc/library/intro.rst b/Doc/library/intro.rst
+index 5a4c9b8b16a..ffc8939d211 100644
+--- a/Doc/library/intro.rst
++++ b/Doc/library/intro.rst
+@@ -58,7 +58,7 @@
+ operating system.
--Copyright 1992-2021 Free Software Foundation, Inc.
-+Copyright 1992-2024 Free Software Foundation, Inc.
+ * If not separately noted, all functions that claim "Availability: Unix" are
+- supported on macOS, which builds on a Unix core.
++ supported on macOS and iOS, both of which build on a Unix core.
- This is free software; see the source for copying conditions. There is NO
- warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+ * If an availability note contains both a minimum Kernel version and a minimum
+ libc version, then both conditions must hold. For example a feature with note
+@@ -119,3 +119,44 @@
+ .. _wasmtime: https://wasmtime.dev/
+ .. _Pyodide: https://pyodide.org/
+ .. _PyScript: https://pyscript.net/
++
++.. _iOS-availability:
++
++iOS
++---
++
++iOS is, in most respects, a POSIX operating system. File I/O, socket handling,
++and threading all behave as they would on any POSIX operating system. However,
++there are several major differences between iOS and other POSIX systems.
++
++* iOS can only use Python in "embedded" mode. There is no Python REPL, and no
++ ability to execute binaries that are part of the normal Python developer
++ experience, such as :program:`pip`. To add Python code to your iOS app, you must use
++ the :ref:`Python embedding API ` to add a Python interpreter to an
++ iOS app created with Xcode. See the :ref:`iOS usage guide ` for
++ more details.
++
++* An iOS app cannot use any form of subprocessing, background processing, or
++ inter-process communication. If an iOS app attempts to create a subprocess,
++ the process creating the subprocess will either lock up, or crash. An iOS app
++ has no visibility of other applications that are running, nor any ability to
++ communicate with other running applications, outside of the iOS-specific APIs
++ that exist for this purpose.
++
++* iOS apps have limited access to modify system resources (such as the system
++ clock). These resources will often be *readable*, but attempts to modify
++ those resources will usually fail.
++
++* iOS apps have a limited concept of console input and output. ``stdout`` and
++ ``stderr`` *exist*, and content written to ``stdout`` and ``stderr`` will be
++ visible in logs when running in Xcode, but this content *won't* be recorded
++ in the system log. If a user who has installed your app provides their app
++ logs as a diagnostic aid, they will not include any detail written to
++ ``stdout`` or ``stderr``.
++
++ iOS apps have no concept of ``stdin`` at all. While iOS apps can have a
++ keyboard, this is a software feature, not something that is attached to
++ ``stdin``.
++
++ As a result, Python library that involve console manipulation (such as
++ :mod:`curses` and :mod:`readline`) are not available on iOS.
+diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
+index 3fec86080c5..0a8e21510c4 100644
+--- a/Doc/library/multiprocessing.rst
++++ b/Doc/library/multiprocessing.rst
+@@ -8,7 +8,7 @@
- help="
--Try \`$me --help' for more information."
-+Try '$me --help' for more information."
+ --------------
- # Parse command line
- while test $# -gt 0 ; do
-@@ -130,7 +131,7 @@
- # Separate into logical components for further validation
- case $1 in
- *-*-*-*-*)
-- echo Invalid configuration \`"$1"\': more than four components >&2
-+ echo "Invalid configuration '$1': more than four components" >&2
- exit 1
- ;;
- *-*-*-*)
-@@ -145,7 +146,8 @@
- nto-qnx* | linux-* | uclinux-uclibc* \
- | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
- | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
-- | storm-chaos* | os2-emx* | rtmk-nova*)
-+ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \
-+ | windows-* )
- basic_machine=$field1
- basic_os=$maybe_os
- ;;
-@@ -943,7 +945,7 @@
- EOF
- IFS=$saved_IFS
- ;;
-- # We use `pc' rather than `unknown'
-+ # We use 'pc' rather than 'unknown'
- # because (1) that's what they normally are, and
- # (2) the word "unknown" tends to confuse beginning users.
- i*86 | x86_64)
-@@ -1020,6 +1022,11 @@
- ;;
+-.. include:: ../includes/wasm-notavail.rst
++.. include:: ../includes/wasm-ios-notavail.rst
- # Here we normalize CPU types with a missing or matching vendor
-+ armh-unknown | armh-alt)
-+ cpu=armv7l
-+ vendor=alt
-+ basic_os=${basic_os:-linux-gnueabihf}
-+ ;;
- dpx20-unknown | dpx20-bull)
- cpu=rs6000
- vendor=bull
-@@ -1070,7 +1077,7 @@
- pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
- cpu=i586
- ;;
-- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
-+ pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*)
- cpu=i686
- ;;
- pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
-@@ -1121,7 +1128,7 @@
- xscale-* | xscalee[bl]-*)
- cpu=`echo "$cpu" | sed 's/^xscale/arm/'`
- ;;
-- arm64-*)
-+ arm64-* | aarch64le-* | arm64_32-*)
- cpu=aarch64
- ;;
+ Introduction
+ ------------
+diff --git a/Doc/library/os.rst b/Doc/library/os.rst
+index 6537ae298b9..0e904ef8615 100644
+--- a/Doc/library/os.rst
++++ b/Doc/library/os.rst
+@@ -34,12 +34,13 @@
+
+ * On VxWorks, os.popen, os.fork, os.execv and os.spawn*p* are not supported.
+
+-* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, large
+- parts of the :mod:`os` module are not available or behave differently. API
+- related to processes (e.g. :func:`~os.fork`, :func:`~os.execve`), signals
+- (e.g. :func:`~os.kill`, :func:`~os.wait`), and resources
+- (e.g. :func:`~os.nice`) are not available. Others like :func:`~os.getuid`
+- and :func:`~os.getpid` are emulated or stubs.
++* On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, and on
++ iOS, large parts of the :mod:`os` module are not available or behave
++ differently. API related to processes (e.g. :func:`~os.fork`,
++ :func:`~os.execve`) and resources (e.g. :func:`~os.nice`) are not available.
++ Others like :func:`~os.getuid` and :func:`~os.getpid` are emulated or stubs.
++ WebAssembly platforms also lack support for signals (e.g. :func:`~os.kill`,
++ :func:`~os.wait`).
+
+
+ .. note::
+@@ -785,6 +786,11 @@
+ :func:`socket.gethostname` or even
+ ``socket.gethostbyaddr(socket.gethostname())``.
+
++ On macOS, iOS and Android, this returns the *kernel* name and version (i.e.,
++ ``'Darwin'`` on macOS and iOS; ``'Linux'`` on Android). :func:`platform.uname()`
++ can be used to get the user-facing operating system name and version on iOS and
++ Android.
++
+ .. availability:: Unix.
+
+ .. versionchanged:: 3.3
+@@ -3999,7 +4005,7 @@
+
+ .. audit-event:: os.exec path,args,env os.execl
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
+
+ .. versionchanged:: 3.3
+ Added support for specifying *path* as an open file descriptor
+@@ -4202,7 +4208,7 @@
+ for technical details of why we're surfacing this longstanding
+ platform compatibility problem to developers.
+
+- .. availability:: POSIX, not Emscripten, not WASI.
++ .. availability:: POSIX, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: forkpty()
+@@ -4229,7 +4235,7 @@
+ threads, this now raises a :exc:`DeprecationWarning`. See the
+ longer explanation on :func:`os.fork`.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: kill(pid, sig, /)
+@@ -4252,7 +4258,7 @@
+
+ .. audit-event:: os.kill pid,sig os.kill
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
+
+ .. versionchanged:: 3.2
+ Added Windows support.
+@@ -4268,7 +4274,7 @@
+
+ .. audit-event:: os.killpg pgid,sig os.killpg
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: nice(increment, /)
+@@ -4305,7 +4311,7 @@
+ Lock program segments into memory. The value of *op* (defined in
+ ````) determines which segments are locked.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: popen(cmd, mode='r', buffering=-1)
+@@ -4337,7 +4343,7 @@
+ documentation for more powerful ways to manage and communicate with
+ subprocesses.
+
+- .. availability:: not Emscripten, not WASI.
++ .. availability:: not Emscripten, not WASI, not iOS.
+
+ .. note::
+ The :ref:`Python UTF-8 Mode ` affects encodings used
+@@ -4432,7 +4438,7 @@
+
+ .. versionadded:: 3.8
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \
+ setpgroup=None, resetids=False, setsid=False, setsigmask=(), \
+@@ -4448,7 +4454,7 @@
+
+ .. versionadded:: 3.8
+
+- .. availability:: POSIX, not Emscripten, not WASI.
++ .. availability:: POSIX, not Emscripten, not WASI, not iOS.
+
+ See :func:`posix_spawn` documentation.
+
+@@ -4481,7 +4487,7 @@
+
+ There is no way to unregister a function.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. versionadded:: 3.7
-@@ -1175,7 +1182,7 @@
- case $cpu in
- 1750a | 580 \
- | a29k \
-- | aarch64 | aarch64_be \
-+ | aarch64 | aarch64_be | aarch64c | arm64ec \
- | abacus \
- | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
- | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
-@@ -1194,50 +1201,29 @@
- | d10v | d30v | dlx | dsp16xx \
- | e2k | elxsi | epiphany \
- | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
-+ | javascript \
- | h8300 | h8500 \
- | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
- | hexagon \
- | i370 | i*86 | i860 | i960 | ia16 | ia64 \
- | ip2k | iq2000 \
- | k1om \
-+ | kvx \
- | le32 | le64 \
- | lm32 \
-- | loongarch32 | loongarch64 | loongarchx32 \
-+ | loongarch32 | loongarch64 \
- | m32c | m32r | m32rle \
- | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
- | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
- | m88110 | m88k | maxq | mb | mcore | mep | metag \
- | microblaze | microblazeel \
-- | mips | mipsbe | mipseb | mipsel | mipsle \
-- | mips16 \
-- | mips64 | mips64eb | mips64el \
-- | mips64octeon | mips64octeonel \
-- | mips64orion | mips64orionel \
-- | mips64r5900 | mips64r5900el \
-- | mips64vr | mips64vrel \
-- | mips64vr4100 | mips64vr4100el \
-- | mips64vr4300 | mips64vr4300el \
-- | mips64vr5000 | mips64vr5000el \
-- | mips64vr5900 | mips64vr5900el \
-- | mipsisa32 | mipsisa32el \
-- | mipsisa32r2 | mipsisa32r2el \
-- | mipsisa32r3 | mipsisa32r3el \
-- | mipsisa32r5 | mipsisa32r5el \
-- | mipsisa32r6 | mipsisa32r6el \
-- | mipsisa64 | mipsisa64el \
-- | mipsisa64r2 | mipsisa64r2el \
-- | mipsisa64r3 | mipsisa64r3el \
-- | mipsisa64r5 | mipsisa64r5el \
-- | mipsisa64r6 | mipsisa64r6el \
-- | mipsisa64sb1 | mipsisa64sb1el \
-- | mipsisa64sr71k | mipsisa64sr71kel \
-- | mipsr5900 | mipsr5900el \
-- | mipstx39 | mipstx39el \
-+ | mips* \
- | mmix \
- | mn10200 | mn10300 \
- | moxie \
- | mt \
- | msp430 \
-+ | nanomips* \
- | nds32 | nds32le | nds32be \
- | nfp \
- | nios | nios2 | nios2eb | nios2el \
-@@ -1269,6 +1255,7 @@
- | ubicom32 \
- | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
- | vax \
-+ | vc4 \
- | visium \
- | w65 \
- | wasm32 | wasm64 \
-@@ -1280,7 +1267,7 @@
- ;;
+@@ -4550,7 +4556,7 @@
- *)
-- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
-+ echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2
- exit 1
- ;;
- esac
-@@ -1301,11 +1288,12 @@
+ .. audit-event:: os.spawn mode,path,args,env os.spawnl
- # Decode manufacturer-specific aliases for certain operating systems.
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
--if test x$basic_os != x
-+if test x"$basic_os" != x
- then
+ :func:`spawnlp`, :func:`spawnlpe`, :func:`spawnvp`
+ and :func:`spawnvpe` are not available on Windows. :func:`spawnle` and
+@@ -4674,7 +4680,7 @@
--# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just
-+# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
- # set os.
-+obj=
- case $basic_os in
- gnu/linux*)
- kernel=linux
-@@ -1336,6 +1324,10 @@
- kernel=linux
- os=`echo "$basic_os" | sed -e 's|linux|gnu|'`
- ;;
-+ managarm*)
-+ kernel=managarm
-+ os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'`
-+ ;;
- *)
- kernel=
- os=$basic_os
-@@ -1501,10 +1493,16 @@
- os=eabi
- ;;
- *)
-- os=elf
-+ os=
-+ obj=elf
- ;;
- esac
- ;;
-+ aout* | coff* | elf* | pe*)
-+ # These are machine code file formats, not OSes
-+ obj=$os
-+ os=
-+ ;;
- *)
- # No normalization, but not necessarily accepted, that comes below.
- ;;
-@@ -1523,12 +1521,15 @@
- # system, and we'll never get to this point.
+ .. audit-event:: os.system command os.system
- kernel=
-+obj=
- case $cpu-$vendor in
- score-*)
-- os=elf
-+ os=
-+ obj=elf
- ;;
- spu-*)
-- os=elf
-+ os=
-+ obj=elf
- ;;
- *-acorn)
- os=riscix1.2
-@@ -1538,28 +1539,35 @@
- os=gnu
- ;;
- arm*-semi)
-- os=aout
-+ os=
-+ obj=aout
- ;;
- c4x-* | tic4x-*)
-- os=coff
-+ os=
-+ obj=coff
- ;;
- c8051-*)
-- os=elf
-+ os=
-+ obj=elf
- ;;
- clipper-intergraph)
- os=clix
- ;;
- hexagon-*)
-- os=elf
-+ os=
-+ obj=elf
- ;;
- tic54x-*)
-- os=coff
-+ os=
-+ obj=coff
- ;;
- tic55x-*)
-- os=coff
-+ os=
-+ obj=coff
- ;;
- tic6x-*)
-- os=coff
-+ os=
-+ obj=coff
- ;;
- # This must come before the *-dec entry.
- pdp10-*)
-@@ -1581,19 +1589,24 @@
- os=sunos3
- ;;
- m68*-cisco)
-- os=aout
-+ os=
-+ obj=aout
- ;;
- mep-*)
-- os=elf
-+ os=
-+ obj=elf
- ;;
- mips*-cisco)
-- os=elf
-+ os=
-+ obj=elf
- ;;
-- mips*-*)
-- os=elf
-+ mips*-*|nanomips*-*)
-+ os=
-+ obj=elf
- ;;
- or32-*)
-- os=coff
-+ os=
-+ obj=coff
- ;;
- *-tti) # must be before sparc entry or we get the wrong os.
- os=sysv3
-@@ -1602,7 +1615,8 @@
- os=sunos4.1.1
- ;;
- pru-*)
-- os=elf
-+ os=
-+ obj=elf
- ;;
- *-be)
- os=beos
-@@ -1683,10 +1697,12 @@
- os=uxpv
- ;;
- *-rom68k)
-- os=coff
-+ os=
-+ obj=coff
- ;;
- *-*bug)
-- os=coff
-+ os=
-+ obj=coff
- ;;
- *-apple)
- os=macos
-@@ -1704,10 +1720,11 @@
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: times()
+@@ -4718,7 +4724,7 @@
+ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
+ exit code.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. seealso::
- fi
+@@ -4752,7 +4758,10 @@
+ Otherwise, if there are no matching children
+ that could be waited for, :exc:`ChildProcessError` is raised.
--# Now, validate our (potentially fixed-up) OS.
-+# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ).
-+
- case $os in
- # Sometimes we do "kernel-libc", so those need to count as OSes.
-- musl* | newlib* | relibc* | uclibc*)
-+ llvm* | musl* | newlib* | relibc* | uclibc*)
- ;;
- # Likewise for "kernel-abi"
- eabi* | gnueabi*)
-@@ -1715,6 +1732,9 @@
- # VxWorks passes extra cpu info in the 4th filed.
- simlinux | simwindows | spe)
- ;;
-+ # See `case $cpu-$os` validation below
-+ ghcjs)
-+ ;;
- # Now accept the basic system types.
- # The portable systems comes first.
- # Each alternative MUST end in a * to match a version number.
-@@ -1723,7 +1743,7 @@
- | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
- | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \
- | hiux* | abug | nacl* | netware* | windows* \
-- | os9* | macos* | osx* | ios* \
-+ | os9* | macos* | osx* | ios* | tvos* | watchos* \
- | mpw* | magic* | mmixware* | mon960* | lnews* \
- | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
- | aos* | aros* | cloudabi* | sortix* | twizzler* \
-@@ -1732,11 +1752,11 @@
- | mirbsd* | netbsd* | dicos* | openedition* | ose* \
- | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
- | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
-- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
-- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
-+ | bosx* | nextstep* | cxux* | oabi* \
-+ | ptx* | ecoff* | winnt* | domain* | vsta* \
- | udi* | lites* | ieee* | go32* | aux* | hcos* \
- | chorusrdb* | cegcc* | glidix* | serenity* \
-- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
-+ | cygwin* | msys* | moss* | proelf* | rtems* \
- | midipix* | mingw32* | mingw64* | mint* \
- | uxpv* | beos* | mpeix* | udk* | moxiebox* \
- | interix* | uwin* | mks* | rhapsody* | darwin* \
-@@ -1748,49 +1768,119 @@
- | skyos* | haiku* | rdos* | toppers* | drops* | es* \
- | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
- | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
-- | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr*)
-+ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
-+ | fiwix* | mlibc* | cos* | mbr* | ironclad* )
- ;;
- # This one is extra strict with allowed versions
- sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
- # Don't forget version if it is 3.2v4 or newer.
- ;;
-+ # This refers to builds using the UEFI calling convention
-+ # (which depends on the architecture) and PE file format.
-+ # Note that this is both a different calling convention and
-+ # different file format than that of GNU-EFI
-+ # (x86_64-w64-mingw32).
-+ uefi)
-+ ;;
- none)
- ;;
-+ kernel* | msvc* )
-+ # Restricted further below
-+ ;;
-+ '')
-+ if test x"$obj" = x
-+ then
-+ echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2
-+ fi
-+ ;;
- *)
-- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
-+ echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2
-+ exit 1
-+ ;;
-+esac
-+
-+case $obj in
-+ aout* | coff* | elf* | pe*)
-+ ;;
-+ '')
-+ # empty is fine
-+ ;;
-+ *)
-+ echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2
-+ exit 1
-+ ;;
-+esac
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
-+# Here we handle the constraint that a (synthetic) cpu and os are
-+# valid only in combination with each other and nowhere else.
-+case $cpu-$os in
-+ # The "javascript-unknown-ghcjs" triple is used by GHC; we
-+ # accept it here in order to tolerate that, but reject any
-+ # variations.
-+ javascript-ghcjs)
-+ ;;
-+ javascript-* | *-ghcjs)
-+ echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2
- exit 1
- ;;
- esac
++ .. note::
++ This function is not available on macOS.
+
+ .. note::
+ This function is not available on macOS.
+@@ -4793,7 +4802,7 @@
+ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
+ exit code.
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
+
+ .. versionchanged:: 3.5
+ If the system call is interrupted and the signal handler does not raise an
+@@ -4813,7 +4822,7 @@
+ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
+ exitcode.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: wait4(pid, options)
+@@ -4827,7 +4836,7 @@
+ :func:`waitstatus_to_exitcode` can be used to convert the exit status into an
+ exitcode.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. data:: P_PID
+@@ -4844,7 +4853,7 @@
+ * :data:`!P_PIDFD` - wait for the child identified by the file descriptor
+ *id* (a process file descriptor created with :func:`pidfd_open`).
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. note:: :data:`!P_PIDFD` is only available on Linux >= 5.4.
+
+@@ -4859,7 +4868,7 @@
+ :func:`waitid` causes child processes to be reported if they have been
+ continued from a job control stop since they were last reported.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. data:: WEXITED
+@@ -4870,7 +4879,7 @@
+ The other ``wait*`` functions always report children that have terminated,
+ so this option is not available for them.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. versionadded:: 3.3
+
+@@ -4882,7 +4891,7 @@
+
+ This option is not available for the other ``wait*`` functions.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. versionadded:: 3.3
+
+@@ -4895,7 +4904,7 @@
+
+ This option is not available for :func:`waitid`.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. data:: WNOHANG
+@@ -4904,7 +4913,7 @@
+ :func:`waitid` to return right away if no child process status is available
+ immediately.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. data:: WNOWAIT
+@@ -4914,7 +4923,7 @@
+
+ This option is not available for the other ``wait*`` functions.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. data:: CLD_EXITED
+@@ -4927,7 +4936,7 @@
+ These are the possible values for :attr:`!si_code` in the result returned by
+ :func:`waitid`.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. versionadded:: 3.3
+
+@@ -4962,7 +4971,7 @@
+ :func:`WIFEXITED`, :func:`WEXITSTATUS`, :func:`WIFSIGNALED`,
+ :func:`WTERMSIG`, :func:`WIFSTOPPED`, :func:`WSTOPSIG` functions.
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not Emscripten, not WASI, not iOS.
+
+ .. versionadded:: 3.9
+
+@@ -4978,7 +4987,7 @@
+
+ This function should be employed only if :func:`WIFSIGNALED` is true.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: WIFCONTINUED(status)
+@@ -4989,7 +4998,7 @@
+
+ See :data:`WCONTINUED` option.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: WIFSTOPPED(status)
+@@ -5001,14 +5010,14 @@
+ done using :data:`WUNTRACED` option or when the process is being traced (see
+ :manpage:`ptrace(2)`).
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+ .. function:: WIFSIGNALED(status)
+
+ Return ``True`` if the process was terminated by a signal, otherwise return
+ ``False``.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: WIFEXITED(status)
+@@ -5017,7 +5026,7 @@
+ by calling ``exit()`` or ``_exit()``, or by returning from ``main()``;
+ otherwise return ``False``.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: WEXITSTATUS(status)
+@@ -5026,7 +5035,7 @@
+
+ This function should be employed only if :func:`WIFEXITED` is true.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
- # As a final step for OS-related things, validate the OS-kernel combination
- # (given a valid OS), if there is a kernel.
--case $kernel-$os in
-- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
-- | linux-musl* | linux-relibc* | linux-uclibc* )
-+case $kernel-$os-$obj in
-+ linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \
-+ | linux-mlibc*- | linux-musl*- | linux-newlib*- \
-+ | linux-relibc*- | linux-uclibc*- )
-+ ;;
-+ uclinux-uclibc*- )
- ;;
-- uclinux-uclibc* )
-+ managarm-mlibc*- | managarm-kernel*- )
- ;;
-- -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* )
-+ windows*-msvc*-)
-+ ;;
-+ -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \
-+ | -uclibc*- )
- # These are just libc implementations, not actual OSes, and thus
- # require a kernel.
-- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
-+ echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2
- exit 1
- ;;
-- kfreebsd*-gnu* | kopensolaris*-gnu*)
-+ -kernel*- )
-+ echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2
-+ exit 1
- ;;
-- vxworks-simlinux | vxworks-simwindows | vxworks-spe)
-+ *-kernel*- )
-+ echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2
-+ exit 1
- ;;
-- nto-qnx*)
-+ *-msvc*- )
-+ echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2
-+ exit 1
- ;;
-- os2-emx)
-+ kfreebsd*-gnu*- | kopensolaris*-gnu*-)
- ;;
-- *-eabi* | *-gnueabi*)
-+ vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-)
- ;;
-- -*)
-+ nto-qnx*-)
-+ ;;
-+ os2-emx-)
-+ ;;
-+ *-eabi*- | *-gnueabi*-)
-+ ;;
-+ ios*-simulator- | tvos*-simulator- | watchos*-simulator- )
-+ ;;
-+ none--*)
-+ # None (no kernel, i.e. freestanding / bare metal),
-+ # can be paired with an machine code file format
-+ ;;
-+ -*-)
- # Blank kernel with real OS is always fine.
- ;;
-- *-*)
-- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
-+ --*)
-+ # Blank kernel and OS with real machine code file format is always fine.
-+ ;;
-+ *-*-*)
-+ echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2
- exit 1
- ;;
- esac
-@@ -1873,7 +1963,7 @@
- ;;
- esac
--echo "$cpu-$vendor-${kernel:+$kernel-}$os"
-+echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}"
- exit
+ .. function:: WSTOPSIG(status)
+@@ -5035,7 +5044,7 @@
- # Local variables:
-diff --git a/configure b/configure
-index 241cf8f3d4a..aecfeb1c99f 100755
---- a/configure
-+++ b/configure
-@@ -975,10 +975,14 @@
- CFLAGS
- CC
- HAS_XCRUN
-+WATCHOS_DEPLOYMENT_TARGET
-+TVOS_DEPLOYMENT_TARGET
-+IPHONEOS_DEPLOYMENT_TARGET
- EXPORT_MACOSX_DEPLOYMENT_TARGET
- CONFIGURE_MACOSX_DEPLOYMENT_TARGET
- _PYTHON_HOST_PLATFORM
--MACHDEP
-+APP_STORE_COMPLIANCE_PATCH
-+INSTALLTARGETS
- FRAMEWORKINSTALLAPPSPREFIX
- FRAMEWORKUNIXTOOLSPREFIX
- FRAMEWORKPYTHONW
-@@ -986,6 +990,8 @@
- FRAMEWORKALTINSTALLFIRST
- FRAMEWORKINSTALLLAST
- FRAMEWORKINSTALLFIRST
-+RESSRCDIR
-+PYTHONFRAMEWORKINSTALLNAMEPREFIX
- PYTHONFRAMEWORKINSTALLDIR
- PYTHONFRAMEWORKPREFIX
- PYTHONFRAMEWORKDIR
-@@ -995,6 +1001,7 @@
- LIPO_32BIT_FLAGS
- ARCH_RUN_32BIT
- UNIVERSALSDK
-+MACHDEP
- PKG_CONFIG_LIBDIR
- PKG_CONFIG_PATH
- PKG_CONFIG
-@@ -1070,6 +1077,7 @@
- with_universal_archs
- with_framework_name
- enable_framework
-+with_app_store_compliance
- with_emscripten_target
- enable_wasm_dynamic_linking
- enable_wasm_pthreads
-@@ -1843,6 +1851,10 @@
- specify the name for the python framework on macOS
- only valid when --enable-framework is set. see
- Mac/README.rst (default is 'Python')
-+ --with-app-store-compliance=[PATCH-FILE]
-+ Enable any patches required for compiliance with app
-+ stores. Optional PATCH-FILE specifies the custom
-+ patch to apply.
- --with-emscripten-target=[browser|node]
- Emscripten platform
- --with-suffix=SUFFIX set executable suffix to SUFFIX (default is empty,
-@@ -4011,6 +4023,164 @@
- as_fn_error $? "pkg-config is required" "$LINENO" 5]
- fi
+ This function should be employed only if :func:`WIFSTOPPED` is true.
-+# Set name for machine-dependent library files
-+
-+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5
-+printf %s "checking MACHDEP... " >&6; }
-+if test -z "$MACHDEP"
-+then
-+ # avoid using uname for cross builds
-+ if test "$cross_compiling" = yes; then
-+ # ac_sys_system and ac_sys_release are used for setting
-+ # a lot of different things including 'define_xopen_source'
-+ # in the case statement below.
-+ case "$host" in
-+ *-*-linux-android*)
-+ ac_sys_system=Linux-android
-+ ;;
-+ *-*-linux*)
-+ ac_sys_system=Linux
-+ ;;
-+ *-*-cygwin*)
-+ ac_sys_system=Cygwin
-+ ;;
-+ *-apple-ios*)
-+ ac_sys_system=iOS
-+ ;;
-+ *-apple-tvos*)
-+ ac_sys_system=tvOS
-+ ;;
-+ *-apple-watchos*)
-+ ac_sys_system=watchOS
-+ ;;
-+ *-*-vxworks*)
-+ ac_sys_system=VxWorks
-+ ;;
-+ *-*-emscripten)
-+ ac_sys_system=Emscripten
-+ ;;
-+ *-*-wasi)
-+ ac_sys_system=WASI
-+ ;;
-+ *)
-+ # for now, limit cross builds to known configurations
-+ MACHDEP="unknown"
-+ as_fn_error $? "cross build not supported for $host" "$LINENO" 5
-+ esac
-+ ac_sys_release=
-+ else
-+ ac_sys_system=`uname -s`
-+ if test "$ac_sys_system" = "AIX" \
-+ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
-+ ac_sys_release=`uname -v`
-+ else
-+ ac_sys_release=`uname -r`
-+ fi
-+ fi
-+ ac_md_system=`echo $ac_sys_system |
-+ tr -d '/ ' | tr '[A-Z]' '[a-z]'`
-+ ac_md_release=`echo $ac_sys_release |
-+ tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'`
-+ MACHDEP="$ac_md_system$ac_md_release"
-+
-+ case $MACHDEP in
-+ aix*) MACHDEP="aix";;
-+ linux*) MACHDEP="linux";;
-+ cygwin*) MACHDEP="cygwin";;
-+ darwin*) MACHDEP="darwin";;
-+ '') MACHDEP="unknown";;
-+ esac
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ .. function:: WTERMSIG(status)
+@@ -5044,7 +5053,7 @@
+
+ This function should be employed only if :func:`WIFSIGNALED` is true.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not Emscripten, not WASI, not iOS.
+
+
+ Interface to the scheduler
+diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst
+index 2f5bf53bc5c..0cc5e532711 100644
+--- a/Doc/library/platform.rst
++++ b/Doc/library/platform.rst
+@@ -148,6 +148,9 @@
+ Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``,
+ ``'Windows'``. An empty string is returned if the value cannot be determined.
+
++ On iOS and Android, this returns the user-facing OS name (i.e, ``'iOS``,
++ ``'iPadOS'`` or ``'Android'``). To obtain the kernel name (``'Darwin'`` or
++ ``'Linux'``), use :func:`os.uname()`.
+
+ .. function:: system_alias(system, release, version)
+
+@@ -161,6 +164,8 @@
+ Returns the system's release version, e.g. ``'#3 on degas'``. An empty string is
+ returned if the value cannot be determined.
+
++ On iOS and Android, this is the user-facing OS version. To obtain the
++ Darwin or Linux kernel version, use :func:`os.uname()`.
+
+ .. function:: uname()
+
+@@ -234,7 +239,6 @@
+ macOS Platform
+ --------------
+
+-
+ .. function:: mac_ver(release='', versioninfo=('','',''), machine='')
+
+ Get macOS version information and return it as tuple ``(release, versioninfo,
+@@ -244,6 +248,24 @@
+ Entries which cannot be determined are set to ``''``. All tuple entries are
+ strings.
+
++iOS Platform
++------------
+
-+ if test "$ac_sys_system" = "SunOS"; then
-+ # For Solaris, there isn't an OS version specific macro defined
-+ # in most compilers, so we define one here.
-+ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'`
++.. function:: ios_ver(system='', release='', model='', is_simulator=False)
+
-+printf "%s\n" "#define Py_SUNOS_VERSION $SUNOS_VERSION" >>confdefs.h
++ Get iOS version information and return it as a
++ :func:`~collections.namedtuple` with the following attributes:
+
-+ fi
-+fi
-+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5
-+printf "%s\n" "\"$MACHDEP\"" >&6; }
++ * ``system`` is the OS name; either ``'iOS'`` or ``'iPadOS'``.
++ * ``release`` is the iOS version number as a string (e.g., ``'17.2'``).
++ * ``model`` is the device model identifier; this will be a string like
++ ``'iPhone13,2'`` for a physical device, or ``'iPhone'`` on a simulator.
++ * ``is_simulator`` is a boolean describing if the app is running on a
++ simulator or a physical device.
+
-+# On cross-compile builds, configure will look for a host-specific compiler by
-+# prepending the user-provided host triple to the required binary name.
-+#
-+# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
-+# 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
-+# these tools; the binary names of these scripts are better defaults than "gcc".
-+# This only requires that the user put the platform scripts folder (e.g.,
-+# "iOS/Resources/bin") in their path, rather than defining platform-specific
-+# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to
-+# either put the platform scripts folder in the path, or specify CC etc,
-+# configure will fail.
-+if test -z "$AR"; then
-+ case "$host" in
-+ 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 ;;
++ Entries which cannot be determined are set to the defaults given as
++ parameters.
+
-+ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;;
-+ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;;
-+ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;;
+
+ Unix Platforms
+ --------------
+diff --git a/Doc/library/pwd.rst b/Doc/library/pwd.rst
+index 98ca174d9e3..d71d7212cfd 100644
+--- a/Doc/library/pwd.rst
++++ b/Doc/library/pwd.rst
+@@ -10,7 +10,7 @@
+ This module provides access to the Unix user account and password database. It
+ is available on all Unix versions.
+
+-.. availability:: Unix, not Emscripten, not WASI.
++.. availability:: Unix, not WASI, not iOS.
+
+ Password database entries are reported as a tuple-like object, whose attributes
+ correspond to the members of the ``passwd`` structure (Attribute field below,
+diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst
+index 5479ccf7f62..069dba24c29 100644
+--- a/Doc/library/readline.rst
++++ b/Doc/library/readline.rst
+@@ -24,6 +24,8 @@
+ allowable constructs of that file, and the capabilities of the
+ Readline library in general.
+
++.. include:: ../includes/wasm-ios-notavail.rst
+
-+ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;;
-+ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;;
-+ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;;
-+ *)
-+ esac
-+fi
-+if test -z "$CC"; then
-+ case "$host" in
-+ 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 ;;
+ .. note::
+
+ The underlying Readline library API may be implemented by
+diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst
+index 02009d82104..0515d205bbc 100644
+--- a/Doc/library/resource.rst
++++ b/Doc/library/resource.rst
+@@ -13,7 +13,7 @@
+ This module provides basic mechanisms for measuring and controlling system
+ resources utilized by a program.
+
+-.. availability:: Unix, not Emscripten, not WASI.
++.. availability:: Unix, not WASI.
+
+ Symbolic constants are used to specify particular system resources and to
+ request usage information about either the current process or its children.
+diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst
+index 641a6c021c1..79c4948e99e 100644
+--- a/Doc/library/signal.rst
++++ b/Doc/library/signal.rst
+@@ -26,9 +26,9 @@
+ underlying implementation), with the exception of the handler for
+ :const:`SIGCHLD`, which follows the underlying implementation.
+
+-On WebAssembly platforms ``wasm32-emscripten`` and ``wasm32-wasi``, signals
+-are emulated and therefore behave differently. Several functions and signals
+-are not available on these platforms.
++On WebAssembly platforms, signals are emulated and therefore behave
++differently. Several functions and signals are not available on these
++platforms.
+
+ Execution of Python signal handlers
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
+index d29e3c20e10..4a627e3f3f9 100644
+--- a/Doc/library/socket.rst
++++ b/Doc/library/socket.rst
+@@ -1244,7 +1244,7 @@
+ buffer. Raises :exc:`OverflowError` if *length* is outside the
+ permissible range of values.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not WASI.
+
+ Most Unix platforms.
+
+@@ -1267,7 +1267,7 @@
+ amount of ancillary data that can be received, since additional
+ data may be able to fit into the padding area.
+
+- .. availability:: Unix, not Emscripten, not WASI.
++ .. availability:: Unix, not WASI.
+
+ most Unix platforms.
+
+@@ -1307,7 +1307,7 @@
+ (index int, name string) tuples.
+ :exc:`OSError` if the system call fails.
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not WASI.
+
+ .. versionadded:: 3.3
+
+@@ -1334,7 +1334,7 @@
+ interface name.
+ :exc:`OSError` if no interface with the given name exists.
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not WASI.
+
+ .. versionadded:: 3.3
+
+@@ -1351,7 +1351,7 @@
+ interface index number.
+ :exc:`OSError` if no interface with the given index exists.
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not WASI.
+
+ .. versionadded:: 3.3
+
+@@ -1368,7 +1368,7 @@
+ The *fds* parameter is a sequence of file descriptors.
+ Consult :meth:`~socket.sendmsg` for the documentation of these parameters.
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not WASI.
+
+ Unix platforms supporting :meth:`~socket.sendmsg`
+ and :const:`SCM_RIGHTS` mechanism.
+@@ -1382,7 +1382,7 @@
+ Return ``(msg, list(fds), flags, addr)``.
+ Consult :meth:`~socket.recvmsg` for the documentation of these parameters.
+
+- .. availability:: Unix, Windows, not Emscripten, not WASI.
++ .. availability:: Unix, Windows, not WASI.
+
+ Unix platforms supporting :meth:`~socket.sendmsg`
+ and :const:`SCM_RIGHTS` mechanism.
+diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
+index 755ff4c6f0f..b03db8f3e0a 100644
+--- a/Doc/library/subprocess.rst
++++ b/Doc/library/subprocess.rst
+@@ -25,7 +25,7 @@
+
+ :pep:`324` -- PEP proposing the subprocess module
+
+-.. include:: ../includes/wasm-notavail.rst
++.. include:: ../includes/wasm-ios-notavail.rst
+
+ Using the :mod:`subprocess` Module
+ ----------------------------------
+diff --git a/Doc/library/syslog.rst b/Doc/library/syslog.rst
+index 79b808ab63c..332b58413d3 100644
+--- a/Doc/library/syslog.rst
++++ b/Doc/library/syslog.rst
+@@ -11,7 +11,7 @@
+ Refer to the Unix manual pages for a detailed description of the ``syslog``
+ facility.
+
+-.. availability:: Unix, not Emscripten, not WASI.
++.. availability:: Unix, not WASI, not iOS.
+
+ This module wraps the system ``syslog`` family of routines. A pure Python
+ library that can speak to a syslog server is available in the
+diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst
+index b32b4af1aa8..69daa381013 100644
+--- a/Doc/library/urllib.parse.rst
++++ b/Doc/library/urllib.parse.rst
+@@ -22,11 +22,19 @@
+
+ The module has been designed to match the internet RFC on Relative Uniform
+ Resource Locators. It supports the following URL schemes: ``file``, ``ftp``,
+-``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``mailto``, ``mms``,
++``gopher``, ``hdl``, ``http``, ``https``, ``imap``, ``itms-services``, ``mailto``, ``mms``,
+ ``news``, ``nntp``, ``prospero``, ``rsync``, ``rtsp``, ``rtsps``, ``rtspu``,
+ ``sftp``, ``shttp``, ``sip``, ``sips``, ``snews``, ``svn``, ``svn+ssh``,
+ ``telnet``, ``wais``, ``ws``, ``wss``.
+
++.. impl-detail::
+
-+ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;;
-+ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;;
-+ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;;
++ The inclusion of the ``itms-services`` URL scheme can prevent an app from
++ passing Apple's App Store review process for the macOS and iOS App Stores.
++ Handling for the ``itms-services`` scheme is always removed on iOS; on
++ macOS, it *may* be removed if CPython has been built with the
++ :option:`--with-app-store-compliance` option.
+
-+ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;;
-+ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;;
-+ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;;
-+ *)
-+ esac
-+fi
-+if test -z "$CPP"; then
-+ case "$host" in
-+ 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 ;;
+ The :mod:`urllib.parse` module defines functions that fall into two broad
+ categories: URL parsing and URL quoting. These are covered in detail in
+ the following sections.
+diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
+index 9eb0e9d191c..cc403f1bb7b 100644
+--- a/Doc/library/venv.rst
++++ b/Doc/library/venv.rst
+@@ -56,7 +56,7 @@
+ `Python Packaging User Guide: Creating and using virtual environments
+ `__
+
+-.. include:: ../includes/wasm-notavail.rst
++.. include:: ../includes/wasm-ios-notavail.rst
+
+ Creating virtual environments
+ -----------------------------
+diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst
+index 9c3ff5efa2e..b99b28b2053 100644
+--- a/Doc/library/webbrowser.rst
++++ b/Doc/library/webbrowser.rst
+@@ -33,6 +33,13 @@
+ browsers are not available on Unix, the controlling process will launch a new
+ browser and wait.
+
++On iOS, the :envvar:`BROWSER` environment variable, as well as any arguments
++controlling autoraise, browser preference, and new tab/window creation will be
++ignored. Web pages will *always* be opened in the user's preferred browser, in
++a new tab, with the browser being brought to the foreground. The use of the
++:mod:`webbrowser` module on iOS requires the :mod:`ctypes` module. If
++:mod:`ctypes` isn't available, calls to :func:`.open` will fail.
+
-+ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;;
-+ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;;
-+ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;;
+ .. program:: webbrowser
+
+ The script :program:`webbrowser` can be used as a command-line interface for the
+@@ -166,6 +173,8 @@
+ +------------------------+-----------------------------------------+-------+
+ | ``'chromium-browser'`` | :class:`Chromium('chromium-browser')` | |
+ +------------------------+-----------------------------------------+-------+
++| ``'iosbrowser'`` | ``IOSBrowser`` | \(4) |
+++------------------------+-----------------------------------------+-------+
+
+ Notes:
+
+@@ -180,7 +189,11 @@
+ Only on Windows platforms.
+
+ (3)
+- Only on macOS platform.
++ Only on macOS.
+
-+ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;;
-+ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;;
-+ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;;
-+ *)
-+ esac
-+fi
-+if test -z "$CXX"; then
-+ case "$host" in
-+ 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++ ;;
++(4)
++ Only on iOS.
+
-+ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;;
-+ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;;
-+ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;;
+
+ .. versionadded:: 3.3
+ Support for Chrome/Chromium has been added.
+@@ -193,6 +206,9 @@
+ .. deprecated-removed:: 3.11 3.13
+ :class:`MacOSX` is deprecated, use :class:`MacOSXOSAScript` instead.
+
++.. versionchanged:: 3.13
++ Support for iOS has been added.
+
-+ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;;
-+ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;;
-+ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;;
-+ *)
-+ esac
-+fi
+ Here are some simple examples::
+
+ url = 'https://docs.python.org/'
+diff --git a/Doc/tools/extensions/availability.py b/Doc/tools/extensions/availability.py
+index 225b3438b94..3164d735a3a 100644
+--- a/Doc/tools/extensions/availability.py
++++ b/Doc/tools/extensions/availability.py
+@@ -24,6 +24,7 @@
+ "DragonFlyBSD",
+ "Emscripten",
+ "FreeBSD",
++ "iOS",
+ "Linux",
+ "macOS",
+ "NetBSD",
+diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
+index 6df75ac9e0b..8883a7af9c0 100644
+--- a/Doc/using/configure.rst
++++ b/Doc/using/configure.rst
+@@ -638,7 +638,7 @@
+ macOS Options
+ -------------
+
+-See ``Mac/README.rst``.
++See :source:`Mac/README.rst`.
+
+ .. option:: --enable-universalsdk
+ .. option:: --enable-universalsdk=SDKDIR
+@@ -679,6 +679,31 @@
+ Specify the name for the python framework on macOS only valid when
+ :option:`--enable-framework` is set (default: ``Python``).
+
++.. option:: --with-app-store-compliance
++.. option:: --with-app-store-compliance=PATCH-FILE
+
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --enable-universalsdk" >&5
- printf %s "checking for --enable-universalsdk... " >&6; }
- # Check whether --enable-universalsdk was given.
-@@ -4126,111 +4296,195 @@
- enableval=$enable_framework;
- case $enableval in
- yes)
-- enableval=/Library/Frameworks
-+ case $ac_sys_system in
-+ Darwin) enableval=/Library/Frameworks ;;
-+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;;
-+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;;
-+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;;
-+ *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
-+ esac
- esac
++ The Python standard library contains strings that are known to trigger
++ automated inspection tool errors when submitted for distribution by
++ the macOS and iOS App Stores. If enabled, this option will apply the list of
++ patches that are known to correct app store compliance. A custom patch
++ file can also be specified. This option is disabled by default.
+
- case $enableval in
- no)
-- PYTHONFRAMEWORK=
-- PYTHONFRAMEWORKDIR=no-framework
-- PYTHONFRAMEWORKPREFIX=
-- PYTHONFRAMEWORKINSTALLDIR=
-- FRAMEWORKINSTALLFIRST=
-- FRAMEWORKINSTALLLAST=
-- FRAMEWORKALTINSTALLFIRST=
-- FRAMEWORKALTINSTALLLAST=
-- FRAMEWORKPYTHONW=
-- if test "x${prefix}" = "xNONE"; then
-- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-- else
-- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-- fi
-- enable_framework=
-+ case $ac_sys_system in
-+ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;;
-+ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;;
-+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;;
-+ *)
-+ PYTHONFRAMEWORK=
-+ PYTHONFRAMEWORKDIR=no-framework
-+ PYTHONFRAMEWORKPREFIX=
-+ PYTHONFRAMEWORKINSTALLDIR=
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-+ RESSRCDIR=
-+ FRAMEWORKINSTALLFIRST=
-+ FRAMEWORKINSTALLLAST=
-+ FRAMEWORKALTINSTALLFIRST=
-+ FRAMEWORKALTINSTALLLAST=
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="commoninstall bininstall maninstall"
++ .. versionadded:: 3.13
+
-+ if test "x${prefix}" = "xNONE"; then
-+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-+ else
-+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-+ fi
-+ enable_framework=
-+ esac
- ;;
- *)
- PYTHONFRAMEWORKPREFIX="${enableval}"
- PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR
-- FRAMEWORKINSTALLFIRST="frameworkinstallstructure"
-- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure "
-- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
-- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
-- FRAMEWORKPYTHONW="frameworkpythonw"
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
--
-- if test "x${prefix}" = "xNONE" ; then
-- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-
-- else
-- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-- fi
--
-- case "${enableval}" in
-- /System*)
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-- if test "${prefix}" = "NONE" ; then
-- # See below
-- FRAMEWORKUNIXTOOLSPREFIX="/usr"
-- fi
-- ;;
-+ case $ac_sys_system in #(
-+ Darwin) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
-+ FRAMEWORKPYTHONW="frameworkpythonw"
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ INSTALLTARGETS="commoninstall bininstall maninstall"
++iOS Options
++-----------
+
-+ if test "x${prefix}" = "xNONE" ; then
-+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
++See :source:`iOS/README.rst`.
+
-+ else
-+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-+ fi
-
-- /Library*)
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-- ;;
-+ case "${enableval}" in
-+ /System*)
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ if test "${prefix}" = "NONE" ; then
-+ # See below
-+ FRAMEWORKUNIXTOOLSPREFIX="/usr"
-+ fi
-+ ;;
++.. option:: --enable-framework=INSTALLDIR
+
-+ /Library*)
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ ;;
++ Create a Python.framework. Unlike macOS, the *INSTALLDIR* argument
++ specifying the installation path is mandatory.
+
-+ */Library/Frameworks)
-+ MDIR="`dirname "${enableval}"`"
-+ MDIR="`dirname "${MDIR}"`"
-+ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
++.. option:: --with-framework-name=FRAMEWORK
++
++ Specify the name for the framework (default: ``Python``).
+
-+ if test "${prefix}" = "NONE"; then
-+ # User hasn't specified the
-+ # --prefix option, but wants to install
-+ # the framework in a non-default location,
-+ # ensure that the compatibility links get
-+ # installed relative to that prefix as well
-+ # instead of in /usr/local.
-+ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
-+ fi
-+ ;;
-
-- */Library/Frameworks)
-- MDIR="`dirname "${enableval}"`"
-- MDIR="`dirname "${MDIR}"`"
-- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
--
-- if test "${prefix}" = "NONE"; then
-- # User hasn't specified the
-- # --prefix option, but wants to install
-- # the framework in a non-default location,
-- # ensure that the compatibility links get
-- # installed relative to that prefix as well
-- # instead of in /usr/local.
-- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
-- fi
-- ;;
-+ *)
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ ;;
-+ esac
-- *)
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-- ;;
-+ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix}
-+ RESSRCDIR=Mac/Resources/framework
+ Cross Compiling Options
+ -----------------------
+diff --git a/Doc/using/index.rst b/Doc/using/index.rst
+index e1a3111f36a..f55a12f1ab8 100644
+--- a/Doc/using/index.rst
++++ b/Doc/using/index.rst
+@@ -18,4 +18,5 @@
+ configure.rst
+ windows.rst
+ mac.rst
++ ios.rst
+ editors.rst
+--- /dev/null
++++ b/Doc/using/ios.rst
+@@ -0,0 +1,359 @@
++.. _using-ios:
+
-+ # Add files for Mac specific code to the list of output
-+ # files:
-+ ac_config_files="$ac_config_files Mac/Makefile"
++===================
++Using Python on iOS
++===================
+
-+ ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile"
++:Authors:
++ Russell Keith-Magee (2024-03)
+
-+ ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist"
++Python on iOS is unlike Python on desktop platforms. On a desktop platform,
++Python is generally installed as a system resource that can be used by any user
++of that computer. Users then interact with Python by running a :program:`python`
++executable and entering commands at an interactive prompt, or by running a
++Python script.
+
-+ ac_config_files="$ac_config_files Mac/Resources/app/Info.plist"
++On iOS, there is no concept of installing as a system resource. The only unit
++of software distribution is an "app". There is also no console where you could
++run a :program:`python` executable, or interact with a Python REPL.
+
-+ ;;
-+ iOS) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++As a result, the only way you can use Python on iOS is in embedded mode - that
++is, by writing a native iOS application, and embedding a Python interpreter
++using ``libPython``, and invoking Python code using the :ref:`Python embedding
++API `. The full Python interpreter, the standard library, and all
++your Python code is then packaged as a standalone bundle that can be
++distributed via the iOS App Store.
+
-+ prefix=$PYTHONFRAMEWORKPREFIX
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=iOS/Resources
++If you're looking to experiment for the first time with writing an iOS app in
++Python, projects such as `BeeWare `__ and `Kivy
++`__ will provide a much more approachable user experience.
++These projects manage the complexities associated with getting an iOS project
++running, so you only need to deal with the Python code itself.
+
-+ ac_config_files="$ac_config_files iOS/Resources/Info.plist"
++Python at runtime on iOS
++========================
+
-+ ;;
-+ tvOS) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++iOS version compatibility
++-------------------------
+
-+ prefix=$PYTHONFRAMEWORKPREFIX
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=tvOS/Resources
++The minimum supported iOS version is specified at compile time, using the
++:option:`--host` option to ``configure``. By default, when compiled for iOS,
++Python will be compiled with a minimum supported iOS version of 13.0. To use a
++different miniumum iOS version, provide the version number as part of the
++:option:`!--host` argument - for example,
++``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 simulator build
++with a deployment target of 15.4.
+
-+ ac_config_files="$ac_config_files tvOS/Resources/Info.plist"
++Platform identification
++-----------------------
+
-+ ;;
-+ watchOS) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++When executing on iOS, ``sys.platform`` will report as ``ios``. This value will
++be returned on an iPhone or iPad, regardless of whether the app is running on
++the simulator or a physical device.
+
-+ prefix=$PYTHONFRAMEWORKPREFIX
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=watchOS/Resources
++Information about the specific runtime environment, including the iOS version,
++device model, and whether the device is a simulator, can be obtained using
++:func:`platform.ios_ver`. :func:`platform.system` will report ``iOS`` or
++``iPadOS``, depending on the device.
+
-+ ac_config_files="$ac_config_files watchOS/Resources/Info.plist"
++:func:`os.uname` reports kernel-level details; it will report a name of
++``Darwin``.
+
-+ ;;
-+ *)
-+ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
-+ ;;
-+ esac
- esac
-
-- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
--
-- # Add files for Mac specific code to the list of output
-- # files:
-- ac_config_files="$ac_config_files Mac/Makefile"
--
-- ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile"
--
-- ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist"
--
-- ac_config_files="$ac_config_files Mac/Resources/app/Info.plist"
-+else $as_nop
-
-+ case $ac_sys_system in
-+ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;;
-+ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;;
-+ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;;
-+ *)
-+ PYTHONFRAMEWORK=
-+ PYTHONFRAMEWORKDIR=no-framework
-+ PYTHONFRAMEWORKPREFIX=
-+ PYTHONFRAMEWORKINSTALLDIR=
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-+ RESSRCDIR=
-+ FRAMEWORKINSTALLFIRST=
-+ FRAMEWORKINSTALLLAST=
-+ FRAMEWORKALTINSTALLFIRST=
-+ FRAMEWORKALTINSTALLLAST=
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="commoninstall bininstall maninstall"
-+ if test "x${prefix}" = "xNONE" ; then
-+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-+ else
-+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-+ fi
-+ enable_framework=
- esac
-
--else $as_nop
-+fi
-
-- PYTHONFRAMEWORK=
-- PYTHONFRAMEWORKDIR=no-framework
-- PYTHONFRAMEWORKPREFIX=
-- PYTHONFRAMEWORKINSTALLDIR=
-- FRAMEWORKINSTALLFIRST=
-- FRAMEWORKINSTALLLAST=
-- FRAMEWORKALTINSTALLFIRST=
-- FRAMEWORKALTINSTALLLAST=
-- FRAMEWORKPYTHONW=
-- if test "x${prefix}" = "xNONE" ; then
-- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-- else
-- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-- fi
-- enable_framework=
-
-
--fi
-
-
-
-@@ -4249,76 +4503,52 @@
- printf "%s\n" "#define _PYTHONFRAMEWORK \"${PYTHONFRAMEWORK}\"" >>confdefs.h
-
-
--# Set name for machine-dependent library files
--
--{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5
--printf %s "checking MACHDEP... " >&6; }
--if test -z "$MACHDEP"
--then
-- # avoid using uname for cross builds
-- if test "$cross_compiling" = yes; then
-- # ac_sys_system and ac_sys_release are used for setting
-- # a lot of different things including 'define_xopen_source'
-- # in the case statement below.
-- case "$host" in
-- *-*-linux-android*)
-- ac_sys_system=Linux-android
-- ;;
-- *-*-linux*)
-- ac_sys_system=Linux
-- ;;
-- *-*-cygwin*)
-- ac_sys_system=Cygwin
-- ;;
-- *-*-vxworks*)
-- ac_sys_system=VxWorks
-- ;;
-- *-*-emscripten)
-- ac_sys_system=Emscripten
-- ;;
-- *-*-wasi)
-- ac_sys_system=WASI
-- ;;
-- *)
-- # for now, limit cross builds to known configurations
-- MACHDEP="unknown"
-- as_fn_error $? "cross build not supported for $host" "$LINENO" 5
-- esac
-- ac_sys_release=
-- else
-- ac_sys_system=`uname -s`
-- if test "$ac_sys_system" = "AIX" \
-- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
-- ac_sys_release=`uname -v`
-- else
-- ac_sys_release=`uname -r`
-- fi
-- fi
-- ac_md_system=`echo $ac_sys_system |
-- tr -d '/ ' | tr '[A-Z]' '[a-z]'`
-- ac_md_release=`echo $ac_sys_release |
-- tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'`
-- MACHDEP="$ac_md_system$ac_md_release"
-+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-app-store-compliance" >&5
-+printf %s "checking for --with-app-store-compliance... " >&6; }
-
-- case $MACHDEP in
-- aix*) MACHDEP="aix";;
-- linux*) MACHDEP="linux";;
-- cygwin*) MACHDEP="cygwin";;
-- darwin*) MACHDEP="darwin";;
-- '') MACHDEP="unknown";;
-+# Check whether --with-app_store_compliance was given.
-+if test ${with_app_store_compliance+y}
-+then :
-+ withval=$with_app_store_compliance;
-+ case "$withval" in
-+ yes)
-+ case $ac_sys_system in
-+ Darwin|iOS|tvOS|watchOS)
-+ # iOS/tvOS/watchOS is able to share the macOS patch
-+ 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 ;;
-+ esac
-+ { 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; }
-+ ;;
-+ *)
-+ APP_STORE_COMPLIANCE_PATCH="${withval}"
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: applying custom app store compliance patch" >&5
-+printf "%s\n" "applying custom app store compliance patch" >&6; }
-+ ;;
- esac
-
-- if test "$ac_sys_system" = "SunOS"; then
-- # For Solaris, there isn't an OS version specific macro defined
-- # in most compilers, so we define one here.
-- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'`
-+else $as_nop
-
--printf "%s\n" "#define Py_SUNOS_VERSION $SUNOS_VERSION" >>confdefs.h
-+ case $ac_sys_system in
-+ iOS|tvOS|watchOS)
-+ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch
-+ 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; }
-+ ;;
-+ *)
-+ # No default app compliance patching on any other platform
-+ APP_STORE_COMPLIANCE_PATCH=
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not patching for app store compliance" >&5
-+printf "%s\n" "not patching for app store compliance" >&6; }
-+ ;;
-+ esac
-
-- fi
- fi
--{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5
--printf "%s\n" "\"$MACHDEP\"" >&6; }
++Standard library availability
++-----------------------------
+
++The Python standard library has some notable omissions and restrictions on
++iOS. See the :ref:`API availability guide for iOS ` for
++details.
+
-
-
- if test "$cross_compiling" = yes; then
-@@ -4326,27 +4556,93 @@
- *-*-linux*)
- case "$host_cpu" in
- arm*)
-- _host_cpu=arm
-+ _host_ident=arm
- ;;
- *)
-- _host_cpu=$host_cpu
-+ _host_ident=$host_cpu
- esac
- ;;
- *-*-cygwin*)
-- _host_cpu=
-+ _host_ident=
-+ ;;
-+ *-apple-ios*)
-+ _host_os=`echo $host | cut -d '-' -f3`
-+ _host_device=`echo $host | cut -d '-' -f4`
-+ _host_device=${_host_device:=os}
++Binary extension modules
++------------------------
+
-+ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking iOS deployment target" >&5
-+printf %s "checking iOS deployment target... " >&6; }
-+ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3}
-+ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0}
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $IPHONEOS_DEPLOYMENT_TARGET" >&5
-+printf "%s\n" "$IPHONEOS_DEPLOYMENT_TARGET" >&6; }
++One notable difference about iOS as a platform is that App Store distribution
++imposes hard requirements on the packaging of an application. One of these
++requirements governs how binary extension modules are distributed.
+
-+ case "$host_cpu" in
-+ aarch64)
-+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device}
-+ ;;
-+ *)
-+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device}
-+ ;;
-+ esac
-+ ;;
-+ *-apple-tvos*)
-+ _host_os=`echo $host | cut -d '-' -f3`
-+ _host_device=`echo $host | cut -d '-' -f4`
-+ _host_device=${_host_device:=os}
++The iOS App Store requires that *all* binary modules in an iOS app must be
++dynamic libraries, contained in a framework with appropriate metadata, stored
++in the ``Frameworks`` folder of the packaged app. There can be only a single
++binary per framework, and there can be no executable binary material outside
++the ``Frameworks`` folder.
+
-+ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking tvOS deployment target" >&5
-+printf %s "checking tvOS deployment target... " >&6; }
-+ TVOS_DEPLOYMENT_TARGET=${_host_os:4}
-+ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0}
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TVOS_DEPLOYMENT_TARGET" >&5
-+printf "%s\n" "$TVOS_DEPLOYMENT_TARGET" >&6; }
++This conflicts with the usual Python approach for distributing binaries, which
++allows a binary extension module to be loaded from any location on
++``sys.path``. To ensure compliance with App Store policies, an iOS project must
++post-process any Python packages, converting ``.so`` binary modules into
++individual standalone frameworks with appropriate metadata and signing. For
++details on how to perform this post-processing, see the guide for :ref:`adding
++Python to your project `.
+
-+ case "$host_cpu" in
-+ aarch64)
-+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device}
-+ ;;
-+ *)
-+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device}
-+ ;;
-+ esac
-+ ;;
-+ *-apple-watchos*)
-+ _host_os=`echo $host | cut -d '-' -f3`
-+ _host_device=`echo $host | cut -d '-' -f4`
-+ _host_device=${_host_device:=os}
++To help Python discover binaries in their new location, the original ``.so``
++file on ``sys.path`` is replaced with a ``.fwork`` file. This file is a text
++file containing the location of the framework binary, relative to the app
++bundle. To allow the framework to resolve back to the original location, the
++framework must contain a ``.origin`` file that contains the location of the
++``.fwork`` file, relative to the app bundle.
+
-+ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version
-+ { 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}
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5
-+printf "%s\n" "$WATCHOS_DEPLOYMENT_TARGET" >&6; }
++For example, consider the case of an import ``from foo.bar import _whiz``,
++where ``_whiz`` is implemented with the binary module
++``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location
++registered on ``sys.path``, relative to the application bundle. This module
++*must* be distributed as ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz``
++(creating the framework name from the full import path of the module), with an
++``Info.plist`` file in the ``.framework`` directory identifying the binary as a
++framework. The ``foo.bar._whiz`` module would be represented in the original
++location with a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing
++the path ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also
++contain ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing
++the path to the ``.fwork`` file.
+
-+ case "$host_cpu" in
-+ aarch64)
-+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device}
-+ ;;
-+ *)
-+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device}
-+ ;;
-+ esac
- ;;
- *-*-vxworks*)
-- _host_cpu=$host_cpu
-+ _host_ident=$host_cpu
- ;;
- wasm32-*-* | wasm64-*-*)
-- _host_cpu=$host_cpu
-+ _host_ident=$host_cpu
- ;;
- *)
- # for now, limit cross builds to known configurations
- MACHDEP="unknown"
- as_fn_error $? "cross build not supported for $host" "$LINENO" 5
- esac
-- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}"
-+ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}"
- fi
-
- # Some systems cannot stand _XOPEN_SOURCE being defined at all; they
-@@ -4413,6 +4709,13 @@
- define_xopen_source=no;;
- Darwin/[12][0-9].*)
- define_xopen_source=no;;
-+ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features.
-+ iOS/*)
-+ define_xopen_source=no;;
-+ tvOS/*)
-+ define_xopen_source=no;;
-+ watchOS/*)
-+ define_xopen_source=no;;
- # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
- # defining NI_NUMERICHOST.
- QNX/6.3.2)
-@@ -4475,6 +4778,12 @@
- CONFIGURE_MACOSX_DEPLOYMENT_TARGET=
- EXPORT_MACOSX_DEPLOYMENT_TARGET='#'
-
-+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET /
-+# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple.
++When running on iOS, the Python interpreter will install an
++:class:`~importlib.machinery.AppleFrameworkLoader` that is able to read and
++import ``.fwork`` files. Once imported, the ``__file__`` attribute of the
++binary module will report as the location of the ``.fwork`` file. However, the
++:class:`~importlib.machinery.ModuleSpec` for the loaded module will report the
++``origin`` as the location of the binary in the framework folder.
++
++Compiler stub binaries
++----------------------
++
++Xcode doesn't expose explicit compilers for iOS; instead, it uses an ``xcrun``
++script that resolves to a full compiler path (e.g., ``xcrun --sdk iphoneos
++clang`` to get the ``clang`` for an iPhone device). However, using this script
++poses two problems:
++
++* The output of ``xcrun`` includes paths that are machine specific, resulting
++ in a sysconfig module that cannot be shared between users; and
++
++* It results in ``CC``/``CPP``/``LD``/``AR`` definitions that include spaces.
++ There is a lot of C ecosystem tooling that assumes that you can split a
++ command line at the first space to get the path to the compiler executable;
++ this isn't the case when using ``xcrun``.
++
++To avoid these problems, Python provided stubs for these tools. These stubs are
++shell script wrappers around the underingly ``xcrun`` tools, distributed in a
++``bin`` folder distributed alongside the compiled iOS framework. These scripts
++are relocatable, and will always resolve to the appropriate local system paths.
++By including these scripts in the bin folder that accompanies a framework, the
++contents of the ``sysconfig`` module becomes useful for end-users to compile
++their own modules. When compiling third-party Python modules for iOS, you
++should ensure these stub binaries are on your path.
++
++Installing Python on iOS
++========================
++
++Tools for building iOS apps
++---------------------------
++
++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 from the Platforms tab of the Xcode Settings panel.
++
++.. _adding-ios:
++
++Adding Python to an iOS project
++-------------------------------
++
++Python can be added to any iOS project, using either Swift or Objective C. The
++following examples will use Objective C; if you are using Swift, you may find a
++library like `PythonKit `__ to be
++helpful.
++
++To add Python to an iOS Xcode project:
++
++1. Build or obtain a Python ``XCFramework``. See the instructions in
++ :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``.
++
++2. Drag the ``XCframework`` into your iOS project. In the following
++ instructions, we'll assume you've dropped the ``XCframework`` into the root
++ of your project; however, you can use any other location that you want by
++ adjusting paths as needed.
++
++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.
++
++4. Select the app target by selecting the root node of your Xcode project, then
++ the target name in the sidebar that appears.
++
++5. In the "General" settings, under "Frameworks, Libraries and Embedded
++ Content", add ``Python.xcframework``, with "Embed & Sign" selected.
++
++6. In the "Build Settings" tab, modify the following:
++
++ - Build Options
++
++ * User Script Sandboxing: No
++ * Enable Testability: Yes
++
++ - Search Paths
++
++ * Framework Search Paths: ``$(PROJECT_DIR)``
++ * Header Search Paths: ``"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"``
++
++ - Apple Clang - Warnings - All languages
++
++ * Quoted Include In Framework Header: No
++
++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
++ source $PROJECT_DIR/Python.xcframework/build/build_utils.sh
++ install_python Python.xcframework app
++
++ If you have placed your XCframework somewhere other than the root of your
++ project, modify the path to the first argument.
++
++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*;
++ * Writing bytecode (:c:member:`PyConfig.write_bytecode`) is *disabled*;
++ * Signal handlers (:c:member:`PyConfig.install_signal_handlers`) are *enabled*;
++ * :envvar:`PYTHONHOME` for the interpreter is configured to point at the
++ ``python`` subfolder of your app's bundle; and
++ * The :envvar:`PYTHONPATH` for the interpreter includes:
+
++ - the ``python/lib/python3.X`` subfolder of your app's bundle,
++ - the ``python/lib/python3.X/lib-dynload`` subfolder of your app's bundle, and
++ - the ``app`` subfolder of your app's bundle
+
++ Your app's bundle location can be determined using ``[[NSBundle mainBundle]
++ resourcePath]``.
+
++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:
+
- # checks for alternative programs
-
- # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just
-@@ -4507,6 +4816,26 @@
- ;;
- esac
-
-+case $ac_sys_system in #(
-+ iOS) :
++* You need to ensure that any folders containing third-party binaries are
++ 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 targetting (i.e., delete any device binaries
++ if you're building app app targeting the simulator).
+
-+ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
-+ as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
-+ ;; #(
-+ tvOS) :
++* 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.
+
-+ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
-+ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
-+ ;; #(
-+ watchOS) :
++* If any of the folders that contain third-party packages will contain ``.pth``
++ files, you should add that folder as a *site directory* (using
++ :meth:`site.addsitedir`), rather than adding to :envvar:`PYTHONPATH` or
++ :attr:`sys.path` directly.
+
-+ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
-+ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
-+ ;; #(
-+ *) :
-+ ;;
-+esac
++Testing a Python package
++------------------------
+
- if test "$ac_sys_system" = "Darwin"
- then
- # Extract the first word of "xcrun", so it can be a program name with args.
-@@ -6904,7 +7233,42 @@
- #elif defined(__gnu_hurd__)
- i386-gnu
- #elif defined(__APPLE__)
-+# include "TargetConditionals.h"
-+# if TARGET_OS_IOS
-+# if TARGET_OS_SIMULATOR
-+# if __x86_64__
-+ x86_64-iphonesimulator
-+# else
-+ arm64-iphonesimulator
-+# endif
-+# else
-+ arm64-iphoneos
-+# endif
-+# elif TARGET_OS_TV
-+# if TARGET_OS_SIMULATOR
-+# if __x86_64__
-+ x86_64-appletvsimulator
-+# else
-+ arm64-appletvsimulator
-+# endif
-+# else
-+ arm64-appletvos
-+# endif
-+# elif TARGET_OS_WATCH
-+# if TARGET_OS_SIMULATOR
-+# if __x86_64__
-+ x86_64-watchsimulator
-+# else
-+ arm64-watchsimulator
-+# endif
-+# else
-+ arm64_32-watchos
-+# endif
-+# elif TARGET_OS_OSX
- darwin
-+# else
-+# error unknown Apple platform
-+# endif
- #elif defined(__VXWORKS__)
- vxworks
- #elif defined(__wasm32__)
-@@ -6953,6 +7317,12 @@
- case $ac_sys_system in #(
- Darwin*) :
- MULTIARCH="" ;; #(
-+ iOS) :
-+ MULTIARCH="" ;; #(
-+ tvOS) :
-+ MULTIARCH="" ;; #(
-+ watchOS) :
-+ MULTIARCH="" ;; #(
- FreeBSD*) :
- MULTIARCH="" ;; #(
- *) :
-@@ -6960,8 +7330,6 @@
- ;;
- esac
-
--{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5
--printf "%s\n" "$MULTIARCH" >&6; }
-
- if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then
- if test x$PLATFORM_TRIPLET != x$MULTIARCH; then
-@@ -6971,6 +7339,16 @@
- MULTIARCH=$PLATFORM_TRIPLET
- fi
-
-+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5
-+printf "%s\n" "$MULTIARCH" >&6; }
++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.
+
-+case $ac_sys_system in #(
-+ iOS|tvOS|watchOS) :
-+ SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #(
-+ *) :
-+ SOABI_PLATFORM=$PLATFORM_TRIPLET
-+ ;;
-+esac
-
- if test x$MULTIARCH != x; then
- MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\""
-@@ -7014,6 +7392,18 @@
- PY_SUPPORT_TIER=3 ;; #(
- x86_64-*-freebsd*/clang) :
- PY_SUPPORT_TIER=3 ;; #(
-+ aarch64-apple-ios*-simulator/clang) :
-+ PY_SUPPORT_TIER=3 ;; #(
-+ aarch64-apple-ios*/clang) :
-+ PY_SUPPORT_TIER=3 ;; #(
-+ aarch64-apple-tvos*-simulator/clang) :
-+ PY_SUPPORT_TIER=3 ;; #(
-+ aarch64-apple-tvos*/clang) :
-+ PY_SUPPORT_TIER=3 ;; #(
-+ aarch64-apple-watchos*-simulator/clang) :
-+ PY_SUPPORT_TIER=3 ;; #(
-+ arm64_32-apple-watchos*/clang) :
-+ PY_SUPPORT_TIER=3 ;; #(
- *) :
- PY_SUPPORT_TIER=0
- ;;
-@@ -7467,17 +7857,25 @@
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking LDLIBRARY" >&5
- printf %s "checking LDLIBRARY... " >&6; }
-
--# MacOSX framework builds need more magic. LDLIBRARY is the dynamic
-+# Apple framework builds need more magic. LDLIBRARY is the dynamic
- # library that we build, but we do not want to link against it (we
- # will find it with a -framework option). For this reason there is an
- # extra variable BLDLIBRARY against which Python and the extension
- # modules are linked, BLDLIBRARY. This is normally the same as
--# LDLIBRARY, but empty for MacOSX framework builds.
-+# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same,
-+# but uses a non-versioned framework layout.
- if test "$enable_framework"
- then
-- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
-- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
-+ case $ac_sys_system in
-+ Darwin)
-+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';;
-+ iOS|tvOS|watchOS)
-+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';;
-+ *)
-+ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;;
-+ esac
- BLDLIBRARY=''
-+ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
- else
- BLDLIBRARY='$(LDLIBRARY)'
- fi
-@@ -7490,64 +7888,70 @@
-
- case $ac_sys_system in
- CYGWIN*)
-- LDLIBRARY='libpython$(LDVERSION).dll.a'
-- DLLLIBRARY='libpython$(LDVERSION).dll'
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).dll.a'
-+ DLLLIBRARY='libpython$(LDVERSION).dll'
-+ ;;
- SunOS*)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
-- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-- INSTSONAME="$LDLIBRARY".$SOVERSION
-- if test "$with_pydebug" != yes
-- then
-- PY3LIBRARY=libpython3.so
-- fi
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
-+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-+ INSTSONAME="$LDLIBRARY".$SOVERSION
-+ if test "$with_pydebug" != yes
-+ then
-+ PY3LIBRARY=libpython3.so
-+ fi
-+ ;;
- Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- BLDLIBRARY='-L. -lpython$(LDVERSION)'
-- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-- INSTSONAME="$LDLIBRARY".$SOVERSION
-- if test "$with_pydebug" != yes
-- then
-- PY3LIBRARY=libpython3.so
-- fi
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ BLDLIBRARY='-L. -lpython$(LDVERSION)'
-+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-+ INSTSONAME="$LDLIBRARY".$SOVERSION
-+ if test "$with_pydebug" != yes
-+ then
-+ PY3LIBRARY=libpython3.so
-+ fi
-+ ;;
- hp*|HP*)
-- case `uname -m` in
-- ia64)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- ;;
-- *)
-- LDLIBRARY='libpython$(LDVERSION).sl'
-- ;;
-- esac
-- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
-- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
-- ;;
-+ case `uname -m` in
-+ ia64)
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ ;;
-+ *)
-+ LDLIBRARY='libpython$(LDVERSION).sl'
-+ ;;
-+ esac
-+ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
-+ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
-+ ;;
- Darwin*)
-- LDLIBRARY='libpython$(LDVERSION).dylib'
-- BLDLIBRARY='-L. -lpython$(LDVERSION)'
-- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).dylib'
-+ BLDLIBRARY='-L. -lpython$(LDVERSION)'
-+ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
-+ ;;
-+ iOS|tvOS|watchOS)
-+ LDLIBRARY='libpython$(LDVERSION).dylib'
-+ ;;
- AIX*)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
-+ ;;
++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:
++
++.. code-block:: bash
++
++ $ python cross-build/iOS/testbed clone --app --app app-testbed
++
++Or, if you've sourced your own XCframework, by running:
++
++.. code-block:: bash
++
++ $ python Apple/testbed clone --platform iOS --framework --app --app app-testbed
++
++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
++could run:
++
++.. code-block:: bash
++
++ $ python app-testbed run -- module1.tests
++
++This is the equivalent of running ``python -m module1.tests`` on a desktop
++Python build. Any arguments after the ``--`` will be passed to the testbed as
++if they were arguments to ``python -m`` on a desktop machine.
++
++You can also open the testbed project in Xcode by running:
++
++.. code-block:: bash
++
++ $ open app-testbed/iOSTestbed.xcodeproj
++
++This will allow you to use the full Xcode suite of tools for debugging.
++
++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.
++
++App Store Compliance
++====================
++
++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. There
++are some steps that must be taken to ensure that your app will be able to pass
++these validation steps.
++
++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
++review rules cannot be challenged; so, it is necessary to modify the Python
++standard library for an app to pass App Store review.
++
++The Python source tree contains
++: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.
++
++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.
++
++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/Doc/using/mac.rst b/Doc/using/mac.rst
+index 8b67652d1df..2dfac075843 100644
+--- a/Doc/using/mac.rst
++++ b/Doc/using/mac.rst
+@@ -188,6 +188,28 @@
+ * `PyInstaller `__: A cross-platform packaging tool that creates
+ a single file or folder as a distributable artifact.
- esac
- else # shared is disabled
- PY_ENABLE_SHARED=0
- case $ac_sys_system in
- CYGWIN*)
-- BLDLIBRARY='$(LIBRARY)'
-- LDLIBRARY='libpython$(LDVERSION).dll.a'
-- ;;
-+ BLDLIBRARY='$(LIBRARY)'
-+ LDLIBRARY='libpython$(LDVERSION).dll.a'
-+ ;;
- esac
- fi
++App Store Compliance
++--------------------
++
++Apps submitted for distribution through the macOS App Store 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.
++
++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
++review rules cannot be challenged. Therefore, it is necessary to modify the
++Python standard library for an app to pass App Store review.
++
++The Python source tree contains
++: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 CPython is configured with the
++:option:`--with-app-store-compliance` option.
++
++This patch is not normally required to use CPython on a Mac; nor is it required
++if you are distributing an app *outside* the macOS App Store. It is *only*
++required if you are using the macOS App Store as a distribution channel.
++
+ Other Resources
+ ===============
-+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5
-+printf "%s\n" "$LDLIBRARY" >&6; }
+--- /dev/null
++++ b/Lib/_apple_support.py
+@@ -0,0 +1,66 @@
++import io
++import sys
++
++
++def init_streams(log_write, stdout_level, stderr_level):
++ # Redirect stdout and stderr to the Apple system log. This method is
++ # invoked by init_apple_streams() (initconfig.c) if config->use_system_logger
++ # is enabled.
++ sys.stdout = SystemLog(log_write, stdout_level, errors=sys.stderr.errors)
++ sys.stderr = SystemLog(log_write, stderr_level, errors=sys.stderr.errors)
++
++
++class SystemLog(io.TextIOWrapper):
++ def __init__(self, log_write, level, **kwargs):
++ kwargs.setdefault("encoding", "UTF-8")
++ kwargs.setdefault("line_buffering", True)
++ super().__init__(LogStream(log_write, level), **kwargs)
++
++ def __repr__(self):
++ return f""
++
++ def write(self, s):
++ if not isinstance(s, str):
++ raise TypeError(
++ f"write() argument must be str, not {type(s).__name__}")
++
++ # In case `s` is a str subclass that writes itself to stdout or stderr
++ # when we call its methods, convert it to an actual str.
++ s = str.__str__(s)
++
++ # We want to emit one log message per line, so split
++ # the string before sending it to the superclass.
++ for line in s.splitlines(keepends=True):
++ super().write(line)
++
++ return len(s)
++
++
++class LogStream(io.RawIOBase):
++ def __init__(self, log_write, level):
++ self.log_write = log_write
++ self.level = level
++
++ def __repr__(self):
++ return f""
++
++ def writable(self):
++ return True
++
++ def write(self, b):
++ if type(b) is not bytes:
++ try:
++ b = bytes(memoryview(b))
++ except TypeError:
++ raise TypeError(
++ f"write() argument must be bytes-like, not {type(b).__name__}"
++ ) from None
++
++ # Writing an empty string to the stream should have no effect.
++ if b:
++ # Encode null bytes using "modified UTF-8" to avoid truncating the
++ # message. This should not affect the return value, as the caller
++ # may be expecting it to match the length of the input.
++ self.log_write(self.level, b.replace(b"\x00", b"\xc0\x80"))
++
++ return len(b)
+--- /dev/null
++++ b/Lib/_ios_support.py
+@@ -0,0 +1,71 @@
++import sys
++try:
++ from ctypes import cdll, c_void_p, c_char_p, util
++except ImportError:
++ # ctypes is an optional module. If it's not present, we're limited in what
++ # we can tell about the system, but we don't want to prevent the module
++ # from working.
++ print("ctypes isn't available; iOS system calls will not be available")
++ objc = None
++else:
++ # ctypes is available. Load the ObjC library, and wrap the objc_getClass,
++ # sel_registerName methods
++ lib = util.find_library("objc")
++ if lib is None:
++ # Failed to load the objc library
++ raise RuntimeError("ObjC runtime library couldn't be loaded")
++
++ objc = cdll.LoadLibrary(lib)
++ objc.objc_getClass.restype = c_void_p
++ objc.objc_getClass.argtypes = [c_char_p]
++ objc.sel_registerName.restype = c_void_p
++ objc.sel_registerName.argtypes = [c_char_p]
++
++
++def get_platform_ios():
++ # Determine if this is a simulator using the multiarch value
++ is_simulator = sys.implementation._multiarch.endswith("simulator")
++
++ # We can't use ctypes; abort
++ if not objc:
++ return None
++
++ # Most of the methods return ObjC objects
++ objc.objc_msgSend.restype = c_void_p
++ # All the methods used have no arguments.
++ objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
++
++ # Equivalent of:
++ # device = [UIDevice currentDevice]
++ UIDevice = objc.objc_getClass(b"UIDevice")
++ SEL_currentDevice = objc.sel_registerName(b"currentDevice")
++ device = objc.objc_msgSend(UIDevice, SEL_currentDevice)
++
++ # Equivalent of:
++ # device_systemVersion = [device systemVersion]
++ SEL_systemVersion = objc.sel_registerName(b"systemVersion")
++ device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion)
++
++ # Equivalent of:
++ # device_systemName = [device systemName]
++ SEL_systemName = objc.sel_registerName(b"systemName")
++ device_systemName = objc.objc_msgSend(device, SEL_systemName)
++
++ # Equivalent of:
++ # device_model = [device model]
++ SEL_model = objc.sel_registerName(b"model")
++ device_model = objc.objc_msgSend(device, SEL_model)
++
++ # UTF8String returns a const char*;
++ SEL_UTF8String = objc.sel_registerName(b"UTF8String")
++ objc.objc_msgSend.restype = c_char_p
++
++ # Equivalent of:
++ # system = [device_systemName UTF8String]
++ # release = [device_systemVersion UTF8String]
++ # model = [device_model UTF8String]
++ system = objc.objc_msgSend(device_systemName, SEL_UTF8String).decode()
++ release = objc.objc_msgSend(device_systemVersion, SEL_UTF8String).decode()
++ model = objc.objc_msgSend(device_model, SEL_UTF8String).decode()
++
++ return system, release, model, is_simulator
+diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
+index 6cedee74236..0e88cffc74f 100644
+--- a/Lib/ctypes/__init__.py
++++ b/Lib/ctypes/__init__.py
+@@ -346,6 +346,17 @@
+ winmode=None):
+ if name:
+ name = _os.fspath(name)
++
++ # 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 name.endswith(".fwork"):
++ with open(name) as f:
++ name = _os.path.join(
++ _os.path.dirname(_sys.executable),
++ f.read().strip()
++ )
+
- if test "$cross_compiling" = yes; then
-- RUNSHARED=
-+ RUNSHARED=
- fi
+ self._name = name
+ flags = self._func_flags_
+ if use_errno:
+diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
+index c550883e7c7..12d7428fe9a 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 == "darwin":
++elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "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 9b8a8dfc5aa..7a4fad2f746 100644
+--- a/Lib/importlib/_bootstrap_external.py
++++ b/Lib/importlib/_bootstrap_external.py
+@@ -52,7 +52,7 @@
-@@ -7742,9 +8146,6 @@
- PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD"
- fi
+ # Bootstrap-related code ######################################################
+ _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win',
+-_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin'
++_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos'
+ _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY
+ + _CASE_INSENSITIVE_PLATFORMS_STR_KEY)
--{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5
--printf "%s\n" "$LDLIBRARY" >&6; }
--
- # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable
- case $ac_sys_system/$ac_sys_emscripten_target in #(
- Emscripten/browser*) :
-@@ -12792,6 +13193,11 @@
- BLDSHARED="$LDSHARED"
- fi
- ;;
-+ iOS/*|tvOS/*|watchOS/*)
-+ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
-+ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
-+ BLDSHARED="$LDSHARED"
-+ ;;
- Emscripten*|WASI*)
- LDSHARED='$(CC) -shared'
- LDCXXSHARED='$(CXX) -shared';;
-@@ -12921,30 +13327,34 @@
- Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
- Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
- # -u libsys_s pulls in all symbols in libsys
-- Darwin/*)
-+ Darwin/*|iOS/*|tvOS/*|watchOS/*)
- LINKFORSHARED="$extra_undefs -framework CoreFoundation"
+@@ -1698,6 +1698,46 @@
+ return f'FileFinder({self.path!r})'
- # Issue #18075: the default maximum stack size (8MBytes) is too
- # small for the default recursion limit. Increase the stack size
- # to ensure that tests don't crash
-- stack_size="1000000" # 16 MB
-- if test "$with_ubsan" = "yes"
-- then
-- # Undefined behavior sanitizer requires an even deeper stack
-- stack_size="4000000" # 64 MB
-- fi
--
-- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
-+ stack_size="1000000" # 16 MB
-+ if test "$with_ubsan" = "yes"
-+ then
-+ # Undefined behavior sanitizer requires an even deeper stack
-+ stack_size="4000000" # 64 MB
-+ fi
++class AppleFrameworkLoader(ExtensionFileLoader):
++ """A loader for modules that have been packaged as frameworks for
++ compatibility with Apple's iOS App Store policies.
++ """
++ def create_module(self, spec):
++ # If the ModuleSpec has been created by the FileFinder, it will have
++ # been created with an origin pointing to the .fwork file. We need to
++ # redirect this to the location in the Frameworks folder, using the
++ # content of the .fwork file.
++ if spec.origin.endswith(".fwork"):
++ with _io.FileIO(spec.origin, 'r') as file:
++ framework_binary = file.read().decode().strip()
++ bundle_path = _path_split(sys.executable)[0]
++ spec.origin = _path_join(bundle_path, framework_binary)
++
++ # If the loader is created based on the spec for a loaded module, the
++ # path will be pointing at the Framework location. If this occurs,
++ # get the original .fwork location to use as the module's __file__.
++ if self.path.endswith(".fwork"):
++ path = self.path
++ else:
++ with _io.FileIO(self.path + ".origin", 'r') as file:
++ origin = file.read().decode().strip()
++ bundle_path = _path_split(sys.executable)[0]
++ path = _path_join(bundle_path, origin)
++
++ module = _bootstrap._call_with_frames_removed(_imp.create_dynamic, spec)
++
++ _bootstrap._verbose_message(
++ "Apple framework extension module {!r} loaded from {!r} (path {!r})",
++ spec.name,
++ spec.origin,
++ path,
++ )
++
++ # Ensure that the __file__ points at the .fwork location
++ module.__file__ = path
++
++ return module
++
+ # Import setup ###############################################################
- printf "%s\n" "#define THREAD_STACK_SIZE 0x$stack_size" >>confdefs.h
+ def _fix_up_module(ns, name, pathname, cpathname=None):
+@@ -1730,10 +1770,17 @@
+ Each item is a tuple (loader, suffixes).
+ """
+- extensions = ExtensionFileLoader, _imp.extension_suffixes()
++ if sys.platform in {"ios", "tvos", "watchos"}:
++ extension_loaders = [(AppleFrameworkLoader, [
++ suffix.replace(".so", ".fwork")
++ for suffix in _imp.extension_suffixes()
++ ])]
++ else:
++ extension_loaders = []
++ extension_loaders.append((ExtensionFileLoader, _imp.extension_suffixes()))
+ source = SourceFileLoader, SOURCE_SUFFIXES
+ bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES
+- return [extensions, source, bytecode]
++ return extension_loaders + [source, bytecode]
-- if test "$enable_framework"
-- then
-- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
-+ if test $ac_sys_system = "Darwin"; then
-+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
-+
-+ if test "$enable_framework"; then
-+ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
-+ fi
-+ LINKFORSHARED="$LINKFORSHARED"
-+ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
-+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
- fi
-- LINKFORSHARED="$LINKFORSHARED";;
-+ ;;
- OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";;
- SCO_SV*) LINKFORSHARED="-Wl,-Bexport";;
- ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";;
-@@ -14333,6 +14743,10 @@
- ctypes_malloc_closure=yes
- ;; #(
-+ iOS|tvOS|watchOS) :
-+
-+ ctypes_malloc_closure=yes
-+ ;; #(
- sunos5) :
- as_fn_append LIBFFI_LIBS " -mimpure-text"
- ;; #(
-@@ -17602,12 +18016,6 @@
- then :
- printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h
+ def _set_bootstrap_module(_bootstrap_module):
+diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
+index b56fa94eb9c..37fef357fe2 100644
+--- a/Lib/importlib/abc.py
++++ b/Lib/importlib/abc.py
+@@ -180,7 +180,11 @@
+ else:
+ return self.source_to_code(source, path)
--fi
--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" "explicit_bzero" "ac_cv_func_explicit_bzero"
- if test "x$ac_cv_func_explicit_bzero" = xyes
-@@ -17668,18 +18076,6 @@
- then :
- printf "%s\n" "#define HAVE_FEXECVE 1" >>confdefs.h
+-_register(ExecutionLoader, machinery.ExtensionFileLoader)
++_register(
++ ExecutionLoader,
++ machinery.ExtensionFileLoader,
++ machinery.AppleFrameworkLoader,
++)
--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" "fpathconf" "ac_cv_func_fpathconf"
- if test "x$ac_cv_func_fpathconf" = xyes
-@@ -17734,12 +18130,6 @@
- then :
- printf "%s\n" "#define HAVE_GETEGID 1" >>confdefs.h
--fi
--ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy"
--if test "x$ac_cv_func_getentropy" = xyes
--then :
-- printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h
--
- fi
- ac_fn_c_check_func "$LINENO" "geteuid" "ac_cv_func_geteuid"
- if test "x$ac_cv_func_geteuid" = xyes
-@@ -17776,12 +18166,6 @@
- then :
- printf "%s\n" "#define HAVE_GETGROUPLIST 1" >>confdefs.h
+ class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader):
+diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py
+index d9a19a13f7b..fbd30b159fb 100644
+--- a/Lib/importlib/machinery.py
++++ b/Lib/importlib/machinery.py
+@@ -12,6 +12,7 @@
+ from ._bootstrap_external import SourceFileLoader
+ from ._bootstrap_external import SourcelessFileLoader
+ from ._bootstrap_external import ExtensionFileLoader
++from ._bootstrap_external import AppleFrameworkLoader
+ from ._bootstrap_external import NamespaceLoader
--fi
--ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups"
--if test "x$ac_cv_func_getgroups" = xyes
--then :
-- printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h
--
- fi
- ac_fn_c_check_func "$LINENO" "gethostname" "ac_cv_func_gethostname"
- if test "x$ac_cv_func_gethostname" = xyes
-@@ -18100,18 +18484,6 @@
- then :
- printf "%s\n" "#define HAVE_POSIX_FALLOCATE 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" "pread" "ac_cv_func_pread"
- if test "x$ac_cv_func_pread" = xyes
-@@ -18376,12 +18748,6 @@
- then :
- printf "%s\n" "#define HAVE_SIGACTION 1" >>confdefs.h
+diff --git a/Lib/inspect.py b/Lib/inspect.py
+index 2a8d9b053cc..4d0bbcb3835 100644
+--- a/Lib/inspect.py
++++ b/Lib/inspect.py
+@@ -961,6 +961,10 @@
+ elif any(filename.endswith(s) for s in
+ importlib.machinery.EXTENSION_SUFFIXES):
+ return None
++ elif filename.endswith(".fwork"):
++ # Apple mobile framework markers are another type of non-source file
++ return None
++
+ # return a filename found in the linecache even if it doesn't exist on disk
+ if filename in linecache.cache:
+ return filename
+@@ -991,6 +995,7 @@
+ return object
+ if hasattr(object, '__module__'):
+ return sys.modules.get(object.__module__)
++
+ # Try the filename to modulename cache
+ if _filename is not None and _filename in modulesbyfile:
+ return sys.modules.get(modulesbyfile[_filename])
+@@ -1084,7 +1089,7 @@
+ # Allow filenames in form of "" to pass through.
+ # `doctest` monkeypatches `linecache` module to enable
+ # inspection, so let `linecache.getlines` to be called.
+- if not (file.startswith('<') and file.endswith('>')):
++ if (not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'):
+ raise OSError('source code not available')
--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
- ac_fn_c_check_func "$LINENO" "sigfillset" "ac_cv_func_sigfillset"
- if test "x$ac_cv_func_sigfillset" = xyes
-@@ -18472,12 +18838,6 @@
- then :
- printf "%s\n" "#define HAVE_SYSCONF 1" >>confdefs.h
+ module = getmodule(object, file)
+diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py
+index a0a020f9eeb..ac478ee7f51 100644
+--- a/Lib/modulefinder.py
++++ b/Lib/modulefinder.py
+@@ -72,7 +72,12 @@
+ if isinstance(spec.loader, importlib.machinery.SourceFileLoader):
+ kind = _PY_SOURCE
--fi
--ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system"
--if test "x$ac_cv_func_system" = xyes
--then :
-- printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h
--
- fi
- ac_fn_c_check_func "$LINENO" "tcgetpgrp" "ac_cv_func_tcgetpgrp"
- if test "x$ac_cv_func_tcgetpgrp" = xyes
-@@ -18650,6 +19010,73 @@
+- elif isinstance(spec.loader, importlib.machinery.ExtensionFileLoader):
++ elif isinstance(
++ spec.loader, (
++ importlib.machinery.ExtensionFileLoader,
++ importlib.machinery.AppleFrameworkLoader,
++ )
++ ):
+ kind = _C_EXTENSION
- fi
+ elif isinstance(spec.loader, importlib.machinery.SourcelessFileLoader):
+diff --git a/Lib/platform.py b/Lib/platform.py
+index b86e6834911..115c2f6d21c 100755
+--- a/Lib/platform.py
++++ b/Lib/platform.py
+@@ -498,6 +498,78 @@
+ # If that also doesn't work return the default values
+ return release, versioninfo, machine
-+# iOS/tvOS/watchOS define some system methods that can be linked (so they are
-+# found by configure), but either raise a compilation error (because the
-+# header definition prevents usage - autoconf doesn't use the headers), or
-+# raise an error if used at runtime. Force these symbols off.
-+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
-+ ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy"
-+if test "x$ac_cv_func_getentropy" = xyes
-+then :
-+ printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h
+
-+fi
-+ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups"
-+if test "x$ac_cv_func_getgroups" = xyes
-+then :
-+ printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h
++# A namedtuple for iOS version information.
++IOSVersionInfo = collections.namedtuple(
++ "IOSVersionInfo",
++ ["system", "release", "model", "is_simulator"]
++)
+
-+fi
-+ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system"
-+if test "x$ac_cv_func_system" = xyes
-+then :
-+ printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h
+
-+fi
++def ios_ver(system="", release="", model="", is_simulator=False):
++ """Get iOS version information, and return it as a namedtuple:
++ (system, release, model, is_simulator).
+
-+fi
++ If values can't be determined, they are set to values provided as
++ parameters.
++ """
++ if sys.platform == "ios":
++ import _ios_support
++ result = _ios_support.get_platform_ios()
++ if result is not None:
++ return IOSVersionInfo(*result)
+
-+# 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
++ return IOSVersionInfo(system, release, model, is_simulator)
+
-+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
++# A namedtuple for tvOS version information.
++TVOSVersionInfo = collections.namedtuple(
++ "TVOSVersionInfo",
++ ["system", "release", "model", "is_simulator"]
++)
+
-+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
++def tvos_ver(system="", release="", model="", is_simulator=False):
++ """Get tvOS version information, and return it as a namedtuple:
++ (system, release, model, is_simulator).
+
-+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
++ 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)
+
-+fi
++ return TVOSVersionInfo(system, release, model, is_simulator)
+
-+fi
+
- { 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}
-@@ -21402,7 +21829,8 @@
-
-
- # check for openpty, login_tty, and forkpty
--
-+# tvOS/watchOS have functions for tty, but can't use them
-+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
-
- for ac_func in openpty
- do :
-@@ -21498,7 +21926,7 @@
- fi
++# A namedtuple for watchOS version information.
++WatchOSVersionInfo = collections.namedtuple(
++ "WatchOSVersionInfo",
++ ["system", "release", "model", "is_simulator"]
++)
++
++
++def watchos_ver(system="", release="", model="", is_simulator=False):
++ """Get watchOS version information, and return it as a namedtuple:
++ (system, release, model, is_simulator).
++
++ If values can't be determined, they are set to values provided as
++ parameters.
++ """
++ if sys.platform == "watchos":
++ # TODO: Can the iOS implementation be used here?
++ import _ios_support
++ result = _ios_support.get_platform_ios()
++ if result is not None:
++ return WatchOSVersionInfo(*result)
++
++ return WatchOSVersionInfo(system, release, model, is_simulator)
++
++
+ def _java_getprop(name, default):
- done
--{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5
- printf %s "checking for library containing login_tty... " >&6; }
- if test ${ac_cv_search_login_tty+y}
- then :
-@@ -21655,6 +22083,7 @@
- fi
+ from java.lang import System
+@@ -613,7 +685,7 @@
+ if cleaned == platform:
+ break
+ platform = cleaned
+- while platform[-1] == '-':
++ while platform and platform[-1] == '-':
+ platform = platform[:-1]
- done
-+fi
+ return platform
+@@ -654,7 +726,7 @@
+ default in case the command should fail.
- # check for long file support functions
- ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64"
-@@ -22202,6 +22631,11 @@
+ """
+- if sys.platform in ('dos', 'win32', 'win16'):
++ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}:
+ # XXX Others too ?
+ return default
- done
+@@ -816,6 +888,25 @@
+ csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
+ return 'Alpha' if cpu_number >= 128 else 'VAX'
-+# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by
-+# configure), but when used in an unprivileged process, it crashes rather than
-+# returning an error. Force the symbol off.
-+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS"
-+then
++ # On the iOS/tvOS/watchOS simulator, os.uname returns the architecture as
++ # uname.machine. On device it returns the model name for some reason; but
++ # there's only one CPU architecture for devices, so we know the right
++ # answer.
++ def get_ios():
++ if sys.implementation._multiarch.endswith("simulator"):
++ return os.uname().machine
++ return 'arm64'
++
++ def get_tvos():
++ if sys.implementation._multiarch.endswith("simulator"):
++ return os.uname().machine
++ return 'arm64'
++
++ def get_watchos():
++ if sys.implementation._multiarch.endswith("simulator"):
++ return os.uname().machine
++ return 'arm64_32'
++
+ def from_subprocess():
+ """
+ Fall back to `uname -p`
+@@ -970,6 +1061,14 @@
+ system = 'Windows'
+ release = 'Vista'
- for ac_func in clock_settime
- do :
-@@ -22212,7 +22646,7 @@
++ # 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 == 'watchos':
++ system, release, _, _ = watchos_ver()
++
+ vals = system, node, release, version, machine
+ # Replace 'unknown' values with the more portable ''
+ _uname_cache = uname_result(*map(_unknown_as_blank, vals))
+@@ -1249,11 +1348,18 @@
+ system, release, version = system_alias(system, release, version)
- else $as_nop
+ if system == 'Darwin':
+- # macOS (darwin kernel)
+- macos_release = mac_ver()[0]
+- if macos_release:
+- system = 'macOS'
+- release = macos_release
++ # 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 == "watchos":
++ system, release, _, _ = watchos_ver()
++ else:
++ macos_release = mac_ver()[0]
++ if macos_release:
++ system = 'macOS'
++ release = macos_release
-- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5
- printf %s "checking for clock_settime in -lrt... " >&6; }
- if test ${ac_cv_lib_rt_clock_settime+y}
- then :
-@@ -22250,7 +22684,7 @@
- if test "x$ac_cv_lib_rt_clock_settime" = xyes
- then :
+ if system == 'Windows':
+ # MS platforms
+diff --git a/Lib/site.py b/Lib/site.py
+index aed254ad504..ecbb2f57b3c 100644
+--- a/Lib/site.py
++++ b/Lib/site.py
+@@ -287,8 +287,8 @@
+ if env_base:
+ return env_base
-- printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h
-+ printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h
+- # Emscripten, VxWorks, and WASI have no home directories
+- if sys.platform in {"emscripten", "vxworks", "wasi"}:
++ # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
++ if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
+ return None
+ def joinuser(*args):
+diff --git a/Lib/subprocess.py b/Lib/subprocess.py
+index 881a9ce800a..e209f51013a 100644
+--- a/Lib/subprocess.py
++++ b/Lib/subprocess.py
+@@ -74,8 +74,8 @@
+ else:
+ _mswindows = True
- fi
-@@ -22259,6 +22693,7 @@
- fi
+-# wasm32-emscripten and wasm32-wasi do not support processes
+-_can_fork_exec = sys.platform not in {"emscripten", "wasi"}
++# some platforms do not support subprocesses
++_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"}
- done
-+fi
+ if _mswindows:
+ import _winapi
+@@ -103,18 +103,22 @@
+ if _can_fork_exec:
+ from _posixsubprocess import fork_exec as _fork_exec
+ # used in methods that are called by __del__
+- _waitpid = os.waitpid
+- _waitstatus_to_exitcode = os.waitstatus_to_exitcode
+- _WIFSTOPPED = os.WIFSTOPPED
+- _WSTOPSIG = os.WSTOPSIG
+- _WNOHANG = os.WNOHANG
++ class _del_safe:
++ waitpid = os.waitpid
++ waitstatus_to_exitcode = os.waitstatus_to_exitcode
++ WIFSTOPPED = os.WIFSTOPPED
++ WSTOPSIG = os.WSTOPSIG
++ WNOHANG = os.WNOHANG
++ ECHILD = errno.ECHILD
+ else:
+- _fork_exec = None
+- _waitpid = None
+- _waitstatus_to_exitcode = None
+- _WIFSTOPPED = None
+- _WSTOPSIG = None
+- _WNOHANG = None
++ class _del_safe:
++ waitpid = None
++ waitstatus_to_exitcode = None
++ WIFSTOPPED = None
++ WSTOPSIG = None
++ WNOHANG = None
++ ECHILD = errno.ECHILD
++
+ import select
+ import selectors
+@@ -1958,20 +1962,16 @@
+ raise child_exception_type(err_msg)
- for ac_func in clock_nanosleep
-@@ -22480,7 +22915,9 @@
- if test "$cross_compiling" = yes
- then :
--if test "${enable_ipv6+set}" = set; then
-+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
-+ ac_cv_buggy_getaddrinfo="no"
-+elif test "${enable_ipv6+set}" = set; then
- ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
- else
- ac_cv_buggy_getaddrinfo=yes
-@@ -24384,7 +24821,7 @@
- printf "%s\n" "$ABIFLAGS" >&6; }
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking SOABI" >&5
- printf %s "checking SOABI... " >&6; }
--SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
-+SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM}
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SOABI" >&5
- printf "%s\n" "$SOABI" >&6; }
+- def _handle_exitstatus(self, sts,
+- _waitstatus_to_exitcode=_waitstatus_to_exitcode,
+- _WIFSTOPPED=_WIFSTOPPED,
+- _WSTOPSIG=_WSTOPSIG):
++ def _handle_exitstatus(self, sts, _del_safe=_del_safe):
+ """All callers to this function MUST hold self._waitpid_lock."""
+ # This method is called (indirectly) by __del__, so it cannot
+ # refer to anything outside of its local scope.
+- if _WIFSTOPPED(sts):
+- self.returncode = -_WSTOPSIG(sts)
++ if _del_safe.WIFSTOPPED(sts):
++ self.returncode = -_del_safe.WSTOPSIG(sts)
+ else:
+- self.returncode = _waitstatus_to_exitcode(sts)
++ self.returncode = _del_safe.waitstatus_to_exitcode(sts)
-@@ -24392,7 +24829,7 @@
- if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then
- # Similar to SOABI but remove "d" flag from ABIFLAGS
+- def _internal_poll(self, _deadstate=None, _waitpid=_waitpid,
+- _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD):
++ def _internal_poll(self, _deadstate=None, _del_safe=_del_safe):
+ """Check if child process has terminated. Returns returncode
+ attribute.
-- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
-+ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM}
+@@ -1987,13 +1987,13 @@
+ try:
+ if self.returncode is not None:
+ return self.returncode # Another thread waited.
+- pid, sts = _waitpid(self.pid, _WNOHANG)
++ pid, sts = _del_safe.waitpid(self.pid, _del_safe.WNOHANG)
+ if pid == self.pid:
+ self._handle_exitstatus(sts)
+ except OSError as e:
+ if _deadstate is not None:
+ self.returncode = _deadstate
+- elif e.errno == _ECHILD:
++ elif e.errno == _del_safe.ECHILD:
+ # This happens if SIGCLD is set to be ignored or
+ # waiting for child processes has otherwise been
+ # disabled for our process. This child is dead, we
+diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
+index acc8d4d1826..7d4ebe3ccfa 100644
+--- a/Lib/sysconfig.py
++++ b/Lib/sysconfig.py
+@@ -21,6 +21,7 @@
- printf "%s\n" "#define ALT_SOABI \"${ALT_SOABI}\"" >>confdefs.h
+ # Keys for get_config_var() that are never converted to Python integers.
+ _ALWAYS_STR = {
++ 'IPHONEOS_DEPLOYMENT_TARGET',
+ 'MACOSX_DEPLOYMENT_TARGET',
+ }
-@@ -27143,24 +27580,28 @@
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5
- printf "%s\n" "$as_me: checking for device files" >&6;}
+@@ -57,6 +58,7 @@
+ 'scripts': '{base}/Scripts',
+ 'data': '{base}',
+ },
++
+ # Downstream distributors can overwrite the default install scheme.
+ # This is done to support downstream modifications where distributors change
+ # the installation layout (eg. different site-packages directory).
+@@ -112,8 +114,8 @@
+ if env_base:
+ return env_base
--if test "x$cross_compiling" = xyes; then
-- if test "${ac_cv_file__dev_ptmx+set}" != set; then
-- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
-+if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then
-+ ac_cv_file__dev_ptmx=no
-+ ac_cv_file__dev_ptc=no
-+else
-+ if test "x$cross_compiling" = xyes; then
-+ if test "${ac_cv_file__dev_ptmx+set}" != set; then
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
- printf %s "checking for /dev/ptmx... " >&6; }
-- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
- printf "%s\n" "not set" >&6; }
-- as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
-- fi
-- if test "${ac_cv_file__dev_ptc+set}" != set; then
-- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
-+ as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
-+ fi
-+ if test "${ac_cv_file__dev_ptc+set}" != set; then
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
- printf %s "checking for /dev/ptc... " >&6; }
-- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
- printf "%s\n" "not set" >&6; }
-- as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
-+ as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
-+ fi
- fi
--fi
+- # Emscripten, VxWorks, and WASI have no home directories
+- if sys.platform in {"emscripten", "vxworks", "wasi"}:
++ # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
++ if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
+ return None
--{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
- printf %s "checking for /dev/ptmx... " >&6; }
- if test ${ac_cv_file__dev_ptmx+y}
- then :
-@@ -27181,12 +27622,12 @@
+ def joinuser(*args):
+@@ -299,6 +301,7 @@
+ 'home': 'posix_home',
+ 'user': 'osx_framework_user',
+ }
++
+ return {
+ 'prefix': 'posix_prefix',
+ 'home': 'posix_home',
+@@ -831,10 +834,23 @@
+ if m:
+ release = m.group()
+ elif osname[:6] == "darwin":
+- import _osx_support
+- osname, release, machine = _osx_support.get_platform_osx(
+- get_config_vars(),
+- osname, release, machine)
++ if sys.platform == "ios":
++ 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", "9.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
++ else:
++ import _osx_support
++ osname, release, machine = _osx_support.get_platform_osx(
++ get_config_vars(),
++ osname, release, machine)
- fi
+ return f"{osname}-{release}-{machine}"
--if test "x$ac_cv_file__dev_ptmx" = xyes; then
-+ if test "x$ac_cv_file__dev_ptmx" = xyes; then
+diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
+index 6efeaad8126..e9b0df085d0 100644
+--- a/Lib/test/pythoninfo.py
++++ b/Lib/test/pythoninfo.py
+@@ -287,6 +287,7 @@
+ "HOMEDRIVE",
+ "HOMEPATH",
+ "IDLESTARTUP",
++ "IPHONEOS_DEPLOYMENT_TARGET",
+ "LANG",
+ "LDFLAGS",
+ "LDSHARED",
+diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
+index 4c22f131e31..a80b86e4dfe 100644
+--- a/Lib/test/support/__init__.py
++++ b/Lib/test/support/__init__.py
+@@ -59,6 +59,7 @@
+ "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "C_RECURSION_LIMIT",
+ "skip_on_s390x",
+ "BrokenIter",
++ "reset_code", "on_github_actions"
+ ]
- printf "%s\n" "#define HAVE_DEV_PTMX 1" >>confdefs.h
--fi
--{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
-+ fi
-+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
- printf %s "checking for /dev/ptc... " >&6; }
- if test ${ac_cv_file__dev_ptc+y}
- then :
-@@ -27207,10 +27648,11 @@
+@@ -1238,6 +1239,8 @@
+ opcode.ENABLE_SPECIALIZATION, "requires specialization")(test)
- fi
--if test "x$ac_cv_file__dev_ptc" = xyes; then
-+ if test "x$ac_cv_file__dev_ptc" = xyes; then
++on_github_actions = "GITHUB_ACTIONS" in os.environ
++
+ #=======================================================================
+ # Check for the presence of docstrings.
- printf "%s\n" "#define HAVE_DEV_PTC 1" >>confdefs.h
+diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
+index de7ca79dc81..e7058ba5233 100644
+--- a/Lib/test/support/os_helper.py
++++ b/Lib/test/support/os_helper.py
+@@ -22,8 +22,8 @@
-+ fi
- fi
+ # TESTFN_UNICODE is a non-ascii filename
+ TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f"
+-if sys.platform == 'darwin':
+- # In Mac OS X's VFS API file names are, by definition, canonically
++if support.is_apple:
++ # On Apple's VFS API file names are, by definition, canonically
+ # decomposed Unicode, encoded using UTF-8. See QA1173:
+ # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
+ import unicodedata
+@@ -48,8 +48,8 @@
+ 'encoding (%s). Unicode filename tests may not be effective'
+ % (TESTFN_UNENCODABLE, sys.getfilesystemencoding()))
+ TESTFN_UNENCODABLE = None
+-# macOS and Emscripten deny unencodable filenames (invalid utf-8)
+-elif sys.platform not in {'darwin', 'emscripten', 'wasi'}:
++# Apple and Emscripten deny unencodable filenames (invalid utf-8)
++elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}:
+ try:
+ # ascii and utf-8 cannot encode the byte 0xff
+ b'\xff'.decode(sys.getfilesystemencoding())
+@@ -615,7 +615,8 @@
+ if hasattr(os, 'sysconf'):
+ try:
+ MAXFD = os.sysconf("SC_OPEN_MAX")
+- except OSError:
++ except (OSError, ValueError):
++ # gh-118201: ValueError is raised intermittently on iOS
+ pass
- if test $ac_sys_system = Darwin
-@@ -27652,6 +28094,8 @@
- with_ensurepip=no ;; #(
- WASI) :
- with_ensurepip=no ;; #(
-+ iOS|tvOS|watchOS) :
-+ with_ensurepip=no ;; #(
- *) :
- with_ensurepip=upgrade
- ;;
-@@ -28593,6 +29037,27 @@
- py_cv_module_ossaudiodev=n/a
- py_cv_module_spwd=n/a
- ;; #(
-+ iOS|tvOS|watchOS) :
-+
+ old_modes = None
+--- /dev/null
++++ b/Lib/test/test_apple.py
+@@ -0,0 +1,155 @@
++import unittest
++from _apple_support import SystemLog
++from test.support import is_apple_mobile
++from unittest.mock import Mock, call
+
++if not is_apple_mobile:
++ raise unittest.SkipTest("iOS-specific")
+
-+ py_cv_module__curses=n/a
-+ py_cv_module__curses_panel=n/a
-+ py_cv_module__gdbm=n/a
-+ py_cv_module__multiprocessing=n/a
-+ py_cv_module__posixshmem=n/a
-+ py_cv_module__posixsubprocess=n/a
-+ py_cv_module__scproxy=n/a
-+ py_cv_module__tkinter=n/a
-+ py_cv_module_grp=n/a
-+ py_cv_module_nis=n/a
-+ py_cv_module_readline=n/a
-+ py_cv_module_pwd=n/a
-+ py_cv_module_spwd=n/a
-+ py_cv_module_syslog=n/a
-+ py_cv_module_=n/a
+
-+ ;; #(
- CYGWIN*) :
-
-
-@@ -32339,6 +32804,9 @@
- "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" ;;
-+ "tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES tvOS/Resources/Info.plist" ;;
-+ "watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES 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 9270b5f7172..252fc9750e5 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -307,6 +307,161 @@
- AC_MSG_ERROR([pkg-config is required])]
- fi
-
-+# Set name for machine-dependent library files
-+AC_ARG_VAR([MACHDEP], [name for machine-dependent library files])
-+AC_MSG_CHECKING([MACHDEP])
-+if test -z "$MACHDEP"
-+then
-+ # avoid using uname for cross builds
-+ if test "$cross_compiling" = yes; then
-+ # ac_sys_system and ac_sys_release are used for setting
-+ # a lot of different things including 'define_xopen_source'
-+ # in the case statement below.
-+ case "$host" in
-+ *-*-linux-android*)
-+ ac_sys_system=Linux-android
-+ ;;
-+ *-*-linux*)
-+ ac_sys_system=Linux
-+ ;;
-+ *-*-cygwin*)
-+ ac_sys_system=Cygwin
-+ ;;
-+ *-apple-ios*)
-+ ac_sys_system=iOS
-+ ;;
-+ *-apple-tvos*)
-+ ac_sys_system=tvOS
-+ ;;
-+ *-apple-watchos*)
-+ ac_sys_system=watchOS
-+ ;;
-+ *-*-vxworks*)
-+ ac_sys_system=VxWorks
-+ ;;
-+ *-*-emscripten)
-+ ac_sys_system=Emscripten
-+ ;;
-+ *-*-wasi)
-+ ac_sys_system=WASI
-+ ;;
-+ *)
-+ # for now, limit cross builds to known configurations
-+ MACHDEP="unknown"
-+ AC_MSG_ERROR([cross build not supported for $host])
-+ esac
-+ ac_sys_release=
-+ else
-+ ac_sys_system=`uname -s`
-+ if test "$ac_sys_system" = "AIX" \
-+ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
-+ ac_sys_release=`uname -v`
-+ else
-+ ac_sys_release=`uname -r`
-+ fi
-+ fi
-+ ac_md_system=`echo $ac_sys_system |
-+ tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'`
-+ ac_md_release=`echo $ac_sys_release |
-+ tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'`
-+ MACHDEP="$ac_md_system$ac_md_release"
++# Test redirection of stdout and stderr to the Apple system log.
++class TestAppleSystemLogOutput(unittest.TestCase):
++ maxDiff = None
+
-+ case $MACHDEP in
-+ aix*) MACHDEP="aix";;
-+ linux*) MACHDEP="linux";;
-+ cygwin*) MACHDEP="cygwin";;
-+ darwin*) MACHDEP="darwin";;
-+ '') MACHDEP="unknown";;
-+ esac
++ def assert_writes(self, output):
++ self.assertEqual(
++ self.log_write.mock_calls,
++ [
++ call(self.log_level, line)
++ for line in output
++ ]
++ )
+
-+ if test "$ac_sys_system" = "SunOS"; then
-+ # For Solaris, there isn't an OS version specific macro defined
-+ # in most compilers, so we define one here.
-+ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'`
-+ AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION],
-+ [The version of SunOS/Solaris as reported by `uname -r' without the dot.])
-+ fi
-+fi
-+AC_MSG_RESULT(["$MACHDEP"])
++ self.log_write.reset_mock()
+
-+# On cross-compile builds, configure will look for a host-specific compiler by
-+# prepending the user-provided host triple to the required binary name.
-+#
-+# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
-+# 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
-+# these tools; the binary names of these scripts are better defaults than "gcc".
-+# This only requires that the user put the platform scripts folder (e.g.,
-+# "iOS/Resources/bin") in their path, rather than defining platform-specific
-+# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to
-+# either put the platform scripts folder in the path, or specify CC etc,
-+# configure will fail.
-+if test -z "$AR"; then
-+ case "$host" in
-+ 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 ;;
++ def setUp(self):
++ self.log_write = Mock()
++ self.log_level = 42
++ self.log = SystemLog(self.log_write, self.log_level, errors="replace")
+
-+ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;;
-+ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;;
-+ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;;
++ def test_repr(self):
++ self.assertEqual(repr(self.log), "")
++ self.assertEqual(repr(self.log.buffer), "")
+
-+ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;;
-+ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;;
-+ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;;
-+ *)
-+ esac
-+fi
-+if test -z "$CC"; then
-+ case "$host" in
-+ 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 ;;
++ def test_log_config(self):
++ self.assertIs(self.log.writable(), True)
++ self.assertIs(self.log.readable(), False)
+
-+ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;;
-+ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;;
-+ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;;
++ self.assertEqual("UTF-8", self.log.encoding)
++ self.assertEqual("replace", self.log.errors)
+
-+ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;;
-+ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;;
-+ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;;
-+ *)
-+ esac
-+fi
-+if test -z "$CPP"; then
-+ case "$host" in
-+ 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 ;;
++ self.assertIs(self.log.line_buffering, True)
++ self.assertIs(self.log.write_through, False)
+
-+ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;;
-+ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;;
-+ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;;
++ def test_empty_str(self):
++ self.log.write("")
++ self.log.flush()
+
-+ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;;
-+ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;;
-+ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;;
-+ *)
-+ esac
-+fi
-+if test -z "$CXX"; then
-+ case "$host" in
-+ 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++ ;;
++ self.assert_writes([])
+
-+ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;;
-+ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;;
-+ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;;
++ def test_simple_str(self):
++ self.log.write("hello world\n")
+
-+ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;;
-+ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;;
-+ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;;
-+ *)
-+ esac
-+fi
++ self.assert_writes([b"hello world\n"])
+
- AC_MSG_CHECKING([for --enable-universalsdk])
- AC_ARG_ENABLE([universalsdk],
- AS_HELP_STRING([--enable-universalsdk@<:@=SDKDIR@:>@],
-@@ -416,109 +571,189 @@
- [
- case $enableval in
- yes)
-- enableval=/Library/Frameworks
-+ case $ac_sys_system in
-+ Darwin) enableval=/Library/Frameworks ;;
-+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;;
-+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;;
-+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;;
-+ *) AC_MSG_ERROR([Unknown platform for framework build])
-+ esac
- esac
++ def test_buffered_str(self):
++ self.log.write("h")
++ self.log.write("ello")
++ self.log.write(" ")
++ self.log.write("world\n")
++ self.log.write("goodbye.")
++ self.log.flush()
+
- case $enableval in
- no)
-- PYTHONFRAMEWORK=
-- PYTHONFRAMEWORKDIR=no-framework
-- PYTHONFRAMEWORKPREFIX=
-- PYTHONFRAMEWORKINSTALLDIR=
-- FRAMEWORKINSTALLFIRST=
-- FRAMEWORKINSTALLLAST=
-- FRAMEWORKALTINSTALLFIRST=
-- FRAMEWORKALTINSTALLLAST=
-- FRAMEWORKPYTHONW=
-- if test "x${prefix}" = "xNONE"; then
-- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-- else
-- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-- fi
-- enable_framework=
-+ case $ac_sys_system in
-+ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;;
-+ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;;
-+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;;
-+ *)
-+ PYTHONFRAMEWORK=
-+ PYTHONFRAMEWORKDIR=no-framework
-+ PYTHONFRAMEWORKPREFIX=
-+ PYTHONFRAMEWORKINSTALLDIR=
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-+ RESSRCDIR=
-+ FRAMEWORKINSTALLFIRST=
-+ FRAMEWORKINSTALLLAST=
-+ FRAMEWORKALTINSTALLFIRST=
-+ FRAMEWORKALTINSTALLLAST=
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="commoninstall bininstall maninstall"
++ self.assert_writes([b"hello world\n", b"goodbye."])
+
-+ if test "x${prefix}" = "xNONE"; then
-+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-+ else
-+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-+ fi
-+ enable_framework=
-+ esac
- ;;
- *)
- PYTHONFRAMEWORKPREFIX="${enableval}"
- PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR
-- FRAMEWORKINSTALLFIRST="frameworkinstallstructure"
-- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure "
-- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
-- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
-- FRAMEWORKPYTHONW="frameworkpythonw"
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
--
-- if test "x${prefix}" = "xNONE" ; then
-- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
--
-- else
-- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-- fi
-
-- case "${enableval}" in
-- /System*)
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-- if test "${prefix}" = "NONE" ; then
-- # See below
-- FRAMEWORKUNIXTOOLSPREFIX="/usr"
-- fi
-- ;;
-+ case $ac_sys_system in #(
-+ Darwin) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
-+ FRAMEWORKPYTHONW="frameworkpythonw"
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ INSTALLTARGETS="commoninstall bininstall maninstall"
++ def test_manual_flush(self):
++ self.log.write("Hello")
+
-+ if test "x${prefix}" = "xNONE" ; then
-+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
++ self.assert_writes([])
+
-+ else
-+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-+ fi
-
-- /Library*)
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-- ;;
-+ case "${enableval}" in
-+ /System*)
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ if test "${prefix}" = "NONE" ; then
-+ # See below
-+ FRAMEWORKUNIXTOOLSPREFIX="/usr"
-+ fi
-+ ;;
++ self.log.write(" world\nHere for a while...\nGoodbye")
++ self.assert_writes([b"Hello world\n", b"Here for a while...\n"])
+
-+ /Library*)
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ ;;
++ self.log.write(" world\nHello again")
++ self.assert_writes([b"Goodbye world\n"])
+
-+ */Library/Frameworks)
-+ MDIR="`dirname "${enableval}"`"
-+ MDIR="`dirname "${MDIR}"`"
-+ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
++ self.log.flush()
++ self.assert_writes([b"Hello again"])
+
-+ if test "${prefix}" = "NONE"; then
-+ # User hasn't specified the
-+ # --prefix option, but wants to install
-+ # the framework in a non-default location,
-+ # ensure that the compatibility links get
-+ # installed relative to that prefix as well
-+ # instead of in /usr/local.
-+ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
-+ fi
-+ ;;
-
-- */Library/Frameworks)
-- MDIR="`dirname "${enableval}"`"
-- MDIR="`dirname "${MDIR}"`"
-- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
--
-- if test "${prefix}" = "NONE"; then
-- # User hasn't specified the
-- # --prefix option, but wants to install
-- # the framework in a non-default location,
-- # ensure that the compatibility links get
-- # installed relative to that prefix as well
-- # instead of in /usr/local.
-- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
-- fi
-- ;;
-+ *)
-+ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-+ ;;
-+ esac
-
-- *)
-- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
-- ;;
-+ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix}
-+ RESSRCDIR=Mac/Resources/framework
++ def test_non_ascii(self):
++ # Spanish
++ self.log.write("ol\u00e9\n")
++ self.assert_writes([b"ol\xc3\xa9\n"])
+
-+ # Add files for Mac specific code to the list of output
-+ # files:
-+ AC_CONFIG_FILES([Mac/Makefile])
-+ AC_CONFIG_FILES([Mac/PythonLauncher/Makefile])
-+ AC_CONFIG_FILES([Mac/Resources/framework/Info.plist])
-+ AC_CONFIG_FILES([Mac/Resources/app/Info.plist])
-+ ;;
-+ iOS) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++ # Chinese
++ self.log.write("\u4e2d\u6587\n")
++ self.assert_writes([b"\xe4\xb8\xad\xe6\x96\x87\n"])
+
-+ prefix=$PYTHONFRAMEWORKPREFIX
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=iOS/Resources
++ # Printing Non-BMP emoji
++ self.log.write("\U0001f600\n")
++ self.assert_writes([b"\xf0\x9f\x98\x80\n"])
+
-+ AC_CONFIG_FILES([iOS/Resources/Info.plist])
-+ ;;
-+ tvOS) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++ # Non-encodable surrogates are replaced
++ self.log.write("\ud800\udc00\n")
++ self.assert_writes([b"??\n"])
+
-+ prefix=$PYTHONFRAMEWORKPREFIX
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=tvOS/Resources
++ def test_modified_null(self):
++ # Null characters are logged using "modified UTF-8".
++ self.log.write("\u0000\n")
++ self.assert_writes([b"\xc0\x80\n"])
++ self.log.write("a\u0000\n")
++ self.assert_writes([b"a\xc0\x80\n"])
++ self.log.write("\u0000b\n")
++ self.assert_writes([b"\xc0\x80b\n"])
++ self.log.write("a\u0000b\n")
++ self.assert_writes([b"a\xc0\x80b\n"])
+
-+ AC_CONFIG_FILES([tvOS/Resources/Info.plist])
-+ ;;
-+ watchOS) :
-+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
-+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
-+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++ def test_nonstandard_str(self):
++ # String subclasses are accepted, but they should be converted
++ # to a standard str without calling any of their methods.
++ class CustomStr(str):
++ def splitlines(self, *args, **kwargs):
++ raise AssertionError()
+
-+ prefix=$PYTHONFRAMEWORKPREFIX
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
-+ RESSRCDIR=watchOS/Resources
++ def __len__(self):
++ raise AssertionError()
+
-+ AC_CONFIG_FILES([watchOS/Resources/Info.plist])
-+ ;;
-+ *)
-+ AC_MSG_ERROR([Unknown platform for framework build])
-+ ;;
-+ esac
- esac
--
-- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
--
-- # Add files for Mac specific code to the list of output
-- # files:
-- AC_CONFIG_FILES([Mac/Makefile])
-- AC_CONFIG_FILES([Mac/PythonLauncher/Makefile])
-- AC_CONFIG_FILES([Mac/Resources/framework/Info.plist])
-- AC_CONFIG_FILES([Mac/Resources/app/Info.plist])
-- esac
- ],[
-- PYTHONFRAMEWORK=
-- PYTHONFRAMEWORKDIR=no-framework
-- PYTHONFRAMEWORKPREFIX=
-- PYTHONFRAMEWORKINSTALLDIR=
-- FRAMEWORKINSTALLFIRST=
-- FRAMEWORKINSTALLLAST=
-- FRAMEWORKALTINSTALLFIRST=
-- FRAMEWORKALTINSTALLLAST=
-- FRAMEWORKPYTHONW=
-- if test "x${prefix}" = "xNONE" ; then
-- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-- else
-- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-- fi
-- enable_framework=
--
-+ case $ac_sys_system in
-+ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;;
-+ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;;
-+ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;;
-+ *)
-+ PYTHONFRAMEWORK=
-+ PYTHONFRAMEWORKDIR=no-framework
-+ PYTHONFRAMEWORKPREFIX=
-+ PYTHONFRAMEWORKINSTALLDIR=
-+ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-+ RESSRCDIR=
-+ FRAMEWORKINSTALLFIRST=
-+ FRAMEWORKINSTALLLAST=
-+ FRAMEWORKALTINSTALLFIRST=
-+ FRAMEWORKALTINSTALLLAST=
-+ FRAMEWORKPYTHONW=
-+ INSTALLTARGETS="commoninstall bininstall maninstall"
-+ if test "x${prefix}" = "xNONE" ; then
-+ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-+ else
-+ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-+ fi
-+ enable_framework=
-+ esac
- ])
- AC_SUBST([PYTHONFRAMEWORK])
- AC_SUBST([PYTHONFRAMEWORKIDENTIFIER])
- AC_SUBST([PYTHONFRAMEWORKDIR])
- AC_SUBST([PYTHONFRAMEWORKPREFIX])
- AC_SUBST([PYTHONFRAMEWORKINSTALLDIR])
-+AC_SUBST([PYTHONFRAMEWORKINSTALLNAMEPREFIX])
-+AC_SUBST([RESSRCDIR])
- AC_SUBST([FRAMEWORKINSTALLFIRST])
- AC_SUBST([FRAMEWORKINSTALLLAST])
- AC_SUBST([FRAMEWORKALTINSTALLFIRST])
-@@ -526,77 +761,51 @@
- AC_SUBST([FRAMEWORKPYTHONW])
- AC_SUBST([FRAMEWORKUNIXTOOLSPREFIX])
- AC_SUBST([FRAMEWORKINSTALLAPPSPREFIX])
-+AC_SUBST([INSTALLTARGETS])
-
- AC_DEFINE_UNQUOTED([_PYTHONFRAMEWORK], ["${PYTHONFRAMEWORK}"],
- [framework name])
-
--# Set name for machine-dependent library files
--AC_ARG_VAR([MACHDEP], [name for machine-dependent library files])
--AC_MSG_CHECKING([MACHDEP])
--if test -z "$MACHDEP"
--then
-- # avoid using uname for cross builds
-- if test "$cross_compiling" = yes; then
-- # ac_sys_system and ac_sys_release are used for setting
-- # a lot of different things including 'define_xopen_source'
-- # in the case statement below.
-- case "$host" in
-- *-*-linux-android*)
-- ac_sys_system=Linux-android
-- ;;
-- *-*-linux*)
-- ac_sys_system=Linux
-- ;;
-- *-*-cygwin*)
-- ac_sys_system=Cygwin
-- ;;
-- *-*-vxworks*)
-- ac_sys_system=VxWorks
-- ;;
-- *-*-emscripten)
-- ac_sys_system=Emscripten
-- ;;
-- *-*-wasi)
-- ac_sys_system=WASI
-- ;;
-- *)
-- # for now, limit cross builds to known configurations
-- MACHDEP="unknown"
-- AC_MSG_ERROR([cross build not supported for $host])
-- esac
-- ac_sys_release=
-- else
-- ac_sys_system=`uname -s`
-- if test "$ac_sys_system" = "AIX" \
-- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
-- ac_sys_release=`uname -v`
-- else
-- ac_sys_release=`uname -r`
-- fi
-- fi
-- ac_md_system=`echo $ac_sys_system |
-- tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'`
-- ac_md_release=`echo $ac_sys_release |
-- tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'`
-- MACHDEP="$ac_md_system$ac_md_release"
--
-- case $MACHDEP in
-- aix*) MACHDEP="aix";;
-- linux*) MACHDEP="linux";;
-- cygwin*) MACHDEP="cygwin";;
-- darwin*) MACHDEP="darwin";;
-- '') MACHDEP="unknown";;
-+dnl quadrigraphs "@<:@" and "@:>@" produce "[" and "]" in the output
-+AC_MSG_CHECKING([for --with-app-store-compliance])
-+AC_ARG_WITH(
-+ [app_store_compliance],
-+ [AS_HELP_STRING(
-+ [--with-app-store-compliance=@<:@PATCH-FILE@:>@],
-+ [Enable any patches required for compiliance with app stores.
-+ Optional PATCH-FILE specifies the custom patch to apply.]
-+ )],[
-+ case "$withval" in
-+ yes)
-+ case $ac_sys_system in
-+ Darwin|iOS|tvOS|watchOS)
-+ # iOS/tvOS/watchOS is able to share the macOS patch
-+ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
-+ ;;
-+ *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;;
-+ esac
-+ AC_MSG_RESULT([applying default app store compliance patch])
-+ ;;
-+ *)
-+ APP_STORE_COMPLIANCE_PATCH="${withval}"
-+ AC_MSG_RESULT([applying custom app store compliance patch])
-+ ;;
- esac
--
-- if test "$ac_sys_system" = "SunOS"; then
-- # For Solaris, there isn't an OS version specific macro defined
-- # in most compilers, so we define one here.
-- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'`
-- AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION],
-- [The version of SunOS/Solaris as reported by `uname -r' without the dot.])
-- fi
--fi
--AC_MSG_RESULT(["$MACHDEP"])
-+ ],[
-+ case $ac_sys_system in
-+ iOS|tvOS|watchOS)
-+ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch
-+ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
-+ AC_MSG_RESULT([applying default app store compliance patch])
-+ ;;
-+ *)
-+ # No default app compliance patching on any other platform
-+ APP_STORE_COMPLIANCE_PATCH=
-+ AC_MSG_RESULT([not patching for app store compliance])
-+ ;;
-+ esac
-+])
-+AC_SUBST([APP_STORE_COMPLIANCE_PATCH])
-
- AC_SUBST([_PYTHON_HOST_PLATFORM])
- if test "$cross_compiling" = yes; then
-@@ -604,27 +813,87 @@
- *-*-linux*)
- case "$host_cpu" in
- arm*)
-- _host_cpu=arm
-+ _host_ident=arm
- ;;
- *)
-- _host_cpu=$host_cpu
-+ _host_ident=$host_cpu
- esac
- ;;
- *-*-cygwin*)
-- _host_cpu=
-+ _host_ident=
-+ ;;
-+ *-apple-ios*)
-+ _host_os=`echo $host | cut -d '-' -f3`
-+ _host_device=`echo $host | cut -d '-' -f4`
-+ _host_device=${_host_device:=os}
++ def __str__(self):
++ raise AssertionError()
+
-+ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version
-+ AC_MSG_CHECKING([iOS deployment target])
-+ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3}
-+ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0}
-+ AC_MSG_RESULT([$IPHONEOS_DEPLOYMENT_TARGET])
++ self.log.write(CustomStr("custom\n"))
++ self.assert_writes([b"custom\n"])
+
-+ case "$host_cpu" in
-+ aarch64)
-+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device}
-+ ;;
-+ *)
-+ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device}
-+ ;;
-+ esac
-+ ;;
-+ *-apple-tvos*)
-+ _host_os=`echo $host | cut -d '-' -f3`
-+ _host_device=`echo $host | cut -d '-' -f4`
-+ _host_device=${_host_device:=os}
++ def test_non_str(self):
++ # Non-string classes are not accepted.
++ for obj in [b"", b"hello", None, 42]:
++ with self.subTest(obj=obj):
++ with self.assertRaisesRegex(
++ TypeError,
++ fr"write\(\) argument must be str, not "
++ fr"{type(obj).__name__}"
++ ):
++ self.log.write(obj)
+
-+ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version
-+ AC_MSG_CHECKING([tvOS deployment target])
-+ TVOS_DEPLOYMENT_TARGET=${_host_os:4}
-+ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0}
-+ AC_MSG_RESULT([$TVOS_DEPLOYMENT_TARGET])
++ def test_byteslike_in_buffer(self):
++ # The underlying buffer *can* accept bytes-like objects
++ self.log.buffer.write(bytearray(b"hello"))
++ self.log.flush()
+
-+ case "$host_cpu" in
-+ aarch64)
-+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device}
-+ ;;
-+ *)
-+ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device}
-+ ;;
-+ esac
-+ ;;
-+ *-apple-watchos*)
-+ _host_os=`echo $host | cut -d '-' -f3`
-+ _host_device=`echo $host | cut -d '-' -f4`
-+ _host_device=${_host_device:=os}
++ self.log.buffer.write(b"")
++ self.log.flush()
+
-+ # 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}
-+ AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET])
++ self.log.buffer.write(b"goodbye")
++ self.log.flush()
+
-+ case "$host_cpu" in
-+ aarch64)
-+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device}
-+ ;;
-+ *)
-+ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device}
-+ ;;
-+ esac
- ;;
- *-*-vxworks*)
-- _host_cpu=$host_cpu
-+ _host_ident=$host_cpu
- ;;
- wasm32-*-* | wasm64-*-*)
-- _host_cpu=$host_cpu
-+ _host_ident=$host_cpu
- ;;
- *)
- # for now, limit cross builds to known configurations
- MACHDEP="unknown"
- AC_MSG_ERROR([cross build not supported for $host])
- esac
-- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}"
-+ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}"
- fi
++ self.assert_writes([b"hello", b"goodbye"])
++
++ def test_non_byteslike_in_buffer(self):
++ for obj in ["hello", None, 42]:
++ with self.subTest(obj=obj):
++ with self.assertRaisesRegex(
++ TypeError,
++ fr"write\(\) argument must be bytes-like, not "
++ fr"{type(obj).__name__}"
++ ):
++ self.log.buffer.write(obj)
+diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
+index abf425f5ef0..ffcde82b63e 100644
+--- a/Lib/test/test_asyncio/test_events.py
++++ b/Lib/test/test_asyncio/test_events.py
+@@ -1894,6 +1894,7 @@
+ else:
+ self.assertEqual(-signal.SIGKILL, returncode)
- # Some systems cannot stand _XOPEN_SOURCE being defined at all; they
-@@ -690,6 +959,13 @@
- define_xopen_source=no;;
- Darwin/@<:@[12]@:>@@<:@0-9@:>@.*)
- define_xopen_source=no;;
-+ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features.
-+ iOS/*)
-+ define_xopen_source=no;;
-+ tvOS/*)
-+ define_xopen_source=no;;
-+ watchOS/*)
-+ define_xopen_source=no;;
- # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
- # defining NI_NUMERICHOST.
- QNX/6.3.2)
-@@ -748,6 +1024,12 @@
- CONFIGURE_MACOSX_DEPLOYMENT_TARGET=
- EXPORT_MACOSX_DEPLOYMENT_TARGET='#'
++ @support.requires_subprocess()
+ def test_subprocess_exec(self):
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
-+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET /
-+# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple.
-+AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET])
-+AC_SUBST([TVOS_DEPLOYMENT_TARGET])
-+AC_SUBST([WATCHOS_DEPLOYMENT_TARGET])
-+
- # checks for alternative programs
+@@ -1915,6 +1916,7 @@
+ self.check_killed(proto.returncode)
+ self.assertEqual(b'Python The Winner', proto.data[1])
- # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just
-@@ -780,6 +1062,20 @@
- ],
- )
++ @support.requires_subprocess()
+ def test_subprocess_interactive(self):
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
-+dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS version.
-+AS_CASE([$ac_sys_system],
-+ [iOS], [
-+ AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
-+ AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
-+ ],[tvOS], [
-+ AS_VAR_APPEND([CFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
-+ AS_VAR_APPEND([LDFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
-+ ],[watchOS], [
-+ AS_VAR_APPEND([CFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
-+ AS_VAR_APPEND([LDFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
-+ ],
-+)
-+
- if test "$ac_sys_system" = "Darwin"
- then
- dnl look for SDKROOT
-@@ -1077,7 +1373,42 @@
- #elif defined(__gnu_hurd__)
- i386-gnu
- #elif defined(__APPLE__)
-+# include "TargetConditionals.h"
-+# if TARGET_OS_IOS
-+# if TARGET_OS_SIMULATOR
-+# if __x86_64__
-+ x86_64-iphonesimulator
-+# else
-+ arm64-iphonesimulator
-+# endif
-+# else
-+ arm64-iphoneos
-+# endif
-+# elif TARGET_OS_TV
-+# if TARGET_OS_SIMULATOR
-+# if __x86_64__
-+ x86_64-appletvsimulator
-+# else
-+ arm64-appletvsimulator
-+# endif
-+# else
-+ arm64-appletvos
-+# endif
-+# elif TARGET_OS_WATCH
-+# if TARGET_OS_SIMULATOR
-+# if __x86_64__
-+ x86_64-watchsimulator
-+# else
-+ arm64-watchsimulator
-+# endif
-+# else
-+ arm64_32-watchos
-+# endif
-+# elif TARGET_OS_OSX
- darwin
-+# else
-+# error unknown Apple platform
-+# endif
- #elif defined(__VXWORKS__)
- vxworks
- #elif defined(__wasm32__)
-@@ -1119,14 +1450,24 @@
- fi
- rm -f conftest.c conftest.out
+@@ -1942,6 +1944,7 @@
+ self.loop.run_until_complete(proto.completed)
+ self.check_killed(proto.returncode)
+
++ @support.requires_subprocess()
+ def test_subprocess_shell(self):
+ connect = self.loop.subprocess_shell(
+ functools.partial(MySubprocessProtocol, self.loop),
+@@ -1958,6 +1961,7 @@
+ self.assertEqual(proto.data[2], b'')
+ transp.close()
+
++ @support.requires_subprocess()
+ def test_subprocess_exitcode(self):
+ connect = self.loop.subprocess_shell(
+ functools.partial(MySubprocessProtocol, self.loop),
+@@ -1969,6 +1973,7 @@
+ self.assertEqual(7, proto.returncode)
+ transp.close()
+
++ @support.requires_subprocess()
+ def test_subprocess_close_after_finish(self):
+ connect = self.loop.subprocess_shell(
+ functools.partial(MySubprocessProtocol, self.loop),
+@@ -1983,6 +1988,7 @@
+ self.assertEqual(7, proto.returncode)
+ self.assertIsNone(transp.close())
+
++ @support.requires_subprocess()
+ def test_subprocess_kill(self):
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
+
+@@ -1999,6 +2005,7 @@
+ self.check_killed(proto.returncode)
+ transp.close()
+
++ @support.requires_subprocess()
+ def test_subprocess_terminate(self):
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
+
+@@ -2016,6 +2023,7 @@
+ transp.close()
+
+ @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP")
++ @support.requires_subprocess()
+ def test_subprocess_send_signal(self):
+ # bpo-31034: Make sure that we get the default signal handler (killing
+ # the process). The parent process may have decided to ignore SIGHUP,
+@@ -2040,6 +2048,7 @@
+ finally:
+ signal.signal(signal.SIGHUP, old_handler)
+
++ @support.requires_subprocess()
+ def test_subprocess_stderr(self):
+ prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
+
+@@ -2061,6 +2070,7 @@
+ self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2])
+ self.assertEqual(0, proto.returncode)
+
++ @support.requires_subprocess()
+ def test_subprocess_stderr_redirect_to_stdout(self):
+ prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
+
+@@ -2086,6 +2096,7 @@
+ transp.close()
+ self.assertEqual(0, proto.returncode)
+
++ @support.requires_subprocess()
+ def test_subprocess_close_client_stream(self):
+ prog = os.path.join(os.path.dirname(__file__), 'echo3.py')
+
+@@ -2120,6 +2131,7 @@
+ self.loop.run_until_complete(proto.completed)
+ self.check_killed(proto.returncode)
+
++ @support.requires_subprocess()
+ def test_subprocess_wait_no_same_group(self):
+ # start the new process in a new session
+ connect = self.loop.subprocess_shell(
+@@ -2132,6 +2144,7 @@
+ self.assertEqual(7, proto.returncode)
+ transp.close()
-+dnl On some platforms, using a true "triplet" for MULTIARCH would be redundant.
-+dnl For example, `arm64-apple-darwin` is redundant, because there isn't a
-+dnl non-Apple Darwin. Including the CPU architecture can also be potentially
-+dnl redundant - on macOS, for example, it's possible to do a single compile
-+dnl pass that includes multiple architectures, so it would be misleading for
-+dnl MULTIARCH (and thus the sysconfigdata module name) to include a single CPU
-+dnl architecture. PLATFORM_TRIPLET will be a pair or single value for these
-+dnl platforms.
- AC_MSG_CHECKING([for multiarch])
- AS_CASE([$ac_sys_system],
- [Darwin*], [MULTIARCH=""],
-+ [iOS], [MULTIARCH=""],
-+ [tvOS], [MULTIARCH=""],
-+ [watchOS], [MULTIARCH=""],
- [FreeBSD*], [MULTIARCH=""],
- [MULTIARCH=$($CC --print-multiarch 2>/dev/null)]
- )
- AC_SUBST([MULTIARCH])
--AC_MSG_RESULT([$MULTIARCH])
++ @support.requires_subprocess()
+ def test_subprocess_exec_invalid_args(self):
+ async def connect(**kwds):
+ await self.loop.subprocess_exec(
+@@ -2145,6 +2158,7 @@
+ with self.assertRaises(ValueError):
+ self.loop.run_until_complete(connect(shell=True))
- if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then
- if test x$PLATFORM_TRIPLET != x$MULTIARCH; then
-@@ -1136,6 +1477,17 @@
- MULTIARCH=$PLATFORM_TRIPLET
- fi
- AC_SUBST([PLATFORM_TRIPLET])
-+AC_MSG_RESULT([$MULTIARCH])
-+
-+dnl Even if we *do* include the CPU architecture in the MULTIARCH value, some
-+dnl platforms don't need the CPU architecture in the SOABI tag. These platforms
-+dnl will have multiple sysconfig modules (one for each CPU architecture), but
-+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],
-+ [iOS|tvOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`],
-+ [SOABI_PLATFORM=$PLATFORM_TRIPLET]
-+)
++ @support.requires_subprocess()
+ def test_subprocess_shell_invalid_args(self):
- if test x$MULTIARCH != x; then
- MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\""
-@@ -1166,6 +1518,12 @@
- [wasm32-unknown-emscripten/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly Emscripten
- [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly System Interface
- [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64
-+ [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64
-+ [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64
-+ [aarch64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl tvOS Simulator on arm64
-+ [aarch64-apple-tvos*/clang], [PY_SUPPORT_TIER=3], dnl tvOS on ARM64
-+ [aarch64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl watchOS Simulator on arm64
-+ [arm64_32-apple-watchos*/clang], [PY_SUPPORT_TIER=3], dnl watchOS on ARM64
- [PY_SUPPORT_TIER=0]
- )
+ async def connect(cmd=None, **kwds):
+diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py
+index 686fef8377f..4f59c31dfe4 100644
+--- a/Lib/test/test_asyncio/test_streams.py
++++ b/Lib/test/test_asyncio/test_streams.py
+@@ -10,7 +10,6 @@
+ import unittest
+ from unittest import mock
+ import warnings
+-from test.support import socket_helper
+ try:
+ import ssl
+ except ImportError:
+@@ -18,6 +17,7 @@
-@@ -1482,17 +1840,25 @@
+ import asyncio
+ from test.test_asyncio import utils as test_utils
++from test.support import requires_subprocess, socket_helper
- AC_MSG_CHECKING([LDLIBRARY])
--# MacOSX framework builds need more magic. LDLIBRARY is the dynamic
-+# Apple framework builds need more magic. LDLIBRARY is the dynamic
- # library that we build, but we do not want to link against it (we
- # will find it with a -framework option). For this reason there is an
- # extra variable BLDLIBRARY against which Python and the extension
- # modules are linked, BLDLIBRARY. This is normally the same as
--# LDLIBRARY, but empty for MacOSX framework builds.
-+# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same,
-+# but uses a non-versioned framework layout.
- if test "$enable_framework"
- then
-- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
-- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
-+ case $ac_sys_system in
-+ Darwin)
-+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';;
-+ iOS|tvOS|watchOS)
-+ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';;
-+ *)
-+ AC_MSG_ERROR([Unknown platform for framework build]);;
-+ esac
- BLDLIBRARY=''
-+ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
- else
- BLDLIBRARY='$(LDLIBRARY)'
- fi
-@@ -1504,64 +1870,69 @@
- [Defined if Python is built as a shared library.])
- case $ac_sys_system in
- CYGWIN*)
-- LDLIBRARY='libpython$(LDVERSION).dll.a'
-- DLLLIBRARY='libpython$(LDVERSION).dll'
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).dll.a'
-+ DLLLIBRARY='libpython$(LDVERSION).dll'
-+ ;;
- SunOS*)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
-- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-- INSTSONAME="$LDLIBRARY".$SOVERSION
-- if test "$with_pydebug" != yes
-- then
-- PY3LIBRARY=libpython3.so
-- fi
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
-+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-+ INSTSONAME="$LDLIBRARY".$SOVERSION
-+ if test "$with_pydebug" != yes
-+ then
-+ PY3LIBRARY=libpython3.so
-+ fi
-+ ;;
- Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- BLDLIBRARY='-L. -lpython$(LDVERSION)'
-- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-- INSTSONAME="$LDLIBRARY".$SOVERSION
-- if test "$with_pydebug" != yes
-- then
-- PY3LIBRARY=libpython3.so
-- fi
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ BLDLIBRARY='-L. -lpython$(LDVERSION)'
-+ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-+ INSTSONAME="$LDLIBRARY".$SOVERSION
-+ if test "$with_pydebug" != yes
-+ then
-+ PY3LIBRARY=libpython3.so
-+ fi
-+ ;;
- hp*|HP*)
-- case `uname -m` in
-- ia64)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- ;;
-- *)
-- LDLIBRARY='libpython$(LDVERSION).sl'
-- ;;
-- esac
-- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
-- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
-- ;;
-+ case `uname -m` in
-+ ia64)
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ ;;
-+ *)
-+ LDLIBRARY='libpython$(LDVERSION).sl'
-+ ;;
-+ esac
-+ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
-+ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
-+ ;;
- Darwin*)
-- LDLIBRARY='libpython$(LDVERSION).dylib'
-- BLDLIBRARY='-L. -lpython$(LDVERSION)'
-- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).dylib'
-+ BLDLIBRARY='-L. -lpython$(LDVERSION)'
-+ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
-+ ;;
-+ iOS|tvOS|watchOS)
-+ LDLIBRARY='libpython$(LDVERSION).dylib'
-+ ;;
- AIX*)
-- LDLIBRARY='libpython$(LDVERSION).so'
-- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
-- ;;
-+ LDLIBRARY='libpython$(LDVERSION).so'
-+ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
-+ ;;
+ def tearDownModule():
+@@ -770,6 +770,7 @@
+ self.assertEqual(msg2, b"hello world 2!\n")
- esac
- else # shared is disabled
- PY_ENABLE_SHARED=0
- case $ac_sys_system in
- CYGWIN*)
-- BLDLIBRARY='$(LIBRARY)'
-- LDLIBRARY='libpython$(LDVERSION).dll.a'
-- ;;
-+ BLDLIBRARY='$(LIBRARY)'
-+ LDLIBRARY='libpython$(LDVERSION).dll.a'
-+ ;;
- esac
- fi
+ @unittest.skipIf(sys.platform == 'win32', "Don't have pipes")
++ @requires_subprocess()
+ def test_read_all_from_pipe_reader(self):
+ # See asyncio issue 168. This test is derived from the example
+ # subprocess_attach_read_pipe.py, but we configure the
+diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py
+index 859d2932c33..ed43895fd68 100644
+--- a/Lib/test/test_asyncio/test_subprocess.py
++++ b/Lib/test/test_asyncio/test_subprocess.py
+@@ -47,6 +47,7 @@
+ self._proc.pid = -1
+
+
++@support.requires_subprocess()
+ class SubprocessTransportTests(test_utils.TestCase):
+ def setUp(self):
+ super().setUp()
+@@ -110,6 +111,7 @@
+ transport.close()
+
+
++@support.requires_subprocess()
+ class SubprocessMixin:
+
+ def test_stdin_stdout(self):
+diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py
+index 35c924a0cd6..9452213c685 100644
+--- a/Lib/test/test_asyncio/test_unix_events.py
++++ b/Lib/test/test_asyncio/test_unix_events.py
+@@ -1873,7 +1873,7 @@
+ wsock.close()
+
+
+-@unittest.skipUnless(hasattr(os, 'fork'), 'requires os.fork()')
++@support.requires_fork()
+ class TestFork(unittest.IsolatedAsyncioTestCase):
+
+ async def test_fork_not_share_event_loop(self):
+diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
+index f284e665d6f..90d9cdd45c0 100644
+--- a/Lib/test/test_capi/test_misc.py
++++ b/Lib/test/test_capi/test_misc.py
+@@ -1949,6 +1949,13 @@
+ self.addCleanup(os.close, r)
+ self.addCleanup(os.close, w)
-+AC_MSG_RESULT([$LDLIBRARY])
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if support.is_apple_mobile:
++ loader = "AppleFrameworkLoader"
++ else:
++ loader = "ExtensionFileLoader"
+
- if test "$cross_compiling" = yes; then
-- RUNSHARED=
-+ RUNSHARED=
- fi
+ script = textwrap.dedent(f"""
+ import importlib.machinery
+ import importlib.util
+@@ -1956,7 +1963,7 @@
- AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform])
-@@ -1617,8 +1988,6 @@
- PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD"
- fi
+ fullname = '_test_module_state_shared'
+ origin = importlib.util.find_spec('_testmultiphase').origin
+- loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
++ loader = importlib.machinery.{loader}(fullname, origin)
+ spec = importlib.util.spec_from_loader(fullname, loader)
+ module = importlib.util.module_from_spec(spec)
+ attr_id = str(id(module.Error)).encode()
+@@ -2130,7 +2137,12 @@
+ def setUp(self):
+ fullname = '_testmultiphase_meth_state_access' # XXX
+ origin = importlib.util.find_spec('_testmultiphase').origin
+- loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if support.is_apple_mobile:
++ loader = importlib.machinery.AppleFrameworkLoader(fullname, origin)
++ else:
++ loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
+ spec = importlib.util.spec_from_loader(fullname, loader)
+ module = importlib.util.module_from_spec(spec)
+ loader.exec_module(module)
+diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
+index 7109e3d164e..6f7e102c2ff 100644
+--- a/Lib/test/test_cmd_line_script.py
++++ b/Lib/test/test_cmd_line_script.py
+@@ -14,8 +14,7 @@
--AC_MSG_RESULT([$LDLIBRARY])
--
- # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable
- AS_CASE([$ac_sys_system/$ac_sys_emscripten_target],
- [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js'],
-@@ -3359,6 +3728,11 @@
- BLDSHARED="$LDSHARED"
- fi
- ;;
-+ iOS/*|tvOS/*|watchOS/*)
-+ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
-+ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
-+ BLDSHARED="$LDSHARED"
-+ ;;
- Emscripten*|WASI*)
- LDSHARED='$(CC) -shared'
- LDCXXSHARED='$(CXX) -shared';;
-@@ -3479,30 +3853,34 @@
- Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
- Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
- # -u libsys_s pulls in all symbols in libsys
-- Darwin/*)
-+ Darwin/*|iOS/*|tvOS/*|watchOS/*)
- LINKFORSHARED="$extra_undefs -framework CoreFoundation"
+ import textwrap
+ from test import support
+-from test.support import import_helper
+-from test.support import os_helper
++from test.support import import_helper, is_apple, os_helper
+ from test.support.script_helper import (
+ make_pkg, make_script, make_zip_pkg, make_zip_script,
+ assert_python_ok, assert_python_failure, spawn_python, kill_python)
+@@ -555,12 +554,17 @@
+ self.assertTrue(text[3].startswith('NameError'))
- # Issue #18075: the default maximum stack size (8MBytes) is too
- # small for the default recursion limit. Increase the stack size
- # to ensure that tests don't crash
-- stack_size="1000000" # 16 MB
-- if test "$with_ubsan" = "yes"
-- then
-- # Undefined behavior sanitizer requires an even deeper stack
-- stack_size="4000000" # 64 MB
-- fi
-+ stack_size="1000000" # 16 MB
-+ if test "$with_ubsan" = "yes"
-+ then
-+ # Undefined behavior sanitizer requires an even deeper stack
-+ stack_size="4000000" # 64 MB
-+ fi
+ def test_non_ascii(self):
+- # Mac OS X denies the creation of a file with an invalid UTF-8 name.
++ # Apple platforms deny the creation of a file with an invalid UTF-8 name.
+ # Windows allows creating a name with an arbitrary bytes name, but
+ # Python cannot a undecodable bytes argument to a subprocess.
+- # WASI does not permit invalid UTF-8 names.
+- if (os_helper.TESTFN_UNDECODABLE
+- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')):
++ # Emscripten/WASI does not permit invalid UTF-8 names.
++ if (
++ os_helper.TESTFN_UNDECODABLE
++ and sys.platform not in {
++ "win32", "emscripten", "wasi"
++ }
++ and not is_apple
++ ):
+ name = os.fsdecode(os_helper.TESTFN_UNDECODABLE)
+ elif os_helper.TESTFN_NONASCII:
+ name = os_helper.TESTFN_NONASCII
+diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py
+index 6e4a4b7caff..d1357ea7fcd 100644
+--- a/Lib/test/test_concurrent_futures/test_thread_pool.py
++++ b/Lib/test/test_concurrent_futures/test_thread_pool.py
+@@ -49,6 +49,7 @@
+ self.assertEqual(len(executor._threads), 1)
+ executor.shutdown(wait=True)
-- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
-+ AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE],
-+ [0x$stack_size],
-+ [Custom thread stack size depending on chosen sanitizer runtimes.])
++ @support.requires_fork()
+ @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork')
+ @support.requires_resource('cpu')
+ def test_hang_global_shutdown_lock(self):
+diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py
+index 203dd6fe57d..6d734d05245 100644
+--- a/Lib/test/test_fcntl.py
++++ b/Lib/test/test_fcntl.py
+@@ -6,7 +6,9 @@
+ import struct
+ import sys
+ import unittest
+-from test.support import verbose, cpython_only, get_pagesize
++from test.support import (
++ cpython_only, get_pagesize, is_apple, requires_subprocess, verbose
++)
+ from test.support.import_helper import import_module
+ from test.support.os_helper import TESTFN, unlink
-- AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE],
-- [0x$stack_size],
-- [Custom thread stack size depending on chosen sanitizer runtimes.])
-+ if test $ac_sys_system = "Darwin"; then
-+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
+@@ -56,8 +58,10 @@
+ else:
+ start_len = "qq"
-- if test "$enable_framework"
-- then
-- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
-+ if test "$enable_framework"; then
-+ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
-+ fi
-+ LINKFORSHARED="$LINKFORSHARED"
-+ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
-+ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
- fi
-- LINKFORSHARED="$LINKFORSHARED";;
-+ ;;
- OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";;
- SCO_SV*) LINKFORSHARED="-Wl,-Bexport";;
- ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";;
-@@ -3876,6 +4254,9 @@
- dnl when do we need USING_APPLE_OS_LIBFFI?
- ctypes_malloc_closure=yes
- ],
-+ [iOS|tvOS|watchOS], [
-+ ctypes_malloc_closure=yes
-+ ],
- [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])]
- )
- AS_VAR_IF([ctypes_malloc_closure], [yes], [
-@@ -4899,27 +5280,27 @@
- # checks for library functions
- AC_CHECK_FUNCS([ \
- accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \
-- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \
-+ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \
- faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \
-- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \
-- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \
-- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \
-+ fpathconf fstatat ftime ftruncate futimens futimes futimesat \
-+ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \
-+ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \
- getpeername getpgid getpid getppid getpriority _getpty \
- getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \
- getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \
- lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
- mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
-- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \
-+ pipe2 plock poll posix_fadvise posix_fallocate \
- pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \
- pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
- rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \
- sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \
- sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \
- setitimer setlocale setpgid setpgrp setpriority setregid setresgid \
-- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \
-+ setresuid setreuid setsid setuid setvbuf shutdown sigaction \
- sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \
- sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \
-- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
-+ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
- tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \
- wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \
- ])
-@@ -4931,6 +5312,22 @@
- AC_CHECK_FUNCS([lchmod])
- fi
+- if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd'))
+- or sys.platform == 'darwin'):
++ if (
++ sys.platform.startswith(('netbsd', 'freebsd', 'openbsd'))
++ or is_apple
++ ):
+ if struct.calcsize('l') == 8:
+ off_t = 'l'
+ pid_t = 'i'
+@@ -157,6 +161,7 @@
+ self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH)
+
+ @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
++ @requires_subprocess()
+ def test_lockf_exclusive(self):
+ self.f = open(TESTFN, 'wb+')
+ cmd = fcntl.LOCK_EX | fcntl.LOCK_NB
+@@ -169,6 +174,7 @@
+ self.assertEqual(p.exitcode, 0)
+
+ @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
++ @requires_subprocess()
+ def test_lockf_share(self):
+ self.f = open(TESTFN, 'wb+')
+ cmd = fcntl.LOCK_SH | fcntl.LOCK_NB
+diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
+index 204a77d14f0..c864d401f9e 100644
+--- a/Lib/test/test_ftplib.py
++++ b/Lib/test/test_ftplib.py
+@@ -18,6 +18,7 @@
+
+ from unittest import TestCase, skipUnless
+ from test import support
++from test.support import requires_subprocess
+ from test.support import threading_helper
+ from test.support import socket_helper
+ from test.support import warnings_helper
+@@ -903,6 +904,7 @@
+
+
+ @skipUnless(ssl, "SSL not available")
++@requires_subprocess()
+ class TestTLS_FTPClassMixin(TestFTPClass):
+ """Repeat TestFTPClass tests starting the TLS layer for both control
+ and data connections first.
+@@ -919,6 +921,7 @@
+
+
+ @skipUnless(ssl, "SSL not available")
++@requires_subprocess()
+ class TestTLS_FTPClass(TestCase):
+ """Specific TLS_FTP class tests."""
+
+diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
+index 81bb5bb288e..dddf5e8cd93 100644
+--- a/Lib/test/test_gc.py
++++ b/Lib/test/test_gc.py
+@@ -1188,6 +1188,7 @@
+ self.assertEqual(len(gc.garbage), 0)
-+# iOS/tvOS/watchOS define some system methods that can be linked (so they are
-+# found by configure), but either raise a compilation error (because the
-+# header definition prevents usage - autoconf doesn't use the headers), or
-+# raise an error if used at runtime. Force these symbols off.
-+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
-+ AC_CHECK_FUNCS([ getentropy getgroups system ])
-+fi
-+
-+# tvOS/watchOS have some additional methods that can be found, but not used.
-+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
-+ AC_CHECK_FUNCS([ \
-+ execv fork fork1 posix_spawn posix_spawnp \
-+ sigaltstack \
-+ ])
-+fi
-+
- AC_CHECK_DECL([dirfd],
- [AC_DEFINE([HAVE_DIRFD], [1],
- [Define if you have the 'dirfd' function or macro.])],
-@@ -5172,20 +5569,22 @@
- ])
- # check for openpty, login_tty, and forkpty
--
--AC_CHECK_FUNCS([openpty], [],
-- [AC_CHECK_LIB([util], [openpty],
-- [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"],
-- [AC_CHECK_LIB([bsd], [openpty],
-- [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])])
--AC_SEARCH_LIBS([login_tty], [util],
-- [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])]
--)
--AC_CHECK_FUNCS([forkpty], [],
-- [AC_CHECK_LIB([util], [forkpty],
-- [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"],
-- [AC_CHECK_LIB([bsd], [forkpty],
-- [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])])
-+# tvOS/watchOS have functions for tty, but can't use them
-+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
-+ AC_CHECK_FUNCS([openpty], [],
-+ [AC_CHECK_LIB([util], [openpty],
-+ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"],
-+ [AC_CHECK_LIB([bsd], [openpty],
-+ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])])
-+ AC_SEARCH_LIBS([login_tty], [util],
-+ [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])]
-+ )
-+ AC_CHECK_FUNCS([forkpty], [],
-+ [AC_CHECK_LIB([util], [forkpty],
-+ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"],
-+ [AC_CHECK_LIB([bsd], [forkpty],
-+ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])])
-+fi
++ @requires_subprocess()
+ @unittest.skipIf(BUILD_WITH_NDEBUG,
+ 'built with -NDEBUG')
+ def test_refcount_errors(self):
+diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
+index 3eefb722b81..3a92a65e10f 100644
+--- a/Lib/test/test_genericpath.py
++++ b/Lib/test/test_genericpath.py
+@@ -7,9 +7,9 @@
+ import sys
+ import unittest
+ import warnings
+-from test.support import is_emscripten
+-from test.support import os_helper
+-from test.support import warnings_helper
++from test.support import (
++ is_apple, is_emscripten, os_helper, warnings_helper
++)
+ from test.support.script_helper import assert_python_ok
+ from test.support.os_helper import FakePath
- # check for long file support functions
- AC_CHECK_FUNCS([fseek64 fseeko fstatvfs ftell64 ftello statvfs])
-@@ -5268,11 +5667,17 @@
- ])
- ])
+@@ -492,12 +492,16 @@
+ self.assertIsInstance(abspath(path), str)
--AC_CHECK_FUNCS([clock_settime], [], [
-- AC_CHECK_LIB([rt], [clock_settime], [
-- AC_DEFINE([HAVE_CLOCK_SETTIME], [1])
-- ])
--])
-+# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by
-+# configure), but when used in an unprivileged process, it crashes rather than
-+# returning an error. Force the symbol off.
-+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS"
-+then
-+ AC_CHECK_FUNCS([clock_settime], [], [
-+ AC_CHECK_LIB([rt], [clock_settime], [
-+ AC_DEFINE([HAVE_CLOCK_SETTIME], [1])
-+ ])
-+ ])
-+fi
+ def test_nonascii_abspath(self):
+- if (os_helper.TESTFN_UNDECODABLE
+- # macOS and Emscripten deny the creation of a directory with an
+- # invalid UTF-8 name. Windows allows creating a directory with an
+- # arbitrary bytes name, but fails to enter this directory
+- # (when the bytes name is used).
+- and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')):
++ if (
++ os_helper.TESTFN_UNDECODABLE
++ # Apple platforms and Emscripten/WASI deny the creation of a
++ # directory with an invalid UTF-8 name. Windows allows creating a
++ # directory with an arbitrary bytes name, but fails to enter this
++ # directory (when the bytes name is used).
++ and sys.platform not in {
++ "win32", "emscripten", "wasi"
++ } and not is_apple
++ ):
+ name = os_helper.TESTFN_UNDECODABLE
+ elif os_helper.TESTFN_NONASCII:
+ name = os_helper.TESTFN_NONASCII
+diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
+index 88d06fe04fb..1dc38dba3d3 100644
+--- a/Lib/test/test_httpservers.py
++++ b/Lib/test/test_httpservers.py
+@@ -31,8 +31,9 @@
- AC_CHECK_FUNCS([clock_nanosleep], [], [
- AC_CHECK_LIB([rt], [clock_nanosleep], [
-@@ -5418,7 +5823,9 @@
- [ac_cv_buggy_getaddrinfo=no],
- [ac_cv_buggy_getaddrinfo=yes],
- [
--if test "${enable_ipv6+set}" = set; then
-+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
-+ ac_cv_buggy_getaddrinfo="no"
-+elif test "${enable_ipv6+set}" = set; then
- ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
- else
- ac_cv_buggy_getaddrinfo=yes
-@@ -5971,14 +6378,14 @@
- AC_MSG_CHECKING([ABIFLAGS])
- AC_MSG_RESULT([$ABIFLAGS])
- AC_MSG_CHECKING([SOABI])
--SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
-+SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM}
- AC_MSG_RESULT([$SOABI])
+ import unittest
+ from test import support
+-from test.support import os_helper
+-from test.support import threading_helper
++from test.support import (
++ is_apple, os_helper, requires_subprocess, threading_helper
++)
- # Release and debug (Py_DEBUG) ABI are compatible, but not Py_TRACE_REFS ABI
- if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then
- # Similar to SOABI but remove "d" flag from ABIFLAGS
- AC_SUBST([ALT_SOABI])
-- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
-+ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM}
- AC_DEFINE_UNQUOTED([ALT_SOABI], ["${ALT_SOABI}"],
- [Alternative SOABI used in debug build to load C extensions built in release mode])
- fi
-@@ -6627,28 +7034,35 @@
- AC_MSG_NOTICE([checking for device files])
+ support.requires_working_socket(module=True)
- dnl NOTE: Inform user how to proceed with files when cross compiling.
--if test "x$cross_compiling" = xyes; then
-- if test "${ac_cv_file__dev_ptmx+set}" != set; then
-- AC_MSG_CHECKING([for /dev/ptmx])
-- AC_MSG_RESULT([not set])
-- AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling])
-- fi
-- if test "${ac_cv_file__dev_ptc+set}" != set; then
-- AC_MSG_CHECKING([for /dev/ptc])
-- AC_MSG_RESULT([not set])
-- AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling])
-+dnl iOS cross-compile builds are predictable; they won't ever
-+dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly.
-+if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then
-+ ac_cv_file__dev_ptmx=no
-+ ac_cv_file__dev_ptc=no
-+else
-+ if test "x$cross_compiling" = xyes; then
-+ if test "${ac_cv_file__dev_ptmx+set}" != set; then
-+ AC_MSG_CHECKING([for /dev/ptmx])
-+ AC_MSG_RESULT([not set])
-+ AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling])
-+ fi
-+ if test "${ac_cv_file__dev_ptc+set}" != set; then
-+ AC_MSG_CHECKING([for /dev/ptc])
-+ AC_MSG_RESULT([not set])
-+ AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling])
-+ fi
- fi
--fi
+@@ -411,8 +412,8 @@
+ reader.close()
+ return body
+
+- @unittest.skipIf(sys.platform == 'darwin',
+- 'undecodable name cannot always be decoded on macOS')
++ @unittest.skipIf(is_apple,
++ 'undecodable name cannot always be decoded on Apple platforms')
+ @unittest.skipIf(sys.platform == 'win32',
+ 'undecodable name cannot be decoded on win32')
+ @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE,
+@@ -423,11 +424,11 @@
+ with open(os.path.join(self.tempdir, filename), 'wb') as f:
+ f.write(os_helper.TESTFN_UNDECODABLE)
+ response = self.request(self.base_url + '/')
+- if sys.platform == 'darwin':
+- # On Mac OS the HFS+ filesystem replaces bytes that aren't valid
+- # UTF-8 into a percent-encoded value.
++ if is_apple:
++ # On Apple platforms the HFS+ filesystem replaces bytes that
++ # aren't valid UTF-8 into a percent-encoded value.
+ for name in os.listdir(self.tempdir):
+- if name != 'test': # Ignore a filename created in setUp().
++ if name != 'test': # Ignore a filename created in setUp().
+ filename = name
+ break
+ body = self.check_status_and_reason(response, HTTPStatus.OK)
+@@ -698,6 +699,7 @@
+
+ @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
+ "This test can't be run reliably as root (issue #13308).")
++@requires_subprocess()
+ class CGIHTTPServerTestCase(BaseTestCase):
+ class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
+ def run_cgi(self):
+diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
+index 1ac756f1b15..a0cb51afcba 100644
+--- a/Lib/test/test_import/__init__.py
++++ b/Lib/test/test_import/__init__.py
+@@ -5,7 +5,11 @@
+ import importlib.util
+ from importlib._bootstrap_external import _get_sourcefile
+ from importlib.machinery import (
+- BuiltinImporter, ExtensionFileLoader, FrozenImporter, SourceFileLoader,
++ AppleFrameworkLoader,
++ BuiltinImporter,
++ ExtensionFileLoader,
++ FrozenImporter,
++ SourceFileLoader,
+ )
+ import marshal
+ import os
+@@ -26,7 +30,7 @@
+
+ from test.support import os_helper
+ from test.support import (
+- STDLIB_DIR, swap_attr, swap_item, cpython_only, is_emscripten,
++ STDLIB_DIR, swap_attr, swap_item, cpython_only, is_apple_mobile, is_emscripten,
+ is_wasi, run_in_subinterp, run_in_subinterp_with_config)
+ from test.support.import_helper import (
+ forget, make_legacy_pyc, unlink, unload, ready_to_import,
+@@ -63,6 +67,7 @@
+ MODULE_KINDS = {
+ BuiltinImporter: 'built-in',
+ ExtensionFileLoader: 'extension',
++ AppleFrameworkLoader: 'framework extension',
+ FrozenImporter: 'frozen',
+ SourceFileLoader: 'pure Python',
+ }
+@@ -88,7 +93,12 @@
+ assert module.__spec__.origin == 'built-in', module.__spec__
--AC_CHECK_FILE([/dev/ptmx], [], [])
--if test "x$ac_cv_file__dev_ptmx" = xyes; then
-- AC_DEFINE([HAVE_DEV_PTMX], [1],
-- [Define to 1 if you have the /dev/ptmx device file.])
--fi
--AC_CHECK_FILE([/dev/ptc], [], [])
--if test "x$ac_cv_file__dev_ptc" = xyes; then
-- AC_DEFINE([HAVE_DEV_PTC], [1],
-- [Define to 1 if you have the /dev/ptc device file.])
-+ AC_CHECK_FILE([/dev/ptmx], [], [])
-+ if test "x$ac_cv_file__dev_ptmx" = xyes; then
-+ AC_DEFINE([HAVE_DEV_PTMX], [1],
-+ [Define to 1 if you have the /dev/ptmx device file.])
-+ fi
-+ AC_CHECK_FILE([/dev/ptc], [], [])
-+ if test "x$ac_cv_file__dev_ptc" = xyes; then
-+ AC_DEFINE([HAVE_DEV_PTC], [1],
-+ [Define to 1 if you have the /dev/ptc device file.])
-+ fi
- fi
+ def require_extension(module, *, skip=False):
+- _require_loader(module, ExtensionFileLoader, skip)
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if is_apple_mobile:
++ _require_loader(module, AppleFrameworkLoader, skip)
++ else:
++ _require_loader(module, ExtensionFileLoader, skip)
- if test $ac_sys_system = Darwin
-@@ -6920,6 +7334,7 @@
- AS_CASE([$ac_sys_system],
- [Emscripten], [with_ensurepip=no],
- [WASI], [with_ensurepip=no],
-+ [iOS|tvOS|watchOS], [with_ensurepip=no],
- [with_ensurepip=upgrade]
- )
- ])
-@@ -7262,6 +7677,28 @@
- [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])],
- [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])],
- [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])],
-+ [iOS|tvOS|watchOS], [
-+ dnl subprocess and multiprocessing are not supported (no fork syscall).
-+ dnl curses and tkinter user interface are not available.
-+ dnl gdbm and nis aren't available
-+ dnl Stub implementations are provided for pwd, grp etc APIs
-+ PY_STDLIB_MOD_SET_NA(
-+ [_curses],
-+ [_curses_panel],
-+ [_gdbm],
-+ [_multiprocessing],
-+ [_posixshmem],
-+ [_posixsubprocess],
-+ [_scproxy],
-+ [_tkinter],
-+ [grp],
-+ [nis],
-+ [readline],
-+ [pwd],
-+ [spwd],
-+ [syslog],
-+ )
-+ ],
- [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])],
- [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])],
- [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])],
---- /dev/null
-+++ b/iOS/README.rst
-@@ -0,0 +1,375 @@
-+====================
-+Python on iOS README
-+====================
-+
-+:Authors:
-+ Russell Keith-Magee (2023-11)
-+
-+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
-+`__ and `Kivy's Buildozer
-+`__ 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.
-+
-+iOS specific arguments to configure
-+===================================
-+
-+* ``--enable-framework[=DIR]``
-+
-+ This argument specifies the location where the Python.framework will be
-+ installed. If ``DIR`` is not specified, the framework will be installed into
-+ a subdirectory of the ``iOS/Frameworks`` folder.
-+
-+ This argument *must* be provided when configuring iOS builds. iOS does not
-+ support non-framework builds.
-+
-+* ``--with-framework-name=NAME``
-+
-+ Specify the name for the Python framework; defaults to ``Python``.
-+
-+ .. admonition:: Use this option with care!
-+
-+ 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 ``make testios`` target without making signficant manual
-+ alterations, and you won't be able to use any binary packages unless you
-+ compile them yourself using your own framework name.
-+
-+Building Python 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 single-architecture framework
-+----------------------------------------
-+
-+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)/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
-+ $ ./configure \
-+ --enable-framework \
-+ --host=arm64-apple-ios-simulator \
-+ --build=arm64-apple-darwin \
-+ --with-build-python=/path/to/python.exe
-+ $ make
-+ $ make install
-+
-+In this invocation:
-+
-+* ``iOS/Resources/bin`` has been added to the path, providing some shims for the
-+ compilers and linkers needed by the build. Xcode requires the use of ``xcrun``
-+ to invoke compiler tooling. However, if ``xcrun`` is pre-evaluated and the
-+ result passed to ``configure``, these results can embed user- and
-+ version-specific paths into the sysconfig data, which limits the portability
-+ of the compiled Python. Alternatively, if ``xcrun`` is used *as* the compiler,
-+ it requires that compiler variables like ``CC`` include spaces, which can
-+ cause significant problems with many C configuration systems which assume that
-+ ``CC`` will be a single executable.
-+
-+ To work around this problem, the ``iOS/Resources/bin`` folder contains some
-+ wrapper scripts that present as simple compilers and linkers, but wrap
-+ underlying calls to ``xcrun``. This allows configure to use a ``CC``
-+ definition without spaces, and without user- or version-specific paths, while
-+ retaining the ability to adapt to the local Xcode install. These scripts are
-+ included in the ``bin`` directory of an iOS install.
-+
-+ These scripts will, by default, use the currently active Xcode installation.
-+ If you want to use a different Xcode installation, you can use
-+ ``xcode-select`` to set a new default Xcode globally, or you can use the
-+ ``DEVELOPER_DIR`` environment variable to specify an Xcode install. The
-+ scripts will use the default ``iphoneos``/``iphonesimulator`` SDK version for
-+ the select Xcode install; if you want to use a different SDK, you can set the
-+ ``IOS_SDK_VERSION`` environment variable. (e.g, setting
-+ ``IOS_SDK_VERSION=17.1`` would cause the scripts to use the ``iphoneos17.1``
-+ and ``iphonesimulator17.1`` SDKs, regardless of the Xcode default.)
-+
-+ The path has also been cleared of any user customizations. A common source of
-+ bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS
-+ build. Resetting the path to a known "bare bones" value is the easiest way to
-+ avoid these problems.
-+
-+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
-+ triple format. This will be one of:
-+
-+ - ``arm64-apple-ios`` for ARM64 iOS devices.
-+ - ``arm64-apple-ios-simulator`` for the iOS simulator running on Apple
-+ Silicon devices.
-+ - ``x86_64-apple-ios-simulator`` for the iOS simulator running on Intel
-+ devices.
-+
-+* ``--build`` is the GNU compiler triple for the machine that will be running
-+ the compiler. This is one of:
-+
-+ - ``arm64-apple-darwin`` for Apple Silicon devices.
-+ - ``x86_64-apple-darwin`` for Intel devices.
-+
-+* ``/path/to/python.exe`` is the path to a Python binary on the machine that
-+ will be running the compiler. This is needed because the Python compilation
-+ process involves running some Python code. On a normal desktop build of
-+ Python, you can compile a python interpreter and then use that interpreter to
-+ run Python code. However, the binaries produced for iOS won't run on macOS, so
-+ you need to provide an external Python interpreter. This interpreter must be
-+ the same version as the Python that is being compiled. To be completely safe,
-+ this should be the *exact* same commit hash. However, the longer a Python
-+ release has been stable, the more likely it is that this constraint can be
-+ relaxed - the same micro version will often be sufficient.
-+
-+* The ``install`` target for iOS builds is slightly different to other
-+ platforms. On most platforms, ``make install`` will install the build into
-+ the final runtime location. This won't be the case for iOS, as the final
-+ runtime location will be on a physical device.
-+
-+ However, you still need to run the ``install`` target for iOS builds, as it
-+ performs some final framework assembly steps. The location specified with
-+ ``--enable-framework`` will be the location where ``make install`` will
-+ assemble the complete iOS framework. This completed framework can then
-+ be copied and relocated as required.
-+
-+For a full CPython build, you also need to specify the paths to iOS builds of
-+the binary libraries that CPython depends on (XZ, BZip2, LibFFI and OpenSSL).
-+This can be done by defining the ``LIBLZMA_CFLAGS``, ``LIBLZMA_LIBS``,
-+``BZIP2_CFLAGS``, ``BZIP2_LIBS``, ``LIBFFI_CFLAGS``, and ``LIBFFI_LIBS``
-+environment variables, and the ``--with-openssl`` configure option. Versions of
-+these libraries pre-compiled for iOS can be found in `this repository
-+`__. 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.
-+
-+Merge thin frameworks into fat frameworks
-+-----------------------------------------
-+
-+Once you've built a ``Python.framework`` for each ABI and and architecture, you
-+must produce a "fat" framework for each ABI that contains all the architectures
-+for that ABI.
-+
-+The ``iphoneos`` build only needs to support a single architecture, so it can be
-+used without modification.
-+
-+If you only want to support a single simulator architecture, (e.g., only support
-+ARM64 simulators), you can use a single architecture ``Python.framework`` build.
-+However, if you want to create ``Python.xcframework`` that supports *all*
-+architectures, you'll need to merge the ``iphonesimulator`` builds for ARM64 and
-+x86_64 into a single "fat" framework.
-+
-+The "fat" framework can be constructed by performing a directory merge of the
-+content of the two "thin" ``Python.framework`` directories, plus the ``bin`` and
-+``lib`` folders for each thin framework. When performing this merge:
-+
-+* The pure Python standard library content is identical for each architecture,
-+ except for a handful of platform-specific files (such as the ``sysconfig``
-+ module). Ensure that the "fat" framework has the union of all standard library
-+ files.
-+
-+* Any binary files in the standard library, plus the main
-+ ``libPython3.X.dylib``, can be merged using the ``lipo`` tool, provide by
-+ Xcode::
-+
-+ $ lipo -create -output module.dylib path/to/x86_64/module.dylib path/to/arm64/module.dylib
-+
-+* The header files will be indentical on both architectures, except for
-+ ``pyconfig.h``. Copy all the headers from one platform (say, arm64), rename
-+ ``pyconfig.h`` to ``pyconfig-arm64.h``, and copy the ``pyconfig.h`` for the
-+ other architecture into the merged header folder as ``pyconfig-x86_64.h``.
-+ Then copy the ``iOS/Resources/pyconfig.h`` file from the CPython sources into
-+ the merged headers folder. This will allow the two Python architectures to
-+ share a common ``pyconfig.h`` header file.
-+
-+At this point, you should have 2 Python.framework folders - one for ``iphoneos``,
-+and one for ``iphonesimulator`` that is a merge of x86+64 and ARM64 content.
-+
-+Merge frameworks into an XCframework
-+------------------------------------
-+
-+Now that we have 2 (potentially fat) ABI-specific frameworks, we can merge those
-+frameworks into a single ``XCframework``.
-+
-+The initial skeleton of an ``XCframework`` is built using::
-+
-+ xcodebuild -create-xcframework -output Python.xcframework -framework path/to/iphoneos/Python.framework -framework path/to/iphonesimulator/Python.framework
-+
-+Then, copy the ``bin`` and ``lib`` folders into the architecture-specific slices of
-+the XCframework::
-+
-+ cp path/to/iphoneos/bin Python.xcframework/ios-arm64
-+ cp path/to/iphoneos/lib Python.xcframework/ios-arm64
-+
-+ cp path/to/iphonesimulator/bin Python.xcframework/ios-arm64_x86_64-simulator
-+ cp path/to/iphonesimulator/lib Python.xcframework/ios-arm64_x86_64-simulator
-+
-+Note that the name of the architecture-specific slice for the simulator will
-+depend on the CPU architecture(s) that you build.
-+
-+You now have a Python.xcframework that can be used in a project.
-+
-+Testing Python on iOS
-+=====================
-+
-+The ``iOS/testbed`` folder that contains an Xcode project that is able to run
-+the iOS test suite. This project converts the Python test suite into a single
-+test case in Xcode's XCTest framework. The single XCTest passes if the test
-+suite passes.
-+
-+To run the test suite, configure a Python build for an iOS simulator (i.e.,
-+``--host=arm64-apple-ios-simulator`` or ``--host=x86_64-apple-ios-simulator``
-+), specifying a framework build (i.e. ``--enable-framework``). Ensure that your
-+``PATH`` has been configured to include the ``iOS/Resources/bin`` folder and
-+exclude any non-iOS tools, then run::
-+
-+ $ make all
-+ $ make install
-+ $ make testios
-+
-+This will:
-+
-+* Build an iOS framework for your chosen architecture;
-+* Finalize the single-platform framework;
-+* Make a clean copy of the testbed project;
-+* Install the Python iOS framework into the copy of the testbed project; and
-+* Run the test suite on an "iPhone SE (3rd generation)" simulator.
+ def require_frozen(module, *, skip=True):
+ module = _require_loader(module, FrozenImporter, skip)
+@@ -131,7 +141,8 @@
+ # it to its nominal state.
+ sys.modules.pop('_testsinglephase', None)
+ _orig._clear_globals()
+- _testinternalcapi.clear_extension('_testsinglephase', _orig.__file__)
++ origin = _orig.__spec__.origin
++ _testinternalcapi.clear_extension('_testsinglephase', origin)
+ import _testsinglephase
+
+
+@@ -354,10 +365,14 @@
+ from _testcapi import i_dont_exist
+ self.assertEqual(cm.exception.name, '_testcapi')
+ if hasattr(_testcapi, "__file__"):
+- self.assertEqual(cm.exception.path, _testcapi.__file__)
++ # The path on the exception is strictly the spec origin, not the
++ # module's __file__. For most cases, these are the same; but on
++ # iOS, the Framework relocation process results in the exception
++ # being raised from the spec location.
++ self.assertEqual(cm.exception.path, _testcapi.__spec__.origin)
+ self.assertRegex(
+ str(cm.exception),
+- r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|pyd)\)"
++ r"cannot import name 'i_dont_exist' from '_testcapi' \(.*(\.(so|pyd))?\)"
+ )
+ else:
+ self.assertEqual(
+@@ -1719,6 +1734,14 @@
+ os.set_blocking(r, False)
+ return (r, w)
+
++ def create_extension_loader(self, modname, filename):
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if is_apple_mobile:
++ return AppleFrameworkLoader(modname, filename)
++ else:
++ return ExtensionFileLoader(modname, filename)
+
-+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.
+ def import_script(self, name, fd, filename=None, check_override=None):
+ override_text = ''
+ if check_override is not None:
+@@ -1727,12 +1750,19 @@
+ _imp._override_multi_interp_extensions_check({check_override})
+ '''
+ if filename:
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if is_apple_mobile:
++ loader = "AppleFrameworkLoader"
++ else:
++ loader = "ExtensionFileLoader"
+
-+Debugging test failures
-+-----------------------
+ return textwrap.dedent(f'''
+ from importlib.util import spec_from_loader, module_from_spec
+- from importlib.machinery import ExtensionFileLoader
++ from importlib.machinery import {loader}
+ import os, sys
+ {override_text}
+- loader = ExtensionFileLoader({name!r}, {filename!r})
++ loader = {loader}({name!r}, {filename!r})
+ spec = spec_from_loader({name!r}, loader)
+ try:
+ module = module_from_spec(spec)
+@@ -1916,7 +1946,7 @@
+ # and Py_MOD_GIL_NOT_USED
+ modname = '_test_non_isolated'
+ filename = _testmultiphase.__file__
+- loader = ExtensionFileLoader(modname, filename)
++ loader = self.create_extension_loader(modname, filename)
+ spec = importlib.util.spec_from_loader(modname, loader)
+ module = importlib.util.module_from_spec(spec)
+ loader.exec_module(module)
+@@ -1932,33 +1962,31 @@
+
+ @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
+ def test_multi_init_extension_per_interpreter_gil_compat(self):
+- # _test_shared_gil_only:
+- # Explicit Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED (default)
+- # and Py_MOD_GIL_NOT_USED
+- # _test_no_multiple_interpreter_slot:
+- # No Py_mod_multiple_interpreters slot
+- # and Py_MOD_GIL_NOT_USED
+- for modname in ('_test_shared_gil_only',
+- '_test_no_multiple_interpreter_slot'):
+- with self.subTest(modname=modname):
+-
+- filename = _testmultiphase.__file__
+- loader = ExtensionFileLoader(modname, filename)
+- spec = importlib.util.spec_from_loader(modname, loader)
+- module = importlib.util.module_from_spec(spec)
+- loader.exec_module(module)
+- sys.modules[modname] = module
+-
+- require_extension(module)
+- with self.subTest(f'{modname}: isolated, strict'):
+- self.check_incompatible_here(modname, filename,
+- isolated=True)
+- with self.subTest(f'{modname}: not isolated, strict'):
+- self.check_compatible_here(modname, filename,
+- strict=True, isolated=False)
+- with self.subTest(f'{modname}: not isolated, not strict'):
+- self.check_compatible_here(
+- modname, filename, strict=False, isolated=False)
++ modname = '_test_shared_gil_only'
++ filename = _testmultiphase.__file__
++ loader = self.create_extension_loader(modname, filename)
++ spec = importlib.util.spec_from_loader(modname, loader)
++ module = importlib.util.module_from_spec(spec)
++ loader.exec_module(module)
++ sys.modules[modname] = module
++
++ filename = _testmultiphase.__file__
++ loader = ExtensionFileLoader(modname, filename)
++ spec = importlib.util.spec_from_loader(modname, loader)
++ module = importlib.util.module_from_spec(spec)
++ loader.exec_module(module)
++ sys.modules[modname] = module
++
++ require_extension(module)
++ with self.subTest(f'{modname}: isolated, strict'):
++ self.check_incompatible_here(modname, filename,
++ isolated=True)
++ with self.subTest(f'{modname}: not isolated, strict'):
++ self.check_compatible_here(modname, filename,
++ strict=True, isolated=False)
++ with self.subTest(f'{modname}: not isolated, not strict'):
++ self.check_compatible_here(
++ modname, filename, strict=False, isolated=False)
+
+ def test_python_compat(self):
+ module = 'threading'
+@@ -2074,10 +2102,25 @@
+ @classmethod
+ def setUpClass(cls):
+ spec = importlib.util.find_spec(cls.NAME)
+- from importlib.machinery import ExtensionFileLoader
+- cls.FILE = spec.origin
+ cls.LOADER = type(spec.loader)
+- assert cls.LOADER is ExtensionFileLoader
+
-+Running ``make test`` generates a standalone version of the ``iOS/testbed``
-+project, and runs the full test suite. It does this using ``iOS/testbed``
-+itself - the folder is an executable module that can be used to create and run
-+a clone of the testbed project.
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader, and we need to differentiate between the
++ # spec.origin and the original file location.
++ if is_apple_mobile:
++ assert cls.LOADER is AppleFrameworkLoader
+
-+You can generate your own standalone testbed instance by running::
++ cls.ORIGIN = spec.origin
++ with open(spec.origin + ".origin", "r") as f:
++ cls.FILE = os.path.join(
++ os.path.dirname(sys.executable),
++ f.read().strip()
++ )
++ else:
++ assert cls.LOADER is ExtensionFileLoader
+
-+ $ python iOS/testbed clone --framework iOS/Frameworks/arm64-iphonesimulator my-testbed
++ cls.ORIGIN = spec.origin
++ cls.FILE = spec.origin
+
+ # Start fresh.
+ cls.clean_up()
+@@ -2093,14 +2136,15 @@
+ @classmethod
+ def clean_up(cls):
+ name = cls.NAME
+- filename = cls.FILE
+ if name in sys.modules:
+ if hasattr(sys.modules[name], '_clear_globals'):
+- assert sys.modules[name].__file__ == filename
++ assert sys.modules[name].__file__ == cls.FILE, \
++ f"{sys.modules[name].__file__} != {cls.FILE}"
+
-+This invocation assumes that ``iOS/Frameworks/arm64-iphonesimulator`` is the
-+path to the iOS simulator framework for your platform (ARM64 in this case);
-+``my-testbed`` is the name of the folder for the new testbed clone.
+ sys.modules[name]._clear_globals()
+ del sys.modules[name]
+ # Clear all internally cached data for the extension.
+- _testinternalcapi.clear_extension(name, filename)
++ _testinternalcapi.clear_extension(name, cls.ORIGIN)
+
+ #########################
+ # helpers
+@@ -2108,7 +2152,7 @@
+ def add_module_cleanup(self, name):
+ def clean_up():
+ # Clear all internally cached data for the extension.
+- _testinternalcapi.clear_extension(name, self.FILE)
++ _testinternalcapi.clear_extension(name, self.ORIGIN)
+ self.addCleanup(clean_up)
+
+ def _load_dynamic(self, name, path):
+@@ -2131,7 +2175,7 @@
+ except AttributeError:
+ already_loaded = self.already_loaded = {}
+ assert name not in already_loaded
+- mod = self._load_dynamic(name, self.FILE)
++ mod = self._load_dynamic(name, self.ORIGIN)
+ self.assertNotIn(mod, already_loaded.values())
+ already_loaded[name] = mod
+ return types.SimpleNamespace(
+@@ -2143,7 +2187,7 @@
+ def re_load(self, name, mod):
+ assert sys.modules[name] is mod
+ assert mod.__dict__ == mod.__dict__
+- reloaded = self._load_dynamic(name, self.FILE)
++ reloaded = self._load_dynamic(name, self.ORIGIN)
+ return types.SimpleNamespace(
+ name=name,
+ module=reloaded,
+@@ -2163,7 +2207,7 @@
+ name = {self.NAME!r}
+ if name in sys.modules:
+ sys.modules.pop(name)._clear_globals()
+- _testinternalcapi.clear_extension(name, {self.FILE!r})
++ _testinternalcapi.clear_extension(name, {self.ORIGIN!r})
+ '''))
+ _interpreters.destroy(interpid)
+ self.addCleanup(clean_up)
+@@ -2180,7 +2224,7 @@
+ postcleanup = f'''
+ {import_}
+ mod._clear_globals()
+- _testinternalcapi.clear_extension(name, {self.FILE!r})
++ _testinternalcapi.clear_extension(name, {self.ORIGIN!r})
+ '''
+
+ try:
+@@ -2218,7 +2262,7 @@
+ # mod.__name__ might not match, but the spec will.
+ self.assertEqual(mod.__spec__.name, loaded.name)
+ self.assertEqual(mod.__file__, self.FILE)
+- self.assertEqual(mod.__spec__.origin, self.FILE)
++ self.assertEqual(mod.__spec__.origin, self.ORIGIN)
+ if not isolated:
+ self.assertTrue(issubclass(mod.error, Exception))
+ self.assertEqual(mod.int_const, 1969)
+@@ -2612,7 +2656,7 @@
+ # First, load in the main interpreter but then completely clear it.
+ loaded_main = self.load(self.NAME)
+ loaded_main.module._clear_globals()
+- _testinternalcapi.clear_extension(self.NAME, self.FILE)
++ _testinternalcapi.clear_extension(self.NAME, self.ORIGIN)
+
+ # At this point:
+ # * alive in 0 interpreters
+diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py
+index 3de120958fd..cdc8884d668 100644
+--- a/Lib/test/test_importlib/extension/test_finder.py
++++ b/Lib/test/test_importlib/extension/test_finder.py
+@@ -1,3 +1,4 @@
++from test.support import is_apple_mobile
+ from test.test_importlib import abc, util
+
+ machinery = util.import_importlib('importlib.machinery')
+@@ -19,9 +20,27 @@
+ )
+
+ def find_spec(self, fullname):
+- importer = self.machinery.FileFinder(util.EXTENSIONS.path,
+- (self.machinery.ExtensionFileLoader,
+- self.machinery.EXTENSION_SUFFIXES))
++ if is_apple_mobile:
++ # Apple mobile platforms require a specialist loader that uses
++ # .fwork files as placeholders for the true `.so` files.
++ loaders = [
++ (
++ self.machinery.AppleFrameworkLoader,
++ [
++ ext.replace(".so", ".fwork")
++ for ext in self.machinery.EXTENSION_SUFFIXES
++ ]
++ )
++ ]
++ else:
++ loaders = [
++ (
++ self.machinery.ExtensionFileLoader,
++ self.machinery.EXTENSION_SUFFIXES
++ )
++ ]
+
-+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::
++ importer = self.machinery.FileFinder(util.EXTENSIONS.path, *loaders)
+
+ return importer.find_spec(fullname)
+
+diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py
+index 12f9e43d123..7e8c9c8184a 100644
+--- a/Lib/test/test_importlib/extension/test_loader.py
++++ b/Lib/test/test_importlib/extension/test_loader.py
+@@ -1,3 +1,4 @@
++from test.support import is_apple_mobile
+ from warnings import catch_warnings
+ from test.test_importlib import abc, util
+
+@@ -25,8 +26,15 @@
+ raise unittest.SkipTest(
+ f"{util.EXTENSIONS.name} is a builtin module"
+ )
+- self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
+- util.EXTENSIONS.file_path)
+
-+ $ python my-testbed run -- test -W test_os
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if is_apple_mobile:
++ self.LoaderClass = self.machinery.AppleFrameworkLoader
++ else:
++ self.LoaderClass = self.machinery.ExtensionFileLoader
+
-+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.
++ self.loader = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
+
+ def load_module(self, fullname):
+ with warnings.catch_warnings():
+@@ -34,13 +42,11 @@
+ return self.loader.load_module(fullname)
+
+ def test_equality(self):
+- other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
+- util.EXTENSIONS.file_path)
++ other = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
+ self.assertEqual(self.loader, other)
+
+ def test_inequality(self):
+- other = self.machinery.ExtensionFileLoader('_' + util.EXTENSIONS.name,
+- util.EXTENSIONS.file_path)
++ other = self.LoaderClass('_' + util.EXTENSIONS.name, util.EXTENSIONS.file_path)
+ self.assertNotEqual(self.loader, other)
+
+ def test_load_module_API(self):
+@@ -60,8 +66,7 @@
+ ('__package__', '')]:
+ self.assertEqual(getattr(module, attr), value)
+ self.assertIn(util.EXTENSIONS.name, sys.modules)
+- self.assertIsInstance(module.__loader__,
+- self.machinery.ExtensionFileLoader)
++ self.assertIsInstance(module.__loader__, self.LoaderClass)
+
+ # No extension module as __init__ available for testing.
+ test_package = None
+@@ -88,7 +93,7 @@
+ self.assertFalse(self.loader.is_package(util.EXTENSIONS.name))
+ for suffix in self.machinery.EXTENSION_SUFFIXES:
+ path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
+- loader = self.machinery.ExtensionFileLoader('pkg', path)
++ loader = self.LoaderClass('pkg', path)
+ self.assertTrue(loader.is_package('pkg'))
+
+
+@@ -103,6 +108,14 @@
+ def setUp(self):
+ if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
+ raise unittest.SkipTest("Requires dynamic loading support.")
+
-+You can also open the testbed project in Xcode by running::
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if is_apple_mobile:
++ self.LoaderClass = self.machinery.AppleFrameworkLoader
++ else:
++ self.LoaderClass = self.machinery.ExtensionFileLoader
+
-+ $ open my-testbed/iOSTestbed.xcodeproj
+ self.name = '_testsinglephase'
+ if self.name in sys.builtin_module_names:
+ raise unittest.SkipTest(
+@@ -111,8 +124,8 @@
+ finder = self.machinery.FileFinder(None)
+ self.spec = importlib.util.find_spec(self.name)
+ assert self.spec
+- self.loader = self.machinery.ExtensionFileLoader(
+- self.name, self.spec.origin)
+
-+This will allow you to use the full Xcode suite of tools for debugging.
++ self.loader = self.LoaderClass(self.name, self.spec.origin)
+
+ def load_module(self):
+ with warnings.catch_warnings():
+@@ -122,7 +135,7 @@
+ def load_module_by_name(self, fullname):
+ # Load a module from the test extension by name.
+ origin = self.spec.origin
+- loader = self.machinery.ExtensionFileLoader(fullname, origin)
++ loader = self.LoaderClass(fullname, origin)
+ spec = importlib.util.spec_from_loader(fullname, loader)
+ module = importlib.util.module_from_spec(spec)
+ loader.exec_module(module)
+@@ -139,8 +152,7 @@
+ with self.assertRaises(AttributeError):
+ module.__path__
+ self.assertIs(module, sys.modules[self.name])
+- self.assertIsInstance(module.__loader__,
+- self.machinery.ExtensionFileLoader)
++ self.assertIsInstance(module.__loader__, self.LoaderClass)
+
+ # No extension module as __init__ available for testing.
+ test_package = None
+@@ -184,6 +196,14 @@
+ def setUp(self):
+ if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
+ raise unittest.SkipTest("Requires dynamic loading support.")
+
-+Testing on an iOS device
-+^^^^^^^^^^^^^^^^^^^^^^^^
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if is_apple_mobile:
++ self.LoaderClass = self.machinery.AppleFrameworkLoader
++ else:
++ self.LoaderClass = self.machinery.ExtensionFileLoader
+
-+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).
+ self.name = '_testmultiphase'
+ if self.name in sys.builtin_module_names:
+ raise unittest.SkipTest(
+@@ -192,8 +212,7 @@
+ finder = self.machinery.FileFinder(None)
+ self.spec = importlib.util.find_spec(self.name)
+ assert self.spec
+- self.loader = self.machinery.ExtensionFileLoader(
+- self.name, self.spec.origin)
++ self.loader = self.LoaderClass(self.name, self.spec.origin)
+
+ def load_module(self):
+ # Load the module from the test extension.
+@@ -204,7 +223,7 @@
+ def load_module_by_name(self, fullname):
+ # Load a module from the test extension by name.
+ origin = self.spec.origin
+- loader = self.machinery.ExtensionFileLoader(fullname, origin)
++ loader = self.LoaderClass(fullname, origin)
+ spec = importlib.util.spec_from_loader(fullname, loader)
+ module = importlib.util.module_from_spec(spec)
+ loader.exec_module(module)
+@@ -230,8 +249,7 @@
+ with self.assertRaises(AttributeError):
+ module.__path__
+ self.assertIs(module, sys.modules[self.name])
+- self.assertIsInstance(module.__loader__,
+- self.machinery.ExtensionFileLoader)
++ self.assertIsInstance(module.__loader__, self.LoaderClass)
+
+ def test_functionality(self):
+ # Test basic functionality of stuff defined in an extension module.
+diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
+index 553e2087421..e23949aaa22 100644
+--- a/Lib/test/test_importlib/test_util.py
++++ b/Lib/test/test_importlib/test_util.py
+@@ -703,13 +703,20 @@
+
+ @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
+ def test_incomplete_multi_phase_init_module(self):
++ # Apple extensions must be distributed as frameworks. This requires
++ # a specialist loader.
++ if support.is_apple_mobile:
++ loader = "AppleFrameworkLoader"
++ else:
++ loader = "ExtensionFileLoader"
+
-+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.
+ prescript = textwrap.dedent(f'''
+ from importlib.util import spec_from_loader, module_from_spec
+- from importlib.machinery import ExtensionFileLoader
++ from importlib.machinery import {loader}
+
+ name = '_test_shared_gil_only'
+ filename = {_testmultiphase.__file__!r}
+- loader = ExtensionFileLoader(name, filename)
++ loader = {loader}(name, filename)
+ spec = spec_from_loader(name, loader)
+
+ ''')
+diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py
+index a900cc1dddf..89272484009 100644
+--- a/Lib/test/test_importlib/util.py
++++ b/Lib/test/test_importlib/util.py
+@@ -8,6 +8,7 @@
+ import os.path
+ from test import support
+ from test.support import import_helper
++from test.support import is_apple_mobile
+ from test.support import os_helper
+ import unittest
+ import sys
+@@ -43,6 +44,11 @@
+ global EXTENSIONS
+ for path in sys.path:
+ for ext in machinery.EXTENSION_SUFFIXES:
++ # Apple mobile platforms mechanically load .so files,
++ # but the findable files are labelled .fwork
++ if is_apple_mobile:
++ ext = ext.replace(".so", ".fwork")
+
-+Running specific tests
-+^^^^^^^^^^^^^^^^^^^^^^
-+
-+As the test suite is being executed on an iOS simulator, it is not possible to
-+pass in command line arguments to configure test suite operation. To work
-+around this limitation, the arguments that would normally be passed as command
-+line arguments are configured as part of the ``iOSTestbed-Info.plist`` file
-+that is used to configure the iOS testbed app. In this file, the ``TestArgs``
-+key is an array containing the arguments that would be passed to ``python -m``
-+on the command line (including ``test`` in position 0, the name of the test
-+module to be executed).
-+
-+Disabling automated breakpoints
-+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-+
-+By default, Xcode will inserts an automatic breakpoint whenever a signal is
-+raised. The Python test suite raises many of these signals as part of normal
-+operation; unless you are trying to diagnose an issue with signals, the
-+automatic breakpoints can be inconvenient. However, they can be disabled by
-+creating a symbolic breakpoint that is triggered at the start of the test run.
-+
-+Select "Debug > Breakpoints > Create Symbolic Breakpoint" from the Xcode menu, and
-+populate the new brewpoint with the following details:
-+
-+* **Name**: IgnoreSignals
-+* **Symbol**: UIApplicationMain
-+* **Action**: Add debugger commands for:
-+ - ``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``
-+* Check the "Automatically continue after evaluating" box.
-+
-+All other details can be left blank. When the process executes the
-+``UIApplicationMain`` entry point, the breakpoint will trigger, run the debugger
-+commands to disable the automatic breakpoints, and automatically resume.
---- /dev/null
-+++ b/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@
-+
-+
---- /dev/null
-+++ b/iOS/Resources/bin/arm64-apple-ios-ar
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/iOS/Resources/bin/arm64-apple-ios-clang
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios "$@"
---- /dev/null
-+++ b/iOS/Resources/bin/arm64-apple-ios-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios "$@"
---- /dev/null
-+++ b/iOS/Resources/bin/arm64-apple-ios-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E "$@"
---- /dev/null
-+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/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-simulator "$@"
---- /dev/null
-+++ b/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-simulator "$@"
---- /dev/null
-+++ b/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-simulator -E "$@"
---- /dev/null
-+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar
-@@ -0,0 +1,2 @@
-+#!/bin/sh
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/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-simulator "$@"
---- /dev/null
-+++ b/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-simulator "$@"
---- /dev/null
-+++ b/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-simulator -E "$@"
---- /dev/null
-+++ b/iOS/Resources/dylib-Info-template.plist
-@@ -0,0 +1,26 @@
-+
-+
-+
-+
-+ CFBundleDevelopmentRegion
-+ en
-+ CFBundleExecutable
-+
-+ CFBundleIdentifier
-+
-+ CFBundleInfoDictionaryVersion
-+ 6.0
-+ CFBundlePackageType
-+ APPL
-+ CFBundleShortVersionString
-+ 1.0
-+ CFBundleSupportedPlatforms
-+
-+ iPhoneOS
-+
-+ MinimumOSVersion
-+ 12.0
-+ CFBundleVersion
-+ 1
-+
-+
---- /dev/null
-+++ b/iOS/Resources/pyconfig.h
-@@ -0,0 +1,7 @@
-+#ifdef __arm64__
-+#include "pyconfig-arm64.h"
-+#endif
+ filename = EXTENSIONS.name + ext
+ file_path = os.path.join(path, filename)
+ if os.path.exists(file_path):
+diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
+index 0cd9e721b20..ffa58230425 100644
+--- a/Lib/test/test_interpreters.py
++++ b/Lib/test/test_interpreters.py
+@@ -752,6 +752,7 @@
+
+ class FinalizationTests(TestBase):
+
++ @support.requires_subprocess()
+ def test_gh_109793(self):
+ import subprocess
+ argv = [sys.executable, '-c', '''if True:
+diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
+index d85040a3083..9d634acfd3e 100644
+--- a/Lib/test/test_io.py
++++ b/Lib/test/test_io.py
+@@ -39,11 +39,9 @@
+ from test import support
+ from test.support.script_helper import (
+ assert_python_ok, assert_python_failure, run_python_until_end)
+-from test.support import import_helper
+-from test.support import os_helper
+-from test.support import threading_helper
+-from test.support import warnings_helper
+-from test.support import skip_if_sanitizer
++from test.support import (
++ import_helper, is_apple, os_helper, skip_if_sanitizer, threading_helper, warnings_helper
++)
+ from test.support.os_helper import FakePath
+
+ import codecs
+@@ -631,10 +629,10 @@
+ self.read_ops(f, True)
+
+ def test_large_file_ops(self):
+- # On Windows and Mac OSX this test consumes large resources; It takes
+- # a long time to build the >2 GiB file and takes >2 GiB of disk space
+- # therefore the resource must be enabled to run this test.
+- if sys.platform[:3] == 'win' or sys.platform == 'darwin':
++ # On Windows and Apple platforms this test consumes large resources; It
++ # takes a long time to build the >2 GiB file and takes >2 GiB of disk
++ # space therefore the resource must be enabled to run this test.
++ if sys.platform[:3] == 'win' or is_apple:
+ support.requires(
+ 'largefile',
+ 'test requires %s bytes and a long time to run' % self.LARGE)
+diff --git a/Lib/test/test_lib2to3/test_parser.py b/Lib/test/test_lib2to3/test_parser.py
+index 2c798b181fd..e12ed1e9389 100644
+--- a/Lib/test/test_lib2to3/test_parser.py
++++ b/Lib/test/test_lib2to3/test_parser.py
+@@ -62,9 +62,7 @@
+ shutil.rmtree(tmpdir)
+
+ @unittest.skipIf(sys.executable is None, 'sys.executable required')
+- @unittest.skipIf(
+- sys.platform in {'emscripten', 'wasi'}, 'requires working subprocess'
+- )
++ @test.support.requires_subprocess()
+ def test_load_grammar_from_subprocess(self):
+ tmpdir = tempfile.mkdtemp()
+ tmpsubdir = os.path.join(tmpdir, 'subdir')
+diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py
+index 3d9d6d5d0ac..9c759170450 100644
+--- a/Lib/test/test_marshal.py
++++ b/Lib/test/test_marshal.py
+@@ -1,5 +1,5 @@
+ from test import support
+-from test.support import os_helper, requires_debug_ranges
++from test.support import is_apple_mobile, os_helper, requires_debug_ranges
+ from test.support.script_helper import assert_python_ok
+ import array
+ import io
+@@ -260,7 +260,7 @@
+ #if os.name == 'nt' and support.Py_DEBUG:
+ if os.name == 'nt':
+ MAX_MARSHAL_STACK_DEPTH = 1000
+- elif sys.platform == 'wasi':
++ elif sys.platform == 'wasi' or is_apple_mobile:
+ MAX_MARSHAL_STACK_DEPTH = 1500
+ else:
+ MAX_MARSHAL_STACK_DEPTH = 2000
+diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
+index 1867e8c957f..f75e40940e4 100644
+--- a/Lib/test/test_mmap.py
++++ b/Lib/test/test_mmap.py
+@@ -1,5 +1,5 @@
+ from test.support import (
+- requires, _2G, _4G, gc_collect, cpython_only, is_emscripten
++ requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple,
+ )
+ from test.support.import_helper import import_module
+ from test.support.os_helper import TESTFN, unlink
+@@ -1009,7 +1009,7 @@
+ unlink(TESTFN)
+
+ def _make_test_file(self, num_zeroes, tail):
+- if sys.platform[:3] == 'win' or sys.platform == 'darwin':
++ if sys.platform[:3] == 'win' or is_apple:
+ requires('largefile',
+ 'test requires %s bytes and a long time to run' % str(0x180000000))
+ f = open(TESTFN, 'w+b')
+diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
+index 1d6d92e0235..90afbf1d14a 100644
+--- a/Lib/test/test_os.py
++++ b/Lib/test/test_os.py
+@@ -2372,6 +2372,7 @@
+ support.is_emscripten or support.is_wasi,
+ "musl libc issue on Emscripten/WASI, bpo-46390"
+ )
++ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS")
+ def test_fpathconf(self):
+ self.check(os.pathconf, "PC_NAME_MAX")
+ self.check(os.fpathconf, "PC_NAME_MAX")
+@@ -3946,6 +3947,7 @@
+ self.assertGreaterEqual(size.columns, 0)
+ self.assertGreaterEqual(size.lines, 0)
+
++ @support.requires_subprocess()
+ def test_stty_match(self):
+ """Check if stty returns the same results
+
+diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
+index 4a16ff6939c..b29731fbb42 100644
+--- a/Lib/test/test_platform.py
++++ b/Lib/test/test_platform.py
+@@ -10,6 +10,14 @@
+ from test import support
+ from test.support import os_helper
+
++try:
++ # Some of the iOS tests need ctypes to operate.
++ # Confirm that the ctypes module is available
++ # is available.
++ import _ctypes
++except ImportError:
++ _ctypes = None
+
-+#ifdef __x86_64__
-+#include "pyconfig-x86_64.h"
-+#endif
---- /dev/null
-+++ b/iOS/testbed/Python.xcframework/Info.plist
-@@ -0,0 +1,44 @@
-+
-+
-+
-+
-+ 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
-+
-+
-+ CFBundlePackageType
-+ XFWK
-+ XCFrameworkFormatVersion
-+ 1.0
-+
-+
---- /dev/null
-+++ b/iOS/testbed/Python.xcframework/ios-arm64/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
+ FEDORA_OS_RELEASE = """\
+ NAME=Fedora
+ VERSION="32 (Thirty Two)"
+@@ -219,6 +227,30 @@
+ self.assertEqual(res[-1], res.processor)
+ self.assertEqual(len(res), 6)
+
++ if os.name == "posix":
++ uname = os.uname()
++ self.assertEqual(res.node, uname.nodename)
++ self.assertEqual(res.version, uname.version)
++ self.assertEqual(res.machine, uname.machine)
+
-+It should be used as a target for `--enable-framework` when compiling an iOS on-device
-+build for testing purposes.
---- /dev/null
-+++ b/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README
-@@ -0,0 +1,4 @@
-+This directory is intentionally empty.
++ if sys.platform == "android":
++ self.assertEqual(res.system, "Android")
++ self.assertEqual(res.release, platform.android_ver().release)
++ elif sys.platform == "ios":
++ # 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)
++ else:
++ self.assertEqual(res.system, "")
++ self.assertEqual(res.release, "")
++ else:
++ self.assertEqual(res.system, uname.sysname)
++ self.assertEqual(res.release, uname.release)
+
-+It should be used as a target for `--enable-framework` when compiling an iOS simulator
-+build for testing purposes (either x86_64 or ARM64).
---- /dev/null
-+++ b/iOS/testbed/__main__.py
-@@ -0,0 +1,456 @@
-+import argparse
-+import asyncio
-+import json
-+import plistlib
-+import re
-+import shutil
-+import subprocess
-+import sys
-+from contextlib import asynccontextmanager
-+from datetime import datetime
-+from pathlib import Path
+
+ @unittest.skipUnless(sys.platform.startswith('win'), "windows only test")
+ def test_uname_win32_without_wmi(self):
+ def raises_oserror(*a):
+@@ -404,6 +436,56 @@
+ # parent
+ support.wait_process(pid, exitcode=0)
+
++ def test_ios_ver(self):
++ result = platform.ios_ver()
+
-+DECODE_ARGS = ("UTF-8", "backslashreplace")
++ # ios_ver is only fully available on iOS where ctypes is available.
++ if sys.platform == "ios" and _ctypes:
++ system, release, model, is_simulator = result
++ # Result is a namedtuple
++ self.assertEqual(result.system, system)
++ self.assertEqual(result.release, release)
++ self.assertEqual(result.model, model)
++ self.assertEqual(result.is_simulator, is_simulator)
+
-+# The system log prefixes each line:
-+# 2025-01-17 16:14:29.090 Df iOSTestbed[23987:1fd393b4] (Python) ...
-+# 2025-01-17 16:14:29.090 E iOSTestbed[23987:1fd393b4] (Python) ...
++ # We can't assert specific values without reproducing the logic of
++ # ios_ver(), so we check that the values are broadly what we expect.
+
-+LOG_PREFIX_REGEX = re.compile(
-+ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD
-+ r"\s+\d+:\d{2}:\d{2}\.\d+" # HH:MM:SS.sss
-+ r"\s+\w+" # Df/E
-+ r"\s+iOSTestbed\[\d+:\w+\]" # Process/thread ID
-+ r"\s+\(Python\)\s" # Logger name
-+)
++ # System is either iOS or iPadOS, depending on the test device
++ self.assertIn(system, {"iOS", "iPadOS"})
+
++ # Release is a numeric version specifier with at least 2 parts
++ parts = release.split(".")
++ self.assertGreaterEqual(len(parts), 2)
++ self.assertTrue(all(part.isdigit() for part in parts))
++
++ # If this is a simulator, we get a high level device descriptor
++ # with no identifying model number. If this is a physical device,
++ # we get a model descriptor like "iPhone13,1"
++ if is_simulator:
++ self.assertIn(model, {"iPhone", "iPad"})
++ else:
++ self.assertTrue(
++ (model.startswith("iPhone") or model.startswith("iPad"))
++ and "," in model
++ )
+
-+# Work around a bug involving sys.exit and TaskGroups
-+# (https://github.com/python/cpython/issues/101515).
-+def exit(*args):
-+ raise MySystemExit(*args)
++ self.assertEqual(type(is_simulator), bool)
++ else:
++ # On non-iOS platforms, calling ios_ver doesn't fail; you get
++ # default values
++ self.assertEqual(result.system, "")
++ self.assertEqual(result.release, "")
++ self.assertEqual(result.model, "")
++ self.assertFalse(result.is_simulator)
+
++ # Check the fallback values can be overridden by arguments
++ override = platform.ios_ver("Foo", "Bar", "Whiz", True)
++ self.assertEqual(override.system, "Foo")
++ self.assertEqual(override.release, "Bar")
++ self.assertEqual(override.model, "Whiz")
++ self.assertTrue(override.is_simulator)
+
-+class MySystemExit(Exception):
-+ pass
+ @unittest.skipIf(support.is_emscripten, "Does not apply to Emscripten")
+ def test_libc_ver(self):
+ # check that libc_ver(executable) doesn't raise an exception
+@@ -499,7 +581,8 @@
+ 'root:xnu-4570.71.2~1/RELEASE_X86_64'),
+ 'x86_64', 'i386')
+ arch = ('64bit', '')
+- with mock.patch.object(platform, 'uname', return_value=uname), \
++ with mock.patch.object(sys, "platform", "darwin"), \
++ mock.patch.object(platform, 'uname', return_value=uname), \
+ mock.patch.object(platform, 'architecture', return_value=arch):
+ for mac_ver, expected_terse, expected in [
+ # darwin: mac_ver() returns empty strings
+diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
+index e225b8919d1..5d8920ddd60 100644
+--- a/Lib/test/test_posix.py
++++ b/Lib/test/test_posix.py
+@@ -1,7 +1,7 @@
+ "Test posix functions"
+
+ from test import support
+-from test.support import import_helper
++from test.support import is_apple
+ from test.support import os_helper
+ from test.support import warnings_helper
+ from test.support.script_helper import assert_python_ok
+@@ -568,6 +568,7 @@
+
+ @unittest.skipUnless(hasattr(posix, 'confstr'),
+ 'test needs posix.confstr()')
++ @unittest.skipIf(support.is_apple_mobile, "gh-118201: Test is flaky on iOS")
+ def test_confstr(self):
+ self.assertRaises(ValueError, posix.confstr, "CS_garbage")
+ self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True)
+@@ -796,9 +797,10 @@
+ check_stat(uid, gid)
+ self.assertRaises(OSError, chown_func, first_param, 0, -1)
+ check_stat(uid, gid)
+- if 0 not in os.getgroups():
+- self.assertRaises(OSError, chown_func, first_param, -1, 0)
+- check_stat(uid, gid)
++ if hasattr(os, 'getgroups'):
++ if 0 not in os.getgroups():
++ self.assertRaises(OSError, chown_func, first_param, -1, 0)
++ check_stat(uid, gid)
+ # test illegal types
+ for t in str, float:
+ self.assertRaises(TypeError, chown_func, first_param, t(uid), gid)
+@@ -1264,8 +1266,8 @@
+ self.assertIsInstance(lo, int)
+ self.assertIsInstance(hi, int)
+ self.assertGreaterEqual(hi, lo)
+- # OSX evidently just returns 15 without checking the argument.
+- if sys.platform != "darwin":
++ # Apple plaforms return 15 without checking the argument.
++ if not is_apple:
+ self.assertRaises(OSError, posix.sched_get_priority_min, -23)
+ self.assertRaises(OSError, posix.sched_get_priority_max, -23)
+
+@@ -2058,11 +2060,13 @@
+
+
+ @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
++@support.requires_subprocess()
+ class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin):
+ spawn_func = getattr(posix, 'posix_spawn', None)
+
+
+ @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp")
++@support.requires_subprocess()
+ class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin):
+ spawn_func = getattr(posix, 'posix_spawnp', None)
+
+diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py
+index 51e3a46d0df..3f2bac0155f 100644
+--- a/Lib/test/test_pty.py
++++ b/Lib/test/test_pty.py
+@@ -1,12 +1,17 @@
+-from test.support import verbose, reap_children
+-from test.support.os_helper import TESTFN, unlink
++import sys
++import unittest
++from test.support import (
++ is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose
++)
+ from test.support.import_helper import import_module
++from test.support.os_helper import TESTFN, unlink
+
+-# Skip these tests if termios or fcntl are not available
++# Skip these tests if termios is not available
+ import_module('termios')
+-# fcntl is a proxy for not being one of the wasm32 platforms even though we
+-# don't use this module... a proper check for what crashes those is needed.
+-import_module("fcntl")
+
++# Skip tests on WASM platforms, plus iOS/tvOS/watchOS
++if is_apple_mobile or is_emscripten or is_wasi:
++ raise unittest.SkipTest(f"pty tests not required on {sys.platform}")
+
+ import errno
+ import os
+@@ -17,7 +22,6 @@
+ import signal
+ import socket
+ import io # readline
+-import unittest
+ import warnings
+
+ TEST_STRING_1 = b"I wish to buy a fish license.\n"
+diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py
+index 31757205ca3..6b88b121580 100644
+--- a/Lib/test/test_selectors.py
++++ b/Lib/test/test_selectors.py
+@@ -6,8 +6,7 @@
+ import socket
+ import sys
+ from test import support
+-from test.support import os_helper
+-from test.support import socket_helper
++from test.support import is_apple, os_helper, socket_helper
+ from time import sleep
+ import unittest
+ import unittest.mock
+@@ -520,7 +519,7 @@
+ try:
+ fds = s.select()
+ except OSError as e:
+- if e.errno == errno.EINVAL and sys.platform == 'darwin':
++ if e.errno == errno.EINVAL and is_apple:
+ # unexplainable errors on macOS don't need to fail the test
+ self.skipTest("Invalid argument error calling poll()")
+ raise
+diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
+index 669f6a28781..77490b3dd9c 100644
+--- a/Lib/test/test_shutil.py
++++ b/Lib/test/test_shutil.py
+@@ -2223,6 +2223,7 @@
+ check_chown(dirname, uid, gid)
+
+
++@support.requires_subprocess()
+ class TestWhich(BaseTest, unittest.TestCase):
+
+ def setUp(self):
+@@ -3318,6 +3319,7 @@
+ self.assertGreaterEqual(size.lines, 0)
+
+ @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty")
++ @support.requires_subprocess()
+ @unittest.skipUnless(hasattr(os, 'get_terminal_size'),
+ 'need os.get_terminal_size()')
+ def test_stty_match(self):
+diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
+index 9a01ad0dd5c..e21f7dd3999 100644
+--- a/Lib/test/test_signal.py
++++ b/Lib/test/test_signal.py
+@@ -13,9 +13,10 @@
+ import time
+ import unittest
+ from test import support
+-from test.support import os_helper
++from test.support import (
++ is_apple, is_apple_mobile, os_helper, threading_helper
++)
+ from test.support.script_helper import assert_python_ok, spawn_python
+-from test.support import threading_helper
+ try:
+ import _testcapi
+ except ImportError:
+@@ -834,7 +835,7 @@
+ self.assertEqual(self.hndl_called, True)
+
+ # Issue 3864, unknown if this affects earlier versions of freebsd also
+- @unittest.skipIf(sys.platform in ('netbsd5',),
++ @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile,
+ 'itimer not reliable (does not mix well with threading) on some BSDs.')
+ def test_itimer_virtual(self):
+ self.itimer = signal.ITIMER_VIRTUAL
+@@ -1346,7 +1347,7 @@
+ # Python handler
+ self.assertEqual(len(sigs), N, "Some signals were lost")
+
+- @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)")
++ @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)")
+ @unittest.skipUnless(hasattr(signal, "SIGUSR1"),
+ "test needs SIGUSR1")
+ @threading_helper.requires_working_threading()
+diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
+index f200fc97927..613be5a5117 100644
+--- a/Lib/test/test_socket.py
++++ b/Lib/test/test_socket.py
+@@ -3,6 +3,7 @@
+ from test.support import os_helper
+ from test.support import socket_helper
+ from test.support import threading_helper
++from test.support import is_apple
+
+ import _thread as thread
+ import array
+@@ -1184,8 +1185,11 @@
+ # Find one service that exists, then check all the related interfaces.
+ # I've ordered this by protocols that have both a tcp and udp
+ # protocol, at least for modern Linuxes.
+- if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd'))
+- or sys.platform in ('linux', 'darwin')):
++ if (
++ sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd'))
++ or sys.platform == 'linux'
++ or is_apple
++ ):
+ # avoid the 'echo' service on this platform, as there is an
+ # assumption breaking non-standard port/protocol entry
+ services = ('daytime', 'qotd', 'domain')
+@@ -3696,7 +3700,7 @@
+ def _testFDPassCMSG_LEN(self):
+ self.createAndSendFDs(1)
+
+- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
++ @unittest.skipIf(is_apple, "skipping, see issue #12958")
+ @unittest.skipIf(AIX, "skipping, see issue #22397")
+ @requireAttrs(socket, "CMSG_SPACE")
+ def testFDPassSeparate(self):
+@@ -3707,7 +3711,7 @@
+ maxcmsgs=2)
+
+ @testFDPassSeparate.client_skip
+- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
++ @unittest.skipIf(is_apple, "skipping, see issue #12958")
+ @unittest.skipIf(AIX, "skipping, see issue #22397")
+ def _testFDPassSeparate(self):
+ fd0, fd1 = self.newFDs(2)
+@@ -3720,7 +3724,7 @@
+ array.array("i", [fd1]))]),
+ len(MSG))
+
+- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
++ @unittest.skipIf(is_apple, "skipping, see issue #12958")
+ @unittest.skipIf(AIX, "skipping, see issue #22397")
+ @requireAttrs(socket, "CMSG_SPACE")
+ def testFDPassSeparateMinSpace(self):
+@@ -3734,7 +3738,7 @@
+ maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC)
+
+ @testFDPassSeparateMinSpace.client_skip
+- @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958")
++ @unittest.skipIf(is_apple, "skipping, see issue #12958")
+ @unittest.skipIf(AIX, "skipping, see issue #22397")
+ def _testFDPassSeparateMinSpace(self):
+ fd0, fd1 = self.newFDs(2)
+@@ -3758,7 +3762,7 @@
+ nbytes = self.sendmsgToServer([msg])
+ self.assertEqual(nbytes, len(msg))
+
+- @unittest.skipIf(sys.platform == "darwin", "see issue #24725")
++ @unittest.skipIf(is_apple, "skipping, see issue #12958")
+ def testFDPassEmpty(self):
+ # Try to pass an empty FD array. Can receive either no array
+ # or an empty array.
+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_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py
+index 9d3856a226d..76999792a11 100644
+--- a/Lib/test/test_sqlite3/test_dbapi.py
++++ b/Lib/test/test_sqlite3/test_dbapi.py
+@@ -32,7 +32,7 @@
+
+ from test.support import (
+ SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess,
+- is_emscripten, is_wasi
++ is_apple, is_emscripten, is_wasi
+ )
+ from test.support import threading_helper
+ from _testcapi import INT_MAX, ULLONG_MAX
+@@ -679,7 +679,7 @@
+ cx.execute(self._sql)
+
+ @unittest.skipIf(sys.platform == "win32", "skipped on Windows")
+- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS")
++ @unittest.skipIf(is_apple, "skipped on Apple platforms")
+ @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI")
+ @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths")
+ def test_open_with_undecodable_path(self):
+@@ -725,7 +725,7 @@
+ cx.execute(self._sql)
+
+ @unittest.skipIf(sys.platform == "win32", "skipped on Windows")
+- @unittest.skipIf(sys.platform == "darwin", "skipped on macOS")
++ @unittest.skipIf(is_apple, "skipped on Apple platforms")
+ @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI")
+ @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths")
+ def test_open_undecodable_uri(self):
+diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py
+index c77fec3d39d..ca55d429aec 100644
+--- a/Lib/test/test_stat.py
++++ b/Lib/test/test_stat.py
+@@ -2,8 +2,7 @@
+ import os
+ import socket
+ import sys
+-from test.support import os_helper
+-from test.support import socket_helper
++from test.support import is_apple, os_helper, socket_helper
+ from test.support.import_helper import import_fresh_module
+ from test.support.os_helper import TESTFN
+
+diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py
+index 35985b34a42..fb4bc7fce9a 100644
+--- a/Lib/test/test_sys_settrace.py
++++ b/Lib/test/test_sys_settrace.py
+@@ -7,7 +7,7 @@
+ import gc
+ from functools import wraps
+ import asyncio
+-from test.support import import_helper
++from test.support import import_helper, requires_subprocess
+ import contextlib
+ import warnings
+
+diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
+index 67647e1b787..4e4ccd12f69 100644
+--- a/Lib/test/test_sysconfig.py
++++ b/Lib/test/test_sysconfig.py
+@@ -8,7 +8,11 @@
+ from copy import copy
+
+ from test.support import (
+- captured_stdout, PythonSymlink, requires_subprocess, is_wasi
++ captured_stdout,
++ is_apple_mobile,
++ is_wasi,
++ PythonSymlink,
++ requires_subprocess,
+ )
+ from test.support.import_helper import import_module
+ from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink,
+@@ -348,6 +352,8 @@
+ # XXX more platforms to tests here
+
+ @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
++ @unittest.skipIf(is_apple_mobile,
++ f"{sys.platform} doesn't distribute header files in the runtime environment")
+ def test_get_config_h_filename(self):
+ config_h = sysconfig.get_config_h_filename()
+ self.assertTrue(os.path.isfile(config_h), config_h)
+@@ -457,6 +463,8 @@
+ self.assertEqual(my_platform, test_platform)
+
+ @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
++ @unittest.skipIf(is_apple_mobile,
++ f"{sys.platform} doesn't include config folder at runtime")
+ def test_srcdir(self):
+ # See Issues #15322, #15364.
+ srcdir = sysconfig.get_config_var('srcdir')
+@@ -591,6 +599,8 @@
+ @unittest.skipIf(sys.platform.startswith('win'),
+ 'Test is not Windows compatible')
+ @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
++ @unittest.skipIf(is_apple_mobile,
++ f"{sys.platform} doesn't include config folder at runtime")
+ def test_get_makefile_filename(self):
+ makefile = sysconfig.get_makefile_filename()
+ self.assertTrue(os.path.isfile(makefile), makefile)
+diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py
+index 47619c8807b..25c16e3a0b7 100644
+--- a/Lib/test/test_unicode_file_functions.py
++++ b/Lib/test/test_unicode_file_functions.py
+@@ -5,7 +5,7 @@
+ import unittest
+ import warnings
+ from unicodedata import normalize
+-from test.support import os_helper
++from test.support import is_apple, os_helper
+ from test import support
+
+
+@@ -23,13 +23,13 @@
+ '10_\u1fee\u1ffd',
+ ]
+
+-# Mac OS X decomposes Unicode names, using Normal Form D.
++# Apple platforms decompose Unicode names, using Normal Form D.
+ # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
+ # "However, most volume formats do not follow the exact specification for
+ # these normal forms. For example, HFS Plus uses a variant of Normal Form D
+ # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through
+ # U+2FAFF are not decomposed."
+-if sys.platform != 'darwin':
++if not is_apple:
+ filenames.extend([
+ # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different
+ '11_\u0385\u03d3\u03d4',
+@@ -119,11 +119,11 @@
+ os.stat(name)
+ self._apply_failure(os.listdir, name, self._listdir_failure)
+
+- # Skip the test on darwin, because darwin does normalize the filename to
++ # Skip the test on Apple platforms, because they don't normalize the filename to
+ # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC,
+ # NFKD in Python is useless, because darwin will normalize it later and so
+ # open(), os.stat(), etc. don't raise any exception.
+- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X')
++ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms')
+ @unittest.skipIf(
+ support.is_emscripten or support.is_wasi,
+ "test fails on Emscripten/WASI when host platform is macOS."
+@@ -142,10 +142,10 @@
+ self._apply_failure(os.remove, name)
+ self._apply_failure(os.listdir, name)
+
+- # Skip the test on darwin, because darwin uses a normalization different
++ # Skip the test on Apple platforms, because they use a normalization different
+ # than Python NFD normalization: filenames are different even if we use
+ # Python NFD normalization.
+- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X')
++ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms')
+ def test_listdir(self):
+ sf0 = set(self.files)
+ with warnings.catch_warnings():
+diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
+index 50d06edd93b..146bb67ce0a 100644
+--- a/Lib/test/test_urllib2.py
++++ b/Lib/test/test_urllib2.py
+@@ -1,6 +1,7 @@
+ import unittest
+ from test import support
+ from test.support import os_helper
++from test.support import requires_subprocess
+ from test.support import warnings_helper
+ from test import test_urllib
+ from unittest import mock
+@@ -996,6 +997,7 @@
+
+ file_obj.close()
+
++ @requires_subprocess()
+ def test_http_body_pipe(self):
+ # A file reading from a pipe.
+ # A pipe cannot be seek'ed. There is no way to determine the
+diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
+index 43c67ac751d..ba6c9cf53ad 100644
+--- a/Lib/test/test_venv.py
++++ b/Lib/test/test_venv.py
+@@ -20,8 +20,8 @@
+ import shlex
+ from test.support import (captured_stdout, captured_stderr,
+ skip_if_broken_multiprocessing_synchronize, verbose,
+- requires_subprocess, is_emscripten, is_wasi,
+- requires_venv_with_pip, TEST_HOME_DIR,
++ requires_subprocess, is_apple_mobile, is_emscripten,
++ is_wasi, requires_venv_with_pip, TEST_HOME_DIR,
+ requires_resource, copy_python_src_ignore)
+ from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree,
+ TESTFN, FakePath)
+@@ -42,8 +42,10 @@
+ or sys._base_executable != sys.executable,
+ 'cannot run venv.create from within a venv on this platform')
+
+-if is_emscripten or is_wasi:
+- raise unittest.SkipTest("venv is not available on Emscripten/WASI.")
++# Skip tests on WASM platforms, plus iOS/tvOS/watchOS
++if is_apple_mobile or is_emscripten or is_wasi:
++ raise unittest.SkipTest(f"venv tests not required on {sys.platform}")
+
-+# All subprocesses are executed through this context manager so that no matter
-+# what happens, they can always be cancelled from another task, and they will
-+# always be cleaned up on exit.
-+@asynccontextmanager
-+async def async_process(*args, **kwargs):
-+ process = await asyncio.create_subprocess_exec(*args, **kwargs)
-+ try:
-+ yield process
-+ finally:
-+ if process.returncode is None:
-+ # Allow a reasonably long time for Xcode to clean itself up,
-+ # because we don't want stale emulators left behind.
-+ timeout = 10
-+ process.terminate()
-+ try:
-+ await asyncio.wait_for(process.wait(), timeout)
-+ except TimeoutError:
-+ print(
-+ f"Command {args} did not terminate after {timeout} seconds "
-+ f" - sending SIGKILL"
-+ )
-+ process.kill()
+
+ @requires_subprocess()
+ def check_output(cmd, encoding=None):
+diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
+index 2d695bc8831..4a6586fb1dd 100644
+--- a/Lib/test/test_webbrowser.py
++++ b/Lib/test/test_webbrowser.py
+@@ -5,11 +5,14 @@
+ import subprocess
+ from unittest import mock
+ from test import support
++from test.support import is_apple_mobile
+ from test.support import import_helper
+ from test.support import os_helper
++from test.support import requires_subprocess
++from test.support import threading_helper
+
+-if not support.has_subprocess_support:
+- raise unittest.SkipTest("test webserver requires subprocess")
++# The webbrowser module uses threading locks
++threading_helper.requires_working_threading(module=True)
+
+ URL = 'https://www.example.com'
+ CMD_NAME = 'test'
+@@ -24,6 +27,7 @@
+ return 0
+
+
++@requires_subprocess()
+ class CommandTestMixin:
+
+ def _test(self, meth, *, args=[URL], kw={}, options, arguments):
+@@ -219,6 +223,73 @@
+ arguments=['openURL({},new-tab)'.format(URL)])
+
+
++@unittest.skipUnless(sys.platform == "ios", "Test only applicable to iOS")
++class IOSBrowserTest(unittest.TestCase):
++ def _obj_ref(self, *args):
++ # Construct a string representation of the arguments that can be used
++ # as a proxy for object instance references
++ return "|".join(str(a) for a in args)
+
-+ # Even after killing the process we must still wait for it,
-+ # otherwise we'll get the warning "Exception ignored in __del__".
-+ await asyncio.wait_for(process.wait(), timeout=1)
++ @unittest.skipIf(getattr(webbrowser, "objc", None) is None,
++ "iOS Webbrowser tests require ctypes")
++ def setUp(self):
++ # Intercept the the objc library. Wrap the calls to get the
++ # references to classes and selectors to return strings, and
++ # wrap msgSend to return stringified object references
++ self.orig_objc = webbrowser.objc
+
++ webbrowser.objc = mock.Mock()
++ webbrowser.objc.objc_getClass = lambda cls: f"C#{cls.decode()}"
++ webbrowser.objc.sel_registerName = lambda sel: f"S#{sel.decode()}"
++ webbrowser.objc.objc_msgSend.side_effect = self._obj_ref
+
-+async def async_check_output(*args, **kwargs):
-+ async with async_process(
-+ *args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs
-+ ) as process:
-+ stdout, stderr = await process.communicate()
-+ if process.returncode == 0:
-+ return stdout.decode(*DECODE_ARGS)
-+ else:
-+ raise subprocess.CalledProcessError(
-+ process.returncode,
-+ args,
-+ stdout.decode(*DECODE_ARGS),
-+ stderr.decode(*DECODE_ARGS),
-+ )
++ def tearDown(self):
++ webbrowser.objc = self.orig_objc
+
++ def _test(self, meth, **kwargs):
++ # The browser always gets focus, there's no concept of separate browser
++ # windows, and there's no API-level control over creating a new tab.
++ # Therefore, all calls to webbrowser are effectively the same.
++ getattr(webbrowser, meth)(URL, **kwargs)
+
-+# Return a list of UDIDs associated with booted simulators
-+async def list_devices():
-+ try:
-+ # List the testing simulators, in JSON format
-+ raw_json = await async_check_output(
-+ "xcrun", "simctl", "--set", "testing", "list", "-j"
-+ )
-+ json_data = json.loads(raw_json)
-+
-+ # Filter out the booted iOS simulators
-+ return [
-+ simulator["udid"]
-+ for runtime, simulators in json_data["devices"].items()
-+ for simulator in simulators
-+ if runtime.split(".")[-1].startswith("iOS") and simulator["state"] == "Booted"
++ # The ObjC String version of the URL is created with UTF-8 encoding
++ url_string_args = [
++ "C#NSString",
++ "S#stringWithCString:encoding:",
++ b'https://www.example.com',
++ 4,
+ ]
-+ except subprocess.CalledProcessError as e:
-+ # If there's no ~/Library/Developer/XCTestDevices folder (which is the
-+ # case on fresh installs, and in some CI environments), `simctl list`
-+ # returns error code 1, rather than an empty list. Handle that case,
-+ # but raise all other errors.
-+ if e.returncode == 1:
-+ return []
-+ else:
-+ raise
-+
-+
-+async def find_device(initial_devices):
-+ while True:
-+ new_devices = set(await list_devices()).difference(initial_devices)
-+ if len(new_devices) == 0:
-+ await asyncio.sleep(1)
-+ elif len(new_devices) == 1:
-+ udid = new_devices.pop()
-+ print(f"{datetime.now():%Y-%m-%d %H:%M:%S}: New test simulator detected")
-+ print(f"UDID: {udid}")
-+ return udid
-+ else:
-+ exit(f"Found more than one new device: {new_devices}")
-+
-+
-+async def log_stream_task(initial_devices):
-+ # Wait up to 5 minutes for the build to complete and the simulator to boot.
-+ udid = await asyncio.wait_for(find_device(initial_devices), 5 * 60)
++ # The NSURL version of the URL is created from that string
++ url_obj_args = [
++ "C#NSURL",
++ "S#URLWithString:",
++ self._obj_ref(*url_string_args),
++ ]
++ # The openURL call is invoked on the shared application
++ shared_app_args = ["C#UIApplication", "S#sharedApplication"]
+
-+ # Stream the iOS device's logs, filtering out messages that come from the
-+ # XCTest test suite (catching NSLog messages from the test method), or
-+ # Python itself (catching stdout/stderr content routed to the system log
-+ # with config->use_system_logger).
-+ args = [
-+ "xcrun",
-+ "simctl",
-+ "--set",
-+ "testing",
-+ "spawn",
-+ udid,
-+ "log",
-+ "stream",
-+ "--style",
-+ "compact",
-+ "--predicate",
-+ (
-+ 'senderImagePath ENDSWITH "/iOSTestbedTests.xctest/iOSTestbedTests"'
-+ ' OR senderImagePath ENDSWITH "/Python.framework/Python"'
-+ ),
-+ ]
++ # Verify that the last call is the one that opens the URL.
++ webbrowser.objc.objc_msgSend.assert_called_with(
++ self._obj_ref(*shared_app_args),
++ "S#openURL:options:completionHandler:",
++ self._obj_ref(*url_obj_args),
++ None,
++ None
++ )
+
-+ async with async_process(
-+ *args,
-+ stdout=subprocess.PIPE,
-+ stderr=subprocess.STDOUT,
-+ ) as process:
-+ suppress_dupes = False
-+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
-+ # Strip the prefix from each log line
-+ line = LOG_PREFIX_REGEX.sub("", line)
-+ # The iOS log streamer can sometimes lag; when it does, it outputs
-+ # a warning about messages being dropped... often multiple times.
-+ # Only print the first of these duplicated warnings.
-+ if line.startswith("=== Messages dropped "):
-+ if not suppress_dupes:
-+ suppress_dupes = True
-+ sys.stdout.write(line)
-+ else:
-+ suppress_dupes = False
-+ sys.stdout.write(line)
-+ sys.stdout.flush()
++ def test_open(self):
++ self._test('open')
+
++ def test_open_with_autoraise_false(self):
++ self._test('open', autoraise=False)
+
-+async def xcode_test(location, simulator, verbose):
-+ # Run the test suite on the named simulator
-+ print("Starting xcodebuild...")
-+ args = [
-+ "xcodebuild",
-+ "test",
-+ "-project",
-+ str(location / "iOSTestbed.xcodeproj"),
-+ "-scheme",
-+ "iOSTestbed",
-+ "-destination",
-+ f"platform=iOS Simulator,name={simulator}",
-+ "-resultBundlePath",
-+ str(location / f"{datetime.now():%Y%m%d-%H%M%S}.xcresult"),
-+ "-derivedDataPath",
-+ str(location / "DerivedData"),
-+ ]
-+ if not verbose:
-+ args += ["-quiet"]
++ def test_open_new(self):
++ self._test('open_new')
+
-+ async with async_process(
-+ *args,
-+ stdout=subprocess.PIPE,
-+ stderr=subprocess.STDOUT,
-+ ) as process:
-+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
-+ sys.stdout.write(line)
-+ sys.stdout.flush()
++ def test_open_new_tab(self):
++ self._test('open_new_tab')
+
-+ status = await asyncio.wait_for(process.wait(), timeout=1)
-+ exit(status)
+
+ class BrowserRegistrationTest(unittest.TestCase):
+
+ def setUp(self):
+@@ -302,6 +373,10 @@
+ webbrowser.register(name, None, webbrowser.GenericBrowser(name))
+ webbrowser.get(sys.executable)
+
++ @unittest.skipIf(
++ is_apple_mobile,
++ "Apple mobile doesn't allow modifying browser with environment"
++ )
+ def test_environment(self):
+ webbrowser = import_helper.import_fresh_module('webbrowser')
+ try:
+@@ -313,6 +388,10 @@
+ webbrowser = import_helper.import_fresh_module('webbrowser')
+ webbrowser.get()
+
++ @unittest.skipIf(
++ is_apple_mobile,
++ "Apple mobile doesn't allow modifying browser with environment"
++ )
+ def test_environment_preferred(self):
+ webbrowser = import_helper.import_fresh_module('webbrowser')
+ try:
+diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
+index 13b9e85f9e1..a6792fa8d56 100755
+--- a/Lib/webbrowser.py
++++ b/Lib/webbrowser.py
+@@ -476,6 +476,9 @@
+ # OS X can use below Unix support (but we prefer using the OS X
+ # specific stuff)
+
++ if sys.platform == "ios":
++ register("iosbrowser", None, IOSBrowser(), preferred=True)
+
-+def clone_testbed(
-+ source: Path,
-+ target: Path,
-+ framework: Path,
-+ apps: list[Path],
-+) -> None:
-+ if target.exists():
-+ print(f"{target} already exists; aborting without creating project.")
-+ sys.exit(10)
+ if sys.platform == "serenityos":
+ # SerenityOS webbrowser, simply called "Browser".
+ register("Browser", None, BackgroundBrowser("Browser"))
+@@ -656,6 +659,70 @@
+ rc = osapipe.close()
+ return not rc
+
++#
++# Platform support for iOS
++#
++if sys.platform == "ios":
++ from _ios_support import objc
++ if objc:
++ # If objc exists, we know ctypes is also importable.
++ from ctypes import c_void_p, c_char_p, c_ulong
+
-+ if framework is None:
-+ if not (
-+ source / "Python.xcframework/ios-arm64_x86_64-simulator/bin"
-+ ).is_dir():
-+ print(
-+ f"The testbed being cloned ({source}) does not contain "
-+ f"a simulator framework. Re-run with --framework"
-+ )
-+ sys.exit(11)
-+ else:
-+ if not framework.is_dir():
-+ print(f"{framework} does not exist.")
-+ sys.exit(12)
-+ elif not (
-+ framework.suffix == ".xcframework"
-+ or (framework / "Python.framework").is_dir()
-+ ):
-+ print(
-+ f"{framework} is not an XCframework, "
-+ f"or a simulator slice of a framework build."
-+ )
-+ sys.exit(13)
++ class IOSBrowser(BaseBrowser):
++ def open(self, url, new=0, autoraise=True):
++ sys.audit("webbrowser.open", url)
++ # If ctypes isn't available, we can't open a browser
++ if objc is None:
++ return False
+
-+ print("Cloning testbed project:")
-+ print(f" Cloning {source}...", end="", flush=True)
-+ shutil.copytree(source, target, symlinks=True)
-+ print(" done")
++ # All the messages in this call return object references.
++ objc.objc_msgSend.restype = c_void_p
+
-+ xc_framework_path = target / "Python.xcframework"
-+ sim_framework_path = xc_framework_path / "ios-arm64_x86_64-simulator"
-+ if framework is not None:
-+ if framework.suffix == ".xcframework":
-+ print(" Installing XCFramework...", end="", flush=True)
-+ if xc_framework_path.is_dir():
-+ shutil.rmtree(xc_framework_path)
-+ else:
-+ xc_framework_path.unlink(missing_ok=True)
-+ xc_framework_path.symlink_to(
-+ framework.relative_to(xc_framework_path.parent, walk_up=True)
-+ )
-+ print(" done")
-+ else:
-+ print(" Installing simulator framework...", end="", flush=True)
-+ if sim_framework_path.is_dir():
-+ shutil.rmtree(sim_framework_path)
-+ else:
-+ sim_framework_path.unlink(missing_ok=True)
-+ sim_framework_path.symlink_to(
-+ framework.relative_to(sim_framework_path.parent, walk_up=True)
-+ )
-+ print(" done")
-+ else:
-+ if (
-+ xc_framework_path.is_symlink()
-+ and not xc_framework_path.readlink().is_absolute()
-+ ):
-+ # XCFramework is a relative symlink. Rewrite the symlink relative
-+ # to the new location.
-+ print(" Rewriting symlink to XCframework...", end="", flush=True)
-+ orig_xc_framework_path = (
-+ source
-+ / xc_framework_path.readlink()
-+ ).resolve()
-+ xc_framework_path.unlink()
-+ xc_framework_path.symlink_to(
-+ orig_xc_framework_path.relative_to(
-+ xc_framework_path.parent, walk_up=True
-+ )
-+ )
-+ print(" done")
-+ elif (
-+ sim_framework_path.is_symlink()
-+ and not sim_framework_path.readlink().is_absolute()
-+ ):
-+ print(" Rewriting symlink to simulator framework...", end="", flush=True)
-+ # Simulator framework is a relative symlink. Rewrite the symlink
-+ # relative to the new location.
-+ orig_sim_framework_path = (
-+ source
-+ / "Python.XCframework"
-+ / sim_framework_path.readlink()
-+ ).resolve()
-+ sim_framework_path.unlink()
-+ sim_framework_path.symlink_to(
-+ orig_sim_framework_path.relative_to(
-+ sim_framework_path.parent, walk_up=True
-+ )
++ # This is the equivalent of:
++ # NSString url_string =
++ # [NSString stringWithCString:url.encode("utf-8")
++ # encoding:NSUTF8StringEncoding];
++ NSString = objc.objc_getClass(b"NSString")
++ constructor = objc.sel_registerName(b"stringWithCString:encoding:")
++ objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_char_p, c_ulong]
++ url_string = objc.objc_msgSend(
++ NSString,
++ constructor,
++ url.encode("utf-8"),
++ 4, # NSUTF8StringEncoding = 4
+ )
-+ print(" done")
-+ else:
-+ print(" Using pre-existing iOS framework.")
-+
-+ for app_src in apps:
-+ print(f" Installing app {app_src.name!r}...", end="", flush=True)
-+ app_target = target / f"iOSTestbed/app/{app_src.name}"
-+ if app_target.is_dir():
-+ shutil.rmtree(app_target)
-+ shutil.copytree(app_src, app_target)
-+ print(" done")
+
-+ print(f"Successfully cloned testbed: {target.resolve()}")
++ # Create an NSURL object representing the URL
++ # This is the equivalent of:
++ # NSURL *nsurl = [NSURL URLWithString:url];
++ NSURL = objc.objc_getClass(b"NSURL")
++ urlWithString_ = objc.sel_registerName(b"URLWithString:")
++ objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_void_p]
++ ns_url = objc.objc_msgSend(NSURL, urlWithString_, url_string)
+
++ # Get the shared UIApplication instance
++ # This code is the equivalent of:
++ # UIApplication shared_app = [UIApplication sharedApplication]
++ UIApplication = objc.objc_getClass(b"UIApplication")
++ sharedApplication = objc.sel_registerName(b"sharedApplication")
++ objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
++ shared_app = objc.objc_msgSend(UIApplication, sharedApplication)
+
-+def update_plist(testbed_path, args):
-+ # Add the test runner arguments to the testbed's Info.plist file.
-+ info_plist = testbed_path / "iOSTestbed" / "iOSTestbed-Info.plist"
-+ with info_plist.open("rb") as f:
-+ info = plistlib.load(f)
++ # Open the URL on the shared application
++ # This code is the equivalent of:
++ # [shared_app openURL:ns_url
++ # options:NIL
++ # completionHandler:NIL];
++ openURL_ = objc.sel_registerName(b"openURL:options:completionHandler:")
++ objc.objc_msgSend.argtypes = [
++ c_void_p, c_void_p, c_void_p, c_void_p, c_void_p
++ ]
++ # Method returns void
++ objc.objc_msgSend.restype = None
++ objc.objc_msgSend(shared_app, openURL_, ns_url, None, None)
+
-+ info["TestArgs"] = args
++ return True
+
-+ with info_plist.open("wb") as f:
-+ plistlib.dump(info, f)
+
+ def main():
+ import getopt
+--- /dev/null
++++ b/Mac/Resources/app-store-compliance.patch
+@@ -0,0 +1,29 @@
++diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
++index d6c83a75c1c..19ed4e01091 100644
++--- a/Lib/test/test_urlparse.py
+++++ b/Lib/test/test_urlparse.py
++@@ -237,11 +237,6 @@ def test_roundtrips(self):
++ '','',''),
++ ('git+ssh', 'git@github.com','/user/project.git',
++ '', '')),
++- ('itms-services://?action=download-manifest&url=https://example.com/app',
++- ('itms-services', '', '', '',
++- 'action=download-manifest&url=https://example.com/app', ''),
++- ('itms-services', '', '',
++- 'action=download-manifest&url=https://example.com/app', '')),
++ ('+scheme:path/to/file',
++ ('', '', '+scheme:path/to/file', '', '', ''),
++ ('', '', '+scheme:path/to/file', '', '')),
++diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
++index 8f724f907d4..148caf742c9 100644
++--- a/Lib/urllib/parse.py
+++++ b/Lib/urllib/parse.py
++@@ -59,7 +59,7 @@
++ 'imap', 'wais', 'file', 'mms', 'https', 'shttp',
++ 'snews', 'prospero', 'rtsp', 'rtsps', 'rtspu', 'rsync',
++ 'svn', 'svn+ssh', 'sftp', 'nfs', 'git', 'git+ssh',
++- 'ws', 'wss', 'itms-services']
+++ 'ws', 'wss']
+
++ uses_params = ['', 'ftp', 'hdl', 'prospero', 'http', 'imap',
++ 'https', 'shttp', 'rtsp', 'rtsps', 'rtspu', 'sip',
+diff --git a/Makefile.pre.in b/Makefile.pre.in
+index 7ca3dc62c01..a1e28beff42 100644
+--- a/Makefile.pre.in
++++ b/Makefile.pre.in
+@@ -178,18 +178,29 @@
+ EXE= @EXEEXT@
+ BUILDEXE= @BUILDEXEEXT@
+
++# Name of the patch file to apply for app store compliance
++APP_STORE_COMPLIANCE_PATCH=@APP_STORE_COMPLIANCE_PATCH@
+
-+async def run_testbed(simulator: str, args: list[str], verbose: bool=False):
-+ location = Path(__file__).parent
-+ print("Updating plist...", end="", flush=True)
-+ update_plist(location, args)
-+ print(" done.")
+ # Short name and location for Mac OS X Python framework
+ UNIVERSALSDK=@UNIVERSALSDK@
+ PYTHONFRAMEWORK= @PYTHONFRAMEWORK@
+ PYTHONFRAMEWORKDIR= @PYTHONFRAMEWORKDIR@
+ PYTHONFRAMEWORKPREFIX= @PYTHONFRAMEWORKPREFIX@
+ PYTHONFRAMEWORKINSTALLDIR= @PYTHONFRAMEWORKINSTALLDIR@
+-# Deployment target selected during configure, to be checked
++PYTHONFRAMEWORKINSTALLNAMEPREFIX= @PYTHONFRAMEWORKINSTALLNAMEPREFIX@
++RESSRCDIR= @RESSRCDIR@
++# macOS deployment target selected during configure, to be checked
+ # by distutils. The export statement is needed to ensure that the
+ # deployment target is active during build.
+ MACOSX_DEPLOYMENT_TARGET=@CONFIGURE_MACOSX_DEPLOYMENT_TARGET@
+ @EXPORT_MACOSX_DEPLOYMENT_TARGET@export MACOSX_DEPLOYMENT_TARGET
+
++# iOS Deployment target selected during configure. Unlike macOS, the iOS
++# deployment target is controlled using `-mios-version-min` arguments added to
++# CFLAGS and LDFLAGS by the configure script. This variable is not used during
++# the build, and is only listed here so it will be included in sysconfigdata.
++IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@
+
-+ # Get the list of devices that are booted at the start of the test run.
-+ # The simulator started by the test suite will be detected as the new
-+ # entry that appears on the device list.
-+ initial_devices = await list_devices()
+ # Option to install to strip binaries
+ STRIPFLAG=-s
+
+@@ -614,7 +625,7 @@
+ .PHONY: all
+
+ .PHONY: build_all
+-build_all: check-clean-src $(BUILDPYTHON) platform sharedmods \
++build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sharedmods \
+ gdbhooks Programs/_testembed scripts checksharedmods rundsymutil
+
+ .PHONY: build_wasm
+@@ -637,6 +648,16 @@
+ exit 1; \
+ fi
+
++# Check that the app store compliance patch can be applied (if configured).
++# This is checked as a dry-run against the original library sources;
++# the patch will be actually applied during the install phase.
++.PHONY: check-app-store-compliance
++check-app-store-compliance:
++ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \
++ patch --dry-run --quiet --force --strip 1 --directory "$(abs_srcdir)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)"; \
++ echo "App store compliance patch can be applied."; \
++ fi
+
-+ try:
-+ async with asyncio.TaskGroup() as tg:
-+ tg.create_task(log_stream_task(initial_devices))
-+ tg.create_task(xcode_test(location, simulator=simulator, verbose=verbose))
-+ except* MySystemExit as e:
-+ raise SystemExit(*e.exceptions[0].args) from None
-+ except* subprocess.CalledProcessError as e:
-+ # Extract it from the ExceptionGroup so it can be handled by `main`.
-+ raise e.exceptions[0]
+ # Profile generation build must start from a clean tree.
+ profile-clean-stamp:
+ $(MAKE) clean
+@@ -826,7 +847,7 @@
+ $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^
+
+ libpython$(LDVERSION).dylib: $(LIBRARY_OBJS)
+- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \
++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \
+
+
+ libpython$(VERSION).sl: $(LIBRARY_OBJS)
+@@ -851,14 +872,13 @@
+ # This rule is here for OPENSTEP/Rhapsody/MacOSX. It builds a temporary
+ # minimal framework (not including the Lib directory and such) in the current
+ # directory.
+-RESSRCDIR=Mac/Resources/framework
+ $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \
+ $(LIBRARY) \
+ $(RESSRCDIR)/Info.plist
+ $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)
+ $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \
+- -all_load $(LIBRARY) -Wl,-single_module \
+- -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK) \
++ -all_load $(LIBRARY) \
++ -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \
+ -compatibility_version $(VERSION) \
+ -current_version $(VERSION) \
+ -framework CoreFoundation $(LIBS);
+@@ -870,6 +890,21 @@
+ $(LN) -fsn Versions/Current/$(PYTHONFRAMEWORK) $(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)
+ $(LN) -fsn Versions/Current/Resources $(PYTHONFRAMEWORKDIR)/Resources
+
++# This rule is for iOS, which requires an annoyingly just slighly different
++# format for frameworks to macOS. It *doesn't* use a versioned framework, and
++# the Info.plist must be in the root of the framework.
++$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK): \
++ $(LIBRARY) \
++ $(RESSRCDIR)/Info.plist
++ $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR)
++ $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \
++ -all_load $(LIBRARY) \
++ -install_name $(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \
++ -compatibility_version $(VERSION) \
++ -current_version $(VERSION) \
++ -framework CoreFoundation $(LIBS);
++ $(INSTALL_DATA) $(RESSRCDIR)/Info.plist $(PYTHONFRAMEWORKDIR)/Info.plist
+
+ # This rule builds the Cygwin Python DLL and import library if configured
+ # for a shared core library; otherwise, this rule is a noop.
+ $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS)
+@@ -1857,6 +1892,36 @@
+ $(RUNSHARED) /usr/libexec/oah/translate \
+ ./$(BUILDPYTHON) -E -m test -j 0 -u all $(TESTOPTS)
+
++# Run the test suite on the iOS simulator. Must be run on a macOS machine with
++# a full Xcode install that has an iPhone SE (3rd edition) simulator available.
++# This must be run *after* a `make install` has completed the build. The
++# `--with-framework-name` argument *cannot* be used when configuring the build.
++XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s)
++.PHONY: testios
++testios:
++ @if test "$(MACHDEP)" != "ios"; then \
++ echo "Cannot run the iOS testbed for a non-iOS build."; \
++ exit 1;\
++ fi
++ @if test "$(findstring -iphonesimulator,$(MULTIARCH))" != "-iphonesimulator"; then \
++ echo "Cannot run the iOS testbed for non-simulator builds."; \
++ exit 1;\
++ fi
++ @if test $(PYTHONFRAMEWORK) != "Python"; then \
++ echo "Cannot run the iOS testbed with a non-default framework name."; \
++ exit 1;\
++ fi
++ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \
++ echo "Cannot find a finalized iOS Python.framework. Have you run 'make install' to finalize the framework build?"; \
++ exit 1;\
++ fi
+
-+def main():
-+ parser = argparse.ArgumentParser(
-+ description=(
-+ "Manages the process of testing a Python project in the iOS simulator."
-+ ),
-+ )
++ # Clone the testbed project into the XCFOLDER
++ $(PYTHON_FOR_BUILD) $(srcdir)/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)"
+
-+ subcommands = parser.add_subparsers(dest="subcommand")
++ # Run the testbed project
++ $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W
+
-+ clone = subcommands.add_parser(
-+ "clone",
-+ description=(
-+ "Clone the testbed project, copying in an iOS Python framework and"
-+ "any specified application code."
-+ ),
-+ help="Clone a testbed project to a new location.",
-+ )
-+ clone.add_argument(
-+ "--framework",
-+ help=(
-+ "The location of the XCFramework (or simulator-only slice of an "
-+ "XCFramework) to use when running the testbed"
-+ ),
-+ )
-+ clone.add_argument(
-+ "--app",
-+ dest="apps",
-+ action="append",
-+ default=[],
-+ help="The location of any code to include in the testbed project",
-+ )
-+ clone.add_argument(
-+ "location",
-+ help="The path where the testbed will be cloned.",
-+ )
+ # Like testall, but with only one pass and without multiple processes.
+ # Run an optional script to include information about the build environment.
+ .PHONY: buildbottest
+@@ -1900,7 +1965,7 @@
+ # which can lead to two parallel `./python setup.py build` processes that
+ # step on each others toes.
+ .PHONY: install
+-install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKINSTALLLAST@
++install: @FRAMEWORKINSTALLFIRST@ @INSTALLTARGETS@ @FRAMEWORKINSTALLLAST@
+ if test "x$(ENSUREPIP)" != "xno" ; then \
+ case $(ENSUREPIP) in \
+ upgrade) ensurepip="--upgrade" ;; \
+@@ -2336,6 +2401,14 @@
+ $(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py \
+ $(DESTDIR)$(LIBDEST); \
+ $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
++ @ # If app store compliance has been configured, apply the patch to the
++ @ # installed library code. The patch has been previously validated against
++ @ # the original source tree, so we can ignore any errors that are raised
++ @ # due to files that are missing because of --disable-test-modules etc.
++ @if [ "$(APP_STORE_COMPLIANCE_PATCH)" != "" ]; then \
++ echo "Applying app store compliance patch"; \
++ patch --force --reject-file "$(abs_builddir)/app-store-compliance.rej" --strip 2 --directory "$(DESTDIR)$(LIBDEST)" --input "$(abs_srcdir)/$(APP_STORE_COMPLIANCE_PATCH)" || true ; \
++ fi
+ @ # Build PYC files for the 3 optimization levels (0, 1, 2)
+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
+ $(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \
+@@ -2512,10 +2585,11 @@
+ # only have to cater for the structural bits of the framework.
+
+ .PHONY: frameworkinstallframework
+-frameworkinstallframework: frameworkinstallstructure install frameworkinstallmaclib
++frameworkinstallframework: @FRAMEWORKINSTALLFIRST@ install frameworkinstallmaclib
+
+-.PHONY: frameworkinstallstructure
+-frameworkinstallstructure: $(LDLIBRARY)
++# macOS uses a versioned frameworks structure that includes a full install
++.PHONY: frameworkinstallversionedstructure
++frameworkinstallversionedstructure: $(LDLIBRARY)
+ @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \
+ echo Not configured with --enable-framework; \
+ exit 1; \
+@@ -2536,6 +2610,30 @@
+ $(LN) -fsn Versions/Current/Resources $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Resources
+ $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY)
+
++# iOS/tvOS/watchOS uses a non-versioned framework with Info.plist in the
++# framework root, no .lproj data, and only stub compilation assistance binaries
++.PHONY: frameworkinstallunversionedstructure
++frameworkinstallunversionedstructure: $(LDLIBRARY)
++ @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \
++ echo Not configured with --enable-framework; \
++ exit 1; \
++ else true; \
++ fi
++ if test -d $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; then \
++ echo "Clearing stale header symlink directory"; \
++ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; \
++ fi
++ $(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); \
++ done
+
-+ run = subcommands.add_parser(
-+ "run",
-+ usage="%(prog)s [-h] [--simulator SIMULATOR] -- [ ...]",
-+ description=(
-+ "Run a testbed project. The arguments provided after `--` will be "
-+ "passed to the running iOS process as if they were arguments to "
-+ "`python -m`."
-+ ),
-+ help="Run a testbed project",
-+ )
-+ run.add_argument(
-+ "--simulator",
-+ default="iPhone SE (3rd Generation)",
-+ help="The name of the simulator to use (default: 'iPhone SE (3rd Generation)')",
-+ )
-+ run.add_argument(
-+ "-v", "--verbose",
-+ action="store_true",
-+ help="Enable verbose output",
-+ )
+ # This installs Mac/Lib into the framework
+ # Install a number of symlinks to keep software that expects a normal unix
+ # install (which includes python-config) happy.
+@@ -2576,6 +2674,19 @@
+ frameworkinstallextras:
+ cd Mac && $(MAKE) installextras DESTDIR="$(DESTDIR)"
+
++# On iOS, bin/lib can't live inside the framework; include needs to be called
++# "Headers", but *must* be in the framework, and *not* include the `python3.X`
++# subdirectory. The install has put these folders in the same folder as
++# Python.framework; Move the headers to their final framework-compatible home.
++.PHONY: frameworkinstallmobileheaders
++frameworkinstallmobileheaders: frameworkinstallunversionedstructure inclinstall
++ if test -d $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; then \
++ echo "Removing old framework headers"; \
++ rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \
++ fi
++ mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers"
++ $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(LDVERSION)"
+
-+ 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 = []
+ # Build the toplevel Makefile
+ Makefile.pre: $(srcdir)/Makefile.pre.in config.status
+ CONFIG_FILES=Makefile.pre CONFIG_HEADERS= ./config.status
+@@ -2686,6 +2797,10 @@
+ -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';'
+ -rm -f Include/pydtrace_probes.h
+ -rm -f profile-gen-stamp
++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/bin
++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/lib
++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/include
++ -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/Python.framework
+
+ .PHONY: profile-removal
+ profile-removal:
+@@ -2711,6 +2826,8 @@
+ config.cache config.log pyconfig.h Modules/config.c
+ -rm -rf build platform
+ -rm -rf $(PYTHONFRAMEWORKDIR)
++ -rm -rf Apple/iOS/Frameworks
++ -rm -rf iOSTestbed.*
+ -rm -f python-config.py python-config
+
+ # Make things extra clean, before making a distribution:
+diff --git a/Modules/getpath.c b/Modules/getpath.c
+index 0a310000751..83a2bc469ae 100644
+--- a/Modules/getpath.c
++++ b/Modules/getpath.c
+@@ -15,6 +15,7 @@
+ #endif
+
+ #ifdef __APPLE__
++# include "TargetConditionals.h"
+ # include
+ #endif
+
+@@ -759,7 +760,7 @@
+ return winmodule_to_dict(dict, key, PyWin_DLLhModule);
+ }
+ #endif
+-#elif defined(WITH_NEXT_FRAMEWORK)
++#elif defined(WITH_NEXT_FRAMEWORK) && !defined(TARGET_OS_IPHONE)
+ static char modPath[MAXPATHLEN + 1];
+ static int modPathInitialized = -1;
+ if (modPathInitialized < 0) {
+@@ -953,4 +954,3 @@
+
+ return _PyStatus_OK();
+ }
+-
+diff --git a/Python/marshal.c b/Python/marshal.c
+index 3fc3f890422..892debe38dc 100644
+--- a/Python/marshal.c
++++ b/Python/marshal.c
+@@ -16,6 +16,10 @@
+ #include "marshal.h" // Py_MARSHAL_VERSION
+ #include "pycore_pystate.h" // _PyInterpreterState_GET()
+
++#ifdef __APPLE__
++# include "TargetConditionals.h"
++#endif /* __APPLE__ */
+
-+ context = parser.parse_args(testbed_args)
+ /*[clinic input]
+ module marshal
+ [clinic start generated code]*/
+@@ -35,11 +39,14 @@
+ * #if defined(MS_WINDOWS) && defined(_DEBUG)
+ */
+ #if defined(MS_WINDOWS)
+-#define MAX_MARSHAL_STACK_DEPTH 1000
++# define MAX_MARSHAL_STACK_DEPTH 1000
+ #elif defined(__wasi__)
+-#define MAX_MARSHAL_STACK_DEPTH 1500
++# define MAX_MARSHAL_STACK_DEPTH 1500
++// TARGET_OS_IPHONE covers any non-macOS Apple platform.
++#elif defined(__APPLE__) && TARGET_OS_IPHONE
++# define MAX_MARSHAL_STACK_DEPTH 1500
+ #else
+-#define MAX_MARSHAL_STACK_DEPTH 2000
++# define MAX_MARSHAL_STACK_DEPTH 2000
+ #endif
+
+ #define TYPE_NULL '0'
+diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
+index f5bea14b16b..14e44893e53 100644
+--- a/Python/pylifecycle.c
++++ b/Python/pylifecycle.c
+@@ -34,7 +34,21 @@
+ #include // getenv()
+
+ #if defined(__APPLE__)
+-#include
++# include
++# include
++# include
++// The os_log unified logging APIs were introduced in macOS 10.12, iOS 10.0,
++// tvOS 10.0, and watchOS 3.0; we enable the use of the system logger
++// automatically on non-macOS platforms.
++# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
++# define USE_APPLE_SYSTEM_LOG 1
++# else
++# define USE_APPLE_SYSTEM_LOG 0
++# endif
+
-+ 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,
-+ apps=[Path(app) for app in context.apps],
-+ )
-+ elif context.subcommand == "run":
-+ if test_args:
-+ if not (
-+ Path(__file__).parent / "Python.xcframework/ios-arm64_x86_64-simulator/bin"
-+ ).is_dir():
-+ print(
-+ f"Testbed does not contain a compiled iOS framework. Use "
-+ f"`python {sys.argv[0]} clone ...` to create a runnable "
-+ f"clone of this testbed."
-+ )
-+ sys.exit(20)
++# if USE_APPLE_SYSTEM_LOG
++# include
++# endif
+ #endif
+
+ #ifdef HAVE_SIGNAL_H
+@@ -66,6 +80,9 @@
+ static PyStatus init_import_site(void);
+ static PyStatus init_set_builtins_open(void);
+ static PyStatus init_sys_streams(PyThreadState *tstate);
++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG
++static PyStatus init_apple_streams(PyThreadState *tstate);
++#endif
+ static void wait_for_thread_shutdown(PyThreadState *tstate);
+ static void call_ll_exitfuncs(_PyRuntimeState *runtime);
+
+@@ -1182,6 +1199,17 @@
+ return status;
+ }
+
++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG
++ status = init_apple_streams(tstate);
++ if (_PyStatus_EXCEPTION(status)) {
++ return status;
++ }
++#endif
+
-+ asyncio.run(
-+ run_testbed(
-+ simulator=context.simulator,
-+ verbose=context.verbose,
-+ args=test_args,
-+ )
-+ )
-+ else:
-+ print(f"Must specify test arguments (e.g., {sys.argv[0]} run -- test)")
-+ print()
-+ parser.print_help(sys.stderr)
-+ sys.exit(21)
-+ else:
-+ parser.print_help(sys.stderr)
-+ sys.exit(1)
++#ifdef Py_DEBUG
++ run_presite(tstate);
++#endif
+
+ status = add_main_module(interp);
+ if (_PyStatus_EXCEPTION(status)) {
+ return status;
+@@ -2641,6 +2669,69 @@
+ return res;
+ }
+
++#if defined(__APPLE__) && USE_APPLE_SYSTEM_LOG
+
-+if __name__ == "__main__":
-+ main()
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj
-@@ -0,0 +1,580 @@
-+// !$*UTF8*$!
++static PyObject *
++apple_log_write_impl(PyObject *self, PyObject *args)
+{
-+ archiveVersion = 1;
-+ classes = {
-+ };
-+ objectVersion = 56;
-+ objects = {
-+
-+/* Begin PBXBuildFile section */
-+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; };
-+ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; };
-+ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; };
-+ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; };
-+ 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.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, ); }; };
-+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; };
-+ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; };
-+ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; };
-+/* End PBXBuildFile section */
-+
-+/* Begin PBXContainerItemProxy section */
-+ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = {
-+ isa = PBXContainerItemProxy;
-+ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */;
-+ proxyType = 1;
-+ remoteGlobalIDString = 607A66112B0EFA380010BFC8;
-+ remoteInfo = iOSTestbed;
-+ };
-+/* End PBXContainerItemProxy section */
-+
-+/* Begin PBXCopyFilesBuildPhase section */
-+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = {
-+ isa = PBXCopyFilesBuildPhase;
-+ buildActionMask = 2147483647;
-+ dstPath = "";
-+ dstSubfolderSpec = 10;
-+ files = (
-+ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */,
-+ );
-+ name = "Embed Frameworks";
-+ runOnlyForDeploymentPostprocessing = 0;
-+ };
-+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = {
-+ isa = PBXCopyFilesBuildPhase;
-+ buildActionMask = 2147483647;
-+ dstPath = "";
-+ dstSubfolderSpec = 10;
-+ files = (
-+ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */,
-+ );
-+ name = "Embed Frameworks";
-+ runOnlyForDeploymentPostprocessing = 0;
-+ };
-+/* End PBXCopyFilesBuildPhase section */
++ int logtype = 0;
++ const char *text = NULL;
++ if (!PyArg_ParseTuple(args, "iy", &logtype, &text)) {
++ return NULL;
++ }
+
-+/* Begin PBXFileReference section */
-+ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; };
-+ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
-+ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
-+ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
-+ 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
-+ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
-+ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-+ 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSTestbedTests.m; sourceTree = ""; };
-+ 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; };
-+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; 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 = ""; };
-+/* End PBXFileReference section */
++ // Pass the user-provided text through explicit %s formatting
++ // to avoid % literals being interpreted as a formatting directive.
++ os_log_with_type(OS_LOG_DEFAULT, logtype, "%s", text);
++ Py_RETURN_NONE;
++}
+
-+/* 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 = (
-+ 607A664A2B0EFB310010BFC8 /* Python.xcframework */,
-+ 607A66142B0EFA380010BFC8 /* iOSTestbed */,
-+ 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */,
-+ 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 */,
-+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */,
-+ 607A66152B0EFA380010BFC8 /* AppDelegate.h */,
-+ 607A66162B0EFA380010BFC8 /* AppDelegate.m */,
-+ 607A66212B0EFA390010BFC8 /* Assets.xcassets */,
-+ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */,
-+ 607A66272B0EFA390010BFC8 /* main.m */,
-+ );
-+ path = iOSTestbed;
-+ sourceTree = "";
-+ };
-+ 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */ = {
-+ isa = PBXGroup;
-+ children = (
-+ 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */,
-+ );
-+ path = iOSTestbedTests;
-+ sourceTree = "";
-+ };
-+ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = {
-+ isa = PBXGroup;
-+ children = (
-+ );
-+ name = Frameworks;
-+ sourceTree = "";
-+ };
-+/* End PBXGroup section */
++static PyMethodDef apple_log_write_method = {
++ "apple_log_write", apple_log_write_impl, METH_VARARGS
++};
+
-+/* Begin PBXNativeTarget section */
-+ 607A66112B0EFA380010BFC8 /* iOSTestbed */ = {
-+ isa = PBXNativeTarget;
-+ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */;
-+ buildPhases = (
-+ 607A660E2B0EFA380010BFC8 /* Sources */,
-+ 607A660F2B0EFA380010BFC8 /* Frameworks */,
-+ 607A66102B0EFA380010BFC8 /* Resources */,
-+ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */,
-+ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */,
-+ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */,
-+ );
-+ buildRules = (
-+ );
-+ dependencies = (
-+ );
-+ name = iOSTestbed;
-+ productName = iOSTestbed;
-+ productReference = 607A66122B0EFA380010BFC8 /* iOSTestbed.app */;
-+ productType = "com.apple.product-type.application";
-+ };
-+ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */ = {
-+ isa = PBXNativeTarget;
-+ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */;
-+ buildPhases = (
-+ 607A66292B0EFA3A0010BFC8 /* Sources */,
-+ 607A662A2B0EFA3A0010BFC8 /* Frameworks */,
-+ 607A662B2B0EFA3A0010BFC8 /* Resources */,
-+ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */,
-+ );
-+ buildRules = (
-+ );
-+ dependencies = (
-+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */,
-+ );
-+ name = iOSTestbedTests;
-+ productName = iOSTestbedTests;
-+ productReference = 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */;
-+ productType = "com.apple.product-type.bundle.unit-test";
-+ };
-+/* End PBXNativeTarget section */
+
-+/* Begin PBXProject section */
-+ 607A660A2B0EFA380010BFC8 /* Project object */ = {
-+ isa = PBXProject;
-+ attributes = {
-+ BuildIndependentTargetsInParallel = 1;
-+ LastUpgradeCheck = 1500;
-+ TargetAttributes = {
-+ 607A66112B0EFA380010BFC8 = {
-+ CreatedOnToolsVersion = 15.0.1;
-+ };
-+ 607A662C2B0EFA3A0010BFC8 = {
-+ CreatedOnToolsVersion = 15.0.1;
-+ TestTargetID = 607A66112B0EFA380010BFC8;
-+ };
-+ };
-+ };
-+ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */;
-+ compatibilityVersion = "Xcode 14.0";
-+ developmentRegion = en;
-+ hasScannedForEncodings = 0;
-+ knownRegions = (
-+ en,
-+ Base,
-+ );
-+ mainGroup = 607A66092B0EFA380010BFC8;
-+ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */;
-+ projectDirPath = "";
-+ projectRoot = "";
-+ targets = (
-+ 607A66112B0EFA380010BFC8 /* iOSTestbed */,
-+ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */,
-+ );
-+ };
-+/* End PBXProject section */
++static PyStatus
++init_apple_streams(PyThreadState *tstate)
++{
++ PyStatus status = _PyStatus_OK();
++ PyObject *_apple_support = NULL;
++ PyObject *apple_log_write = NULL;
++ PyObject *result = NULL;
+
-+/* Begin PBXResourcesBuildPhase section */
-+ 607A66102B0EFA380010BFC8 /* Resources */ = {
-+ isa = PBXResourcesBuildPhase;
-+ buildActionMask = 2147483647;
-+ files = (
-+ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */,
-+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist 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 */
++ _apple_support = PyImport_ImportModule("_apple_support");
++ if (_apple_support == NULL) {
++ goto error;
++ }
+
-+/* Begin PBXShellScriptBuildPhase section */
-+ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = {
-+ isa = PBXShellScriptBuildPhase;
-+ alwaysOutOfDate = 1;
-+ buildActionMask = 2147483647;
-+ files = (
-+ );
-+ inputFileListPaths = (
-+ );
-+ inputPaths = (
-+ );
-+ name = "Install Target Specific Python Standard Library";
-+ outputFileListPaths = (
-+ );
-+ outputPaths = (
-+ );
-+ runOnlyForDeploymentPostprocessing = 0;
-+ shellPath = /bin/sh;
-+ shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-iphonesimulator\" ]; then\n echo \"Installing Python modules for iOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for iOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n";
-+ showEnvVarsInLog = 0;
-+ };
-+ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = {
-+ isa = PBXShellScriptBuildPhase;
-+ alwaysOutOfDate = 1;
-+ buildActionMask = 2147483647;
-+ files = (
-+ );
-+ inputFileListPaths = (
-+ );
-+ inputPaths = (
-+ );
-+ name = "Prepare Python Binary Modules";
-+ outputFileListPaths = (
-+ );
-+ outputPaths = (
-+ );
-+ runOnlyForDeploymentPostprocessing = 0;
-+ shellPath = /bin/sh;
-+ shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\necho \"Install app package extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app_packages\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app_packages/ \"$FULL_EXT\"\ndone\necho \"Install app extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n";
-+ showEnvVarsInLog = 0;
-+ };
-+/* End PBXShellScriptBuildPhase section */
++ apple_log_write = PyCFunction_New(&apple_log_write_method, NULL);
++ if (apple_log_write == NULL) {
++ goto error;
++ }
+
-+/* 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 /* iOSTestbedTests.m in Sources */,
-+ );
-+ runOnlyForDeploymentPostprocessing = 0;
-+ };
-+/* End PBXSourcesBuildPhase section */
++ // Initialize the logging streams, sending stdout -> Default; stderr -> Error
++ result = PyObject_CallMethod(
++ _apple_support, "init_streams", "Oii",
++ apple_log_write, OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_ERROR);
++ if (result == NULL) {
++ goto error;
++ }
++ goto done;
+
-+/* Begin PBXTargetDependency section */
-+ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = {
-+ isa = PBXTargetDependency;
-+ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */;
-+ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */;
-+ };
-+/* End PBXTargetDependency section */
++error:
++ _PyErr_Print(tstate);
++ status = _PyStatus_ERR("failed to initialize Apple log streams");
+
-+/* Begin PBXVariantGroup section */
-+ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = {
-+ isa = PBXVariantGroup;
-+ children = (
-+ 607A66242B0EFA390010BFC8 /* Base */,
-+ );
-+ name = LaunchScreen.storyboard;
-+ sourceTree = "";
-+ };
-+/* End PBXVariantGroup section */
++done:
++ Py_XDECREF(result);
++ Py_XDECREF(apple_log_write);
++ Py_XDECREF(_apple_support);
++ return status;
++}
+
-+/* 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 = 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 = 12.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 = 12.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 = 12.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 = 12.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 = 12.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 */
++#endif // __APPLE__ && USE_APPLE_SYSTEM_LOG
++
+
+ static void
+ _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
+diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h
+index 1b1a1bdee78..a37f531984e 100644
+--- a/Python/stdlib_module_names.h
++++ b/Python/stdlib_module_names.h
+@@ -5,6 +5,7 @@
+ "__future__",
+ "_abc",
+ "_aix_support",
++"_apple_support",
+ "_ast",
+ "_asyncio",
+ "_bisect",
+@@ -39,6 +40,7 @@
+ "_heapq",
+ "_imp",
+ "_io",
++"_ios_support",
+ "_json",
+ "_locale",
+ "_lsprof",
+diff --git a/config.sub b/config.sub
+index d74fb6deac9..1bb6a05dc11 100755
+--- a/config.sub
++++ b/config.sub
+@@ -1,14 +1,15 @@
+ #! /bin/sh
+ # Configuration validation subroutine script.
+-# Copyright 1992-2021 Free Software Foundation, Inc.
++# Copyright 1992-2024 Free Software Foundation, Inc.
+
+ # shellcheck disable=SC2006,SC2268 # see below for rationale
+
+-timestamp='2021-08-14'
++# Patched 2024-02-03 to include support for arm64_32 and iOS/tvOS/watchOS simulators
++timestamp='2024-01-01'
+
+ # This file is free software; you can redistribute it and/or modify it
+ # under the terms of the GNU General Public License as published by
+-# the Free Software Foundation; either version 3 of the License, or
++# the Free Software Foundation, either version 3 of the License, or
+ # (at your option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful, but
+@@ -76,13 +77,13 @@
+ version="\
+ GNU config.sub ($timestamp)
+
+-Copyright 1992-2021 Free Software Foundation, Inc.
++Copyright 1992-2024 Free Software Foundation, Inc.
+
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+ help="
+-Try \`$me --help' for more information."
++Try '$me --help' for more information."
+
+ # Parse command line
+ while test $# -gt 0 ; do
+@@ -130,7 +131,7 @@
+ # Separate into logical components for further validation
+ case $1 in
+ *-*-*-*-*)
+- echo Invalid configuration \`"$1"\': more than four components >&2
++ echo "Invalid configuration '$1': more than four components" >&2
+ exit 1
+ ;;
+ *-*-*-*)
+@@ -145,7 +146,8 @@
+ nto-qnx* | linux-* | uclinux-uclibc* \
+ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
+ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
+- | storm-chaos* | os2-emx* | rtmk-nova*)
++ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \
++ | windows-* )
+ basic_machine=$field1
+ basic_os=$maybe_os
+ ;;
+@@ -943,7 +945,7 @@
+ EOF
+ IFS=$saved_IFS
+ ;;
+- # We use `pc' rather than `unknown'
++ # We use 'pc' rather than 'unknown'
+ # because (1) that's what they normally are, and
+ # (2) the word "unknown" tends to confuse beginning users.
+ i*86 | x86_64)
+@@ -1020,6 +1022,11 @@
+ ;;
+
+ # Here we normalize CPU types with a missing or matching vendor
++ armh-unknown | armh-alt)
++ cpu=armv7l
++ vendor=alt
++ basic_os=${basic_os:-linux-gnueabihf}
++ ;;
+ dpx20-unknown | dpx20-bull)
+ cpu=rs6000
+ vendor=bull
+@@ -1070,7 +1077,7 @@
+ pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+ cpu=i586
+ ;;
+- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
++ pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*)
+ cpu=i686
+ ;;
+ pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+@@ -1121,7 +1128,7 @@
+ xscale-* | xscalee[bl]-*)
+ cpu=`echo "$cpu" | sed 's/^xscale/arm/'`
+ ;;
+- arm64-*)
++ arm64-* | aarch64le-* | arm64_32-*)
+ cpu=aarch64
+ ;;
+
+@@ -1175,7 +1182,7 @@
+ case $cpu in
+ 1750a | 580 \
+ | a29k \
+- | aarch64 | aarch64_be \
++ | aarch64 | aarch64_be | aarch64c | arm64ec \
+ | abacus \
+ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
+ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
+@@ -1194,50 +1201,29 @@
+ | d10v | d30v | dlx | dsp16xx \
+ | e2k | elxsi | epiphany \
+ | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
++ | javascript \
+ | h8300 | h8500 \
+ | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+ | hexagon \
+ | i370 | i*86 | i860 | i960 | ia16 | ia64 \
+ | ip2k | iq2000 \
+ | k1om \
++ | kvx \
+ | le32 | le64 \
+ | lm32 \
+- | loongarch32 | loongarch64 | loongarchx32 \
++ | loongarch32 | loongarch64 \
+ | m32c | m32r | m32rle \
+ | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
+ | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
+ | m88110 | m88k | maxq | mb | mcore | mep | metag \
+ | microblaze | microblazeel \
+- | mips | mipsbe | mipseb | mipsel | mipsle \
+- | mips16 \
+- | mips64 | mips64eb | mips64el \
+- | mips64octeon | mips64octeonel \
+- | mips64orion | mips64orionel \
+- | mips64r5900 | mips64r5900el \
+- | mips64vr | mips64vrel \
+- | mips64vr4100 | mips64vr4100el \
+- | mips64vr4300 | mips64vr4300el \
+- | mips64vr5000 | mips64vr5000el \
+- | mips64vr5900 | mips64vr5900el \
+- | mipsisa32 | mipsisa32el \
+- | mipsisa32r2 | mipsisa32r2el \
+- | mipsisa32r3 | mipsisa32r3el \
+- | mipsisa32r5 | mipsisa32r5el \
+- | mipsisa32r6 | mipsisa32r6el \
+- | mipsisa64 | mipsisa64el \
+- | mipsisa64r2 | mipsisa64r2el \
+- | mipsisa64r3 | mipsisa64r3el \
+- | mipsisa64r5 | mipsisa64r5el \
+- | mipsisa64r6 | mipsisa64r6el \
+- | mipsisa64sb1 | mipsisa64sb1el \
+- | mipsisa64sr71k | mipsisa64sr71kel \
+- | mipsr5900 | mipsr5900el \
+- | mipstx39 | mipstx39el \
++ | mips* \
+ | mmix \
+ | mn10200 | mn10300 \
+ | moxie \
+ | mt \
+ | msp430 \
++ | nanomips* \
+ | nds32 | nds32le | nds32be \
+ | nfp \
+ | nios | nios2 | nios2eb | nios2el \
+@@ -1269,6 +1255,7 @@
+ | ubicom32 \
+ | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
+ | vax \
++ | vc4 \
+ | visium \
+ | w65 \
+ | wasm32 | wasm64 \
+@@ -1280,7 +1267,7 @@
+ ;;
+
+ *)
+- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
++ echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2
+ exit 1
+ ;;
+ esac
+@@ -1301,11 +1288,12 @@
+
+ # Decode manufacturer-specific aliases for certain operating systems.
+
+-if test x$basic_os != x
++if test x"$basic_os" != x
+ then
+
+-# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just
++# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
+ # set os.
++obj=
+ case $basic_os in
+ gnu/linux*)
+ kernel=linux
+@@ -1336,6 +1324,10 @@
+ kernel=linux
+ os=`echo "$basic_os" | sed -e 's|linux|gnu|'`
+ ;;
++ managarm*)
++ kernel=managarm
++ os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'`
++ ;;
+ *)
+ kernel=
+ os=$basic_os
+@@ -1501,10 +1493,16 @@
+ os=eabi
+ ;;
+ *)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ esac
+ ;;
++ aout* | coff* | elf* | pe*)
++ # These are machine code file formats, not OSes
++ obj=$os
++ os=
++ ;;
+ *)
+ # No normalization, but not necessarily accepted, that comes below.
+ ;;
+@@ -1523,12 +1521,15 @@
+ # system, and we'll never get to this point.
+
+ kernel=
++obj=
+ case $cpu-$vendor in
+ score-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ spu-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ *-acorn)
+ os=riscix1.2
+@@ -1538,28 +1539,35 @@
+ os=gnu
+ ;;
+ arm*-semi)
+- os=aout
++ os=
++ obj=aout
+ ;;
+ c4x-* | tic4x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ c8051-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ clipper-intergraph)
+ os=clix
+ ;;
+ hexagon-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ tic54x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ tic55x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ tic6x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ # This must come before the *-dec entry.
+ pdp10-*)
+@@ -1581,19 +1589,24 @@
+ os=sunos3
+ ;;
+ m68*-cisco)
+- os=aout
++ os=
++ obj=aout
+ ;;
+ mep-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ mips*-cisco)
+- os=elf
++ os=
++ obj=elf
+ ;;
+- mips*-*)
+- os=elf
++ mips*-*|nanomips*-*)
++ os=
++ obj=elf
+ ;;
+ or32-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-tti) # must be before sparc entry or we get the wrong os.
+ os=sysv3
+@@ -1602,7 +1615,8 @@
+ os=sunos4.1.1
+ ;;
+ pru-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ *-be)
+ os=beos
+@@ -1683,10 +1697,12 @@
+ os=uxpv
+ ;;
+ *-rom68k)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-*bug)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-apple)
+ os=macos
+@@ -1704,10 +1720,11 @@
+
+ fi
+
+-# Now, validate our (potentially fixed-up) OS.
++# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ).
++
+ case $os in
+ # Sometimes we do "kernel-libc", so those need to count as OSes.
+- musl* | newlib* | relibc* | uclibc*)
++ llvm* | musl* | newlib* | relibc* | uclibc*)
+ ;;
+ # Likewise for "kernel-abi"
+ eabi* | gnueabi*)
+@@ -1715,6 +1732,9 @@
+ # VxWorks passes extra cpu info in the 4th filed.
+ simlinux | simwindows | spe)
+ ;;
++ # See `case $cpu-$os` validation below
++ ghcjs)
++ ;;
+ # Now accept the basic system types.
+ # The portable systems comes first.
+ # Each alternative MUST end in a * to match a version number.
+@@ -1723,7 +1743,7 @@
+ | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
+ | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \
+ | hiux* | abug | nacl* | netware* | windows* \
+- | os9* | macos* | osx* | ios* \
++ | os9* | macos* | osx* | ios* | tvos* | watchos* \
+ | mpw* | magic* | mmixware* | mon960* | lnews* \
+ | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
+ | aos* | aros* | cloudabi* | sortix* | twizzler* \
+@@ -1732,11 +1752,11 @@
+ | mirbsd* | netbsd* | dicos* | openedition* | ose* \
+ | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
+ | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
+- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
+- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
++ | bosx* | nextstep* | cxux* | oabi* \
++ | ptx* | ecoff* | winnt* | domain* | vsta* \
+ | udi* | lites* | ieee* | go32* | aux* | hcos* \
+ | chorusrdb* | cegcc* | glidix* | serenity* \
+- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
++ | cygwin* | msys* | moss* | proelf* | rtems* \
+ | midipix* | mingw32* | mingw64* | mint* \
+ | uxpv* | beos* | mpeix* | udk* | moxiebox* \
+ | interix* | uwin* | mks* | rhapsody* | darwin* \
+@@ -1748,49 +1768,119 @@
+ | skyos* | haiku* | rdos* | toppers* | drops* | es* \
+ | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
+ | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
+- | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr*)
++ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
++ | fiwix* | mlibc* | cos* | mbr* | ironclad* )
+ ;;
+ # This one is extra strict with allowed versions
+ sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
+ # Don't forget version if it is 3.2v4 or newer.
+ ;;
++ # This refers to builds using the UEFI calling convention
++ # (which depends on the architecture) and PE file format.
++ # Note that this is both a different calling convention and
++ # different file format than that of GNU-EFI
++ # (x86_64-w64-mingw32).
++ uefi)
++ ;;
+ none)
+ ;;
++ kernel* | msvc* )
++ # Restricted further below
++ ;;
++ '')
++ if test x"$obj" = x
++ then
++ echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2
++ fi
++ ;;
+ *)
+- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
++ echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2
++ exit 1
++ ;;
++esac
++
++case $obj in
++ aout* | coff* | elf* | pe*)
++ ;;
++ '')
++ # empty is fine
++ ;;
++ *)
++ echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2
++ exit 1
++ ;;
++esac
++
++# Here we handle the constraint that a (synthetic) cpu and os are
++# valid only in combination with each other and nowhere else.
++case $cpu-$os in
++ # The "javascript-unknown-ghcjs" triple is used by GHC; we
++ # accept it here in order to tolerate that, but reject any
++ # variations.
++ javascript-ghcjs)
++ ;;
++ javascript-* | *-ghcjs)
++ echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2
+ exit 1
+ ;;
+ esac
+
+ # As a final step for OS-related things, validate the OS-kernel combination
+ # (given a valid OS), if there is a kernel.
+-case $kernel-$os in
+- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
+- | linux-musl* | linux-relibc* | linux-uclibc* )
++case $kernel-$os-$obj in
++ linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \
++ | linux-mlibc*- | linux-musl*- | linux-newlib*- \
++ | linux-relibc*- | linux-uclibc*- )
++ ;;
++ uclinux-uclibc*- )
+ ;;
+- uclinux-uclibc* )
++ managarm-mlibc*- | managarm-kernel*- )
+ ;;
+- -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* )
++ windows*-msvc*-)
++ ;;
++ -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \
++ | -uclibc*- )
+ # These are just libc implementations, not actual OSes, and thus
+ # require a kernel.
+- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
++ echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2
+ exit 1
+ ;;
+- kfreebsd*-gnu* | kopensolaris*-gnu*)
++ -kernel*- )
++ echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2
++ exit 1
+ ;;
+- vxworks-simlinux | vxworks-simwindows | vxworks-spe)
++ *-kernel*- )
++ echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2
++ exit 1
+ ;;
+- nto-qnx*)
++ *-msvc*- )
++ echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2
++ exit 1
+ ;;
+- os2-emx)
++ kfreebsd*-gnu*- | kopensolaris*-gnu*-)
+ ;;
+- *-eabi* | *-gnueabi*)
++ vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-)
+ ;;
+- -*)
++ nto-qnx*-)
++ ;;
++ os2-emx-)
++ ;;
++ *-eabi*- | *-gnueabi*-)
++ ;;
++ ios*-simulator- | tvos*-simulator- | watchos*-simulator- )
++ ;;
++ none--*)
++ # None (no kernel, i.e. freestanding / bare metal),
++ # can be paired with an machine code file format
++ ;;
++ -*-)
+ # Blank kernel with real OS is always fine.
+ ;;
+- *-*)
+- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
++ --*)
++ # Blank kernel and OS with real machine code file format is always fine.
++ ;;
++ *-*-*)
++ echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2
+ exit 1
+ ;;
+ esac
+@@ -1873,7 +1963,7 @@
+ ;;
+ esac
+
+-echo "$cpu-$vendor-${kernel:+$kernel-}$os"
++echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}"
+ exit
+
+ # Local variables:
+diff --git a/configure b/configure
+index 89edc42f45c..707a5f011da 100755
+--- a/configure
++++ b/configure
+@@ -976,10 +976,14 @@
+ CFLAGS
+ CC
+ HAS_XCRUN
++WATCHOS_DEPLOYMENT_TARGET
++TVOS_DEPLOYMENT_TARGET
++IPHONEOS_DEPLOYMENT_TARGET
+ EXPORT_MACOSX_DEPLOYMENT_TARGET
+ CONFIGURE_MACOSX_DEPLOYMENT_TARGET
+ _PYTHON_HOST_PLATFORM
+-MACHDEP
++APP_STORE_COMPLIANCE_PATCH
++INSTALLTARGETS
+ FRAMEWORKINSTALLAPPSPREFIX
+ FRAMEWORKUNIXTOOLSPREFIX
+ FRAMEWORKPYTHONW
+@@ -987,6 +991,8 @@
+ FRAMEWORKALTINSTALLFIRST
+ FRAMEWORKINSTALLLAST
+ FRAMEWORKINSTALLFIRST
++RESSRCDIR
++PYTHONFRAMEWORKINSTALLNAMEPREFIX
+ PYTHONFRAMEWORKINSTALLDIR
+ PYTHONFRAMEWORKPREFIX
+ PYTHONFRAMEWORKDIR
+@@ -996,6 +1002,7 @@
+ LIPO_32BIT_FLAGS
+ ARCH_RUN_32BIT
+ UNIVERSALSDK
++MACHDEP
+ PKG_CONFIG_LIBDIR
+ PKG_CONFIG_PATH
+ PKG_CONFIG
+@@ -1071,6 +1078,7 @@
+ with_universal_archs
+ with_framework_name
+ enable_framework
++with_app_store_compliance
+ with_emscripten_target
+ enable_wasm_dynamic_linking
+ enable_wasm_pthreads
+@@ -1845,6 +1853,10 @@
+ specify the name for the python framework on macOS
+ only valid when --enable-framework is set. see
+ Mac/README.rst (default is 'Python')
++ --with-app-store-compliance=[PATCH-FILE]
++ Enable any patches required for compiliance with app
++ stores. Optional PATCH-FILE specifies the custom
++ patch to apply.
+ --with-emscripten-target=[browser|node]
+ Emscripten platform
+ --with-suffix=SUFFIX set executable suffix to SUFFIX (default is empty,
+@@ -4015,6 +4027,164 @@
+ as_fn_error $? "pkg-config is required" "$LINENO" 5]
+ fi
+
++# Set name for machine-dependent library files
+
-+/* 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 */;
-+}
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/AppDelegate.h
-@@ -0,0 +1,11 @@
-+//
-+// AppDelegate.h
-+// iOSTestbed
-+//
++{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5
++printf %s "checking MACHDEP... " >&6; }
++if test -z "$MACHDEP"
++then
++ # avoid using uname for cross builds
++ if test "$cross_compiling" = yes; then
++ # ac_sys_system and ac_sys_release are used for setting
++ # a lot of different things including 'define_xopen_source'
++ # in the case statement below.
++ case "$host" in
++ *-*-linux-android*)
++ ac_sys_system=Linux-android
++ ;;
++ *-*-linux*)
++ ac_sys_system=Linux
++ ;;
++ *-*-cygwin*)
++ ac_sys_system=Cygwin
++ ;;
++ *-apple-ios*)
++ ac_sys_system=iOS
++ ;;
++ *-apple-tvos*)
++ ac_sys_system=tvOS
++ ;;
++ *-apple-watchos*)
++ ac_sys_system=watchOS
++ ;;
++ *-*-vxworks*)
++ ac_sys_system=VxWorks
++ ;;
++ *-*-emscripten)
++ ac_sys_system=Emscripten
++ ;;
++ *-*-wasi)
++ ac_sys_system=WASI
++ ;;
++ *)
++ # for now, limit cross builds to known configurations
++ MACHDEP="unknown"
++ as_fn_error $? "cross build not supported for $host" "$LINENO" 5
++ esac
++ ac_sys_release=
++ else
++ ac_sys_system=`uname -s`
++ if test "$ac_sys_system" = "AIX" \
++ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
++ ac_sys_release=`uname -v`
++ else
++ ac_sys_release=`uname -r`
++ fi
++ fi
++ ac_md_system=`echo $ac_sys_system |
++ tr -d '/ ' | tr '[A-Z]' '[a-z]'`
++ ac_md_release=`echo $ac_sys_release |
++ tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'`
++ MACHDEP="$ac_md_system$ac_md_release"
+
-+#import
++ case $MACHDEP in
++ aix*) MACHDEP="aix";;
++ linux*) MACHDEP="linux";;
++ cygwin*) MACHDEP="cygwin";;
++ darwin*) MACHDEP="darwin";;
++ '') MACHDEP="unknown";;
++ esac
+
-+@interface AppDelegate : UIResponder
++ if test "$ac_sys_system" = "SunOS"; then
++ # For Solaris, there isn't an OS version specific macro defined
++ # in most compilers, so we define one here.
++ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'`
+
++printf "%s\n" "#define Py_SUNOS_VERSION $SUNOS_VERSION" >>confdefs.h
+
-+@end
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/AppDelegate.m
-@@ -0,0 +1,19 @@
-+//
-+// AppDelegate.m
-+// iOSTestbed
-+//
++ fi
++fi
++{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5
++printf "%s\n" "\"$MACHDEP\"" >&6; }
+
-+#import "AppDelegate.h"
++# On cross-compile builds, configure will look for a host-specific compiler by
++# prepending the user-provided host triple to the required binary name.
++#
++# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
++# 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
++# these tools; the binary names of these scripts are better defaults than "gcc".
++# This only requires that the user put the platform scripts folder (e.g.,
++# "iOS/Resources/bin") in their path, rather than defining platform-specific
++# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to
++# either put the platform scripts folder in the path, or specify CC etc,
++# configure will fail.
++if test -z "$AR"; then
++ case "$host" in
++ 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 ;;
+
-+@interface AppDelegate ()
++ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;;
++ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;;
++ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;;
+
-+@end
++ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;;
++ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;;
++ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;;
++ *)
++ esac
++fi
++if test -z "$CC"; then
++ case "$host" in
++ 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 ;;
+
-+@implementation AppDelegate
++ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;;
++ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;;
++ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;;
++
++ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;;
++ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;;
++ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;;
++ *)
++ esac
++fi
++if test -z "$CPP"; then
++ case "$host" in
++ 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 ;;
+
++ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;;
++ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;;
++ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;;
+
-+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-+ return YES;
-+}
++ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;;
++ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;;
++ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;;
++ *)
++ esac
++fi
++if test -z "$CXX"; then
++ case "$host" in
++ 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++ ;;
+
-+@end
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json
-@@ -0,0 +1,11 @@
-+{
-+ "colors" : [
-+ {
-+ "idiom" : "universal"
-+ }
-+ ],
-+ "info" : {
-+ "author" : "xcode",
-+ "version" : 1
-+ }
-+}
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json
-@@ -0,0 +1,13 @@
-+{
-+ "images" : [
-+ {
-+ "idiom" : "universal",
-+ "platform" : "ios",
-+ "size" : "1024x1024"
-+ }
-+ ],
-+ "info" : {
-+ "author" : "xcode",
-+ "version" : 1
-+ }
-+}
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/Assets.xcassets/Contents.json
-@@ -0,0 +1,6 @@
-+{
-+ "info" : {
-+ "author" : "xcode",
-+ "version" : 1
-+ }
-+}
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard
-@@ -0,0 +1,9 @@
-+
-+
-+
-+
-+
-+
-+
-+
-+
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/app/README
-@@ -0,0 +1,7 @@
-+This folder can contain any Python application code.
++ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;;
++ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;;
++ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;;
+
-+During the build, any binary modules found in this folder will be processed into
-+iOS Framework form.
++ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;;
++ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;;
++ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;;
++ *)
++ esac
++fi
+
-+When the test suite runs, this folder will be on the PYTHONPATH, and will be the
-+working directory for the test suite.
---- /dev/null
-+++ b/iOS/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.
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --enable-universalsdk" >&5
+ printf %s "checking for --enable-universalsdk... " >&6; }
+ # Check whether --enable-universalsdk was given.
+@@ -4130,111 +4300,195 @@
+ enableval=$enable_framework;
+ case $enableval in
+ yes)
+- enableval=/Library/Frameworks
++ case $ac_sys_system in
++ Darwin) enableval=/Library/Frameworks ;;
++ iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
++ tvOS) enableval=Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;;
++ watchOS) enableval=Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;;
++ *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
++ esac
+ esac
+
-+During the build, any binary modules found in this folder will be processed into
-+iOS Framework form.
+ case $enableval in
+ no)
+- PYTHONFRAMEWORK=
+- PYTHONFRAMEWORKDIR=no-framework
+- PYTHONFRAMEWORKPREFIX=
+- PYTHONFRAMEWORKINSTALLDIR=
+- FRAMEWORKINSTALLFIRST=
+- FRAMEWORKINSTALLLAST=
+- FRAMEWORKALTINSTALLFIRST=
+- FRAMEWORKALTINSTALLLAST=
+- FRAMEWORKPYTHONW=
+- if test "x${prefix}" = "xNONE"; then
+- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+- else
+- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+- fi
+- enable_framework=
++ case $ac_sys_system in
++ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;;
++ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;;
++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;;
++ *)
++ PYTHONFRAMEWORK=
++ PYTHONFRAMEWORKDIR=no-framework
++ PYTHONFRAMEWORKPREFIX=
++ PYTHONFRAMEWORKINSTALLDIR=
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
++ RESSRCDIR=
++ FRAMEWORKINSTALLFIRST=
++ FRAMEWORKINSTALLLAST=
++ FRAMEWORKALTINSTALLFIRST=
++ FRAMEWORKALTINSTALLLAST=
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="commoninstall bininstall maninstall"
+
-+When the test suite runs, this folder will be on the PYTHONPATH.
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/dylib-Info-template.plist
-@@ -0,0 +1,26 @@
-+
-+
-+
-+
-+ CFBundleDevelopmentRegion
-+ en
-+ CFBundleExecutable
-+
-+ CFBundleIdentifier
-+
-+ CFBundleInfoDictionaryVersion
-+ 6.0
-+ CFBundlePackageType
-+ APPL
-+ CFBundleShortVersionString
-+ 1.0
-+ CFBundleSupportedPlatforms
-+
-+ iPhoneOS
-+
-+ MinimumOSVersion
-+ 12.0
-+ CFBundleVersion
-+ 1
-+
-+
---- /dev/null
-+++ b/iOS/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
-+
-+ TestArgs
-+
-+ test
-+ -uall
-+ -W
-+
-+
-+ UIApplicationSceneManifest
-+
-+ UIApplicationSupportsMultipleScenes
-+
-+ UISceneConfigurations
-+
-+
-+
-+
---- /dev/null
-+++ b/iOS/testbed/iOSTestbed/main.m
-@@ -0,0 +1,16 @@
-+//
-+// main.m
-+// iOSTestbed
-+//
++ if test "x${prefix}" = "xNONE"; then
++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
++ else
++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
++ fi
++ enable_framework=
++ esac
+ ;;
+ *)
+ PYTHONFRAMEWORKPREFIX="${enableval}"
+ PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR
+- FRAMEWORKINSTALLFIRST="frameworkinstallstructure"
+- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure "
+- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
+- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
+- FRAMEWORKPYTHONW="frameworkpythonw"
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+-
+- if test "x${prefix}" = "xNONE" ; then
+- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+
+- else
+- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+- fi
+-
+- case "${enableval}" in
+- /System*)
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+- if test "${prefix}" = "NONE" ; then
+- # See below
+- FRAMEWORKUNIXTOOLSPREFIX="/usr"
+- fi
+- ;;
++ case $ac_sys_system in #(
++ Darwin) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
++ FRAMEWORKPYTHONW="frameworkpythonw"
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ INSTALLTARGETS="commoninstall bininstall maninstall"
+
-+#import
-+#import "AppDelegate.h"
++ if test "x${prefix}" = "xNONE" ; then
++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+
-+int main(int argc, char * argv[]) {
-+ NSString * appDelegateClassName;
-+ @autoreleasepool {
-+ appDelegateClassName = NSStringFromClass([AppDelegate class]);
++ else
++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
++ fi
+
+- /Library*)
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+- ;;
++ case "${enableval}" in
++ /System*)
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ if test "${prefix}" = "NONE" ; then
++ # See below
++ FRAMEWORKUNIXTOOLSPREFIX="/usr"
++ fi
++ ;;
+
-+ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
-+ }
-+}
---- /dev/null
-+++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
-@@ -0,0 +1,160 @@
-+#import
-+#import
++ /Library*)
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ ;;
+
-+@interface iOSTestbedTests : XCTestCase
++ */Library/Frameworks)
++ MDIR="`dirname "${enableval}"`"
++ MDIR="`dirname "${MDIR}"`"
++ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
+
-+@end
++ if test "${prefix}" = "NONE"; then
++ # User hasn't specified the
++ # --prefix option, but wants to install
++ # the framework in a non-default location,
++ # ensure that the compatibility links get
++ # installed relative to that prefix as well
++ # instead of in /usr/local.
++ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
++ fi
++ ;;
+
+- */Library/Frameworks)
+- MDIR="`dirname "${enableval}"`"
+- MDIR="`dirname "${MDIR}"`"
+- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
+-
+- if test "${prefix}" = "NONE"; then
+- # User hasn't specified the
+- # --prefix option, but wants to install
+- # the framework in a non-default location,
+- # ensure that the compatibility links get
+- # installed relative to that prefix as well
+- # instead of in /usr/local.
+- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
+- fi
+- ;;
++ *)
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ ;;
++ esac
+
+- *)
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+- ;;
++ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix}
++ RESSRCDIR=Mac/Resources/framework
+
-+@implementation iOSTestbedTests
++ # Add files for Mac specific code to the list of output
++ # files:
++ ac_config_files="$ac_config_files Mac/Makefile"
+
++ ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile"
+
-+- (void)testPython {
-+ const char **argv;
-+ int exit_code;
-+ int failed;
-+ PyStatus status;
-+ PyPreConfig preconfig;
-+ PyConfig config;
-+ PyObject *sys_module;
-+ PyObject *sys_path_attr;
-+ NSArray *test_args;
-+ NSString *python_home;
-+ NSString *path;
-+ wchar_t *wtmp_str;
++ ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist"
+
-+ NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
++ ac_config_files="$ac_config_files Mac/Resources/app/Info.plist"
+
-+ // 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);
++ ;;
++ iOS) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
-+ // 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`
-+ test_args = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"TestArgs"];
-+ if (test_args == NULL) {
-+ NSLog(@"Unable to identify test arguments.");
-+ }
-+ argv = malloc(sizeof(char *) * ([test_args count] + 1));
-+ argv[0] = "iOSTestbed";
-+ for (int i = 1; i < [test_args count]; i++) {
-+ argv[i] = [[test_args objectAtIndex:i] UTF8String];
-+ }
-+ NSLog(@"Test command: %@", test_args);
++ prefix=$PYTHONFRAMEWORKPREFIX
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
++ RESSRCDIR=Apple/iOS/Resources
+
-+ // Generate an isolated Python configuration.
-+ NSLog(@"Configuring isolated Python...");
-+ PyPreConfig_InitIsolatedConfig(&preconfig);
-+ PyConfig_InitIsolatedConfig(&config);
++ ac_config_files="$ac_config_files Apple/iOS/Resources/Info.plist"
+
-+ // 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:0] UTF8String], NULL);
-+ // For debugging - enable verbose mode.
-+ // config.verbose = 1;
++ ;;
++ tvOS) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++
++ prefix=$PYTHONFRAMEWORKPREFIX
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
++ RESSRCDIR=Apple/tvOS/Resources
++
++ ac_config_files="$ac_config_files Apple/tvOS/Resources/Info.plist"
++
++ ;;
++ watchOS) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="libinstall inclinstall sharedinstall"
++
++ prefix=$PYTHONFRAMEWORKPREFIX
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
++ RESSRCDIR=Apple/watchOS/Resources
++
++ ac_config_files="$ac_config_files Apple/watchOS/Resources/Info.plist"
++
++ ;;
++ *)
++ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5
++ ;;
++ esac
+ esac
+
+- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
+-
+- # Add files for Mac specific code to the list of output
+- # files:
+- ac_config_files="$ac_config_files Mac/Makefile"
+-
+- ac_config_files="$ac_config_files Mac/PythonLauncher/Makefile"
+-
+- ac_config_files="$ac_config_files Mac/Resources/framework/Info.plist"
+-
+- ac_config_files="$ac_config_files Mac/Resources/app/Info.plist"
++else $as_nop
+
++ case $ac_sys_system in
++ iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;;
++ tvOS) as_fn_error $? "tvOS builds must use --enable-framework" "$LINENO" 5 ;;
++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;;
++ *)
++ PYTHONFRAMEWORK=
++ PYTHONFRAMEWORKDIR=no-framework
++ PYTHONFRAMEWORKPREFIX=
++ PYTHONFRAMEWORKINSTALLDIR=
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
++ RESSRCDIR=
++ FRAMEWORKINSTALLFIRST=
++ FRAMEWORKINSTALLLAST=
++ FRAMEWORKALTINSTALLFIRST=
++ FRAMEWORKALTINSTALLLAST=
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="commoninstall bininstall maninstall"
++ if test "x${prefix}" = "xNONE" ; then
++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
++ else
++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
++ fi
++ enable_framework=
+ esac
+
+-else $as_nop
++fi
+
+- PYTHONFRAMEWORK=
+- PYTHONFRAMEWORKDIR=no-framework
+- PYTHONFRAMEWORKPREFIX=
+- PYTHONFRAMEWORKINSTALLDIR=
+- FRAMEWORKINSTALLFIRST=
+- FRAMEWORKINSTALLLAST=
+- FRAMEWORKALTINSTALLFIRST=
+- FRAMEWORKALTINSTALLLAST=
+- FRAMEWORKPYTHONW=
+- if test "x${prefix}" = "xNONE" ; then
+- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+- else
+- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+- fi
+- enable_framework=
+
+
+-fi
+
+
+
+@@ -4253,76 +4507,52 @@
+ printf "%s\n" "#define _PYTHONFRAMEWORK \"${PYTHONFRAMEWORK}\"" >>confdefs.h
+
+
+-# Set name for machine-dependent library files
+-
+-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5
+-printf %s "checking MACHDEP... " >&6; }
+-if test -z "$MACHDEP"
+-then
+- # avoid using uname for cross builds
+- if test "$cross_compiling" = yes; then
+- # ac_sys_system and ac_sys_release are used for setting
+- # a lot of different things including 'define_xopen_source'
+- # in the case statement below.
+- case "$host" in
+- *-*-linux-android*)
+- ac_sys_system=Linux-android
+- ;;
+- *-*-linux*)
+- ac_sys_system=Linux
+- ;;
+- *-*-cygwin*)
+- ac_sys_system=Cygwin
+- ;;
+- *-*-vxworks*)
+- ac_sys_system=VxWorks
+- ;;
+- *-*-emscripten)
+- ac_sys_system=Emscripten
+- ;;
+- *-*-wasi)
+- ac_sys_system=WASI
+- ;;
+- *)
+- # for now, limit cross builds to known configurations
+- MACHDEP="unknown"
+- as_fn_error $? "cross build not supported for $host" "$LINENO" 5
+- esac
+- ac_sys_release=
+- else
+- ac_sys_system=`uname -s`
+- if test "$ac_sys_system" = "AIX" \
+- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
+- ac_sys_release=`uname -v`
+- else
+- ac_sys_release=`uname -r`
+- fi
+- fi
+- ac_md_system=`echo $ac_sys_system |
+- tr -d '/ ' | tr '[A-Z]' '[a-z]'`
+- ac_md_release=`echo $ac_sys_release |
+- tr -d '/ ' | sed 's/^[A-Z]\.//' | sed 's/\..*//'`
+- MACHDEP="$ac_md_system$ac_md_release"
++{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-app-store-compliance" >&5
++printf %s "checking for --with-app-store-compliance... " >&6; }
+
+- case $MACHDEP in
+- aix*) MACHDEP="aix";;
+- linux*) MACHDEP="linux";;
+- cygwin*) MACHDEP="cygwin";;
+- darwin*) MACHDEP="darwin";;
+- '') MACHDEP="unknown";;
++# Check whether --with-app_store_compliance was given.
++if test ${with_app_store_compliance+y}
++then :
++ withval=$with_app_store_compliance;
++ case "$withval" in
++ yes)
++ case $ac_sys_system in
++ Darwin|iOS|tvOS|watchOS)
++ # iOS/tvOS/watchOS is able to share the macOS patch
++ 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 ;;
++ esac
++ { 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; }
++ ;;
++ *)
++ APP_STORE_COMPLIANCE_PATCH="${withval}"
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: applying custom app store compliance patch" >&5
++printf "%s\n" "applying custom app store compliance patch" >&6; }
++ ;;
+ esac
+
+- if test "$ac_sys_system" = "SunOS"; then
+- # For Solaris, there isn't an OS version specific macro defined
+- # in most compilers, so we define one here.
+- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\(0-9\)$!.0\1!g' | tr -d '.'`
++else $as_nop
+
+-printf "%s\n" "#define Py_SUNOS_VERSION $SUNOS_VERSION" >>confdefs.h
++ case $ac_sys_system in
++ iOS|tvOS|watchOS)
++ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch
++ 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; }
++ ;;
++ *)
++ # No default app compliance patching on any other platform
++ APP_STORE_COMPLIANCE_PATCH=
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not patching for app store compliance" >&5
++printf "%s\n" "not patching for app store compliance" >&6; }
++ ;;
++ esac
+
+- fi
+ fi
+-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5
+-printf "%s\n" "\"$MACHDEP\"" >&6; }
+
-+ 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);
+
+
+ if test "$cross_compiling" = yes; then
+@@ -4330,27 +4560,93 @@
+ *-*-linux*)
+ case "$host_cpu" in
+ arm*)
+- _host_cpu=arm
++ _host_ident=arm
+ ;;
+ *)
+- _host_cpu=$host_cpu
++ _host_ident=$host_cpu
+ esac
+ ;;
+ *-*-cygwin*)
+- _host_cpu=
++ _host_ident=
++ ;;
++ *-apple-ios*)
++ _host_os=`echo $host | cut -d '-' -f3`
++ _host_device=`echo $host | cut -d '-' -f4`
++ _host_device=${_host_device:=os}
+
-+ // 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;
-+ }
++ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking iOS deployment target" >&5
++printf %s "checking iOS deployment target... " >&6; }
++ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3}
++ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0}
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $IPHONEOS_DEPLOYMENT_TARGET" >&5
++printf "%s\n" "$IPHONEOS_DEPLOYMENT_TARGET" >&6; }
+
-+ NSLog(@"Configure argc/argv...");
-+ status = PyConfig_SetBytesArgv(&config, [test_args count], (char**) argv);
-+ if (PyStatus_Exception(status)) {
-+ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg);
-+ PyConfig_Clear(&config);
-+ return;
-+ }
++ case "$host_cpu" in
++ aarch64)
++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device}
++ ;;
++ *)
++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device}
++ ;;
++ esac
++ ;;
++ *-apple-tvos*)
++ _host_os=`echo $host | cut -d '-' -f3`
++ _host_device=`echo $host | cut -d '-' -f4`
++ _host_device=${_host_device:=os}
+
-+ 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;
-+ }
++ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking tvOS deployment target" >&5
++printf %s "checking tvOS deployment target... " >&6; }
++ TVOS_DEPLOYMENT_TARGET=${_host_os:4}
++ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0}
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TVOS_DEPLOYMENT_TARGET" >&5
++printf "%s\n" "$TVOS_DEPLOYMENT_TARGET" >&6; }
+
-+ sys_module = PyImport_ImportModule("sys");
-+ if (sys_module == NULL) {
-+ XCTFail(@"Could not import sys module");
-+ return;
-+ }
++ case "$host_cpu" in
++ aarch64)
++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device}
++ ;;
++ *)
++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device}
++ ;;
++ esac
++ ;;
++ *-apple-watchos*)
++ _host_os=`echo $host | cut -d '-' -f3`
++ _host_device=`echo $host | cut -d '-' -f4`
++ _host_device=${_host_device:=os}
+
-+ sys_path_attr = PyObject_GetAttrString(sys_module, "path");
-+ if (sys_path_attr == NULL) {
-+ XCTFail(@"Could not access sys.path");
-+ return;
-+ }
++ # WATCHOS_DEPLOYMENT_TARGET is the minimum supported watchOS version
++ { 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}
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $WATCHOS_DEPLOYMENT_TARGET" >&5
++printf "%s\n" "$WATCHOS_DEPLOYMENT_TARGET" >&6; }
+
-+ // Add the app packages path
-+ path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil];
-+ NSLog(@"App packages 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 packages to sys.path");
-+ return;
-+ }
-+ PyMem_RawFree(wtmp_str);
++ case "$host_cpu" in
++ aarch64)
++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device}
++ ;;
++ *)
++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device}
++ ;;
++ esac
+ ;;
+ *-*-vxworks*)
+- _host_cpu=$host_cpu
++ _host_ident=$host_cpu
+ ;;
+ wasm32-*-* | wasm64-*-*)
+- _host_cpu=$host_cpu
++ _host_ident=$host_cpu
+ ;;
+ *)
+ # for now, limit cross builds to known configurations
+ MACHDEP="unknown"
+ as_fn_error $? "cross build not supported for $host" "$LINENO" 5
+ esac
+- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}"
++ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}"
+ fi
+
+ # Some systems cannot stand _XOPEN_SOURCE being defined at all; they
+@@ -4417,6 +4713,13 @@
+ define_xopen_source=no;;
+ Darwin/[12][0-9].*)
+ define_xopen_source=no;;
++ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features.
++ iOS/*)
++ define_xopen_source=no;;
++ tvOS/*)
++ define_xopen_source=no;;
++ watchOS/*)
++ define_xopen_source=no;;
+ # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
+ # defining NI_NUMERICHOST.
+ QNX/6.3.2)
+@@ -4479,6 +4782,12 @@
+ CONFIGURE_MACOSX_DEPLOYMENT_TARGET=
+ EXPORT_MACOSX_DEPLOYMENT_TARGET='#'
+
++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET /
++# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple.
+
-+ 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");
+ # checks for alternative programs
+
+ # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just
+@@ -4511,6 +4820,26 @@
+ ;;
+ esac
+
++case $ac_sys_system in #(
++ iOS) :
+
-+ NSLog(@"---------------------------------------------------------------------------");
++ as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
++ as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
++ ;; #(
++ tvOS) :
+
-+ Py_Finalize();
-+}
++ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
++ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
++ ;; #(
++ watchOS) :
+
++ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
++ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
++ ;; #(
++ *) :
++ ;;
++esac
+
-+@end
---- /dev/null
-+++ b/tvOS/README.rst
-@@ -0,0 +1,108 @@
-+=====================
-+Python on tvOS README
-+=====================
+ if test "$ac_sys_system" = "Darwin"
+ then
+ # Extract the first word of "xcrun", so it can be a program name with args.
+@@ -6908,7 +7237,42 @@
+ #elif defined(__gnu_hurd__)
+ i386-gnu
+ #elif defined(__APPLE__)
++# include "TargetConditionals.h"
++# if TARGET_OS_IOS
++# if TARGET_OS_SIMULATOR
++# if __x86_64__
++ x86_64-iphonesimulator
++# else
++ arm64-iphonesimulator
++# endif
++# else
++ arm64-iphoneos
++# endif
++# elif TARGET_OS_TV
++# if TARGET_OS_SIMULATOR
++# if __x86_64__
++ x86_64-appletvsimulator
++# else
++ arm64-appletvsimulator
++# endif
++# else
++ arm64-appletvos
++# endif
++# elif TARGET_OS_WATCH
++# if TARGET_OS_SIMULATOR
++# if __x86_64__
++ x86_64-watchsimulator
++# else
++ arm64-watchsimulator
++# endif
++# else
++ arm64_32-watchos
++# endif
++# elif TARGET_OS_OSX
+ darwin
++# else
++# error unknown Apple platform
++# endif
+ #elif defined(__VXWORKS__)
+ vxworks
+ #elif defined(__wasm32__)
+@@ -6957,6 +7321,12 @@
+ case $ac_sys_system in #(
+ Darwin*) :
+ MULTIARCH="" ;; #(
++ iOS) :
++ MULTIARCH="" ;; #(
++ tvOS) :
++ MULTIARCH="" ;; #(
++ watchOS) :
++ MULTIARCH="" ;; #(
+ FreeBSD*) :
+ MULTIARCH="" ;; #(
+ *) :
+@@ -6964,8 +7334,6 @@
+ ;;
+ esac
+
+-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5
+-printf "%s\n" "$MULTIARCH" >&6; }
+
+ if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then
+ if test x$PLATFORM_TRIPLET != x$MULTIARCH; then
+@@ -6975,6 +7343,16 @@
+ MULTIARCH=$PLATFORM_TRIPLET
+ fi
+
++{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MULTIARCH" >&5
++printf "%s\n" "$MULTIARCH" >&6; }
+
-+:Authors:
-+ Russell Keith-Magee (2023-11)
++case $ac_sys_system in #(
++ iOS|tvOS|watchOS) :
++ SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #(
++ *) :
++ SOABI_PLATFORM=$PLATFORM_TRIPLET
++ ;;
++esac
+
+ if test x$MULTIARCH != x; then
+ MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\""
+@@ -7018,6 +7396,18 @@
+ PY_SUPPORT_TIER=3 ;; #(
+ x86_64-*-freebsd*/clang) :
+ PY_SUPPORT_TIER=3 ;; #(
++ aarch64-apple-ios*-simulator/clang) :
++ PY_SUPPORT_TIER=3 ;; #(
++ aarch64-apple-ios*/clang) :
++ PY_SUPPORT_TIER=3 ;; #(
++ aarch64-apple-tvos*-simulator/clang) :
++ PY_SUPPORT_TIER=3 ;; #(
++ aarch64-apple-tvos*/clang) :
++ PY_SUPPORT_TIER=3 ;; #(
++ aarch64-apple-watchos*-simulator/clang) :
++ PY_SUPPORT_TIER=3 ;; #(
++ arm64_32-apple-watchos*/clang) :
++ PY_SUPPORT_TIER=3 ;; #(
+ *) :
+ PY_SUPPORT_TIER=0
+ ;;
+@@ -7471,17 +7861,25 @@
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking LDLIBRARY" >&5
+ printf %s "checking LDLIBRARY... " >&6; }
+
+-# MacOSX framework builds need more magic. LDLIBRARY is the dynamic
++# Apple framework builds need more magic. LDLIBRARY is the dynamic
+ # library that we build, but we do not want to link against it (we
+ # will find it with a -framework option). For this reason there is an
+ # extra variable BLDLIBRARY against which Python and the extension
+ # modules are linked, BLDLIBRARY. This is normally the same as
+-# LDLIBRARY, but empty for MacOSX framework builds.
++# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same,
++# but uses a non-versioned framework layout.
+ if test "$enable_framework"
+ then
+- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
+- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
++ case $ac_sys_system in
++ Darwin)
++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';;
++ iOS|tvOS|watchOS)
++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';;
++ *)
++ as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;;
++ esac
+ BLDLIBRARY=''
++ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
+ else
+ BLDLIBRARY='$(LDLIBRARY)'
+ fi
+@@ -7494,64 +7892,70 @@
+
+ case $ac_sys_system in
+ CYGWIN*)
+- LDLIBRARY='libpython$(LDVERSION).dll.a'
+- DLLLIBRARY='libpython$(LDVERSION).dll'
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).dll.a'
++ DLLLIBRARY='libpython$(LDVERSION).dll'
++ ;;
+ SunOS*)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
+- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
+- INSTSONAME="$LDLIBRARY".$SOVERSION
+- if test "$with_pydebug" != yes
+- then
+- PY3LIBRARY=libpython3.so
+- fi
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).so'
++ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
++ INSTSONAME="$LDLIBRARY".$SOVERSION
++ if test "$with_pydebug" != yes
++ then
++ PY3LIBRARY=libpython3.so
++ fi
++ ;;
+ Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- BLDLIBRARY='-L. -lpython$(LDVERSION)'
+- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
+- INSTSONAME="$LDLIBRARY".$SOVERSION
+- if test "$with_pydebug" != yes
+- then
+- PY3LIBRARY=libpython3.so
+- fi
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).so'
++ BLDLIBRARY='-L. -lpython$(LDVERSION)'
++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
++ INSTSONAME="$LDLIBRARY".$SOVERSION
++ if test "$with_pydebug" != yes
++ then
++ PY3LIBRARY=libpython3.so
++ fi
++ ;;
+ hp*|HP*)
+- case `uname -m` in
+- ia64)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- ;;
+- *)
+- LDLIBRARY='libpython$(LDVERSION).sl'
+- ;;
+- esac
+- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
+- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
+- ;;
++ case `uname -m` in
++ ia64)
++ LDLIBRARY='libpython$(LDVERSION).so'
++ ;;
++ *)
++ LDLIBRARY='libpython$(LDVERSION).sl'
++ ;;
++ esac
++ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
++ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
++ ;;
+ Darwin*)
+- LDLIBRARY='libpython$(LDVERSION).dylib'
+- BLDLIBRARY='-L. -lpython$(LDVERSION)'
+- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).dylib'
++ BLDLIBRARY='-L. -lpython$(LDVERSION)'
++ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
++ ;;
++ iOS|tvOS|watchOS)
++ LDLIBRARY='libpython$(LDVERSION).dylib'
++ ;;
+ AIX*)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).so'
++ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
++ ;;
+
+ esac
+ else # shared is disabled
+ PY_ENABLE_SHARED=0
+ case $ac_sys_system in
+ CYGWIN*)
+- BLDLIBRARY='$(LIBRARY)'
+- LDLIBRARY='libpython$(LDVERSION).dll.a'
+- ;;
++ BLDLIBRARY='$(LIBRARY)'
++ LDLIBRARY='libpython$(LDVERSION).dll.a'
++ ;;
+ esac
+ fi
+
++{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5
++printf "%s\n" "$LDLIBRARY" >&6; }
+
-+This document provides a quick overview of some tvOS specific features in the
-+Python distribution.
+ if test "$cross_compiling" = yes; then
+- RUNSHARED=
++ RUNSHARED=
+ fi
+
+
+@@ -7746,9 +8150,6 @@
+ PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD"
+ fi
+
+-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDLIBRARY" >&5
+-printf "%s\n" "$LDLIBRARY" >&6; }
+-
+ # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable
+ case $ac_sys_system/$ac_sys_emscripten_target in #(
+ Emscripten/browser*) :
+@@ -12812,6 +13213,11 @@
+ BLDSHARED="$LDSHARED"
+ fi
+ ;;
++ iOS/*|tvOS/*|watchOS/*)
++ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
++ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
++ BLDSHARED="$LDSHARED"
++ ;;
+ Emscripten*|WASI*)
+ LDSHARED='$(CC) -shared'
+ LDCXXSHARED='$(CXX) -shared';;
+@@ -12941,30 +13347,34 @@
+ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
+ Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
+ # -u libsys_s pulls in all symbols in libsys
+- Darwin/*)
++ Darwin/*|iOS/*|tvOS/*|watchOS/*)
+ LINKFORSHARED="$extra_undefs -framework CoreFoundation"
+
+ # Issue #18075: the default maximum stack size (8MBytes) is too
+ # small for the default recursion limit. Increase the stack size
+ # to ensure that tests don't crash
+- stack_size="1000000" # 16 MB
+- if test "$with_ubsan" = "yes"
+- then
+- # Undefined behavior sanitizer requires an even deeper stack
+- stack_size="4000000" # 64 MB
+- fi
+-
+- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
++ stack_size="1000000" # 16 MB
++ if test "$with_ubsan" = "yes"
++ then
++ # Undefined behavior sanitizer requires an even deeper stack
++ stack_size="4000000" # 64 MB
++ fi
+
+
+ printf "%s\n" "#define THREAD_STACK_SIZE 0x$stack_size" >>confdefs.h
+
+
+- if test "$enable_framework"
+- then
+- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
++ if test $ac_sys_system = "Darwin"; then
++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
+
-+Compilers for building on tvOS
-+==============================
++ if test "$enable_framework"; then
++ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
++ fi
++ LINKFORSHARED="$LINKFORSHARED"
++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
+ fi
+- LINKFORSHARED="$LINKFORSHARED";;
++ ;;
+ OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";;
+ SCO_SV*) LINKFORSHARED="-Wl,-Bexport";;
+ ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";;
+@@ -14353,6 +14763,10 @@
+
+ ctypes_malloc_closure=yes
+ ;; #(
++ iOS|tvOS|watchOS) :
+
-+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.
++ ctypes_malloc_closure=yes
++ ;; #(
+ sunos5) :
+ as_fn_append LIBFFI_LIBS " -mimpure-text"
+ ;; #(
+@@ -17622,12 +18036,6 @@
+ then :
+ printf "%s\n" "#define HAVE_DUP3 1" >>confdefs.h
+
+-fi
+-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" "explicit_bzero" "ac_cv_func_explicit_bzero"
+ if test "x$ac_cv_func_explicit_bzero" = xyes
+@@ -17688,18 +18096,6 @@
+ then :
+ printf "%s\n" "#define HAVE_FEXECVE 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" "fpathconf" "ac_cv_func_fpathconf"
+ if test "x$ac_cv_func_fpathconf" = xyes
+@@ -17754,12 +18150,6 @@
+ then :
+ printf "%s\n" "#define HAVE_GETEGID 1" >>confdefs.h
+
+-fi
+-ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy"
+-if test "x$ac_cv_func_getentropy" = xyes
+-then :
+- printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h
+-
+ fi
+ ac_fn_c_check_func "$LINENO" "geteuid" "ac_cv_func_geteuid"
+ if test "x$ac_cv_func_geteuid" = xyes
+@@ -17796,12 +18186,6 @@
+ then :
+ printf "%s\n" "#define HAVE_GETGROUPLIST 1" >>confdefs.h
+
+-fi
+-ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups"
+-if test "x$ac_cv_func_getgroups" = xyes
+-then :
+- printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h
+-
+ fi
+ ac_fn_c_check_func "$LINENO" "gethostname" "ac_cv_func_gethostname"
+ if test "x$ac_cv_func_gethostname" = xyes
+@@ -18120,18 +18504,6 @@
+ then :
+ printf "%s\n" "#define HAVE_POSIX_FALLOCATE 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" "pread" "ac_cv_func_pread"
+ if test "x$ac_cv_func_pread" = xyes
+@@ -18396,12 +18768,6 @@
+ then :
+ printf "%s\n" "#define HAVE_SIGACTION 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
+ ac_fn_c_check_func "$LINENO" "sigfillset" "ac_cv_func_sigfillset"
+ if test "x$ac_cv_func_sigfillset" = xyes
+@@ -18492,12 +18858,6 @@
+ then :
+ printf "%s\n" "#define HAVE_SYSCONF 1" >>confdefs.h
+
+-fi
+-ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system"
+-if test "x$ac_cv_func_system" = xyes
+-then :
+- printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h
+-
+ fi
+ ac_fn_c_check_func "$LINENO" "tcgetpgrp" "ac_cv_func_tcgetpgrp"
+ if test "x$ac_cv_func_tcgetpgrp" = xyes
+@@ -18553,10 +18913,10 @@
+ printf "%s\n" "#define HAVE_TRUNCATE 1" >>confdefs.h
+
+ fi
+-ac_fn_c_check_func "$LINENO" "ttyname_r" "ac_cv_func_ttyname_r"
+-if test "x$ac_cv_func_ttyname_r" = xyes
++ac_fn_c_check_func "$LINENO" "ttyname" "ac_cv_func_ttyname"
++if test "x$ac_cv_func_ttyname" = xyes
+ then :
+- printf "%s\n" "#define HAVE_TTYNAME_R 1" >>confdefs.h
++ printf "%s\n" "#define HAVE_TTYNAME 1" >>confdefs.h
+
+ fi
+ ac_fn_c_check_func "$LINENO" "umask" "ac_cv_func_umask"
+@@ -18670,6 +19030,73 @@
+
+ fi
+
++# iOS/tvOS/watchOS define some system methods that can be linked (so they are
++# found by configure), but either raise a compilation error (because the
++# header definition prevents usage - autoconf doesn't use the headers), or
++# raise an error if used at runtime. Force these symbols off.
++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
++ ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy"
++if test "x$ac_cv_func_getentropy" = xyes
++then :
++ printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h
+
-+tvOS specific arguments to configure
-+===================================
++fi
++ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups"
++if test "x$ac_cv_func_getgroups" = xyes
++then :
++ printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h
+
-+* ``--enable-framework[=DIR]``
++fi
++ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system"
++if test "x$ac_cv_func_system" = xyes
++then :
++ printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h
+
-+ This argument specifies the location where the Python.framework will
-+ be installed.
++fi
+
-+* ``--with-framework-name=NAME``
++fi
+
-+ Specify the name for the python framework, defaults to ``Python``.
++# 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
+
-+Building and using Python on tvOS
-+=================================
++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
+
-+ABIs and Architectures
-+----------------------
++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
+
-+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.
++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
+
-+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.)
++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
+
-+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.
++fi
+
-+How do I build Python for tvOS?
-+-------------------------------
++fi
+
-+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:
+ { 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}
+@@ -21422,7 +21849,8 @@
+
+
+ # check for openpty, login_tty, and forkpty
+-
++# tvOS/watchOS have functions for tty, but can't use them
++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
+
+ for ac_func in openpty
+ do :
+@@ -21518,7 +21946,7 @@
+ fi
+
+ done
+-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing login_tty" >&5
+ printf %s "checking for library containing login_tty... " >&6; }
+ if test ${ac_cv_search_login_tty+y}
+ then :
+@@ -21675,6 +22103,7 @@
+ fi
+
+ done
++fi
+
+ # check for long file support functions
+ ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64"
+@@ -22222,6 +22651,11 @@
+
+ done
+
++# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by
++# configure), but when used in an unprivileged process, it crashes rather than
++# returning an error. Force the symbol off.
++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS"
++then
+
+ for ac_func in clock_settime
+ do :
+@@ -22232,7 +22666,7 @@
+
+ else $as_nop
+
+- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5
+ printf %s "checking for clock_settime in -lrt... " >&6; }
+ if test ${ac_cv_lib_rt_clock_settime+y}
+ then :
+@@ -22270,7 +22704,7 @@
+ if test "x$ac_cv_lib_rt_clock_settime" = xyes
+ then :
+
+- printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h
++ printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h
+
+
+ fi
+@@ -22279,6 +22713,7 @@
+ fi
+
+ done
++fi
+
+
+ for ac_func in clock_nanosleep
+@@ -22500,7 +22935,9 @@
+ if test "$cross_compiling" = yes
+ then :
+
+-if test "${enable_ipv6+set}" = set; then
++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
++ ac_cv_buggy_getaddrinfo="no"
++elif test "${enable_ipv6+set}" = set; then
+ ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
+ else
+ ac_cv_buggy_getaddrinfo=yes
+@@ -24404,7 +24841,7 @@
+ printf "%s\n" "$ABIFLAGS" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking SOABI" >&5
+ printf %s "checking SOABI... " >&6; }
+-SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
++SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SOABI" >&5
+ printf "%s\n" "$SOABI" >&6; }
+
+@@ -24412,7 +24849,7 @@
+ if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then
+ # Similar to SOABI but remove "d" flag from ABIFLAGS
+
+- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
++ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM}
+
+ printf "%s\n" "#define ALT_SOABI \"${ALT_SOABI}\"" >>confdefs.h
+
+@@ -27163,24 +27600,28 @@
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5
+ printf "%s\n" "$as_me: checking for device files" >&6;}
+
+-if test "x$cross_compiling" = xyes; then
+- if test "${ac_cv_file__dev_ptmx+set}" != set; then
+- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then
++ ac_cv_file__dev_ptmx=no
++ ac_cv_file__dev_ptc=no
++else
++ if test "x$cross_compiling" = xyes; then
++ if test "${ac_cv_file__dev_ptmx+set}" != set; then
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
+ printf %s "checking for /dev/ptmx... " >&6; }
+- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
+ printf "%s\n" "not set" >&6; }
+- as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
+- fi
+- if test "${ac_cv_file__dev_ptc+set}" != set; then
+- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
++ as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
++ fi
++ if test "${ac_cv_file__dev_ptc+set}" != set; then
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
+ printf %s "checking for /dev/ptc... " >&6; }
+- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5
+ printf "%s\n" "not set" >&6; }
+- as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
++ as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5
++ fi
+ fi
+-fi
+
+-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5
+ printf %s "checking for /dev/ptmx... " >&6; }
+ if test ${ac_cv_file__dev_ptmx+y}
+ then :
+@@ -27201,12 +27642,12 @@
+
+ fi
+
+-if test "x$ac_cv_file__dev_ptmx" = xyes; then
++ if test "x$ac_cv_file__dev_ptmx" = xyes; then
+
+ printf "%s\n" "#define HAVE_DEV_PTMX 1" >>confdefs.h
+
+-fi
+-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
++ fi
++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5
+ printf %s "checking for /dev/ptc... " >&6; }
+ if test ${ac_cv_file__dev_ptc+y}
+ then :
+@@ -27227,10 +27668,11 @@
+
+ fi
+
+-if test "x$ac_cv_file__dev_ptc" = xyes; then
++ if test "x$ac_cv_file__dev_ptc" = xyes; then
+
+ printf "%s\n" "#define HAVE_DEV_PTC 1" >>confdefs.h
+
++ fi
+ fi
+
+ if test $ac_sys_system = Darwin
+@@ -27672,6 +28114,8 @@
+ with_ensurepip=no ;; #(
+ WASI) :
+ with_ensurepip=no ;; #(
++ iOS|tvOS|watchOS) :
++ with_ensurepip=no ;; #(
+ *) :
+ with_ensurepip=upgrade
+ ;;
+@@ -28613,6 +29057,27 @@
+ py_cv_module_ossaudiodev=n/a
+ py_cv_module_spwd=n/a
+ ;; #(
++ iOS|tvOS|watchOS) :
+
-+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.
+
-+tvOS 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 tvOS will look somethign like::
++ py_cv_module__curses=n/a
++ py_cv_module__curses_panel=n/a
++ py_cv_module__gdbm=n/a
++ py_cv_module__multiprocessing=n/a
++ py_cv_module__posixshmem=n/a
++ py_cv_module__posixsubprocess=n/a
++ py_cv_module__scproxy=n/a
++ py_cv_module__tkinter=n/a
++ py_cv_module_grp=n/a
++ py_cv_module_nis=n/a
++ py_cv_module_readline=n/a
++ py_cv_module_pwd=n/a
++ py_cv_module_spwd=n/a
++ py_cv_module_syslog=n/a
++ py_cv_module_=n/a
+
-+ $ ./configure \
-+ --enable-framework=/path/to/install \
-+ --host=aarch64-apple-tvos \
-+ --build=aarch64-apple-darwin \
-+ --with-build-python=/path/to/python.exe
-+ $ make
-+ $ make install
++ ;; #(
+ CYGWIN*) :
+
+
+@@ -32359,6 +32824,9 @@
+ "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;;
+ "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;;
+ "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;;
++ "Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/iOS/Resources/Info.plist" ;;
++ "Apple/tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/tvOS/Resources/Info.plist" ;;
++ "Apple/watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES 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 1a02d19f1b2..7881beb6d24 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -307,6 +307,161 @@
+ AC_MSG_ERROR([pkg-config is required])]
+ fi
+
++# Set name for machine-dependent library files
++AC_ARG_VAR([MACHDEP], [name for machine-dependent library files])
++AC_MSG_CHECKING([MACHDEP])
++if test -z "$MACHDEP"
++then
++ # avoid using uname for cross builds
++ if test "$cross_compiling" = yes; then
++ # ac_sys_system and ac_sys_release are used for setting
++ # a lot of different things including 'define_xopen_source'
++ # in the case statement below.
++ case "$host" in
++ *-*-linux-android*)
++ ac_sys_system=Linux-android
++ ;;
++ *-*-linux*)
++ ac_sys_system=Linux
++ ;;
++ *-*-cygwin*)
++ ac_sys_system=Cygwin
++ ;;
++ *-apple-ios*)
++ ac_sys_system=iOS
++ ;;
++ *-apple-tvos*)
++ ac_sys_system=tvOS
++ ;;
++ *-apple-watchos*)
++ ac_sys_system=watchOS
++ ;;
++ *-*-vxworks*)
++ ac_sys_system=VxWorks
++ ;;
++ *-*-emscripten)
++ ac_sys_system=Emscripten
++ ;;
++ *-*-wasi)
++ ac_sys_system=WASI
++ ;;
++ *)
++ # for now, limit cross builds to known configurations
++ MACHDEP="unknown"
++ AC_MSG_ERROR([cross build not supported for $host])
++ esac
++ ac_sys_release=
++ else
++ ac_sys_system=`uname -s`
++ if test "$ac_sys_system" = "AIX" \
++ -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
++ ac_sys_release=`uname -v`
++ else
++ ac_sys_release=`uname -r`
++ fi
++ fi
++ ac_md_system=`echo $ac_sys_system |
++ tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'`
++ ac_md_release=`echo $ac_sys_release |
++ tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'`
++ MACHDEP="$ac_md_system$ac_md_release"
+
-+In this invocation:
++ case $MACHDEP in
++ aix*) MACHDEP="aix";;
++ linux*) MACHDEP="linux";;
++ cygwin*) MACHDEP="cygwin";;
++ darwin*) MACHDEP="darwin";;
++ '') MACHDEP="unknown";;
++ esac
+
-+* ``/path/to/install`` is the location where the final Python.framework will be
-+ output.
++ if test "$ac_sys_system" = "SunOS"; then
++ # For Solaris, there isn't an OS version specific macro defined
++ # in most compilers, so we define one here.
++ SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'`
++ AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION],
++ [The version of SunOS/Solaris as reported by `uname -r' without the dot.])
++ fi
++fi
++AC_MSG_RESULT(["$MACHDEP"])
+
-+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
-+ triple format. This will be one of:
++# On cross-compile builds, configure will look for a host-specific compiler by
++# prepending the user-provided host triple to the required binary name.
++#
++# On iOS/tvOS/watchOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc",
++# 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
++# these tools; the binary names of these scripts are better defaults than "gcc".
++# This only requires that the user put the platform scripts folder (e.g.,
++# "iOS/Resources/bin") in their path, rather than defining platform-specific
++# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to
++# either put the platform scripts folder in the path, or specify CC etc,
++# configure will fail.
++if test -z "$AR"; then
++ case "$host" in
++ 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 ;;
+
-+ - ``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.
++ aarch64-apple-tvos*-simulator) AR=arm64-apple-tvos-simulator-ar ;;
++ aarch64-apple-tvos*) AR=arm64-apple-tvos-ar ;;
++ x86_64-apple-tvos*-simulator) AR=x86_64-apple-tvos-simulator-ar ;;
+
-+* ``--build`` is the GNU compiler triple for the machine that will be running
-+ the compiler. This is one of:
++ aarch64-apple-watchos*-simulator) AR=arm64-apple-watchos-simulator-ar ;;
++ aarch64-apple-watchos*) AR=arm64_32-apple-watchos-ar ;;
++ x86_64-apple-watchos*-simulator) AR=x86_64-apple-watchos-simulator-ar ;;
++ *)
++ esac
++fi
++if test -z "$CC"; then
++ case "$host" in
++ 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 ;;
+
-+ - ``aarch64-apple-darwin`` for Apple Silicon devices.
-+ - ``x86_64-apple-darwin`` for Intel devices.
++ aarch64-apple-tvos*-simulator) CC=arm64-apple-tvos-simulator-clang ;;
++ aarch64-apple-tvos*) CC=arm64-apple-tvos-clang ;;
++ x86_64-apple-tvos*-simulator) CC=x86_64-apple-tvos-simulator-clang ;;
+
-+* ``/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.
++ aarch64-apple-watchos*-simulator) CC=arm64-apple-watchos-simulator-clang ;;
++ aarch64-apple-watchos*) CC=arm64_32-apple-watchos-clang ;;
++ x86_64-apple-watchos*-simulator) CC=x86_64-apple-watchos-simulator-clang ;;
++ *)
++ esac
++fi
++if test -z "$CPP"; then
++ case "$host" in
++ 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 ;;
+
-+Using a framework-based Python on tvOS
-+======================================
---- /dev/null
-+++ b/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@
-+
-+
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-ar
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos -E "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos-simulator "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator -E "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos-simulator "$@"
---- /dev/null
-+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp
-@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator -E "$@"
---- /dev/null
-+++ b/tvOS/Resources/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
-+
-+
---- /dev/null
-+++ b/tvOS/Resources/pyconfig.h
-@@ -0,0 +1,7 @@
-+#ifdef __arm64__
-+#include "pyconfig-arm64.h"
-+#endif
++ aarch64-apple-tvos*-simulator) CPP=arm64-apple-tvos-simulator-cpp ;;
++ aarch64-apple-tvos*) CPP=arm64-apple-tvos-cpp ;;
++ x86_64-apple-tvos*-simulator) CPP=x86_64-apple-tvos-simulator-cpp ;;
+
-+#ifdef __x86_64__
-+#include "pyconfig-x86_64.h"
-+#endif
---- /dev/null
-+++ b/watchOS/README.rst
-@@ -0,0 +1,108 @@
-+========================
-+Python on watchOS README
-+========================
++ aarch64-apple-watchos*-simulator) CPP=arm64-apple-watchos-simulator-cpp ;;
++ aarch64-apple-watchos*) CPP=arm64_32-apple-watchos-cpp ;;
++ x86_64-apple-watchos*-simulator) CPP=x86_64-apple-watchos-simulator-cpp ;;
++ *)
++ esac
++fi
++if test -z "$CXX"; then
++ case "$host" in
++ 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++ ;;
+
-+:Authors:
-+ Russell Keith-Magee (2023-11)
++ aarch64-apple-tvos*-simulator) CXX=arm64-apple-tvos-simulator-clang++ ;;
++ aarch64-apple-tvos*) CXX=arm64-apple-tvos-clang++ ;;
++ x86_64-apple-tvos*-simulator) CXX=x86_64-apple-tvos-simulator-clang++ ;;
+
-+This document provides a quick overview of some watchOS specific features in the
-+Python distribution.
++ aarch64-apple-watchos*-simulator) CXX=arm64-apple-watchos-simulator-clang++ ;;
++ aarch64-apple-watchos*) CXX=arm64_32-apple-watchos-clang++ ;;
++ x86_64-apple-watchos*-simulator) CXX=x86_64-apple-watchos-simulator-clang++ ;;
++ *)
++ esac
++fi
+
-+Compilers for building on watchOS
-+=================================
+ AC_MSG_CHECKING([for --enable-universalsdk])
+ AC_ARG_ENABLE([universalsdk],
+ AS_HELP_STRING([--enable-universalsdk@<:@=SDKDIR@:>@],
+@@ -416,109 +571,189 @@
+ [
+ case $enableval in
+ yes)
+- enableval=/Library/Frameworks
++ case $ac_sys_system in
++ Darwin) enableval=/Library/Frameworks ;;
++ iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;;
++ tvOS) enableval=Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;;
++ watchOS) enableval=Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;;
++ *) AC_MSG_ERROR([Unknown platform for framework build])
++ esac
+ esac
+
-+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.
+ case $enableval in
+ no)
+- PYTHONFRAMEWORK=
+- PYTHONFRAMEWORKDIR=no-framework
+- PYTHONFRAMEWORKPREFIX=
+- PYTHONFRAMEWORKINSTALLDIR=
+- FRAMEWORKINSTALLFIRST=
+- FRAMEWORKINSTALLLAST=
+- FRAMEWORKALTINSTALLFIRST=
+- FRAMEWORKALTINSTALLLAST=
+- FRAMEWORKPYTHONW=
+- if test "x${prefix}" = "xNONE"; then
+- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+- else
+- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+- fi
+- enable_framework=
++ case $ac_sys_system in
++ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;;
++ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;;
++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;;
++ *)
++ PYTHONFRAMEWORK=
++ PYTHONFRAMEWORKDIR=no-framework
++ PYTHONFRAMEWORKPREFIX=
++ PYTHONFRAMEWORKINSTALLDIR=
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
++ RESSRCDIR=
++ FRAMEWORKINSTALLFIRST=
++ FRAMEWORKINSTALLLAST=
++ FRAMEWORKALTINSTALLFIRST=
++ FRAMEWORKALTINSTALLLAST=
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="commoninstall bininstall maninstall"
+
-+watchOS specific arguments to configure
-+=======================================
++ if test "x${prefix}" = "xNONE"; then
++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
++ else
++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
++ fi
++ enable_framework=
++ esac
+ ;;
+ *)
+ PYTHONFRAMEWORKPREFIX="${enableval}"
+ PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR
+- FRAMEWORKINSTALLFIRST="frameworkinstallstructure"
+- FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure "
+- FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
+- FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
+- FRAMEWORKPYTHONW="frameworkpythonw"
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+-
+- if test "x${prefix}" = "xNONE" ; then
+- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+-
+- else
+- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+- fi
+
+- case "${enableval}" in
+- /System*)
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+- if test "${prefix}" = "NONE" ; then
+- # See below
+- FRAMEWORKUNIXTOOLSPREFIX="/usr"
+- fi
+- ;;
++ case $ac_sys_system in #(
++ Darwin) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
++ FRAMEWORKPYTHONW="frameworkpythonw"
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ INSTALLTARGETS="commoninstall bininstall maninstall"
+
-+* ``--enable-framework[=DIR]``
++ if test "x${prefix}" = "xNONE" ; then
++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+
-+ This argument specifies the location where the Python.framework will
-+ be installed.
++ else
++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
++ fi
+
+- /Library*)
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+- ;;
++ case "${enableval}" in
++ /System*)
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ if test "${prefix}" = "NONE" ; then
++ # See below
++ FRAMEWORKUNIXTOOLSPREFIX="/usr"
++ fi
++ ;;
+
-+* ``--with-framework-name=NAME``
++ /Library*)
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ ;;
+
-+ Specify the name for the python framework, defaults to ``Python``.
++ */Library/Frameworks)
++ MDIR="`dirname "${enableval}"`"
++ MDIR="`dirname "${MDIR}"`"
++ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
+
++ if test "${prefix}" = "NONE"; then
++ # User hasn't specified the
++ # --prefix option, but wants to install
++ # the framework in a non-default location,
++ # ensure that the compatibility links get
++ # installed relative to that prefix as well
++ # instead of in /usr/local.
++ FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
++ fi
++ ;;
+
+- */Library/Frameworks)
+- MDIR="`dirname "${enableval}"`"
+- MDIR="`dirname "${MDIR}"`"
+- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
+-
+- if test "${prefix}" = "NONE"; then
+- # User hasn't specified the
+- # --prefix option, but wants to install
+- # the framework in a non-default location,
+- # ensure that the compatibility links get
+- # installed relative to that prefix as well
+- # instead of in /usr/local.
+- FRAMEWORKUNIXTOOLSPREFIX="${MDIR}"
+- fi
+- ;;
++ *)
++ FRAMEWORKINSTALLAPPSPREFIX="/Applications"
++ ;;
++ esac
+
+- *)
+- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+- ;;
++ prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=${prefix}
++ RESSRCDIR=Mac/Resources/framework
+
-+Building and using Python on watchOS
-+====================================
++ # Add files for Mac specific code to the list of output
++ # files:
++ AC_CONFIG_FILES([Mac/Makefile])
++ AC_CONFIG_FILES([Mac/PythonLauncher/Makefile])
++ AC_CONFIG_FILES([Mac/Resources/framework/Info.plist])
++ AC_CONFIG_FILES([Mac/Resources/app/Info.plist])
++ ;;
++ iOS) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
-+ABIs and Architectures
-+----------------------
++ prefix=$PYTHONFRAMEWORKPREFIX
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
++ RESSRCDIR=Apple/iOS/Resources
+
-+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.
++ AC_CONFIG_FILES([Apple/iOS/Resources/Info.plist])
++ ;;
++ tvOS) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
-+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.)
++ prefix=$PYTHONFRAMEWORKPREFIX
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
++ RESSRCDIR=Apple/tvOS/Resources
+
-+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.
++ AC_CONFIG_FILES([Apple/tvOS/Resources/Info.plist])
++ ;;
++ watchOS) :
++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure"
++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure "
++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders"
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="libinstall inclinstall sharedinstall"
+
-+How do I build Python for watchOS?
-+-------------------------------
++ prefix=$PYTHONFRAMEWORKPREFIX
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR"
++ RESSRCDIR=Apple/watchOS/Resources
+
-+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:
++ AC_CONFIG_FILES([Apple/watchOS/Resources/Info.plist])
++ ;;
++ *)
++ AC_MSG_ERROR([Unknown platform for framework build])
++ ;;
++ esac
+ esac
+-
+- prefix=$PYTHONFRAMEWORKINSTALLDIR/Versions/$VERSION
+-
+- # Add files for Mac specific code to the list of output
+- # files:
+- AC_CONFIG_FILES([Mac/Makefile])
+- AC_CONFIG_FILES([Mac/PythonLauncher/Makefile])
+- AC_CONFIG_FILES([Mac/Resources/framework/Info.plist])
+- AC_CONFIG_FILES([Mac/Resources/app/Info.plist])
+- esac
+ ],[
+- PYTHONFRAMEWORK=
+- PYTHONFRAMEWORKDIR=no-framework
+- PYTHONFRAMEWORKPREFIX=
+- PYTHONFRAMEWORKINSTALLDIR=
+- FRAMEWORKINSTALLFIRST=
+- FRAMEWORKINSTALLLAST=
+- FRAMEWORKALTINSTALLFIRST=
+- FRAMEWORKALTINSTALLLAST=
+- FRAMEWORKPYTHONW=
+- if test "x${prefix}" = "xNONE" ; then
+- FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+- else
+- FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+- fi
+- enable_framework=
+-
++ case $ac_sys_system in
++ iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;;
++ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;;
++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;;
++ *)
++ PYTHONFRAMEWORK=
++ PYTHONFRAMEWORKDIR=no-framework
++ PYTHONFRAMEWORKPREFIX=
++ PYTHONFRAMEWORKINSTALLDIR=
++ PYTHONFRAMEWORKINSTALLNAMEPREFIX=
++ RESSRCDIR=
++ FRAMEWORKINSTALLFIRST=
++ FRAMEWORKINSTALLLAST=
++ FRAMEWORKALTINSTALLFIRST=
++ FRAMEWORKALTINSTALLLAST=
++ FRAMEWORKPYTHONW=
++ INSTALLTARGETS="commoninstall bininstall maninstall"
++ if test "x${prefix}" = "xNONE" ; then
++ FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
++ else
++ FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
++ fi
++ enable_framework=
++ esac
+ ])
+ AC_SUBST([PYTHONFRAMEWORK])
+ AC_SUBST([PYTHONFRAMEWORKIDENTIFIER])
+ AC_SUBST([PYTHONFRAMEWORKDIR])
+ AC_SUBST([PYTHONFRAMEWORKPREFIX])
+ AC_SUBST([PYTHONFRAMEWORKINSTALLDIR])
++AC_SUBST([PYTHONFRAMEWORKINSTALLNAMEPREFIX])
++AC_SUBST([RESSRCDIR])
+ AC_SUBST([FRAMEWORKINSTALLFIRST])
+ AC_SUBST([FRAMEWORKINSTALLLAST])
+ AC_SUBST([FRAMEWORKALTINSTALLFIRST])
+@@ -526,77 +761,51 @@
+ AC_SUBST([FRAMEWORKPYTHONW])
+ AC_SUBST([FRAMEWORKUNIXTOOLSPREFIX])
+ AC_SUBST([FRAMEWORKINSTALLAPPSPREFIX])
++AC_SUBST([INSTALLTARGETS])
+
+ AC_DEFINE_UNQUOTED([_PYTHONFRAMEWORK], ["${PYTHONFRAMEWORK}"],
+ [framework name])
+
+-# Set name for machine-dependent library files
+-AC_ARG_VAR([MACHDEP], [name for machine-dependent library files])
+-AC_MSG_CHECKING([MACHDEP])
+-if test -z "$MACHDEP"
+-then
+- # avoid using uname for cross builds
+- if test "$cross_compiling" = yes; then
+- # ac_sys_system and ac_sys_release are used for setting
+- # a lot of different things including 'define_xopen_source'
+- # in the case statement below.
+- case "$host" in
+- *-*-linux-android*)
+- ac_sys_system=Linux-android
+- ;;
+- *-*-linux*)
+- ac_sys_system=Linux
+- ;;
+- *-*-cygwin*)
+- ac_sys_system=Cygwin
+- ;;
+- *-*-vxworks*)
+- ac_sys_system=VxWorks
+- ;;
+- *-*-emscripten)
+- ac_sys_system=Emscripten
+- ;;
+- *-*-wasi)
+- ac_sys_system=WASI
+- ;;
+- *)
+- # for now, limit cross builds to known configurations
+- MACHDEP="unknown"
+- AC_MSG_ERROR([cross build not supported for $host])
+- esac
+- ac_sys_release=
+- else
+- ac_sys_system=`uname -s`
+- if test "$ac_sys_system" = "AIX" \
+- -o "$ac_sys_system" = "UnixWare" -o "$ac_sys_system" = "OpenUNIX"; then
+- ac_sys_release=`uname -v`
+- else
+- ac_sys_release=`uname -r`
+- fi
+- fi
+- ac_md_system=`echo $ac_sys_system |
+- tr -d '[/ ]' | tr '[[A-Z]]' '[[a-z]]'`
+- ac_md_release=`echo $ac_sys_release |
+- tr -d '[/ ]' | sed 's/^[[A-Z]]\.//' | sed 's/\..*//'`
+- MACHDEP="$ac_md_system$ac_md_release"
+-
+- case $MACHDEP in
+- aix*) MACHDEP="aix";;
+- linux*) MACHDEP="linux";;
+- cygwin*) MACHDEP="cygwin";;
+- darwin*) MACHDEP="darwin";;
+- '') MACHDEP="unknown";;
++dnl quadrigraphs "@<:@" and "@:>@" produce "[" and "]" in the output
++AC_MSG_CHECKING([for --with-app-store-compliance])
++AC_ARG_WITH(
++ [app_store_compliance],
++ [AS_HELP_STRING(
++ [--with-app-store-compliance=@<:@PATCH-FILE@:>@],
++ [Enable any patches required for compiliance with app stores.
++ Optional PATCH-FILE specifies the custom patch to apply.]
++ )],[
++ case "$withval" in
++ yes)
++ case $ac_sys_system in
++ Darwin|iOS|tvOS|watchOS)
++ # iOS/tvOS/watchOS is able to share the macOS patch
++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
++ ;;
++ *) AC_MSG_ERROR([no default app store compliance patch available for $ac_sys_system]) ;;
++ esac
++ AC_MSG_RESULT([applying default app store compliance patch])
++ ;;
++ *)
++ APP_STORE_COMPLIANCE_PATCH="${withval}"
++ AC_MSG_RESULT([applying custom app store compliance patch])
++ ;;
+ esac
+-
+- if test "$ac_sys_system" = "SunOS"; then
+- # For Solaris, there isn't an OS version specific macro defined
+- # in most compilers, so we define one here.
+- SUNOS_VERSION=`echo $ac_sys_release | sed -e 's!\.\([0-9]\)$!.0\1!g' | tr -d '.'`
+- AC_DEFINE_UNQUOTED([Py_SUNOS_VERSION], [$SUNOS_VERSION],
+- [The version of SunOS/Solaris as reported by `uname -r' without the dot.])
+- fi
+-fi
+-AC_MSG_RESULT(["$MACHDEP"])
++ ],[
++ case $ac_sys_system in
++ iOS|tvOS|watchOS)
++ # Always apply the compliance patch on iOS/tvOS/watchOS; we can use the macOS patch
++ APP_STORE_COMPLIANCE_PATCH="Mac/Resources/app-store-compliance.patch"
++ AC_MSG_RESULT([applying default app store compliance patch])
++ ;;
++ *)
++ # No default app compliance patching on any other platform
++ APP_STORE_COMPLIANCE_PATCH=
++ AC_MSG_RESULT([not patching for app store compliance])
++ ;;
++ esac
++])
++AC_SUBST([APP_STORE_COMPLIANCE_PATCH])
+
+ AC_SUBST([_PYTHON_HOST_PLATFORM])
+ if test "$cross_compiling" = yes; then
+@@ -604,27 +813,87 @@
+ *-*-linux*)
+ case "$host_cpu" in
+ arm*)
+- _host_cpu=arm
++ _host_ident=arm
+ ;;
+ *)
+- _host_cpu=$host_cpu
++ _host_ident=$host_cpu
+ esac
+ ;;
+ *-*-cygwin*)
+- _host_cpu=
++ _host_ident=
++ ;;
++ *-apple-ios*)
++ _host_os=`echo $host | cut -d '-' -f3`
++ _host_device=`echo $host | cut -d '-' -f4`
++ _host_device=${_host_device:=os}
+
-+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.
++ # IPHONEOS_DEPLOYMENT_TARGET is the minimum supported iOS version
++ AC_MSG_CHECKING([iOS deployment target])
++ IPHONEOS_DEPLOYMENT_TARGET=${_host_os:3}
++ IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:=13.0}
++ AC_MSG_RESULT([$IPHONEOS_DEPLOYMENT_TARGET])
+
-+watchOS builds of Python *must* be constructed as framework builds. To support this,
-+you must provide the ``--enable-framework`` flag when configuring the build.
++ case "$host_cpu" in
++ aarch64)
++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device}
++ ;;
++ *)
++ _host_ident=${IPHONEOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device}
++ ;;
++ esac
++ ;;
++ *-apple-tvos*)
++ _host_os=`echo $host | cut -d '-' -f3`
++ _host_device=`echo $host | cut -d '-' -f4`
++ _host_device=${_host_device:=os}
+
-+The build also requires the use of cross-compilation. The commands for building
-+Python for watchOS will look somethign like::
++ # TVOS_DEPLOYMENT_TARGET is the minimum supported tvOS version
++ AC_MSG_CHECKING([tvOS deployment target])
++ TVOS_DEPLOYMENT_TARGET=${_host_os:4}
++ TVOS_DEPLOYMENT_TARGET=${TVOS_DEPLOYMENT_TARGET:=12.0}
++ AC_MSG_RESULT([$TVOS_DEPLOYMENT_TARGET])
+
-+ $ ./configure \
-+ --enable-framework=/path/to/install \
-+ --host=aarch64-apple-watchos \
-+ --build=aarch64-apple-darwin \
-+ --with-build-python=/path/to/python.exe
-+ $ make
-+ $ make install
++ case "$host_cpu" in
++ aarch64)
++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-arm64-appletv${_host_device}
++ ;;
++ *)
++ _host_ident=${TVOS_DEPLOYMENT_TARGET}-$host_cpu-appletv${_host_device}
++ ;;
++ esac
++ ;;
++ *-apple-watchos*)
++ _host_os=`echo $host | cut -d '-' -f3`
++ _host_device=`echo $host | cut -d '-' -f4`
++ _host_device=${_host_device:=os}
+
-+In this invocation:
++ # 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}
++ AC_MSG_RESULT([$WATCHOS_DEPLOYMENT_TARGET])
+
-+* ``/path/to/install`` is the location where the final Python.framework will be
-+ output.
++ case "$host_cpu" in
++ aarch64)
++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-arm64-watch${_host_device}
++ ;;
++ *)
++ _host_ident=${WATCHOS_DEPLOYMENT_TARGET}-$host_cpu-watch${_host_device}
++ ;;
++ esac
+ ;;
+ *-*-vxworks*)
+- _host_cpu=$host_cpu
++ _host_ident=$host_cpu
+ ;;
+ wasm32-*-* | wasm64-*-*)
+- _host_cpu=$host_cpu
++ _host_ident=$host_cpu
+ ;;
+ *)
+ # for now, limit cross builds to known configurations
+ MACHDEP="unknown"
+ AC_MSG_ERROR([cross build not supported for $host])
+ esac
+- _PYTHON_HOST_PLATFORM="$MACHDEP${_host_cpu:+-$_host_cpu}"
++ _PYTHON_HOST_PLATFORM="$MACHDEP${_host_ident:+-$_host_ident}"
+ fi
+
+ # Some systems cannot stand _XOPEN_SOURCE being defined at all; they
+@@ -690,6 +959,13 @@
+ define_xopen_source=no;;
+ Darwin/@<:@[12]@:>@@<:@0-9@:>@.*)
+ define_xopen_source=no;;
++ # On iOS/tvOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features.
++ iOS/*)
++ define_xopen_source=no;;
++ tvOS/*)
++ define_xopen_source=no;;
++ watchOS/*)
++ define_xopen_source=no;;
+ # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
+ # defining NI_NUMERICHOST.
+ QNX/6.3.2)
+@@ -748,6 +1024,12 @@
+ CONFIGURE_MACOSX_DEPLOYMENT_TARGET=
+ EXPORT_MACOSX_DEPLOYMENT_TARGET='#'
+
++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET /
++# WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple.
++AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET])
++AC_SUBST([TVOS_DEPLOYMENT_TARGET])
++AC_SUBST([WATCHOS_DEPLOYMENT_TARGET])
+
-+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler
-+ triple format. This will be one of:
+ # checks for alternative programs
+
+ # compiler flags are generated in two sets, BASECFLAGS and OPT. OPT is just
+@@ -780,6 +1062,20 @@
+ ],
+ )
+
++dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS version.
++AS_CASE([$ac_sys_system],
++ [iOS], [
++ AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
++ AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
++ ],[tvOS], [
++ AS_VAR_APPEND([CFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
++ AS_VAR_APPEND([LDFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
++ ],[watchOS], [
++ AS_VAR_APPEND([CFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
++ AS_VAR_APPEND([LDFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
++ ],
++)
+
-+ - ``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.
+ if test "$ac_sys_system" = "Darwin"
+ then
+ dnl look for SDKROOT
+@@ -1077,7 +1373,42 @@
+ #elif defined(__gnu_hurd__)
+ i386-gnu
+ #elif defined(__APPLE__)
++# include "TargetConditionals.h"
++# if TARGET_OS_IOS
++# if TARGET_OS_SIMULATOR
++# if __x86_64__
++ x86_64-iphonesimulator
++# else
++ arm64-iphonesimulator
++# endif
++# else
++ arm64-iphoneos
++# endif
++# elif TARGET_OS_TV
++# if TARGET_OS_SIMULATOR
++# if __x86_64__
++ x86_64-appletvsimulator
++# else
++ arm64-appletvsimulator
++# endif
++# else
++ arm64-appletvos
++# endif
++# elif TARGET_OS_WATCH
++# if TARGET_OS_SIMULATOR
++# if __x86_64__
++ x86_64-watchsimulator
++# else
++ arm64-watchsimulator
++# endif
++# else
++ arm64_32-watchos
++# endif
++# elif TARGET_OS_OSX
+ darwin
++# else
++# error unknown Apple platform
++# endif
+ #elif defined(__VXWORKS__)
+ vxworks
+ #elif defined(__wasm32__)
+@@ -1119,14 +1450,24 @@
+ fi
+ rm -f conftest.c conftest.out
+
++dnl On some platforms, using a true "triplet" for MULTIARCH would be redundant.
++dnl For example, `arm64-apple-darwin` is redundant, because there isn't a
++dnl non-Apple Darwin. Including the CPU architecture can also be potentially
++dnl redundant - on macOS, for example, it's possible to do a single compile
++dnl pass that includes multiple architectures, so it would be misleading for
++dnl MULTIARCH (and thus the sysconfigdata module name) to include a single CPU
++dnl architecture. PLATFORM_TRIPLET will be a pair or single value for these
++dnl platforms.
+ AC_MSG_CHECKING([for multiarch])
+ AS_CASE([$ac_sys_system],
+ [Darwin*], [MULTIARCH=""],
++ [iOS], [MULTIARCH=""],
++ [tvOS], [MULTIARCH=""],
++ [watchOS], [MULTIARCH=""],
+ [FreeBSD*], [MULTIARCH=""],
+ [MULTIARCH=$($CC --print-multiarch 2>/dev/null)]
+ )
+ AC_SUBST([MULTIARCH])
+-AC_MSG_RESULT([$MULTIARCH])
+
+ if test x$PLATFORM_TRIPLET != x && test x$MULTIARCH != x; then
+ if test x$PLATFORM_TRIPLET != x$MULTIARCH; then
+@@ -1136,6 +1477,17 @@
+ MULTIARCH=$PLATFORM_TRIPLET
+ fi
+ AC_SUBST([PLATFORM_TRIPLET])
++AC_MSG_RESULT([$MULTIARCH])
+
-+* ``--build`` is the GNU compiler triple for the machine that will be running
-+ the compiler. This is one of:
++dnl Even if we *do* include the CPU architecture in the MULTIARCH value, some
++dnl platforms don't need the CPU architecture in the SOABI tag. These platforms
++dnl will have multiple sysconfig modules (one for each CPU architecture), but
++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],
++ [iOS|tvOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`],
++ [SOABI_PLATFORM=$PLATFORM_TRIPLET]
++)
+
+ if test x$MULTIARCH != x; then
+ MULTIARCH_CPPFLAGS="-DMULTIARCH=\\\"$MULTIARCH\\\""
+@@ -1166,6 +1518,12 @@
+ [wasm32-unknown-emscripten/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly Emscripten
+ [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly System Interface
+ [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64
++ [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64
++ [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64
++ [aarch64-apple-tvos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl tvOS Simulator on arm64
++ [aarch64-apple-tvos*/clang], [PY_SUPPORT_TIER=3], dnl tvOS on ARM64
++ [aarch64-apple-watchos*-simulator/clang], [PY_SUPPORT_TIER=3], dnl watchOS Simulator on arm64
++ [arm64_32-apple-watchos*/clang], [PY_SUPPORT_TIER=3], dnl watchOS on ARM64
+ [PY_SUPPORT_TIER=0]
+ )
+
+@@ -1482,17 +1840,25 @@
+
+ AC_MSG_CHECKING([LDLIBRARY])
+
+-# MacOSX framework builds need more magic. LDLIBRARY is the dynamic
++# Apple framework builds need more magic. LDLIBRARY is the dynamic
+ # library that we build, but we do not want to link against it (we
+ # will find it with a -framework option). For this reason there is an
+ # extra variable BLDLIBRARY against which Python and the extension
+ # modules are linked, BLDLIBRARY. This is normally the same as
+-# LDLIBRARY, but empty for MacOSX framework builds.
++# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same,
++# but uses a non-versioned framework layout.
+ if test "$enable_framework"
+ then
+- LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
+- RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
++ case $ac_sys_system in
++ Darwin)
++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';;
++ iOS|tvOS|watchOS)
++ LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';;
++ *)
++ AC_MSG_ERROR([Unknown platform for framework build]);;
++ esac
+ BLDLIBRARY=''
++ RUNSHARED=DYLD_FRAMEWORK_PATH=`pwd`${DYLD_FRAMEWORK_PATH:+:${DYLD_FRAMEWORK_PATH}}
+ else
+ BLDLIBRARY='$(LDLIBRARY)'
+ fi
+@@ -1504,64 +1870,69 @@
+ [Defined if Python is built as a shared library.])
+ case $ac_sys_system in
+ CYGWIN*)
+- LDLIBRARY='libpython$(LDVERSION).dll.a'
+- DLLLIBRARY='libpython$(LDVERSION).dll'
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).dll.a'
++ DLLLIBRARY='libpython$(LDVERSION).dll'
++ ;;
+ SunOS*)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
+- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
+- INSTSONAME="$LDLIBRARY".$SOVERSION
+- if test "$with_pydebug" != yes
+- then
+- PY3LIBRARY=libpython3.so
+- fi
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).so'
++ BLDLIBRARY='-Wl,-R,$(LIBDIR) -L. -lpython$(LDVERSION)'
++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
++ INSTSONAME="$LDLIBRARY".$SOVERSION
++ if test "$with_pydebug" != yes
++ then
++ PY3LIBRARY=libpython3.so
++ fi
++ ;;
+ Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|OpenBSD*|VxWorks*)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- BLDLIBRARY='-L. -lpython$(LDVERSION)'
+- RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
+- INSTSONAME="$LDLIBRARY".$SOVERSION
+- if test "$with_pydebug" != yes
+- then
+- PY3LIBRARY=libpython3.so
+- fi
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).so'
++ BLDLIBRARY='-L. -lpython$(LDVERSION)'
++ RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
++ INSTSONAME="$LDLIBRARY".$SOVERSION
++ if test "$with_pydebug" != yes
++ then
++ PY3LIBRARY=libpython3.so
++ fi
++ ;;
+ hp*|HP*)
+- case `uname -m` in
+- ia64)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- ;;
+- *)
+- LDLIBRARY='libpython$(LDVERSION).sl'
+- ;;
+- esac
+- BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
+- RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
+- ;;
++ case `uname -m` in
++ ia64)
++ LDLIBRARY='libpython$(LDVERSION).so'
++ ;;
++ *)
++ LDLIBRARY='libpython$(LDVERSION).sl'
++ ;;
++ esac
++ BLDLIBRARY='-Wl,+b,$(LIBDIR) -L. -lpython$(LDVERSION)'
++ RUNSHARED=SHLIB_PATH=`pwd`${SHLIB_PATH:+:${SHLIB_PATH}}
++ ;;
+ Darwin*)
+- LDLIBRARY='libpython$(LDVERSION).dylib'
+- BLDLIBRARY='-L. -lpython$(LDVERSION)'
+- RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).dylib'
++ BLDLIBRARY='-L. -lpython$(LDVERSION)'
++ RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
++ ;;
++ iOS|tvOS|watchOS)
++ LDLIBRARY='libpython$(LDVERSION).dylib'
++ ;;
+ AIX*)
+- LDLIBRARY='libpython$(LDVERSION).so'
+- RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
+- ;;
++ LDLIBRARY='libpython$(LDVERSION).so'
++ RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}}
++ ;;
+
+ esac
+ else # shared is disabled
+ PY_ENABLE_SHARED=0
+ case $ac_sys_system in
+ CYGWIN*)
+- BLDLIBRARY='$(LIBRARY)'
+- LDLIBRARY='libpython$(LDVERSION).dll.a'
+- ;;
++ BLDLIBRARY='$(LIBRARY)'
++ LDLIBRARY='libpython$(LDVERSION).dll.a'
++ ;;
+ esac
+ fi
+
++AC_MSG_RESULT([$LDLIBRARY])
+
-+ - ``aarch64-apple-darwin`` for Apple Silicon devices.
-+ - ``x86_64-apple-darwin`` for Intel devices.
+ if test "$cross_compiling" = yes; then
+- RUNSHARED=
++ RUNSHARED=
+ fi
+
+ AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform])
+@@ -1617,8 +1988,6 @@
+ PYTHON_FOR_BUILD="_PYTHON_HOSTRUNNER='$HOSTRUNNER' $PYTHON_FOR_BUILD"
+ fi
+
+-AC_MSG_RESULT([$LDLIBRARY])
+-
+ # LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable
+ AS_CASE([$ac_sys_system/$ac_sys_emscripten_target],
+ [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js'],
+@@ -3380,6 +3749,11 @@
+ BLDSHARED="$LDSHARED"
+ fi
+ ;;
++ iOS/*|tvOS/*|watchOS/*)
++ LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
++ LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)'
++ BLDSHARED="$LDSHARED"
++ ;;
+ Emscripten*|WASI*)
+ LDSHARED='$(CC) -shared'
+ LDCXXSHARED='$(CXX) -shared';;
+@@ -3500,30 +3874,34 @@
+ Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";;
+ Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";;
+ # -u libsys_s pulls in all symbols in libsys
+- Darwin/*)
++ Darwin/*|iOS/*|tvOS/*|watchOS/*)
+ LINKFORSHARED="$extra_undefs -framework CoreFoundation"
+
+ # Issue #18075: the default maximum stack size (8MBytes) is too
+ # small for the default recursion limit. Increase the stack size
+ # to ensure that tests don't crash
+- stack_size="1000000" # 16 MB
+- if test "$with_ubsan" = "yes"
+- then
+- # Undefined behavior sanitizer requires an even deeper stack
+- stack_size="4000000" # 64 MB
+- fi
++ stack_size="1000000" # 16 MB
++ if test "$with_ubsan" = "yes"
++ then
++ # Undefined behavior sanitizer requires an even deeper stack
++ stack_size="4000000" # 64 MB
++ fi
+
+- LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
++ AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE],
++ [0x$stack_size],
++ [Custom thread stack size depending on chosen sanitizer runtimes.])
+
+- AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE],
+- [0x$stack_size],
+- [Custom thread stack size depending on chosen sanitizer runtimes.])
++ if test $ac_sys_system = "Darwin"; then
++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"
+
+- if test "$enable_framework"
+- then
+- LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
++ if test "$enable_framework"; then
++ LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)'
++ fi
++ LINKFORSHARED="$LINKFORSHARED"
++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
++ LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
+ fi
+- LINKFORSHARED="$LINKFORSHARED";;
++ ;;
+ OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";;
+ SCO_SV*) LINKFORSHARED="-Wl,-Bexport";;
+ ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";;
+@@ -3897,6 +4275,9 @@
+ dnl when do we need USING_APPLE_OS_LIBFFI?
+ ctypes_malloc_closure=yes
+ ],
++ [iOS|tvOS|watchOS], [
++ ctypes_malloc_closure=yes
++ ],
+ [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])]
+ )
+ AS_VAR_IF([ctypes_malloc_closure], [yes], [
+@@ -4920,28 +5301,28 @@
+ # checks for library functions
+ AC_CHECK_FUNCS([ \
+ accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \
+- copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \
++ copy_file_range ctermid dup dup3 explicit_bzero explicit_memset \
+ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \
+- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \
+- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \
+- getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \
++ fpathconf fstatat ftime ftruncate futimens futimes futimesat \
++ gai_strerror getegid geteuid getgid getgrgid getgrgid_r \
++ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \
+ getpeername getpgid getpid getppid getpriority _getpty \
+ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \
+ getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \
+ lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
+ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
+- pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \
++ pipe2 plock poll posix_fadvise posix_fallocate \
+ pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \
+ pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
+ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \
+ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \
+ sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \
+ setitimer setlocale setpgid setpgrp setpriority setregid setresgid \
+- setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \
++ setresuid setreuid setsid setuid setvbuf shutdown sigaction \
+ sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \
+ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \
+- sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
+- tmpnam tmpnam_r truncate ttyname_r umask uname unlinkat utimensat utimes vfork \
++ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
++ tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \
+ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \
+ ])
+
+@@ -4952,6 +5333,22 @@
+ AC_CHECK_FUNCS([lchmod])
+ fi
+
++# iOS/tvOS/watchOS define some system methods that can be linked (so they are
++# found by configure), but either raise a compilation error (because the
++# header definition prevents usage - autoconf doesn't use the headers), or
++# raise an error if used at runtime. Force these symbols off.
++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
++ AC_CHECK_FUNCS([ getentropy getgroups system ])
++fi
+
-+* ``/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.
++# 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 \
++ sigaltstack \
++ ])
++fi
+
-+Using a framework-based Python on watchOS
-+======================================
+ AC_CHECK_DECL([dirfd],
+ [AC_DEFINE([HAVE_DIRFD], [1],
+ [Define if you have the 'dirfd' function or macro.])],
+@@ -5193,20 +5590,22 @@
+ ])
+
+ # check for openpty, login_tty, and forkpty
+-
+-AC_CHECK_FUNCS([openpty], [],
+- [AC_CHECK_LIB([util], [openpty],
+- [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"],
+- [AC_CHECK_LIB([bsd], [openpty],
+- [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])])
+-AC_SEARCH_LIBS([login_tty], [util],
+- [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])]
+-)
+-AC_CHECK_FUNCS([forkpty], [],
+- [AC_CHECK_LIB([util], [forkpty],
+- [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"],
+- [AC_CHECK_LIB([bsd], [forkpty],
+- [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])])
++# tvOS/watchOS have functions for tty, but can't use them
++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then
++ AC_CHECK_FUNCS([openpty], [],
++ [AC_CHECK_LIB([util], [openpty],
++ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lutil"],
++ [AC_CHECK_LIB([bsd], [openpty],
++ [AC_DEFINE([HAVE_OPENPTY]) LIBS="$LIBS -lbsd"])])])
++ AC_SEARCH_LIBS([login_tty], [util],
++ [AC_DEFINE([HAVE_LOGIN_TTY], [1], [Define to 1 if you have the `login_tty' function.])]
++ )
++ AC_CHECK_FUNCS([forkpty], [],
++ [AC_CHECK_LIB([util], [forkpty],
++ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lutil"],
++ [AC_CHECK_LIB([bsd], [forkpty],
++ [AC_DEFINE([HAVE_FORKPTY]) LIBS="$LIBS -lbsd"])])])
++fi
+
+ # check for long file support functions
+ AC_CHECK_FUNCS([fseek64 fseeko fstatvfs ftell64 ftello statvfs])
+@@ -5289,11 +5688,17 @@
+ ])
+ ])
+
+-AC_CHECK_FUNCS([clock_settime], [], [
+- AC_CHECK_LIB([rt], [clock_settime], [
+- AC_DEFINE([HAVE_CLOCK_SETTIME], [1])
+- ])
+-])
++# On iOS, tvOS and watchOS, clock_settime can be linked (so it is found by
++# configure), but when used in an unprivileged process, it crashes rather than
++# returning an error. Force the symbol off.
++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS"
++then
++ AC_CHECK_FUNCS([clock_settime], [], [
++ AC_CHECK_LIB([rt], [clock_settime], [
++ AC_DEFINE([HAVE_CLOCK_SETTIME], [1])
++ ])
++ ])
++fi
+
+ AC_CHECK_FUNCS([clock_nanosleep], [], [
+ AC_CHECK_LIB([rt], [clock_nanosleep], [
+@@ -5439,7 +5844,9 @@
+ [ac_cv_buggy_getaddrinfo=no],
+ [ac_cv_buggy_getaddrinfo=yes],
+ [
+-if test "${enable_ipv6+set}" = set; then
++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS"; then
++ ac_cv_buggy_getaddrinfo="no"
++elif test "${enable_ipv6+set}" = set; then
+ ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6"
+ else
+ ac_cv_buggy_getaddrinfo=yes
+@@ -5992,14 +6399,14 @@
+ AC_MSG_CHECKING([ABIFLAGS])
+ AC_MSG_RESULT([$ABIFLAGS])
+ AC_MSG_CHECKING([SOABI])
+-SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
++SOABI='cpython-'`echo $VERSION | tr -d .`${ABIFLAGS}${SOABI_PLATFORM:+-$SOABI_PLATFORM}
+ AC_MSG_RESULT([$SOABI])
+
+ # Release and debug (Py_DEBUG) ABI are compatible, but not Py_TRACE_REFS ABI
+ if test "$Py_DEBUG" = 'true' -a "$with_trace_refs" != "yes"; then
+ # Similar to SOABI but remove "d" flag from ABIFLAGS
+ AC_SUBST([ALT_SOABI])
+- ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${PLATFORM_TRIPLET:+-$PLATFORM_TRIPLET}
++ ALT_SOABI='cpython-'`echo $VERSION | tr -d .``echo $ABIFLAGS | tr -d d`${SOABI_PLATFORM:+-$SOABI_PLATFORM}
+ AC_DEFINE_UNQUOTED([ALT_SOABI], ["${ALT_SOABI}"],
+ [Alternative SOABI used in debug build to load C extensions built in release mode])
+ fi
+@@ -6648,28 +7055,35 @@
+ AC_MSG_NOTICE([checking for device files])
+
+ dnl NOTE: Inform user how to proceed with files when cross compiling.
+-if test "x$cross_compiling" = xyes; then
+- if test "${ac_cv_file__dev_ptmx+set}" != set; then
+- AC_MSG_CHECKING([for /dev/ptmx])
+- AC_MSG_RESULT([not set])
+- AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling])
+- fi
+- if test "${ac_cv_file__dev_ptc+set}" != set; then
+- AC_MSG_CHECKING([for /dev/ptc])
+- AC_MSG_RESULT([not set])
+- AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling])
++dnl iOS cross-compile builds are predictable; they won't ever
++dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly.
++if test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" ; then
++ ac_cv_file__dev_ptmx=no
++ ac_cv_file__dev_ptc=no
++else
++ if test "x$cross_compiling" = xyes; then
++ if test "${ac_cv_file__dev_ptmx+set}" != set; then
++ AC_MSG_CHECKING([for /dev/ptmx])
++ AC_MSG_RESULT([not set])
++ AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling])
++ fi
++ if test "${ac_cv_file__dev_ptc+set}" != set; then
++ AC_MSG_CHECKING([for /dev/ptc])
++ AC_MSG_RESULT([not set])
++ AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling])
++ fi
+ fi
+-fi
+
+-AC_CHECK_FILE([/dev/ptmx], [], [])
+-if test "x$ac_cv_file__dev_ptmx" = xyes; then
+- AC_DEFINE([HAVE_DEV_PTMX], [1],
+- [Define to 1 if you have the /dev/ptmx device file.])
+-fi
+-AC_CHECK_FILE([/dev/ptc], [], [])
+-if test "x$ac_cv_file__dev_ptc" = xyes; then
+- AC_DEFINE([HAVE_DEV_PTC], [1],
+- [Define to 1 if you have the /dev/ptc device file.])
++ AC_CHECK_FILE([/dev/ptmx], [], [])
++ if test "x$ac_cv_file__dev_ptmx" = xyes; then
++ AC_DEFINE([HAVE_DEV_PTMX], [1],
++ [Define to 1 if you have the /dev/ptmx device file.])
++ fi
++ AC_CHECK_FILE([/dev/ptc], [], [])
++ if test "x$ac_cv_file__dev_ptc" = xyes; then
++ AC_DEFINE([HAVE_DEV_PTC], [1],
++ [Define to 1 if you have the /dev/ptc device file.])
++ fi
+ fi
+
+ if test $ac_sys_system = Darwin
+@@ -6941,6 +7355,7 @@
+ AS_CASE([$ac_sys_system],
+ [Emscripten], [with_ensurepip=no],
+ [WASI], [with_ensurepip=no],
++ [iOS|tvOS|watchOS], [with_ensurepip=no],
+ [with_ensurepip=upgrade]
+ )
+ ])
+@@ -7283,6 +7698,28 @@
+ [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])],
+ [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])],
+ [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])],
++ [iOS|tvOS|watchOS], [
++ dnl subprocess and multiprocessing are not supported (no fork syscall).
++ dnl curses and tkinter user interface are not available.
++ dnl gdbm and nis aren't available
++ dnl Stub implementations are provided for pwd, grp etc APIs
++ PY_STDLIB_MOD_SET_NA(
++ [_curses],
++ [_curses_panel],
++ [_gdbm],
++ [_multiprocessing],
++ [_posixshmem],
++ [_posixsubprocess],
++ [_scproxy],
++ [_tkinter],
++ [grp],
++ [nis],
++ [readline],
++ [pwd],
++ [spwd],
++ [syslog],
++ )
++ ],
+ [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])],
+ [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])],
+ [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])],
--- /dev/null
-+++ b/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@
-+
-+
++++ b/iOS/Resources/bin/arm64-apple-ios-ar
+@@ -0,0 +1,2 @@
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar
++++ b/iOS/Resources/bin/arm64-apple-ios-clang
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang
++++ b/iOS/Resources/bin/arm64-apple-ios-clang++
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos-simulator "$@"
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++
++++ b/iOS/Resources/bin/arm64-apple-ios-cpp
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos-simulator "$@"
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp
++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator clang -target arm64-apple-watchos-simulator -E "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-ar
++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang
++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-clang++
++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-cpp
++++ b/iOS/Resources/bin/arm64-apple-ios-simulator-strip
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos -E "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar
++++ b/iOS/Resources/bin/arm64-apple-ios-strip
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@"
++#!/bin/sh
++xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang
++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++
++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos-simulator "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
--- /dev/null
-+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp
++++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
@@ -0,0 +1,2 @@
-+#!/bin/bash
-+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos-simulator -E "$@"
++#!/bin/sh
++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
--- /dev/null
-+++ b/watchOS/Resources/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
-+
-+
++++ b/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 "$@"
--- /dev/null
-+++ b/watchOS/Resources/pyconfig.h
-@@ -0,0 +1,11 @@
-+#ifdef __arm64__
-+# ifdef __LP64__
-+#include "pyconfig-arm64.h"
-+# else
-+#include "pyconfig-arm64_32.h"
-+# endif
-+#endif
-+
-+#ifdef __x86_64__
-+#include "pyconfig-x86_64.h"
-+#endif
++++ b/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/patch/Python/_cross_target.py.tmpl b/patch/Python/_cross_target.py.tmpl
index 5f822e24..f871db3d 100644
--- a/patch/Python/_cross_target.py.tmpl
+++ b/patch/Python/_cross_target.py.tmpl
@@ -12,6 +12,8 @@ import sysconfig
sys.cross_compiling = True
sys.platform = "{{platform}}"
sys.implementation._multiarch = "{{arch}}-{{sdk}}"
+sys.base_prefix = sysconfig.get_config_var("prefix")
+sys.base_exec_prefix = sysconfig.get_config_var("prefix")
###########################################################################
# subprocess module patches
@@ -74,5 +76,9 @@ def cross_get_sysconfigdata_name():
sysconfig.get_platform = cross_get_platform
sysconfig._get_sysconfigdata_name = cross_get_sysconfigdata_name
+# Ensure module-level values cached at time of import are updated.
+sysconfig._BASE_PREFIX = sys.base_prefix
+sysconfig._BASE_EXEC_PREFIX = sys.base_exec_prefix
+
# Force sysconfig data to be loaded (and cached).
sysconfig._init_config_vars()
diff --git a/tests/test_cross_env.py b/tests/test_cross_env.py
new file mode 100644
index 00000000..24b90457
--- /dev/null
+++ b/tests/test_cross_env.py
@@ -0,0 +1,98 @@
+import os
+import platform
+import sys
+import sysconfig
+from pathlib import Path
+
+import pytest
+
+# To run these tests, the following three environment variables must be set,
+# reflecting the cross-platform environment that is in effect.'
+PYTHON_CROSS_PLATFORM = os.getenv("PYTHON_CROSS_PLATFORM", "unknown")
+PYTHON_CROSS_SLICE = os.getenv("PYTHON_CROSS_SLICE", "unknown")
+PYTHON_CROSS_MULTIARCH = os.getenv("PYTHON_CROSS_MULTIARCH", "unknown")
+
+# Determine some file system anchor points for the tests
+# Assumes that the tests are run in a virtual environment named
+# `cross-venv`,
+VENV_PREFIX = os.getenv("VIRTUAL_ENV", Path(__file__).parent.parent / "cross-venv")
+default_support_base = (
+ f"support/{sys.version_info.major}.{sys.version_info.minor}/{PYTHON_CROSS_PLATFORM}"
+)
+SUPPORT_PREFIX = (
+ Path(__file__).parent.parent
+ / os.getenv("PYTHON_SUPPORT_BASE", default_support_base)
+ / "Python.xcframework"
+ / PYTHON_CROSS_SLICE
+)
+
+
+###########################################################################
+# sys
+###########################################################################
+
+
+def test_sys_platform():
+ assert sys.platform == PYTHON_CROSS_PLATFORM.lower()
+
+
+def test_sys_cross_compiling():
+ assert sys.cross_compiling
+
+
+def test_sys_multiarch():
+ assert sys.implementation._multiarch == PYTHON_CROSS_MULTIARCH
+
+
+def test_sys_base_prefix():
+ assert Path(sys.base_prefix) == SUPPORT_PREFIX
+
+
+def test_sys_base_exec_prefix():
+ assert Path(sys.base_exec_prefix) == SUPPORT_PREFIX
+
+
+###########################################################################
+# platform
+###########################################################################
+
+
+def test_platform_system():
+ assert platform.system() == PYTHON_CROSS_PLATFORM
+
+
+###########################################################################
+# sysconfig
+###########################################################################
+
+
+def test_sysconfig_get_platform():
+ parts = sysconfig.get_platform().split("-", 2)
+ assert parts[0] == PYTHON_CROSS_PLATFORM.lower()
+ assert parts[2] == PYTHON_CROSS_MULTIARCH
+
+
+def test_sysconfig_get_sysconfigdata_name():
+ parts = sysconfig._get_sysconfigdata_name().split("_", 4)
+ assert parts[3] == PYTHON_CROSS_PLATFORM.lower()
+ assert parts[4] == PYTHON_CROSS_MULTIARCH
+
+
+@pytest.mark.parametrize(
+ "name, prefix",
+ [
+ # Paths that should be relative to the support folder
+ ("stdlib", SUPPORT_PREFIX),
+ ("include", SUPPORT_PREFIX),
+ ("platinclude", SUPPORT_PREFIX),
+ ("stdlib", SUPPORT_PREFIX),
+ # paths that should be relative to the venv
+ ("platstdlib", VENV_PREFIX),
+ ("purelib", VENV_PREFIX),
+ ("platlib", VENV_PREFIX),
+ ("scripts", VENV_PREFIX),
+ ("data", VENV_PREFIX),
+ ],
+)
+def test_sysconfig_get_paths(name, prefix):
+ assert sysconfig.get_paths()[name].startswith(str(prefix))