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..2c6d9b2c 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,190 @@ 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.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 + 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 - - uses: actions/checkout@v4.1.7 - if: matrix.run-tests + - 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 + + - 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"] + + 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/tests/test_cross_env.py b/tests/test_cross_env.py new file mode 100644 index 00000000..34e8c5eb --- /dev/null +++ b/tests/test_cross_env.py @@ -0,0 +1,93 @@ +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 = 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))