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/check-pr-template.yml b/.github/workflows/check-pr-template.yml new file mode 100644 index 00000000..9196bacb --- /dev/null +++ b/.github/workflows/check-pr-template.yml @@ -0,0 +1,10 @@ +name: PR Checklist + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +jobs: + check-pr-template: + name: Check PR template + uses: beeware/.github/.github/workflows/pr-checklist.yml@main diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 36edb3b7..4b20a8bc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,6 +29,9 @@ on: XZ_VERSION: description: "The XZ version used for the build." value: ${{ jobs.config.outputs.XZ_VERSION }} + ZSTD_VERSION: + description: "The Zstandard version used for the build." + value: ${{ jobs.config.outputs.ZSTD_VERSION }} env: FORCE_COLOR: "1" @@ -54,167 +57,202 @@ jobs: MPDECIMAL_VERSION: ${{ steps.extract.outputs.MPDECIMAL_VERSION }} OPENSSL_VERSION: ${{ steps.extract.outputs.OPENSSL_VERSION }} XZ_VERSION: ${{ steps.extract.outputs.XZ_VERSION }} + ZSTD_VERSION: ${{ steps.extract.outputs.ZSTD_VERSION }} steps: - - uses: actions/checkout@v4.1.7 - - - name: Extract config variables - id: extract - run: | - PYTHON_VER=$(make config | grep "PYTHON_VER=" | cut -d "=" -f 2) - PYTHON_VERSION=$(make config | grep "PYTHON_VERSION=" | cut -d "=" -f 2) - BZIP2_VERSION=$(make config | grep "BZIP2_VERSION=" | cut -d "=" -f 2) - LIBFFI_VERSION=$(make config | grep "LIBFFI_VERSION=" | cut -d "=" -f 2) - MPDECIMAL_VERSION=$(make config | grep "MPDECIMAL_VERSION=" | cut -d "=" -f 2) - OPENSSL_VERSION=$(make config | grep "OPENSSL_VERSION=" | cut -d "=" -f 2) - XZ_VERSION=$(make config | grep "XZ_VERSION=" | cut -d "=" -f 2) - if [ -z "${{ inputs.build-number }}" ]; then - BUILD_NUMBER=custom - else - BUILD_NUMBER=${{ inputs.build-number }} - fi - - echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} - echo "PYTHON_VERSION=${PYTHON_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} - echo "BZIP2_VERSION=${BZIP2_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "LIBFFI_VERSION=${LIBFFI_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "MPDECIMAL_VERSION=${MPDECIMAL_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "OPENSSL_VERSION=${OPENSSL_VERSION}" | tee -a ${GITHUB_OUTPUT} - echo "XZ_VERSION=${XZ_VERSION}" | tee -a ${GITHUB_OUTPUT} + - uses: actions/checkout@v7 + + - name: Extract config variables + id: extract + run: | + PYTHON_VER=$(make config | grep "PYTHON_VER=" | cut -d "=" -f 2) + PYTHON_VERSION=$(make config | grep "PYTHON_VERSION=" | cut -d "=" -f 2) + BZIP2_VERSION=$(make config | grep "BZIP2_VERSION=" | cut -d "=" -f 2) + LIBFFI_VERSION=$(make config | grep "LIBFFI_VERSION=" | cut -d "=" -f 2) + MPDECIMAL_VERSION=$(make config | grep "MPDECIMAL_VERSION=" | cut -d "=" -f 2) + OPENSSL_VERSION=$(make config | grep "OPENSSL_VERSION=" | cut -d "=" -f 2) + XZ_VERSION=$(make config | grep "XZ_VERSION=" | cut -d "=" -f 2) + ZSTD_VERSION=$(make config | grep "ZSTD_VERSION=" | cut -d "=" -f 2) + if [ -z "${{ inputs.build-number }}" ]; then + BUILD_NUMBER=custom + else + BUILD_NUMBER=${{ inputs.build-number }} + fi + + echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} + echo "PYTHON_VERSION=${PYTHON_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} + echo "BZIP2_VERSION=${BZIP2_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "LIBFFI_VERSION=${LIBFFI_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "MPDECIMAL_VERSION=${MPDECIMAL_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "OPENSSL_VERSION=${OPENSSL_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "XZ_VERSION=${XZ_VERSION}" | tee -a ${GITHUB_OUTPUT} + echo "ZSTD_VERSION=${ZSTD_VERSION}" | tee -a ${GITHUB_OUTPUT} build: - runs-on: macOS-latest - needs: [ config ] + runs-on: macOS-15 + needs: [config] strategy: fail-fast: false matrix: - platform: ['macOS', 'iOS', 'tvOS', 'watchOS', 'visionOS'] + platform: ["macOS", "iOS", "tvOS", "watchOS", "visionOS"] steps: - - uses: actions/checkout@v4.1.7 - - - name: Set up Python - uses: actions/setup-python@v5.6.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - 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: Build ${{ matrix.platform }} - run: | - # 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.2 - with: - name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz - path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + - uses: actions/checkout@v7 + + - 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.3.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: Build ${{ matrix.platform }} + run: | + # 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@v7.0.1 + with: + name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz + path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz briefcase-testbed: name: Briefcase testbed (${{ matrix.platform }}) - runs-on: macOS-latest - needs: [ config, build ] + runs-on: macOS-15 + needs: [config, build] strategy: fail-fast: false matrix: platform: ["macOS", "iOS"] include: - - briefcase-run-args: + - briefcase-run-args: - - platform: iOS - briefcase-run-args: ' -d "iPhone SE (3rd generation)"' + - platform: iOS + briefcase-run-args: ' -d "iPhone 16e::iOS 18.5"' steps: - - uses: actions/checkout@v4.1.7 - - - name: Get build artifact - uses: actions/download-artifact@v5.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@v5.6.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - 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@v4.1.7 - with: - repository: beeware/Python-support-testbed - path: Python-support-testbed - - - name: Install dependencies - run: | - # Use the development version of Briefcase - python -m pip install git+https://github.com/beeware/briefcase.git - - - name: Run support testbed check - timeout-minutes: 10 - working-directory: Python-support-testbed - 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\' + - uses: actions/checkout@v7 + + - name: Set up Xcode + # GitHub recommends explicitly selecting the desired Xcode version: + # https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140 + # This became a necessity as a result of + # https://github.com/actions/runner-images/issues/12541 and + # https://github.com/actions/runner-images/issues/12751. + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app + + - name: Get build artifact + uses: actions/download-artifact@v8.0.1 + 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.3.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@v7 + with: + repository: beeware/Python-support-testbed + path: Python-support-testbed + + - name: Install dependencies + run: | + # Use the development version of Briefcase + python -m pip install git+https://github.com/beeware/briefcase.git + + - name: Run support testbed check + timeout-minutes: 15 + working-directory: Python-support-testbed + 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-latest - needs: [ config, build ] + runs-on: macOS-15 + needs: [config, build] strategy: fail-fast: false matrix: - platform: ["iOS", "visionOS"] + platform: ["iOS", "tvOS", "visionOS"] + include: + - platform: "iOS" + testbed-args: '--simulator "iPhone 16e,arch=arm64,OS=18.5"' + - platform: "visionOS" + testbed-args: '--simulator "Apple Vision Pro,arch=arm64,OS=2.5"' steps: - - uses: actions/checkout@v4.1.7 - - - name: Get build artifact - uses: actions/download-artifact@v5.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@v5.6.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - 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: 10 - 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 -- test --single-process --rerun -W test_builtin test_grammar test_os test_bz2 test_ctypes + - uses: actions/checkout@v7 + + - name: Get build artifact + uses: actions/download-artifact@v8.0.1 + 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.3.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 ] + needs: [config, build] strategy: fail-fast: false matrix: @@ -230,46 +268,46 @@ jobs: multiarch: arm64-iphoneos steps: - - uses: actions/checkout@v4.1.7 - - - name: Get build artifact - uses: actions/download-artifact@v5.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@v5.6.0 - with: - # Appending -dev ensures that we can always build the dev release. - # It's a no-op for versions that have been published. - 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 + - uses: actions/checkout@v7 + + - name: Get build artifact + uses: actions/download-artifact@v8.0.1 + 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.3.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/new-issue.yml b/.github/workflows/new-issue.yml new file mode 100644 index 00000000..e4a99c00 --- /dev/null +++ b/.github/workflows/new-issue.yml @@ -0,0 +1,18 @@ +name: New issue + +on: + issues: + types: + - opened + - reopened + - transferred + +jobs: + add-to-project: + name: Add issue to BeeWare project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v2.0.0 + with: + project-url: https://github.com/orgs/beeware/projects/1 + github-token: ${{ secrets.BRUTUS_PAT_TOKEN }} diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 587abe83..2021ddb3 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -8,46 +8,46 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - name: Set up Python environment - uses: actions/setup-python@v5.6.0 - with: - python-version: "3.X" + - name: Set up Python environment + uses: actions/setup-python@v6.3.0 + with: + python-version: "3.X" - - name: Set build variables - id: build-vars - env: - TAG_NAME: ${{ github.ref }} - run: | - TAG=$(basename $TAG_NAME) - PYTHON_VER="${TAG%-*}" - BUILD_NUMBER="${TAG#*-}" + - name: Set build variables + id: build-vars + env: + TAG_NAME: ${{ github.ref }} + run: | + TAG=$(basename $TAG_NAME) + PYTHON_VER="${TAG%-*}" + BUILD_NUMBER="${TAG#*-}" - echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} - echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} - echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} + echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} + echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} + echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} - - name: Update Release Asset to S3 - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - run: | - python -m pip install -U pip - python -m pip install -U setuptools - python -m pip install awscli - # macOS build - curl -o macOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp macOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/macOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - # iOS build - curl -o iOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp iOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/iOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - # tvOS build - curl -o tvOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp tvOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/tvOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - # watchOS build - curl -o watchOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp watchOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/watchOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - # visionOS build - curl -o visionOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-visionOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz - aws s3 cp visionOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/visionOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-visionOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + - name: Update Release Asset to S3 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + python -m pip install -U pip + python -m pip install -U setuptools + python -m pip install awscli + # macOS build + curl -o macOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp macOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/macOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-macOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + # iOS build + curl -o iOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp iOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/iOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-iOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + # tvOS build + curl -o tvOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp tvOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/tvOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-tvOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + # watchOS build + curl -o watchOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp watchOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/watchOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-watchOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + # visionOS build + curl -o visionOS-artefact.tar.gz -L https://github.com/beeware/Python-Apple-support/releases/download/${{ steps.build-vars.outputs.TAG }}/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-visionOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz + aws s3 cp visionOS-artefact.tar.gz s3://briefcase-support/python/${{ steps.build-vars.outputs.PYTHON_VER }}/visionOS/Python-${{ steps.build-vars.outputs.PYTHON_VER }}-visionOS-support.${{ steps.build-vars.outputs.BUILD_NUMBER }}.tar.gz diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dde7312b..ce5d82a8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,7 +5,7 @@ name: Build support package on: push: tags: - - '*-b*' + - "*-b*" jobs: config: @@ -16,20 +16,20 @@ jobs: BUILD_NUMBER: ${{ steps.build-vars.outputs.BUILD_NUMBER }} steps: - - name: Set Build Variables - id: build-vars - env: - TAG_NAME: ${{ github.ref }} - run: | - export TAG=$(basename $TAG_NAME) - export BUILD_NUMBER="${TAG#*-}" + - name: Set Build Variables + id: build-vars + env: + TAG_NAME: ${{ github.ref }} + run: | + export TAG=$(basename $TAG_NAME) + export BUILD_NUMBER="${TAG#*-}" - echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} - echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} + echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} + echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} ci: name: CI - needs: [ config ] + needs: [config] uses: ./.github/workflows/ci.yaml with: build-number: ${{ needs.config.outputs.BUILD_NUMBER }} @@ -37,29 +37,30 @@ jobs: make-release: name: Make Release runs-on: ubuntu-latest - needs: [ config, ci ] + needs: [config, ci] steps: - - name: Get build artifacts - uses: actions/download-artifact@v5.0.0 - with: - pattern: Python-* - path: dist - merge-multiple: true + - name: Get build artifacts + uses: actions/download-artifact@v8.0.1 + with: + pattern: Python-* + path: dist + merge-multiple: true - - name: Create Release - uses: ncipollo/release-action@v1.18.0 - with: - name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} - tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} - draft: true - body: | - Build ${{ needs.config.outputs.BUILD_NUMBER }} of the BeeWare support package for Python ${{ needs.ci.outputs.PYTHON_VER }}. + - name: Create Release + uses: ncipollo/release-action@v1.21.0 + with: + name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} + tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} + draft: true + body: | + Build ${{ needs.config.outputs.BUILD_NUMBER }} of the BeeWare support package for Python ${{ needs.ci.outputs.PYTHON_VER }}. - Includes: - * Python ${{ needs.ci.outputs.PYTHON_VERSION }} - * BZip2 ${{ needs.ci.outputs.BZIP2_VERSION }} - * libFFI ${{ needs.ci.outputs.LIBFFI_VERSION }} - * mpdecimal ${{ needs.ci.outputs.MPDECIMAL_VERSION }} - * OpenSSL ${{ needs.ci.outputs.OPENSSL_VERSION }} - * XZ ${{ needs.ci.outputs.XZ_VERSION }} - artifacts: "dist/*" + Includes: + * Python ${{ needs.ci.outputs.PYTHON_VERSION }} + * BZip2 ${{ needs.ci.outputs.BZIP2_VERSION }} + * libFFI ${{ needs.ci.outputs.LIBFFI_VERSION }} + * mpdecimal ${{ needs.ci.outputs.MPDECIMAL_VERSION }} + * OpenSSL ${{ needs.ci.outputs.OPENSSL_VERSION }} + * XZ ${{ needs.ci.outputs.XZ_VERSION }} + * Zstandard ${{ needs.ci.outputs.ZSTD_VERSION }} + artifacts: "dist/*" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..c4d88e81 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,162 @@ + + + +# BeeWare Community Code of Conduct + +## Our pledge + +We pledge to make our community welcoming, safe, and equitable for all. + +We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant. + +The guidelines within and enforcement of the BeeWare Community Code of Conduct apply equally to everyone participating in the BeeWare community, including members of the Code of Conduct Response Team. + +## Encouraged behaviors + +While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language. + +With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including: + +1. Respecting the **purpose of our community**, our activities, and our ways of gathering. +2. Engaging **kindly and honestly** with others. +3. Respecting **different viewpoints** and experiences. +4. **Taking responsibility** for our actions and contributions. +5. Gracefully giving and accepting **constructive feedback**. +6. Committing to **repairing harm** when it occurs. +7. Behaving in other ways that promote and sustain the **well-being of our community**. + +## Restricted behaviors + +We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct. + +1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop. +2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people. +3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of a personal identity or trait. +4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community. +5. **Violating confidentiality.** Sharing or acting on someone's personal or private information without their permission. +6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group. +7. Behaving in other ways that **threaten the well-being** of our community. + +### Other restrictions + +1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions. +2. **Misrepresenting project affiliation.** Speaking or acting in a way that implies an official affiliation with the BeeWare project, where one does not exist. +3. **Failing to credit sources.** Not properly crediting the sources of content you contribute. +4. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside the norms of the community. +5. **Excessive communication.** Disrespecting the time and space of others by engaging in an unacceptable volume of communication. +6. **Unhelpful communication.** Offering opinions without relevant experience in the topic being discussed, entering into an ongoing discussion without first gaining familiarity with the history of the topic, or making contributions that are off-topic or otherwise distracting. +7. **Irresponsible messaging.** Presenting content which includes, links, or describes other restricted behaviors without a relevant reason and appropriate prior warnings for consumers of that content. +8. Other conduct that could reasonably be considered **unprofessional or inappropriate**. + +## Reporting an issue + +Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm. Reporting even minor issues is important, as they can be helpful in identifying patterns of behavior that may not be concerning in isolation, but when viewed collectively may be more significant. + +When an incident does occur, it is important to report it promptly to the BeeWare Code of Conduct Response Team. + +**If you believe you or anyone else is in physical danger, please notify appropriate law enforcement first.** + +To report a possible violation, email the Team at [conduct@beeware.org](mailto:conduct@beeware.org). If necessary, you can reach out to individual team members. On the BeeWare Discord server, you can also direct message anyone on the Response Team, or, if appropriate, mention `@moderators` in a public channel. Team members can be reached by the following usernames on Discord or GitHub, or the provided email addresses: + +* Russell Keith-Magee (@freakboy3742; [russell@beeware.org](mailto:russell@beeware.org)) +* Kattni (@kattni; [kattni@beeware.org](mailto:kattni@beeware.org)) +* Katie McLaughlin (@glasnt; [katie@beeware.org](mailto:katie@beeware.org)) +* Philip James (@phildini; [philip@beeware.org](mailto:philip@beeware.org)) +* Charles Whittington (@HalfWhitt; [charles@beeware.org](mailto:charles@beeware.org)) + +The Response Team takes reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. The Team will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution. If we determine that a public statement needs to be made, the identities of all victims and reporters will remain confidential unless those individuals instruct us otherwise. + +In your report, please include: + +* **Your contact info** so we can get in touch with you if we need to follow up. +* **Names (real, nicknames, or pseudonyms) of any individuals involved.** If there were other witnesses besides you, please try to include them as well. +* **When and where the incident occurred.** Please be as specific as possible. +* **Your account of what occurred.** If there is a publicly available record (e.g. a Discord or GitHub message) please include a link. +* **Any extra context** you believe existed for the incident. +* **If you believe this incident is ongoing.** +* **If you believe any member of the Response Team has a conflict of interest** in adjudicating the incident. +* **What, if any, corrective response** you believe would be appropriate. +* **Any other information** you believe we should have. + +Code of Conduct Response Team members are obligated to maintain confidentiality with regard to the reporter and details of an incident. + +## The Response Team's report followup + +You will receive a response acknowledging receipt of your report. We promise to acknowledge receipt within 24 hours (and will aim for much quicker than that). + +The Response Team will immediately meet to review the incident and determine: + +* What happened. +* Whether this event constitutes a code of conduct violation. +* Who the reported person is. +* Whether this is an ongoing situation, or if there is a threat to anyone's physical safety. +* If this is determined to be an ongoing incident or a threat to physical safety, the Response Team's immediate priority will be to protect everyone involved. This means we may delay an official response until we believe that the situation has concluded and that everyone is physically safe. +* If a member of the Response Team is one of the named parties, they will not be included in any discussions, and will not be provided with any confidential details from the reporter. + +If anyone on the Response Team believes they have a conflict of interest in adjudicating on a reported issue, they will inform the other Response Team members, and recuse themselves from any discussion about the issue. Following this declaration, they will not be provided with any confidential details from the reporter. + +We'll respond within one week to the person who filed the report with either a resolution or an explanation of why the situation is not yet resolved. + +Once we've determined our final action, we'll contact the original reporter to let them know what action (if any) we'll be taking. We'll take into account feedback from the reporter on the appropriateness of our response, but we don't guarantee we'll act on it. + +Finally, to maintain transparency in the reporting and enforcement process, whenever possible, the Response Team will make a public report of the incident on [The Buzz](https://beeware.org/news/buzz), the BeeWare blog. A public report may not be made if the specifics of the incident do not allow us to preserve anonymity, or if there is potential for ongoing harm. + +## Enforcement: addressing and repairing harm + +If an investigation by the Response Team finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped. + +1. Warning + * Event: A violation involving a single incident or series of incidents. + * Consequence: A private, written warning from the Response Team. + * Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations. + +2. Temporarily Limited Activities + * Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation. + * Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members. + * Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over. + +3. Temporary Suspension + * Event: A pattern of repeated violation which the Response Team has tried to address with warnings, or a single serious violation. + * Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions. Suspensions will be based on where the violation occurs, and may be limited to the space in which the violation occurs. In the event of a more serious violation, the suspension may apply to all spaces. + * Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted. + +4. Permanent Ban + * Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Response Team determines there is no way to keep the community safe with this person as a member. + * Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior. + * Repair: There is no possible repair in cases of this severity. + +This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community. + +## Scope + +This Code of Conduct applies within all community spaces, including GitHub, the BeeWare Discord server, and in-person events, such as conferences, meetups, and sprints. It also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +Behavior outside of official BeeWare spaces may also be considered as supporting evidence for a report if that behavior establishes a pattern, or represents a potential risk to the BeeWare community. + +This Code of Conduct operates in parallel to any Code of Conduct that is in effect in a given context (e.g., the Code of Conduct for a conference). If an incident occurs, we encourage reporting that incident to all relevant conduct groups. Known violations of other Codes of Conduct may be considered as supporting evidence for a report under this Code of Conduct. The BeeWare Code of Conduct Response Team will cooperate with other Code of Conduct teams, but will not disclose any identifying details without the prior consent of the reporting party. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/). + +Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). + +For answers to common questions about Contributor Covenant, see the [FAQ](https://www.contributor-covenant.org/faq). [Translations](https://www.contributor-covenant.org/translations) are provided. There are [additional enforcement and community guideline resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion). + +## Changes + +Major substantive changes are listed here; for a complete list of changes see the GitHub commit history. + +* **December 15, 2025:** Updated to adapt the Contributor Covenant, version 3.0, with some modifications for BeeWare-specific guidelines and procedures. + +* **July 4, 2016:** Added instructions and guidelines for reporting incidents. + +* **December 5, 2015:** Initial Code of Conduct adopted. diff --git a/Makefile b/Makefile index 3579649c..9efef48d 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ BUILD_NUMBER=custom # of a release cycle, as official binaries won't be published. # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) # PYTHON_VER is the major/minor version (e.g., 3.10) -PYTHON_VERSION=3.14.0rc1 +PYTHON_VERSION=3.14.6 PYTHON_PKG_VERSION=$(PYTHON_VERSION) PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+") @@ -30,8 +30,9 @@ PYTHON_VER=$(basename $(PYTHON_VERSION)) BZIP2_VERSION=1.0.8-2 LIBFFI_VERSION=3.4.7-2 MPDECIMAL_VERSION=4.0.0-2 -OPENSSL_VERSION=3.0.16-2 +OPENSSL_VERSION=3.5.7-1 XZ_VERSION=5.6.4-2 +ZSTD_VERSION=1.5.7-1 # Supported OS OS_LIST=macOS iOS tvOS watchOS visionOS @@ -41,25 +42,30 @@ 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 TARGETS-visionOS=xrsimulator.arm64 xros.arm64 TRIPLE_OS-visionOS=xros +PLATFORM_NAME-visionOS=xrOS VERSION_MIN-visionOS=2.0 # The architecture of the machine doing the build @@ -95,7 +101,7 @@ update-patch: # 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 @@ -187,6 +193,26 @@ $$(XZ_LIB-$(target)): downloads/xz-$(XZ_VERSION)-$(target).tar.gz # Ensure the target is marked as clean. touch $$(XZ_LIB-$(target)) +########################################################################### +# Target: zstd (ZStandard) +########################################################################### + +ZSTD_INSTALL-$(target)=$(PROJECT_DIR)/install/$(os)/$(target)/zstd-$(ZSTD_VERSION) +ZSTD_LIB-$(target)=$$(ZSTD_INSTALL-$(target))/lib/libzstd.a + +downloads/zstd-$(ZSTD_VERSION)-$(target).tar.gz: + @echo ">>> Download zstd for $(target)" + mkdir -p downloads + curl $(CURL_FLAGS) -o $$@ \ + https://github.com/beeware/cpython-apple-source-deps/releases/download/zstd-$(ZSTD_VERSION)/zstd-$(ZSTD_VERSION)-$(target).tar.gz + +$$(ZSTD_LIB-$(target)): downloads/zstd-$(ZSTD_VERSION)-$(target).tar.gz + @echo ">>> Install zstd for $(target)" + mkdir -p $$(ZSTD_INSTALL-$(target)) + cd $$(ZSTD_INSTALL-$(target)) && tar zxvf $(PROJECT_DIR)/downloads/zstd-$(ZSTD_VERSION)-$(target).tar.gz --exclude="*.dylib" + # Ensure the target is marked as clean. + touch $$(ZSTD_LIB-$(target)) + ########################################################################### # Target: mpdecimal ########################################################################### @@ -280,14 +306,15 @@ $$(PYTHON_SRCDIR-$(target))/configure: \ $$(LIBFFI_LIB-$(target)) \ $$(MPDECIMAL_LIB-$(target)) \ $$(OPENSSL_SSL_LIB-$(target)) \ - $$(XZ_LIB-$(target)) + $$(XZ_LIB-$(target)) \ + $$(ZSTD_LIB-$(target)) @echo ">>> Unpack and configure Python for $(target)" mkdir -p $$(PYTHON_SRCDIR-$(target)) tar zxf downloads/Python-$(PYTHON_VERSION).tar.gz --strip-components 1 -C $$(PYTHON_SRCDIR-$(target)) # 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))/Platforms/Apple/$(os)/Resources/bin/* # Touch the configure script to ensure that Make identifies it as up to date. touch $$(PYTHON_SRCDIR-$(target))/configure @@ -295,7 +322,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))/Platforms/Apple/$(os)/Resources/bin:$(PATH)" \ ./configure \ LIBLZMA_CFLAGS="-I$$(XZ_INSTALL-$(target))/include" \ LIBLZMA_LIBS="-L$$(XZ_INSTALL-$(target))/lib -llzma" \ @@ -305,6 +332,8 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \ LIBMPDEC_LIBS="-L$$(MPDECIMAL_INSTALL-$(target))/lib -lmpdec" \ LIBFFI_CFLAGS="-I$$(LIBFFI_INSTALL-$(target))/include" \ LIBFFI_LIBS="-L$$(LIBFFI_INSTALL-$(target))/lib -lffi" \ + LIBZSTD_CFLAGS="-I$$(ZSTD_INSTALL-$(target))/include" \ + LIBZSTD_LIBS="-L$$(ZSTD_INSTALL-$(target))/lib -lzstd" \ --host=$$(TARGET_TRIPLE-$(target)) \ --build=$(HOST_ARCH)-apple-darwin \ --with-build-python=$(HOST_PYTHON) \ @@ -317,14 +346,14 @@ $$(PYTHON_SRCDIR-$(target))/Makefile: \ $$(PYTHON_SRCDIR-$(target))/python.exe: $$(PYTHON_SRCDIR-$(target))/Makefile @echo ">>> Build Python for $(target)" cd $$(PYTHON_SRCDIR-$(target)) && \ - PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ + PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Platforms/Apple/$(os)/Resources/bin:$(PATH)" \ make -j8 all \ 2>&1 | tee -a ../python-$(PYTHON_VERSION).build.log $$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe @echo ">>> Install Python for $(target)" cd $$(PYTHON_SRCDIR-$(target)) && \ - PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ + PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/Platforms/Apple/$(os)/Resources/bin:$(PATH)" \ make install \ 2>&1 | tee -a ../python-$(PYTHON_VERSION).install.log @@ -434,7 +463,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: @@ -449,7 +477,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))) @@ -490,53 +517,37 @@ $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h: $$(PYTHON_LIB-$(sdk)) mkdir -p $$(PYTHON_INSTALL-$(sdk))/include ln -si ../Python.framework/Headers $$(PYTHON_INSTALL-$(sdk))/include/python$(PYTHON_VER) -ifeq ($(os), visionOS) - echo "Skipping arch-specific header copying for visionOS" -else # Add the individual headers from each target in an arch-specific name $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_INCLUDE-$$(target))/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig-$$(ARCH-$$(target)).h; ) # Copy the cross-target header from the source folder of the first target in the $(sdk) SDK - cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h -endif + cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/Platforms/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))/_sysconfig_vars__*.json \ - $$(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 $$(PYTHON_STDLIB-$$(target))/_sysconfig_vars_* $$(PYTHON_INSTALL-$(sdk))/lib-$$(ARCH-$$(target))/python$(PYTHON_VER)/; ) + $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/build-details.json $$(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))/; ) - $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfig_vars_* $$(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))); ) - - # # Disable dSYM production (for now) - # # Create dSYM files for each module - # $$(foreach module,$$(wildcard $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/lib-dynload/*),dsymutil $$(PYTHON_STDLIB-$(sdk))/lib-dynload/$$(notdir $$(module)); ) - - # Copy in known-required xcprivacy files. - # Libraries linking OpenSSL must provide a privacy manifest. The one in this repository - # has been sourced from https://github.com/openssl/openssl/blob/openssl-3.0/os-dep/Apple/PrivacyInfo.xcprivacy - cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$(sdk))/lib-dynload/_hashlib.xcprivacy - cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$(sdk))/lib-dynload/_ssl.xcprivacy endif -$(sdk): $$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT +$(sdk): $$(PYTHON_PLATFORM_CONFIG-$(sdk))/sitecustomize.py ########################################################################### # SDK: Debug @@ -553,8 +564,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 @@ -659,24 +669,45 @@ 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))))))/Platforms/Apple/testbed/Python.xcframework/build/utils.sh $$(PYTHON_XCFRAMEWORK-$(os))/build + cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Platforms/Apple/testbed/Python.xcframework/build/$$(PLATFORM_NAME-$(os))-dylib-Info-template.plist $$(PYTHON_XCFRAMEWORK-$(os))/build + + @echo ">>> Install stdlib for $(os)" + mkdir -p $$(PYTHON_XCFRAMEWORK-$(os))/lib + 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)/_sysconfig_vars__*.json \ + $$(PYTHON_XCFRAMEWORK-$(os))/lib/python$(PYTHON_VER)/build-details.json \ + $$(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)); ) + + # Create symlink for dylib + $$(foreach sdk,$$(SDKS-$(os)),ln -si ../Python.framework/Python $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk))/lib/libpython$(PYTHON_VER).dylib; ) + # Disable dSYM production (for now) # $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/Python.dSYM $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) -ifeq ($(filter $(os),iOS visionOS),$(os)) +ifeq ($(filter $(os),iOS tvOS visionOS),$(os)) @echo ">>> Clone testbed project for $(os)" - $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/$(os)/testbed clone --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed + $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/Platforms/Apple/testbed clone --platform $(os) --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed endif @echo ">>> Create VERSIONS file for $(os)" @@ -689,6 +720,7 @@ endif echo "mpdecimal: $(MPDECIMAL_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS echo "OpenSSL: $(OPENSSL_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS echo "XZ: $(XZ_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS + echo "Zstandard: $(ZSTD_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS dist/Python-$(PYTHON_VER)-$(os)-support.$(BUILD_NUMBER).tar.gz: \ $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist \ @@ -757,6 +789,7 @@ config: @echo "MPDECIMAL_VERSION=$(MPDECIMAL_VERSION)" @echo "OPENSSL_VERSION=$(OPENSSL_VERSION)" @echo "XZ_VERSION=$(XZ_VERSION)" + @echo "ZSTD_VERSION=$(ZSTD_VERSION)" # Expand cross-platform build and clean targets for each output product clean: $(foreach os,$(OS_LIST),clean-$(os)) diff --git a/README.md b/README.md index 33e689de..3da329cc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ embedded into a macOS, iOS, tvOS, watchOS, or visionOS project. **This branch builds a packaged version of Python 3.14**. Other Python versions are available by cloning other branches of the main repository: -- [Python 3.9](https://github.com/beeware/Python-Apple-support/tree/3.9) - [Python 3.10](https://github.com/beeware/Python-Apple-support/tree/3.10) - [Python @@ -29,7 +28,7 @@ packages removed, and the App Store compliance patch applied. The iOS, tvOS, watchOS, and visionOS packages compiled by this project use the official [PEP 730](https://peps.python.org/pep-0730/) code that is part of Python 3.13 to provide iOS support; the relevant patches have -been backported to 3.9-3.12. Additional patches have been applied to add +been backported to 3.10-3.12. Additional patches have been applied to add tvOS, watchOS, and visionOS support. The binaries support x86_64 and arm64 for macOS; arm64 for iOS and @@ -38,14 +37,14 @@ devices. It also supports device simulators on both x86_64 and M1 hardware, except for visionOS, for which x86_64 simulators are officially unsupported. This should enable the code to run on: -- macOS 11 (Big Sur) or later, on: +- macOS 11 (Big Sur) or later, on: - MacBook (including MacBooks using Apple Silicon) - iMac (including iMacs using Apple Silicon) - Mac Mini (including Apple Silicon Mac minis) - Mac Studio (all models) - Mac Pro (all models) -- iOS 13.0 or later, on: +- iOS 13.0 or later, on: - iPhone (6s or later) - iPad (5th gen or later) - iPad Air (all models) @@ -53,13 +52,13 @@ officially unsupported. This should enable the code to run on: - iPad Pro (all models) - iPod Touch (7th gen or later) -- tvOS 12.0 or later, on: +- tvOS 12.0 or later, on: - Apple TV (4th gen or later) -- watchOS 4.0 or later, on: +- watchOS 4.0 or later, on: - Apple Watch (4th gen or later) -- visionOS 2.0 or later, on: +- visionOS 2.0 or later, on: - Apple Vision Pro ## Quickstart @@ -166,3 +165,5 @@ maintained: (EOL September 2022) - [Python 3.8](https://github.com/beeware/Python-Apple-support/tree/3.8) (EOL October 2024) +- [Python 3.9](https://github.com/beeware/Python-Apple-support/tree/3.9) + (EOL October 2025) diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch index f9f6d8e2..94fd56b9 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,30 +1,64 @@ +diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml +index eaa3986bf6a..6329771a3a8 100644 +--- a/.pre-commit-config.yaml ++++ b/.pre-commit-config.yaml +@@ -3,9 +3,9 @@ + rev: a27a2e47c7751b639d2b5badf0ef6ff11fee893f # frozen: v0.15.4 + hooks: + - id: ruff-check +- name: Run Ruff (lint) on Apple/ +- args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml] +- files: ^Apple/ ++ name: Run Ruff (lint) on Platforms/Apple/ ++ args: [--exit-non-zero-on-fix, --config=Platforms/Apple/.ruff.toml] ++ files: ^Platforms/Apple/ + - id: ruff-check + name: Run Ruff (lint) on Doc/ + args: [--exit-non-zero-on-fix] +@@ -35,9 +35,9 @@ + args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml] + files: ^Tools/wasm/ + - id: ruff-format +- name: Run Ruff (format) on Apple/ +- args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml] +- files: ^Apple ++ name: Run Ruff (format) on Platforms/Apple/ ++ args: [--exit-non-zero-on-fix, --config=Platforms/Apple/.ruff.toml] ++ files: ^Platforms/Apple/ + - id: ruff-format + name: Run Ruff (format) on Doc/ + args: [--exit-non-zero-on-fix] diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py -index 823a3692fd1..00639dd8488 100644 +index 7223fc49977..37e3d0df3a9 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py -@@ -419,7 +419,7 @@ - if name: - name = _os.fspath(name) +@@ -452,9 +452,9 @@ + else: + def _load_library(self, name, mode, handle, winmode): - # If the filename that has been provided is an iOS/tvOS/watchOS -+ # If the filename that has been provided is an iOS/tvOS/watchOS/visionOS - # .fwork file, dereference the location to the true origin of the - # binary. - if name.endswith(".fwork"): +- # .fwork file, dereference the location to the true origin of the +- # binary. ++ # If the filename that has been provided is an iOS, tvOS, visionOS ++ # or watchOS .fwork file, dereference the location to the true ++ # origin of the binary. + if name and name.endswith(".fwork"): + with open(name) as f: + name = _os.path.join( diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py -index 99504911a3d..527c2f36dd0 100644 +index 3b21658433b..23780f7ffc0 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py -@@ -126,7 +126,7 @@ +@@ -132,7 +132,7 @@ if (name := _get_module_filename(h)) is not None] return libraries -elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}: -+elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos", "visionos"}: ++elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "visionos", "watchos"}: from ctypes.macholib.dyld import dyld_find as _dyld_find def find_library(name): possible = ['lib%s.dylib' % name, -@@ -425,7 +425,7 @@ +@@ -450,7 +450,7 @@ # https://man.openbsd.org/dl_iterate_phdr # https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html if (os.name == "posix" and @@ -34,7 +68,7 @@ index 99504911a3d..527c2f36dd0 100644 if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"): diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py -index 8bcd741c446..d8a6f28edba 100644 +index 6a828ae75ed..e07de99fec2 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -52,7 +52,7 @@ @@ -42,24 +76,24 @@ index 8bcd741c446..d8a6f28edba 100644 # Bootstrap-related code ###################################################### _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', -_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos' -+_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'watchos', 'visionos' ++_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin', 'ios', 'tvos', 'visionos', 'watchos' _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) -@@ -1535,7 +1535,7 @@ +@@ -1538,7 +1538,7 @@ """ extension_loaders = [] if hasattr(_imp, 'create_dynamic'): - if sys.platform in {"ios", "tvos", "watchos"}: -+ if sys.platform in {"ios", "tvos", "watchos", "visionos"}: ++ if sys.platform in {"ios", "tvos", "visionos", "watchos"}: extension_loaders = [(AppleFrameworkLoader, [ suffix.replace(".so", ".fwork") for suffix in _imp.extension_suffixes() diff --git a/Lib/platform.py b/Lib/platform.py -index 7dd64de6025..a7e3c77f803 100644 +index b017b841311..c8c794199f3 100644 --- a/Lib/platform.py +++ b/Lib/platform.py -@@ -528,6 +528,78 @@ +@@ -539,6 +539,78 @@ return IOSVersionInfo(system, release, model, is_simulator) @@ -138,26 +172,26 @@ index 7dd64de6025..a7e3c77f803 100644 def _java_getprop(name, default): """This private helper is deprecated in 3.13 and will be removed in 3.15""" from java.lang import System -@@ -727,7 +799,7 @@ +@@ -738,7 +810,7 @@ default in case the command should fail. """ - if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos'}: -+ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'watchos', 'visionos'}: ++ if sys.platform in {'dos', 'win32', 'win16', 'ios', 'tvos', 'visionos', 'watchos'}: # XXX Others too ? return default -@@ -891,14 +963,30 @@ +@@ -902,14 +974,30 @@ csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' - # On the iOS simulator, os.uname returns the architecture as uname.machine. - # On device it returns the model name for some reason; but there's only one - # CPU architecture for iOS devices, so we know the right answer. -+ # On the iOS/tvOS/watchOS/visionOS 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. ++ # On the iOS/tvOS/visionOS/watchOS simulator, os.uname returns the ++ # architecture as uname.machine. On device it returns the model name for ++ # some reason; but there's only one CPU architecture for devices, so we know ++ # the right answer. def get_ios(): if sys.implementation._multiarch.endswith("simulator"): return os.uname().machine @@ -168,20 +202,20 @@ index 7dd64de6025..a7e3c77f803 100644 + return os.uname().machine + return 'arm64' + -+ def get_watchos(): ++ def get_visionos(): + if sys.implementation._multiarch.endswith("simulator"): + return os.uname().machine -+ return 'arm64_32' ++ return 'arm64' + -+ def get_visionos(): ++ def get_watchos(): + if sys.implementation._multiarch.endswith("simulator"): + return os.uname().machine -+ return 'arm64' ++ return 'arm64_32' + def from_subprocess(): """ Fall back to `uname -p` -@@ -1058,9 +1146,15 @@ +@@ -1069,9 +1157,15 @@ system = 'Android' release = android_ver().release @@ -191,28 +225,28 @@ index 7dd64de6025..a7e3c77f803 100644 system, release, _, _ = ios_ver() + if sys.platform == 'tvos': + system, release, _, _ = tvos_ver() -+ if sys.platform == 'watchos': -+ system, release, _, _ = watchos_ver() + if sys.platform == 'visionos': + system, release, _, _ = visionos_ver() ++ if sys.platform == 'watchos': ++ system, release, _, _ = watchos_ver() vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' -@@ -1350,6 +1444,12 @@ +@@ -1361,6 +1455,12 @@ # macOS and iOS both report as a "Darwin" kernel if sys.platform == "ios": system, release, _, _ = ios_ver() + elif sys.platform == "tvos": + system, release, _, _ = tvos_ver() -+ elif sys.platform == "watchos": -+ system, release, _, _ = watchos_ver() + elif sys.platform == "visionos": + system, release, _, _ = visionos_ver() ++ elif sys.platform == "watchos": ++ system, release, _, _ = watchos_ver() else: macos_release = mac_ver()[0] if macos_release: diff --git a/Lib/site.py b/Lib/site.py -index f9327197159..74899abecb0 100644 +index aeb7c6cfc71..3fa222ea148 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -298,8 +298,8 @@ @@ -227,7 +261,7 @@ index f9327197159..74899abecb0 100644 def joinuser(*args): diff --git a/Lib/subprocess.py b/Lib/subprocess.py -index 54c2eb515b6..03896a234bf 100644 +index 52b7b711770..173e6a6994a 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -75,7 +75,7 @@ @@ -235,12 +269,12 @@ index 54c2eb515b6..03896a234bf 100644 # some platforms do not support subprocesses -_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"} -+_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos", "visionos"} ++_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "visionos", "watchos"} if _mswindows: import _winapi diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py -index f93b98dd681..0db3dbdce05 100644 +index faf8273bd0b..7e9df528709 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -23,6 +23,9 @@ @@ -262,12 +296,12 @@ index f93b98dd681..0db3dbdce05 100644 return None def joinuser(*args): -@@ -730,6 +733,18 @@ +@@ -742,6 +745,18 @@ release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0") osname = sys.platform machine = sys.implementation._multiarch + elif sys.platform == "tvos": -+ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "9.0") ++ release = get_config_vars().get("TVOS_DEPLOYMENT_TARGET", "12.0") + osname = sys.platform + machine = sys.implementation._multiarch + elif sys.platform == "watchos": @@ -282,10 +316,10 @@ index f93b98dd681..0db3dbdce05 100644 import _osx_support osname, release, machine = _osx_support.get_platform_osx( diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py -index 1c1cbd03d02..1378985de4a 100644 +index c1bb6138a1b..ddd4f8bf669 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py -@@ -7159,9 +7159,9 @@ +@@ -7169,9 +7169,9 @@ self.assertEqual(dt_orig, dt_rt) def test_type_check_in_subinterp(self): @@ -298,27 +332,39 @@ index 1c1cbd03d02..1378985de4a 100644 else: extension_loader = "ExtensionFileLoader" diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 001ecec4dcd..e46a5661fe8 100644 +index 701d34bba2d..f6aff1a6e1f 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py -@@ -559,7 +559,7 @@ +@@ -585,7 +585,7 @@ sys.platform == "android", f"Android blocks {name} with SELinux" ) -if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: -+if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos", "visionos"}: ++if sys.platform not in {"win32", "vxworks", "ios", "tvos", "visionos", "watchos"}: unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None -@@ -575,7 +575,7 @@ +@@ -604,7 +604,7 @@ def skip_wasi_stack_overflow(): return unittest.skipIf(is_wasi, "Exhausts stack on WASI") -is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} -+is_apple_mobile = sys.platform in {"ios", "tvos", "watchos", "visionos"} ++is_apple_mobile = sys.platform in {"ios", "tvos", "visionos", "watchos"} is_apple = is_apple_mobile or sys.platform == "darwin" has_fork_support = hasattr(os, "fork") and not ( +diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py +index a32d5d81d2b..f9421619e98 100644 +--- a/Lib/test/test__interpreters.py ++++ b/Lib/test/test__interpreters.py +@@ -612,6 +612,7 @@ + f'assert(obj == {obj!r})', + ) + ++ @support.requires_subprocess() + def test_os_exec(self): + expected = 'spam spam spam spam spam' + subinterp = _interpreters.create() diff --git a/Lib/test/test_ctypes/test_dllist.py b/Lib/test/test_ctypes/test_dllist.py index 15603dc3d77..bff6c0fb95f 100644 --- a/Lib/test/test_ctypes/test_dllist.py @@ -333,7 +379,7 @@ index 15603dc3d77..bff6c0fb95f 100644 if WINDOWS: KNOWN_LIBRARIES = ["KERNEL32.DLL"] diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py -index 719c4feace6..92a831a9148 100644 +index e879e48571f..07a2c6c76ce 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -271,13 +271,21 @@ @@ -362,10 +408,10 @@ index 719c4feace6..92a831a9148 100644 self.assertEqual(res.system, "") self.assertEqual(res.release, "") diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py -index 4c3ea1cd8df..04a210e5c86 100644 +index bfbcf112b0b..5634b6d2c41 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py -@@ -236,7 +236,8 @@ +@@ -253,7 +253,8 @@ arguments=[f'openURL({URL},new-tab)']) @@ -376,10 +422,10 @@ index 4c3ea1cd8df..04a210e5c86 100644 def _obj_ref(self, *args): # Construct a string representation of the arguments that can be used diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py -index f2e2394089d..2efbbfb0014 100644 +index 97aad6eea50..ec1f55c76ba 100644 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py -@@ -488,7 +488,8 @@ +@@ -499,7 +499,8 @@ # macOS can use below Unix support (but we prefer using the macOS # specific stuff) @@ -389,7 +435,7 @@ index f2e2394089d..2efbbfb0014 100644 register("iosbrowser", None, IOSBrowser(), preferred=True) if sys.platform == "serenityos": -@@ -653,9 +654,10 @@ +@@ -666,9 +667,10 @@ return not rc # @@ -403,10 +449,10 @@ index f2e2394089d..2efbbfb0014 100644 if objc: # If objc exists, we know ctypes is also importable. diff --git a/Makefile.pre.in b/Makefile.pre.in -index 01e10d1ab20..ddc430beb80 100644 +index f86d7363e09..eb3358ba7b9 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in -@@ -209,6 +209,12 @@ +@@ -211,6 +211,12 @@ # the build, and is only listed here so it will be included in sysconfigdata. IPHONEOS_DEPLOYMENT_TARGET=@IPHONEOS_DEPLOYMENT_TARGET@ @@ -419,60 +465,39 @@ index 01e10d1ab20..ddc430beb80 100644 # Option to install to strip binaries STRIPFLAG=-s -@@ -2294,7 +2300,7 @@ - # a full Xcode install that has an iPhone SE (3rd edition) simulator available. - # This must be run *after* a `make install` has completed the build. The - # `--with-framework-name` argument *cannot* be used when configuring the build. --XCFOLDER:=iOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID -+XCFOLDER-iOS:=iOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID - .PHONY: testios - testios: - @if test "$(MACHDEP)" != "ios"; then \ -@@ -2314,11 +2320,41 @@ - exit 1;\ +@@ -2321,7 +2327,7 @@ fi -- # Clone the testbed project into the XCFOLDER -- $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" -+ # Clone the testbed project into the XCFOLDER-iOS -+ $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER-iOS)" -+ -+ # Run the testbed project -+ $(PYTHON_FOR_BUILD) "$(XCFOLDER-iOS)" run --verbose -- test -uall --single-process --rerun -W -+ -+# Run the test suite on the visionOS simulator. Must be run on a macOS machine with -+# a full Xcode install that has an Apple Vision Pro 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-visionOS:=visionOSTestbed.$(MULTIARCH).$(shell date +%s).$$PPID -+.PHONY: testvisionos -+testvisionos: -+ @if test "$(MACHDEP)" != "visionos"; then \ -+ echo "Cannot run the visionOS testbed for a non-visionOS build."; \ -+ exit 1;\ -+ fi -+ @if test "$(findstring -xrsimulator,$(MULTIARCH))" != "-xrsimulator"; then \ -+ echo "Cannot run the visionOS testbed for non-simulator builds."; \ -+ exit 1;\ -+ fi -+ @if test $(PYTHONFRAMEWORK) != "Python"; then \ -+ echo "Cannot run the visionOS testbed with a non-default framework name."; \ -+ exit 1;\ -+ fi -+ @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \ -+ echo "Cannot find a finalized visionOS Python.framework. Have you run 'make install' to finalize the framework build?"; \ -+ exit 1;\ -+ fi -+ -+ # Clone the testbed project into the XCFOLDER-visionOS -+ $(PYTHON_FOR_BUILD) $(srcdir)/visionOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER-visionOS)" + # Clone the testbed project into the XCFOLDER +- $(PYTHON_FOR_BUILD) $(srcdir)/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" ++ $(PYTHON_FOR_BUILD) $(srcdir)/Platforms/Apple/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" # Run the testbed project -- $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W -+ $(PYTHON_FOR_BUILD) "$(XCFOLDER-visionOS)" run --verbose -- test -uall --single-process --rerun -W + $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W +@@ -3230,10 +3236,10 @@ + -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' + -rm -f Include/pydtrace_probes.h + -rm -f profile-gen-stamp +- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/bin +- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/lib +- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/include +- -rm -rf Apple/iOS/testbed/Python.xcframework/ios-*/Python.framework ++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/bin ++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/lib ++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/include ++ -rm -rf Platforms/Apple/iOS/testbed/Python.xcframework/ios-*/Python.framework - # Like test, but using --slow-ci which enables all test resources and use - # longer timeout. Run an optional pybuildbot.identify script to include + .PHONY: profile-removal + profile-removal: +@@ -3267,7 +3273,7 @@ + config.cache config.log pyconfig.h Modules/config.c + -rm -rf build platform + -rm -rf $(PYTHONFRAMEWORKDIR) +- -rm -rf Apple/iOS/Frameworks ++ -rm -rf Platforms/Apple/iOS/Frameworks + -rm -rf iOSTestbed.* + -rm -f python-config.py python-config + -rm -rf cross-build diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c index f5cd73bdea8..6c1863c943b 100644 --- a/Misc/platform_triplet.c @@ -510,1474 +535,1553 @@ index f5cd73bdea8..6c1863c943b 100644 // Older macOS SDKs do not define TARGET_OS_OSX # elif !defined(TARGET_OS_OSX) || TARGET_OS_OSX PLATFORM_TRIPLET=darwin -diff --git a/config.sub b/config.sub -index 1bb6a05dc11..49febd56a37 100755 ---- a/config.sub -+++ b/config.sub -@@ -1743,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* | tvos* | watchos* \ -+ | os9* | macos* | osx* | ios* | tvos* | watchos* | xros* \ - | mpw* | magic* | mmixware* | mon960* | lnews* \ - | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ - | aos* | aros* | cloudabi* | sortix* | twizzler* \ -@@ -1867,7 +1867,7 @@ - ;; - *-eabi*- | *-gnueabi*-) - ;; -- ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) -+ ios*-simulator- | tvos*-simulator- | watchos*-simulator- | xros*-simulator-) - ;; - none--*) - # None (no kernel, i.e. freestanding / bare metal), -diff --git a/configure b/configure -index 345822a6c6d..11acfe0ac9f 100755 ---- a/configure -+++ b/configure -@@ -982,6 +982,10 @@ - CFLAGS - CC - HAS_XCRUN -+EXPORT_XROS_DEPLOYMENT_TARGET -+XROS_DEPLOYMENT_TARGET -+WATCHOS_DEPLOYMENT_TARGET -+TVOS_DEPLOYMENT_TARGET - IPHONEOS_DEPLOYMENT_TARGET - EXPORT_MACOSX_DEPLOYMENT_TARGET - CONFIGURE_MACOSX_DEPLOYMENT_TARGET -@@ -4116,6 +4120,15 @@ - *-apple-ios*) - ac_sys_system=iOS - ;; -+ *-apple-tvos*) -+ ac_sys_system=tvOS -+ ;; -+ *-apple-watchos*) -+ ac_sys_system=watchOS -+ ;; -+ *-apple-xros*) -+ ac_sys_system=visionOS -+ ;; - *-*-darwin*) - ac_sys_system=Darwin - ;; -@@ -4197,7 +4210,7 @@ - # On cross-compile builds, configure will look for a host-specific compiler by - # prepending the user-provided host triple to the required binary name. - # --# On iOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", -+# On iOS/tvOS/watchOS/visionOS, 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 -@@ -4212,6 +4225,17 @@ - aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; - aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; - x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; +diff --git a/Platforms/Apple/.ruff.toml b/Platforms/Apple/.ruff.toml +new file mode 100644 +index 00000000000..f5d74fdb6af +--- /dev/null ++++ b/Platforms/Apple/.ruff.toml +@@ -0,0 +1,22 @@ ++extend = "../../.ruff.toml" # Inherit the project-wide settings ++ ++[format] ++preview = true ++docstring-code-format = true ++ ++[lint] ++select = [ ++ "C4", # flake8-comprehensions ++ "E", # pycodestyle ++ "F", # pyflakes ++ "I", # isort ++ "ISC", # flake8-implicit-str-concat ++ "LOG", # flake8-logging ++ "PGH", # pygrep-hooks ++ "PT", # flake8-pytest-style ++ "PYI", # flake8-pyi ++ "RUF100", # Ban unused `# noqa` comments ++ "UP", # pyupgrade ++ "W", # pycodestyle ++ "YTT", # flake8-2020 ++] +diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py +new file mode 100644 +index 00000000000..22b8ee4a824 +--- /dev/null ++++ b/Platforms/Apple/__main__.py +@@ -0,0 +1,1153 @@ ++#!/usr/bin/env python3 ++########################################################################## ++# Apple XCframework build script ++# ++# This script simplifies the process of configuring, compiling and packaging an ++# XCframework for an Apple platform. ++# ++# At present, it supports iOS, tvOS, visionOS and watchOS, but it has been ++# constructed so that it could be used on any Apple platform. ++# ++# The simplest entry point is: ++# ++# $ python Platforms/Apple ci iOS ++# ++# (replace iOS with tvOS, visionOS or watchOS as required.) ++# ++# which will: ++# * Clean any pre-existing build artefacts ++# * Configure and make a Python that can be used for the build ++# * Configure and make a Python for each supported iOS/tvOS/watchOS/visionOS ++# architecture and ABI ++# * Combine the outputs of the builds from the previous step into a single ++# XCframework, merging binaries into a "fat" binary if necessary ++# * Clone a copy of the testbed, configured to use the XCframework ++# * Construct a tarball containing the release artefacts ++# * Run the test suite using the generated XCframework. ++# ++# This is the complete sequence that would be needed in CI to build and test ++# a candidate release artefact. ++# ++# Each individual step can be invoked individually - there are commands to ++# clean, configure-build, make-build, configure-host, make-host, package, and ++# test. ++# ++# There is also a build command that can be used to combine the configure and ++# make steps for the build Python, an individual host, all hosts, or all ++# builds. ++########################################################################## ++from __future__ import annotations + -+ 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 ;; ++import argparse ++import os ++import platform ++import re ++import shlex ++import shutil ++import signal ++import subprocess ++import sys ++import sysconfig ++import time ++from collections.abc import Callable, Sequence ++from contextlib import contextmanager ++from datetime import datetime, timezone ++from os.path import basename, relpath ++from pathlib import Path ++from subprocess import CalledProcessError ++ ++EnvironmentT = dict[str, str] ++ArgsT = Sequence[str | Path] ++ ++SCRIPT_NAME = Path(__file__).name ++PYTHON_DIR = Path(__file__).resolve().parent.parent.parent ++ ++CROSS_BUILD_DIR = PYTHON_DIR / "cross-build" ++ ++HOSTS: dict[str, dict[str, dict[str, str]]] = { ++ # Structure of this data: ++ # * Platform identifier ++ # * an XCframework slice that must exist for that platform ++ # * a host triple: the multiarch spec for that host ++ "iOS": { ++ "ios-arm64": { ++ "arm64-apple-ios": "arm64-iphoneos", ++ }, ++ "ios-arm64_x86_64-simulator": { ++ "arm64-apple-ios-simulator": "arm64-iphonesimulator", ++ "x86_64-apple-ios-simulator": "x86_64-iphonesimulator", ++ }, ++ }, ++ "tvOS": { ++ "tvos-arm64": { ++ "arm64-apple-tvos": "arm64-appletvos", ++ }, ++ "tvos-arm64_x86_64-simulator": { ++ "arm64-apple-tvos-simulator": "arm64-appletvsimulator", ++ "x86_64-apple-tvos-simulator": "x86_64-appletvsimulator", ++ }, ++ }, ++ "visionOS": { ++ "xros-arm64": { ++ "arm64-apple-xros": "arm64-xros", ++ }, ++ "xros-arm64-simulator": { ++ "arm64-apple-xros-simulator": "arm64-xrsimulator", ++ }, ++ }, ++ "watchOS": { ++ "watchos-arm64_32": { ++ "arm64_32-apple-watchos": "arm64_32-watchos", ++ }, ++ "watchos-arm64_x86_64-simulator": { ++ "arm64-apple-watchos-simulator": "arm64-watchsimulator", ++ "x86_64-apple-watchos-simulator": "x86_64-watchsimulator", ++ }, ++ }, ++} + -+ 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 ;; + -+ aarch64-apple-xros*-simulator) AR=arm64-apple-xros-simulator-ar ;; -+ aarch64-apple-xros*) AR=arm64-apple-xros-ar ;; - *) - esac - fi -@@ -4220,6 +4244,17 @@ - aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; - aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; - x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++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 + -+ 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 ;; ++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. + -+ aarch64-apple-xros*-simulator) CC=arm64-apple-xros-simulator-clang ;; -+ aarch64-apple-xros*) CC=arm64-apple-xros-clang ;; - *) - esac - fi -@@ -4228,6 +4263,17 @@ - aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; - aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; - x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++ Optionally logs the executed command to the console. ++ """ ++ kwargs.setdefault("check", True) ++ if env is None: ++ env = os.environ.copy() + -+ 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 ;; ++ if host: ++ host_env = apple_env(host) ++ print_env(host_env) ++ env.update(host_env) + -+ 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 ;; ++ if log: ++ print(">", join_command(command)) ++ return subprocess.run(command, env=env, **kwargs) + -+ aarch64-apple-xros*-simulator) CPP=arm64-apple-xros-simulator-cpp ;; -+ aarch64-apple-xros*) CPP=arm64-apple-xros-cpp ;; - *) - esac - fi -@@ -4236,6 +4282,17 @@ - aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; - aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; - x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; + -+ 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 join_command(args: str | Path | ArgsT) -> str: ++ """Format a command so it can be copied into a shell. + -+ 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++ ;; ++ Similar to `shlex.join`, but also accepts arguments which are Paths, or a ++ single string/Path outside of a list. ++ """ ++ if isinstance(args, (str, Path)): ++ return str(args) ++ else: ++ return shlex.join(map(str, args)) ++ ++ ++def print_env(env: EnvironmentT) -> None: ++ """Format the environment so it can be pasted into a shell.""" ++ for key, value in sorted(env.items()): ++ print(f"export {key}={shlex.quote(value)}") ++ ++ ++def platform_for_host(host): ++ """Determine the platform for a given host triple.""" ++ for plat, slices in HOSTS.items(): ++ for _, candidates in slices.items(): ++ for candidate in candidates: ++ if candidate == host: ++ return plat ++ raise KeyError(host) ++ ++ ++def apple_env(host: str) -> EnvironmentT: ++ """Construct an Apple development environment for the given host.""" ++ env = { ++ "PATH": ":".join([ ++ str(PYTHON_DIR / f"Platforms/Apple/{platform_for_host(host)}/Resources/bin"), ++ str(subdir(host) / "prefix"), ++ "/usr/bin", ++ "/bin", ++ "/usr/sbin", ++ "/sbin", ++ "/Library/Apple/usr/bin", ++ ]), ++ } + -+ aarch64-apple-xros*-simulator) CXX=arm64-apple-xros-simulator-clang++ ;; -+ aarch64-apple-xros*) CXX=arm64-apple-xros-clang++ ;; - *) - esac - fi -@@ -4358,8 +4415,11 @@ - case $enableval in - yes) - case $ac_sys_system in -- Darwin) enableval=/Library/Frameworks ;; -- iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; -+ Darwin) enableval=/Library/Frameworks ;; -+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; -+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;; -+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;; -+ visionOS) enableval=visionOS/Frameworks/\$\(MULTIARCH\) ;; - *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 - esac - esac -@@ -4368,6 +4428,9 @@ - no) - 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 ;; -+ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -4474,6 +4537,51 @@ - - ac_config_files="$ac_config_files iOS/Resources/Info.plist" - -+ ;; -+ tvOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ return env + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=tvOS/Resources + -+ ac_config_files="$ac_config_files tvOS/Resources/Info.plist" ++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) + -+ ;; -+ watchOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=watchOS/Resources ++def all_host_triples(platform: str) -> list[str]: ++ """Return all host triples for the given platform. + -+ ac_config_files="$ac_config_files watchOS/Resources/Info.plist" ++ The host triples are the platform definitions used as input to configure ++ (e.g., "arm64-apple-ios-simulator"). ++ """ ++ triples = [] ++ for slice_name, slice_parts in HOSTS[platform].items(): ++ triples.extend(list(slice_parts)) ++ return triples ++ ++ ++def clean(context: argparse.Namespace, target: str | None = None) -> None: ++ """The implementation of the "clean" command.""" ++ if target is None: ++ target = context.host ++ ++ # If we're explicitly targeting the build, there's no platform or ++ # distribution artefacts. If we're cleaning tests, we keep all built ++ # artefacts. Otherwise, the built artefacts must be dirty, so we remove ++ # them. ++ if target not in {"build", "test"}: ++ paths = ["dist", context.platform] + list(HOSTS[context.platform]) ++ else: ++ paths = [] ++ ++ if target in {"all", "build"}: ++ paths.append("build") ++ ++ if target in {"all", "hosts"}: ++ paths.extend(all_host_triples(context.platform)) ++ elif target not in {"build", "test", "package"}: ++ paths.append(target) ++ ++ if target in {"all", "hosts", "test"}: ++ paths.extend([ ++ path.name ++ for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*") ++ ]) ++ ++ for path in paths: ++ delete_path(path) ++ ++ ++def build_python_path() -> Path: ++ """The path to the build Python binary.""" ++ build_dir = subdir("build") ++ binary = build_dir / "python" ++ if not binary.is_file(): ++ binary = binary.with_suffix(".exe") ++ if not binary.is_file(): ++ raise FileNotFoundError( ++ f"Unable to find `python(.exe)` in {build_dir}" ++ ) + -+ ;; -+ visionOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ return binary + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=visionOS/Resources + -+ ac_config_files="$ac_config_files visionOS/Resources/Info.plist" ++@contextmanager ++def group(text: str): ++ """A context manager that outputs a log marker around a section of a build. + - ;; - *) - as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 -@@ -4485,6 +4593,9 @@ - e) - 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 ;; -+ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -4539,8 +4650,8 @@ - case "$withval" in - yes) - case $ac_sys_system in -- Darwin|iOS) -- # iOS is able to share the macOS patch -+ Darwin|iOS|tvOS|watchOS|visionOS) -+ # iOS/tvOS/watchOS/visionOS 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 ;; -@@ -4558,8 +4669,8 @@ - else case e in #( - e) - case $ac_sys_system in -- iOS) -- # Always apply the compliance patch on iOS; we can use the macOS patch -+ iOS|tvOS|watchOS|visionOS) -+ # Always apply the compliance patch on iOS/tvOS/watchOS/visionOS; 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; } -@@ -4577,6 +4688,8 @@ - - - -+EXPORT_XROS_DEPLOYMENT_TARGET='#' ++ 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))) + - - if test "$cross_compiling" = yes; then - case "$host" in -@@ -4614,6 +4727,78 @@ - ;; - esac - ;; -+ *-apple-tvos*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++ yield + -+ # 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; } ++ if "GITHUB_ACTIONS" in os.environ: ++ print("::endgroup::") ++ else: ++ print() + -+ 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} + -+ # 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; } ++@contextmanager ++def cwd(subdir: Path): ++ """A context manager that sets the current working directory.""" ++ orig = os.getcwd() ++ os.chdir(subdir) ++ yield ++ os.chdir(orig) + -+ 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 -+ ;; -+ *-apple-xros*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} + -+ # XROS_DEPLOYMENT_TARGET is the minimum supported visionOS version -+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking visionOS deployment target" >&5 -+printf %s "checking visionOS deployment target... " >&6; } -+ XROS_DEPLOYMENT_TARGET=${_host_os:8} -+ XROS_DEPLOYMENT_TARGET=${XROS_DEPLOYMENT_TARGET:=2.0} -+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $XROS_DEPLOYMENT_TARGET" >&5 -+printf "%s\n" "$XROS_DEPLOYMENT_TARGET" >&6; } -+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking exporting flag of visionOS deployment target" >&5 -+printf %s "checking exporting flag of visionOS deployment target... " >&6; } -+ export XROS_DEPLOYMENT_TARGET -+ EXPORT_XROS_DEPLOYMENT_TARGET='' -+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $EXPORT_XROS_DEPLOYMENT_TARGET" >&5 -+printf "%s\n" "$EXPORT_XROS_DEPLOYMENT_TARGET" >&6; } ++def configure_build_python(context: argparse.Namespace) -> None: ++ """The implementation of the "configure-build" command.""" ++ if context.clean: ++ clean(context, "build") + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${XROS_DEPLOYMENT_TARGET}-arm64-xr${_host_device} -+ ;; -+ *) -+ _host_ident=${XROS_DEPLOYMENT_TARGET}-$host_cpu-xr${_host_device} -+ ;; -+ esac -+ ;; - *-*-darwin*) - case "$host_cpu" in - arm*) -@@ -4704,9 +4889,15 @@ - define_xopen_source=no;; - Darwin/[12][0-9].*) - define_xopen_source=no;; -- # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. -+ # On iOS/tvOS/watchOS/visionOS, defining _POSIX_C_SOURCE also disables platform specific features. - iOS/*) - define_xopen_source=no;; -+ tvOS/*) -+ define_xopen_source=no;; -+ watchOS/*) -+ define_xopen_source=no;; -+ visionOS/*) -+ define_xopen_source=no;; - # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from - # defining NI_NUMERICHOST. - QNX/6.3.2) -@@ -4769,7 +4960,13 @@ - CONFIGURE_MACOSX_DEPLOYMENT_TARGET= - EXPORT_MACOSX_DEPLOYMENT_TARGET='#' - --# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple. -+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / -+# WATCHOS_DEPLOYMENT_TARGET / XROS_DEPLOYMENT_TARGET enforced by the selected host triple. ++ with ( ++ group("Configuring build Python"), ++ cwd(subdir("build", create=True)), ++ ): ++ command = [relpath(PYTHON_DIR / "configure")] ++ if context.args: ++ command.extend(context.args) ++ run(command) + + ++def make_build_python(context: argparse.Namespace) -> None: ++ """The implementation of the "make-build" command.""" ++ with ( ++ group("Compiling build Python"), ++ cwd(subdir("build")), ++ ): ++ run(["make", "-j", str(os.cpu_count())]) + + -+# XROS_DEPLOYMENT_TARGET should get exported - - - # checks for alternative programs -@@ -4810,6 +5007,16 @@ - as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" - as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" - ;; #( -+ tvOS) : ++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]) + -+ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" -+ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}" -+ ;; #( -+ watchOS) : ++ raise KeyError(host) + -+ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" -+ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}" -+ ;; #( - *) : - ;; - esac -@@ -7179,6 +7386,12 @@ - MULTIARCH="" ;; #( - iOS) : - MULTIARCH="" ;; #( -+ tvOS) : -+ MULTIARCH="" ;; #( -+ watchOS) : -+ MULTIARCH="" ;; #( -+ visionOS) : -+ MULTIARCH="" ;; #( - FreeBSD*) : - MULTIARCH="" ;; #( - *) : -@@ -7199,7 +7412,7 @@ - printf "%s\n" "$MULTIARCH" >&6; } - - case $ac_sys_system in #( -- iOS) : -+ iOS|tvOS|watchOS|visionOS) : - SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( - *) : - SOABI_PLATFORM=$PLATFORM_TRIPLET -@@ -7250,6 +7463,18 @@ - 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 ;; #( -+ aarch64-apple-xros*-simulator/clang) : -+ PY_SUPPORT_TIER=3 ;; #( -+ aarch64-apple-xros*/clang) : -+ PY_SUPPORT_TIER=3 ;; #( - aarch64-*-linux-android/clang) : - PY_SUPPORT_TIER=3 ;; #( - x86_64-*-linux-android/clang) : -@@ -7688,7 +7913,7 @@ - case $ac_sys_system in - Darwin) - LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; -- iOS) -+ iOS|tvOS|watchOS|visionOS) - LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; - *) - as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; -@@ -7754,7 +7979,7 @@ - BLDLIBRARY='-L. -lpython$(LDVERSION)' - RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} - ;; -- iOS) -+ iOS|tvOS|watchOS|visionOS) - LDLIBRARY='libpython$(LDVERSION).dylib' - ;; - AIX*) -@@ -13570,7 +13795,7 @@ - BLDSHARED="$LDSHARED" - fi - ;; -- iOS/*) -+ iOS/*|tvOS/*|watchOS/*|visionOS/*) - LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' - LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' - BLDSHARED="$LDSHARED" -@@ -13703,7 +13928,7 @@ - Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; - Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; - # -u libsys_s pulls in all symbols in libsys -- Darwin/*|iOS/*) -+ Darwin/*|iOS/*|tvOS/*|watchOS/*|visionOS/*) - LINKFORSHARED="$extra_undefs -framework CoreFoundation" - - # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -13727,7 +13952,7 @@ - LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' - fi - LINKFORSHARED="$LINKFORSHARED" -- elif test $ac_sys_system = "iOS"; then -+ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" -o "$ac_sys_system" = "visionOS"; then - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' - fi - ;; -@@ -15504,7 +15729,7 @@ - - ctypes_malloc_closure=yes - ;; #( -- iOS) : -+ iOS|tvOS|watchOS|visionOS) : - - ctypes_malloc_closure=yes - ;; #( -@@ -19256,12 +19481,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 -@@ -19322,18 +19541,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 -@@ -19766,24 +19973,6 @@ - then : - printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h - --fi --ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn" --if test "x$ac_cv_func_posix_spawn" = xyes --then : -- printf "%s\n" "#define HAVE_POSIX_SPAWN 1" >>confdefs.h -- --fi --ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp" --if test "x$ac_cv_func_posix_spawnp" = xyes --then : -- printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h -- --fi --ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" --if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes --then : -- printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h -- - fi - ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread" - if test "x$ac_cv_func_pread" = xyes -@@ -20102,12 +20291,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 -@@ -20376,11 +20559,11 @@ - - fi - --# iOS defines some system methods that can be linked (so they are -+# iOS/tvOS/watchOS/visionOS 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" ; then -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS" ; then - ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" - if test "x$ac_cv_func_getentropy" = xyes - then : -@@ -20402,6 +20585,53 @@ - - fi - -+# tvOS/watchOS have some additional methods that can be found, but not used. -+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ ac_fn_c_check_func "$LINENO" "execv" "ac_cv_func_execv" -+if test "x$ac_cv_func_execv" = xyes -+then : -+ printf "%s\n" "#define HAVE_EXECV 1" >>confdefs.h + -+fi -+ac_fn_c_check_func "$LINENO" "fork" "ac_cv_func_fork" -+if test "x$ac_cv_func_fork" = xyes -+then : -+ printf "%s\n" "#define HAVE_FORK 1" >>confdefs.h ++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 + -+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 ++ raise KeyError(host) + -+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 unpack_deps( ++ platform: str, ++ host: str, ++ prefix_dir: Path, ++ cache_dir: Path, ++) -> None: ++ """Unpack binary dependencies into a provided directory. + -+fi -+ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" -+if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes -+then : -+ printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h ++ Downloads binaries if they aren't already present. Downloads will be stored ++ in provided cache directory. + -+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 ++ On non-macOS platforms, as a safety mechanism, any dynamic libraries will ++ be purged from the unpacked dependencies. ++ """ ++ # To create new builds of these dependencies, usually all that's necessary ++ # is to push a tag to the cpython-apple-source-deps repository, and GitHub ++ # Actions will do the rest. ++ # ++ # If you're a member of the Python core team, and you'd like to be able to ++ # push these tags yourself, please contact Malcolm Smith or Russell ++ # Keith-Magee. ++ deps_url = "https://github.com/beeware/cpython-apple-source-deps/releases/download" ++ for name_ver in [ ++ "BZip2-1.0.8-2", ++ "libFFI-3.4.7-2", ++ "OpenSSL-3.5.7-1", ++ "XZ-5.6.4-2", ++ "mpdecimal-4.0.0-2", ++ "zstd-1.5.7-1", ++ ]: ++ filename = f"{name_ver.lower()}-{apple_target(host)}.tar.gz" ++ archive_path = download( ++ f"{deps_url}/{name_ver}/{filename}", ++ target_dir=cache_dir, ++ ) ++ shutil.unpack_archive(archive_path, prefix_dir) + -+fi ++ # Dynamic libraries will be preferentially linked over static; On non-macOS ++ # platforms, ensure that no dylibs are available in the prefix folder. ++ if platform != "macOS": ++ for dylib in prefix_dir.glob("**/*.dylib"): ++ dylib.unlink() + -+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} -@@ -23873,7 +24103,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 : -@@ -23987,7 +24218,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 : -@@ -24170,6 +24401,7 @@ - fi - - done -+fi - - # check for long file support functions - ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64" -@@ -24435,10 +24667,10 @@ - - done - --# On Android and iOS, clock_settime can be linked (so it is found by -+# On Android, iOS, tvOS, watchOS, and visionOS, 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" != "Linux-android" && test "$ac_sys_system" != "iOS" -+if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS" - then - - for ac_func in clock_settime -@@ -24755,7 +24987,7 @@ - e) if test "$cross_compiling" = yes - then : - --if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then -+if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "watchOS" || test "$ac_sys_system" = "visionOS"; then - ac_cv_buggy_getaddrinfo="no" - elif test "${enable_ipv6+set}" = set; then - ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" -@@ -26777,8 +27009,8 @@ - LIBPYTHON="\$(BLDLIBRARY)" - fi - --# On iOS the shared libraries must be linked with the Python framework --if test "$ac_sys_system" = "iOS"; then -+# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework -+if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "watchOS" -o $ac_sys_system = "visionOS"; then - MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" - fi - -@@ -29648,7 +29880,7 @@ - { 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 "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then -+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" -o "$ac_sys_system" = "visionOS" ; then - ac_cv_file__dev_ptmx=no - ac_cv_file__dev_ptc=no - else -@@ -30155,7 +30387,7 @@ - with_ensurepip=no ;; #( - WASI) : - with_ensurepip=no ;; #( -- iOS) : -+ iOS|tvOS|watchOS|visionOS) : - with_ensurepip=no ;; #( - *) : - with_ensurepip=upgrade -@@ -31104,7 +31336,7 @@ - SunOS*) _PYTHREAD_NAME_MAXLEN=31;; - NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268 - Darwin) _PYTHREAD_NAME_MAXLEN=63;; -- iOS) _PYTHREAD_NAME_MAXLEN=63;; -+ iOS|tvOS|watchOS|visionOS) _PYTHREAD_NAME_MAXLEN=63;; - FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 - OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 - *) _PYTHREAD_NAME_MAXLEN=;; -@@ -31136,7 +31368,7 @@ - ;; #( - Darwin) : - ;; #( -- iOS) : -+ iOS|tvOS|watchOS|visionOS) : - - - -@@ -35304,6 +35536,9 @@ - "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" ;; -+ "visionOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES visionOS/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 d6059471771..7e1a5ffe604 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -330,6 +330,15 @@ - *-apple-ios*) - ac_sys_system=iOS - ;; -+ *-apple-tvos*) -+ ac_sys_system=tvOS -+ ;; -+ *-apple-watchos*) -+ ac_sys_system=watchOS -+ ;; -+ *-apple-xros*) -+ ac_sys_system=visionOS -+ ;; - *-*-darwin*) - ac_sys_system=Darwin - ;; -@@ -405,7 +414,7 @@ - # On cross-compile builds, configure will look for a host-specific compiler by - # prepending the user-provided host triple to the required binary name. - # --# On iOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", -+# On iOS/tvOS/watchOS/visionOS, 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 -@@ -420,6 +429,17 @@ - aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; - aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; - x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; ++def download(url: str, target_dir: Path) -> Path: ++ """Download the specified URL into the given directory. + -+ 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 ;; ++ :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 + -+ 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 ;; + -+ aarch64-apple-xros*-simulator) AR=arm64-apple-xros-simulator-ar ;; -+ aarch64-apple-xros*) AR=arm64-apple-xros-ar ;; - *) - esac - fi -@@ -428,6 +448,17 @@ - aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; - aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; - x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++def configure_host_python( ++ context: argparse.Namespace, ++ host: str | None = None, ++) -> None: ++ """The implementation of the "configure-host" command.""" ++ if host is None: ++ host = context.host ++ ++ if context.clean: ++ clean(context, host) ++ ++ host_dir = subdir(host, create=True) ++ prefix_dir = host_dir / "prefix" ++ ++ with group(f"Downloading dependencies ({host})"): ++ if not prefix_dir.exists(): ++ prefix_dir.mkdir() ++ cache_dir = ( ++ Path(context.cache_dir).resolve() ++ if context.cache_dir ++ else CROSS_BUILD_DIR / "downloads" ++ ) ++ unpack_deps(context.platform, host, prefix_dir, cache_dir) ++ else: ++ print("Dependencies already installed") ++ ++ with ( ++ group(f"Configuring host Python ({host})"), ++ cwd(host_dir), ++ ): ++ command = [ ++ # Basic cross-compiling configuration ++ relpath(PYTHON_DIR / "configure"), ++ f"--host={host}", ++ f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}", ++ f"--with-build-python={build_python_path()}", ++ "--with-system-libmpdec", ++ "--enable-ipv6", ++ "--enable-framework", ++ # Dependent libraries. ++ f"--with-openssl={prefix_dir}", ++ f"LIBLZMA_CFLAGS=-I{prefix_dir}/include", ++ f"LIBLZMA_LIBS=-L{prefix_dir}/lib -llzma", ++ f"LIBFFI_CFLAGS=-I{prefix_dir}/include", ++ f"LIBFFI_LIBS=-L{prefix_dir}/lib -lffi", ++ f"LIBMPDEC_CFLAGS=-I{prefix_dir}/include", ++ f"LIBMPDEC_LIBS=-L{prefix_dir}/lib -lmpdec", ++ f"LIBZSTD_CFLAGS=-I{prefix_dir}/include", ++ f"LIBZSTD_LIBS=-L{prefix_dir}/lib -lzstd", ++ ] + -+ 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 ;; ++ if context.args: ++ command.extend(context.args) ++ run(command, host=host) + -+ 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 ;; + -+ aarch64-apple-xros*-simulator) CC=arm64-apple-xros-simulator-clang ;; -+ aarch64-apple-xros*) CC=arm64-apple-xros-clang ;; - *) - esac - fi -@@ -436,6 +467,17 @@ - aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; - aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; - x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++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 + -+ 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 ;; ++ with ( ++ group(f"Compiling host Python ({host})"), ++ cwd(subdir(host)), ++ ): ++ run(["make", "-j", str(os.cpu_count())], host=host) ++ run(["make", "install"], host=host) + -+ 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 ;; + -+ aarch64-apple-xros*-simulator) CPP=arm64-apple-xros-simulator-cpp ;; -+ aarch64-apple-xros*) CPP=arm64-apple-xros-cpp ;; - *) - esac - fi -@@ -444,6 +486,17 @@ - aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; - aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; - x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; ++def framework_path(host_triple: str, multiarch: str) -> Path: ++ """The path to a built single-architecture framework product. + -+ 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++ ;; ++ :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 + -+ 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++ ;; ++ / f"{host_triple}/Platforms/Apple/{platform_for_host(host_triple)}" ++ / f"Frameworks/{multiarch}" ++ ) ++ ) + -+ aarch64-apple-xros*-simulator) CXX=arm64-apple-xros-simulator-clang++ ;; -+ aarch64-apple-xros*) CXX=arm64-apple-xros-clang++ ;; - *) - esac - fi -@@ -558,8 +611,11 @@ - case $enableval in - yes) - case $ac_sys_system in -- Darwin) enableval=/Library/Frameworks ;; -- iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; -+ Darwin) enableval=/Library/Frameworks ;; -+ iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; -+ tvOS) enableval=tvOS/Frameworks/\$\(MULTIARCH\) ;; -+ watchOS) enableval=watchOS/Frameworks/\$\(MULTIARCH\) ;; -+ visionOS) enableval=visionOS/Frameworks/\$\(MULTIARCH\) ;; - *) AC_MSG_ERROR([Unknown platform for framework build]) - esac - esac -@@ -568,6 +624,9 @@ - no) - 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]) ;; -+ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -670,6 +729,48 @@ - - AC_CONFIG_FILES([iOS/Resources/Info.plist]) - ;; -+ tvOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=tvOS/Resources ++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() + -+ AC_CONFIG_FILES([tvOS/Resources/Info.plist]) -+ ;; -+ watchOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ return ignored_names + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=watchOS/Resources + -+ AC_CONFIG_FILES([watchOS/Resources/Info.plist]) -+ ;; -+ visionOS) : -+ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" -+ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " -+ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" -+ FRAMEWORKPYTHONW= -+ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++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() + -+ prefix=$PYTHONFRAMEWORKPREFIX -+ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" -+ RESSRCDIR=visionOS/Resources + -+ AC_CONFIG_FILES([visionOS/Resources/Info.plist]) -+ ;; - *) - AC_MSG_ERROR([Unknown platform for framework build]) - ;; -@@ -678,6 +779,9 @@ - ],[ - 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]) ;; -+ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework -@@ -730,8 +834,8 @@ - case "$withval" in - yes) - case $ac_sys_system in -- Darwin|iOS) -- # iOS is able to share the macOS patch -+ Darwin|iOS|tvOS|watchOS|visionOS) -+ # iOS/tvOS/watchOS/visionOS 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]) ;; -@@ -745,8 +849,8 @@ - esac - ],[ - case $ac_sys_system in -- iOS) -- # Always apply the compliance patch on iOS; we can use the macOS patch -+ iOS|tvOS|watchOS|visionOS) -+ # Always apply the compliance patch on iOS/tvOS/watchOS/visionOS; 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]) - ;; -@@ -759,6 +863,8 @@ - ]) - AC_SUBST([APP_STORE_COMPLIANCE_PATCH]) - -+EXPORT_XROS_DEPLOYMENT_TARGET='#' ++def create_xcframework(platform: str) -> str: ++ """Build an XCframework from the component parts for the platform. + - AC_SUBST([_PYTHON_HOST_PLATFORM]) - if test "$cross_compiling" = yes; then - case "$host" in -@@ -794,6 +900,70 @@ - ;; - esac - ;; -+ *-apple-tvos*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++ :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" ++ ) + -+ # 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]) ++ 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", ++ ) + -+ 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} ++ # Copy the headers ++ shutil.copytree( ++ first_framework / "Headers", ++ slice_framework / "Headers", ++ ) + -+ # 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]) ++ # 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() ++ ] ++ ) + -+ 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 -+ ;; -+ *-apple-xros*) -+ _host_os=`echo $host | cut -d '-' -f3` -+ _host_device=`echo $host | cut -d '-' -f4` -+ _host_device=${_host_device:=os} ++ # Add this merged slice to the list to be added to the XCframework ++ frameworks.append(slice_framework) + -+ # XROS_DEPLOYMENT_TARGET is the minimum supported visionOS version -+ AC_MSG_CHECKING([visionOS deployment target]) -+ XROS_DEPLOYMENT_TARGET=${_host_os:8} -+ XROS_DEPLOYMENT_TARGET=${XROS_DEPLOYMENT_TARGET:=2.0} -+ AC_MSG_RESULT([$XROS_DEPLOYMENT_TARGET]) -+ AC_MSG_CHECKING([exporting flag of visionOS deployment target]) -+ export XROS_DEPLOYMENT_TARGET -+ EXPORT_XROS_DEPLOYMENT_TARGET='' -+ AC_MSG_RESULT([$EXPORT_XROS_DEPLOYMENT_TARGET]) ++ 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, ++ ) + -+ case "$host_cpu" in -+ aarch64) -+ _host_ident=${XROS_DEPLOYMENT_TARGET}-arm64-xr${_host_device} -+ ;; -+ *) -+ _host_ident=${XROS_DEPLOYMENT_TARGET}-$host_cpu-xr${_host_device} -+ ;; -+ esac -+ ;; - *-*-darwin*) - case "$host_cpu" in - arm*) -@@ -883,9 +1053,15 @@ - define_xopen_source=no;; - Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) - define_xopen_source=no;; -- # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. -+ # On iOS/tvOS/watchOS/visionOS, defining _POSIX_C_SOURCE also disables platform specific features. - iOS/*) - define_xopen_source=no;; -+ tvOS/*) -+ define_xopen_source=no;; -+ watchOS/*) -+ define_xopen_source=no;; -+ visionOS/*) -+ define_xopen_source=no;; - # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from - # defining NI_NUMERICHOST. - QNX/6.3.2) -@@ -944,8 +1120,14 @@ - CONFIGURE_MACOSX_DEPLOYMENT_TARGET= - EXPORT_MACOSX_DEPLOYMENT_TARGET='#' - --# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple. -+# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / -+# WATCHOS_DEPLOYMENT_TARGET / XROS_DEPLOYMENT_TARGET enforced by the selected host triple. - AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET]) -+AC_SUBST([TVOS_DEPLOYMENT_TARGET]) -+AC_SUBST([WATCHOS_DEPLOYMENT_TARGET]) -+AC_SUBST([XROS_DEPLOYMENT_TARGET]) -+# XROS_DEPLOYMENT_TARGET should get exported -+AC_SUBST([EXPORT_XROS_DEPLOYMENT_TARGET]) - - # checks for alternative programs - -@@ -979,11 +1161,19 @@ - ], - ) - --dnl Add the compiler flag for the iOS minimum supported OS version. -+dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS -+dnl version. visionOS doesn't use an explicit -mxros-version-min option - -+dnl it encodes the min version into the target triple. - 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}"]) - ], - ) - -@@ -1172,6 +1362,9 @@ - AS_CASE([$ac_sys_system], - [Darwin*], [MULTIARCH=""], - [iOS], [MULTIARCH=""], -+ [tvOS], [MULTIARCH=""], -+ [watchOS], [MULTIARCH=""], -+ [visionOS], [MULTIARCH=""], - [FreeBSD*], [MULTIARCH=""], - [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] - ) -@@ -1193,7 +1386,7 @@ - dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of - dnl the PLATFORM_TRIPLET that will be used in binary module extensions. - AS_CASE([$ac_sys_system], -- [iOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], -+ [iOS|tvOS|watchOS|visionOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], - [SOABI_PLATFORM=$PLATFORM_TRIPLET] - ) - -@@ -1220,16 +1413,22 @@ - [wasm32-unknown-wasip1/clang], [PY_SUPPORT_TIER=2], dnl WebAssembly System Interface preview1, clang - [x86_64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], dnl Linux on AMD64, any vendor, glibc, clang - -- [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC -- [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc -- [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang -- [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc -- [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-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64 -- [x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64 -- [wasm32-*-emscripten/emcc], [PY_SUPPORT_TIER=3], dnl Emscripten -+ [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC -+ [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc -+ [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang -+ [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc -+ [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 -+ [aarch64-apple-xros*-simulator/clang], [PY_SUPPORT_TIER=3], dnl visionOS Simulator on arm64 -+ [aarch64-apple-xros*/clang], [PY_SUPPORT_TIER=3], dnl visionOS on ARM64 -+ [aarch64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64 -+ [x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64 -+ [wasm32-*-emscripten/emcc], [PY_SUPPORT_TIER=3], dnl Emscripten - - [PY_SUPPORT_TIER=0] - ) -@@ -1537,7 +1736,7 @@ - case $ac_sys_system in - Darwin) - LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; -- iOS) -+ iOS|tvOS|watchOS|visionOS) - LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; - *) - AC_MSG_ERROR([Unknown platform for framework build]);; -@@ -1602,7 +1801,7 @@ - BLDLIBRARY='-L. -lpython$(LDVERSION)' - RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} - ;; -- iOS) -+ iOS|tvOS|watchOS|visionOS) - LDLIBRARY='libpython$(LDVERSION).dylib' - ;; - AIX*) -@@ -3469,7 +3668,7 @@ - BLDSHARED="$LDSHARED" - fi - ;; -- iOS/*) -+ iOS/*|tvOS/*|watchOS/*|visionOS/*) - LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' - LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' - BLDSHARED="$LDSHARED" -@@ -3593,7 +3792,7 @@ - Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; - Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; - # -u libsys_s pulls in all symbols in libsys -- Darwin/*|iOS/*) -+ Darwin/*|iOS/*|tvOS/*|watchOS/*|visionOS/*) - LINKFORSHARED="$extra_undefs -framework CoreFoundation" - - # Issue #18075: the default maximum stack size (8MBytes) is too -@@ -3617,7 +3816,7 @@ - LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' - fi - LINKFORSHARED="$LINKFORSHARED" -- elif test $ac_sys_system = "iOS"; then -+ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" -o "$ac_sys_system" = "visionOS"; then - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' - fi - ;; -@@ -4105,7 +4304,7 @@ - dnl when do we need USING_APPLE_OS_LIBFFI? - ctypes_malloc_closure=yes - ], -- [iOS], [ -+ [iOS|tvOS|watchOS|visionOS], [ - ctypes_malloc_closure=yes - ], - [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] -@@ -5214,9 +5413,9 @@ - # checks for library functions - AC_CHECK_FUNCS([ \ - accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \ -- copy_file_range ctermid dladdr dup dup3 execv explicit_bzero explicit_memset \ -+ copy_file_range ctermid dladdr dup dup3 explicit_bzero explicit_memset \ - faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ -- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ -+ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \ - getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin getlogin_r \ - getpeername getpgid getpid getppid getpriority _getpty \ -@@ -5224,8 +5423,7 @@ - getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ - lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ - mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ -- pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ -- posix_spawn_file_actions_addclosefrom_np \ -+ pipe2 plock poll posix_fadvise posix_fallocate posix_openpt \ - pread preadv preadv2 process_vm_readv \ - pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ - pthread_kill pthread_get_name_np pthread_getname_np pthread_set_name_np -@@ -5235,7 +5433,7 @@ - sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ - sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ - setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ -- 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 tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ -@@ -5250,12 +5448,20 @@ - AC_CHECK_FUNCS([lchmod]) - fi - --# iOS defines some system methods that can be linked (so they are -+# iOS/tvOS/watchOS/visionOS 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" ; then -- AC_CHECK_FUNCS([getentropy getgroups system]) -+if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS" ; then -+ AC_CHECK_FUNCS([ getentropy getgroups system ]) -+fi ++ # Copy in the cross-architecture pyconfig.h ++ shutil.copy( ++ PYTHON_DIR / f"Platforms/Apple/{platform}/Resources/pyconfig.h", ++ slice_framework / "Headers/pyconfig.h", ++ ) + -+# tvOS/watchOS have some additional methods that can be found, but not used. -+if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then -+ AC_CHECK_FUNCS([ \ -+ execv fork fork1 posix_spawn posix_spawnp posix_spawn_file_actions_addclosefrom_np \ -+ sigaltstack \ -+ ]) - fi - - AC_CHECK_DECL([dirfd], -@@ -5550,20 +5756,22 @@ - [@%:@include ]) - - # 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]) -@@ -5602,10 +5810,10 @@ - ]) - ]) - --# On Android and iOS, clock_settime can be linked (so it is found by -+# On Android, iOS, tvOS, watchOS, and visionOS, 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" != "Linux-android" && test "$ac_sys_system" != "iOS" -+if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" -a "$ac_sys_system" != "visionOS" - then - AC_CHECK_FUNCS([clock_settime], [], [ - AC_CHECK_LIB([rt], [clock_settime], [ -@@ -5763,7 +5971,7 @@ - [ac_cv_buggy_getaddrinfo=no], - [ac_cv_buggy_getaddrinfo=yes], - [ --if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then -+if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "watchOS" || test "$ac_sys_system" = "visionOS"; then - ac_cv_buggy_getaddrinfo="no" - elif test "${enable_ipv6+set}" = set; then - ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" -@@ -6356,8 +6564,8 @@ - LIBPYTHON="\$(BLDLIBRARY)" - fi - --# On iOS the shared libraries must be linked with the Python framework --if test "$ac_sys_system" = "iOS"; then -+# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework -+if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "watchOS" -o $ac_sys_system = "visionOS"; then - MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" - fi - -@@ -7016,7 +7224,7 @@ - dnl NOTE: Inform user how to proceed with files when cross compiling. - dnl Some cross-compile builds are predictable; they won't ever - dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. --if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then -+if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "watchOS" -o "$ac_sys_system" = "visionOS" ; then - ac_cv_file__dev_ptmx=no - ac_cv_file__dev_ptc=no - else -@@ -7316,7 +7524,7 @@ - AS_CASE([$ac_sys_system], - [Emscripten], [with_ensurepip=no], - [WASI], [with_ensurepip=no], -- [iOS], [with_ensurepip=no], -+ [iOS|tvOS|watchOS|visionOS], [with_ensurepip=no], - [with_ensurepip=upgrade] - ) - ]) -@@ -7703,7 +7911,7 @@ - SunOS*) _PYTHREAD_NAME_MAXLEN=31;; - NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268 - Darwin) _PYTHREAD_NAME_MAXLEN=63;; -- iOS) _PYTHREAD_NAME_MAXLEN=63;; -+ iOS|tvOS|watchOS|visionOS) _PYTHREAD_NAME_MAXLEN=63;; - FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 - OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 - *) _PYTHREAD_NAME_MAXLEN=;; -@@ -7728,7 +7936,7 @@ - [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])], - dnl The _scproxy module is available on macOS - [Darwin], [], -- [iOS], [ -+ [iOS|tvOS|watchOS|visionOS], [ - 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 -diff --git a/iOS/Resources/Info.plist.in b/iOS/Resources/Info.plist.in -index c3e261ecd9e..26ef7a95de4 100644 ---- a/iOS/Resources/Info.plist.in -+++ b/iOS/Resources/Info.plist.in -@@ -17,13 +17,13 @@ - CFBundlePackageType - FMWK - CFBundleShortVersionString -- @VERSION@ -+ %VERSION% - CFBundleLongVersionString - %VERSION%, (c) 2001-2024 Python Software Foundation. - CFBundleSignature - ???? - CFBundleVersion -- 1 -+ %VERSION% - CFBundleSupportedPlatforms - - iPhoneOS ---- /dev/null -+++ b/tvOS/README.rst -@@ -0,0 +1,108 @@ -+===================== -+Python on tvOS README -+===================== ++ 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 + -+:Authors: -+ Russell Keith-Magee (2023-11) ++ shutil.copytree( ++ framework_path(host_triple, multiarch) / "lib", ++ slice_path / f"lib-{arch}", ++ ignore=lib_non_platform_files, ++ symlinks=True, ++ ) + -+This document provides a quick overview of some tvOS specific features in the -+Python distribution. ++ # Copy the host's pyconfig.h to an architecture-specific name. ++ arch = multiarch.split("-")[0] ++ host_path = ( ++ CROSS_BUILD_DIR ++ / host_triple ++ / f"Platforms/Apple/{platform}/Frameworks" ++ / multiarch ++ ) ++ host_framework = host_path / "Python.framework" ++ shutil.copy( ++ host_framework / "Headers/pyconfig.h", ++ slice_framework / f"Headers/pyconfig-{arch}.h", ++ ) + -+Compilers for building on tvOS -+============================== ++ # 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", ++ ) + -+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. ++ print(" - build tools") ++ shutil.copytree( ++ PYTHON_DIR / "Platforms/Apple/testbed/Python.xcframework/build", ++ package_path / "Python.xcframework/build", ++ ) + -+tvOS specific arguments to configure -+=================================== ++ return version + -+* ``--enable-framework[=DIR]`` + -+ This argument specifies the location where the Python.framework will -+ be installed. ++def package(context: argparse.Namespace) -> None: ++ """The implementation of the "package" command.""" ++ if context.clean: ++ clean(context, "package") + -+* ``--with-framework-name=NAME`` ++ with group("Building package"): ++ # Create an XCframework ++ version = create_xcframework(context.platform) + -+ Specify the name for the python framework, defaults to ``Python``. ++ # watchOS doesn't have a testbed (yet!) ++ if context.platform != "watchOS": ++ # Clone testbed ++ print() ++ run([ ++ sys.executable, ++ "Platforms/Apple/testbed", ++ "clone", ++ "--platform", ++ context.platform, ++ "--framework", ++ CROSS_BUILD_DIR / context.platform / "Python.xcframework", ++ CROSS_BUILD_DIR / context.platform / "testbed", ++ ]) ++ ++ # Build the final archive ++ archive_name = ( ++ CROSS_BUILD_DIR ++ / "dist" ++ / f"python-{version}-{context.platform}-XCframework" ++ ) + ++ print() ++ print("Create package archive...") ++ shutil.make_archive( ++ str(CROSS_BUILD_DIR / archive_name), ++ format="gztar", ++ root_dir=CROSS_BUILD_DIR / context.platform, ++ base_dir=".", ++ ) ++ print() ++ print(f"{archive_name.relative_to(PYTHON_DIR)}.tar.gz created.") + -+Building and using Python on tvOS -+================================= + -+ABIs and Architectures -+---------------------- ++def build(context: argparse.Namespace, host: str | None = None) -> None: ++ """The implementation of the "build" command.""" ++ if host is None: ++ host = context.host + -+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. ++ if context.clean: ++ clean(context, host) + -+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.) ++ if host in {"all", "build"}: ++ for step in [ ++ configure_build_python, ++ make_build_python, ++ ]: ++ step(context) + -+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. ++ if host == "build": ++ hosts = [] ++ elif host in {"all", "hosts"}: ++ hosts = all_host_triples(context.platform) ++ else: ++ hosts = [host] + -+How do I build Python for tvOS? -+------------------------------- ++ for step_host in hosts: ++ for step in [ ++ configure_host_python, ++ make_host_python, ++ ]: ++ step(context, host=step_host) + -+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: ++ if host == "all": ++ package(context) + -+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. ++def test(context: argparse.Namespace, host: str | None = None) -> None: # noqa: PT028 ++ """The implementation of the "test" command.""" ++ if host is None: ++ host = context.host + -+The build also requires the use of cross-compilation. The commands for building -+Python for tvOS will look somethign like:: ++ if context.clean: ++ clean(context, "test") + -+ $ ./configure \ -+ --enable-framework=/path/to/install \ -+ --host=aarch64-apple-tvos \ -+ --build=aarch64-apple-darwin \ -+ --with-build-python=/path/to/python.exe -+ $ make -+ $ make install ++ 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] + -+In this invocation: ++ if not host.endswith("-simulator"): ++ print("Skipping test suite non-simulator build.") ++ return ++ elif build_arch != host_arch: ++ print( ++ f"Skipping test suite for an {host_arch} build " ++ f"on an {build_arch} machine." ++ ) ++ return ++ else: ++ framework_path = ( ++ CROSS_BUILD_DIR ++ / host ++ / f"Platforms/Apple/{context.platform}" ++ / f"Frameworks/{apple_multiarch(host)}" ++ ) + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++ run([ ++ sys.executable, ++ "Platforms/Apple/testbed", ++ "clone", ++ "--platform", ++ context.platform, ++ "--framework", ++ framework_path, ++ testbed_dir, ++ ]) ++ ++ run( ++ [ ++ sys.executable, ++ testbed_dir, ++ "run", ++ "--verbose", ++ ] ++ + ( ++ ["--simulator", str(context.simulator)] ++ if context.simulator ++ else [] ++ ) ++ + [ ++ "--", ++ "test", ++ f"--{context.ci_mode or 'fast'}-ci", ++ "--single-process", ++ "--no-randomize", ++ # Timeout handling requires subprocesses; explicitly setting ++ # the timeout to -1 disables the faulthandler. ++ "--timeout=-1", ++ # Adding Python options requires the use of a subprocess to ++ # start a new Python interpreter. ++ "--dont-add-python-opts", ++ ] ++ ) + -+* ``--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. ++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 + -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: ++ raise KeyError(platform_name) + -+ - ``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. ++def ci(context: argparse.Namespace) -> None: ++ """The implementation of the "ci" command. + -+Using a framework-based Python on tvOS -+====================================== ---- /dev/null -+++ b/tvOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ Python -+ CFBundleGetInfoString ++ In "Fast" mode, this compiles the build python, and the simulator for the ++ build machine's architecture; and runs the test suite with `--fast-ci` ++ configuration. ++ ++ In "Slow" mode, it compiles the build python, plus all candidate ++ architectures (both device and simulator); then runs the test suite with ++ `--slow-ci` configuration. ++ """ ++ clean(context, "all") ++ if context.ci_mode == "slow": ++ # In slow mode, build and test the full XCframework ++ build(context, host="all") ++ test(context, host="all") ++ else: ++ # In fast mode, just build the simulator platform. ++ sim_host = apple_sim_host(context.platform) ++ build(context, host="build") ++ build(context, host=sim_host) ++ test(context, host=sim_host) ++ ++ ++def parse_args() -> argparse.Namespace: ++ parser = argparse.ArgumentParser( ++ description=( ++ "A tool for managing the build, package and test process of " ++ "CPython on Apple platforms." ++ ), ++ ) ++ parser.suggest_on_error = True ++ subcommands = parser.add_subparsers(dest="subcommand", required=True) ++ ++ clean = subcommands.add_parser( ++ "clean", ++ help="Delete all build directories", ++ ) ++ ++ configure_build = subcommands.add_parser( ++ "configure-build", help="Run `configure` for the build Python" ++ ) ++ make_build = subcommands.add_parser( ++ "make-build", help="Run `make` for the build Python" ++ ) ++ configure_host = subcommands.add_parser( ++ "configure-host", ++ help="Run `configure` for a specific platform and target", ++ ) ++ make_host = subcommands.add_parser( ++ "make-host", ++ help="Run `make` for a specific platform and target", ++ ) ++ package = subcommands.add_parser( ++ "package", ++ help="Create a release package for the platform", ++ ) ++ build = subcommands.add_parser( ++ "build", ++ help="Build all platform targets and create the XCframework", ++ ) ++ test = subcommands.add_parser( ++ "test", ++ help="Run the testbed for a specific platform", ++ ) ++ ci = subcommands.add_parser( ++ "ci", ++ help="Run build, package, and test", ++ ) ++ ++ # platform argument ++ for cmd in [clean, configure_host, make_host, package, build, test, ci]: ++ cmd.add_argument( ++ "platform", ++ choices=HOSTS.keys(), ++ help="The target platform to build", ++ ) ++ ++ # host triple argument ++ for cmd in [configure_host, make_host]: ++ cmd.add_argument( ++ "host", ++ help="The host triple to build (e.g., arm64-apple-ios-simulator)", ++ ) ++ # optional host triple argument ++ for cmd in [clean, build, test]: ++ cmd.add_argument( ++ "host", ++ nargs="?", ++ default="all", ++ help=( ++ "The host triple to build (e.g., arm64-apple-ios-simulator), " ++ "or 'build' for just the build platform, or 'hosts' for all " ++ "host platforms, or 'all' for the build platform and all " ++ "hosts. Defaults to 'all'" ++ ), ++ ) ++ ++ # --cross-build-dir argument ++ for cmd in [ ++ clean, ++ configure_build, ++ make_build, ++ configure_host, ++ make_host, ++ build, ++ package, ++ test, ++ ci, ++ ]: ++ cmd.add_argument( ++ "--cross-build-dir", ++ action="store", ++ default=os.environ.get("CROSS_BUILD_DIR"), ++ dest="cross_build_dir", ++ type=Path, ++ help=( ++ "Path to the cross-build directory " ++ f"(default: {CROSS_BUILD_DIR}). Can also be set " ++ "with the CROSS_BUILD_DIR environment variable." ++ ), ++ ) ++ ++ # --clean option ++ for cmd in [configure_build, configure_host, build, package, test, ci]: ++ cmd.add_argument( ++ "--clean", ++ action="store_true", ++ default=False, ++ dest="clean", ++ help="Delete the relevant build directories first", ++ ) ++ ++ # --cache-dir option ++ for cmd in [configure_host, build, ci]: ++ cmd.add_argument( ++ "--cache-dir", ++ default=os.environ.get("CACHE_DIR"), ++ help="The directory to store cached downloads.", ++ ) ++ ++ # --simulator option ++ for cmd in [test, ci]: ++ cmd.add_argument( ++ "--simulator", ++ help=( ++ "The name of the simulator to use (eg: 'iPhone 16e'). " ++ "Defaults to the most recently released 'entry level' " ++ "iPhone device. Device architecture and OS version can also " ++ "be specified; e.g., " ++ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would " ++ "run on an ARM64 iPhone 16 Pro simulator running iOS 26.0." ++ ), ++ ) ++ group = cmd.add_mutually_exclusive_group() ++ group.add_argument( ++ "--fast-ci", ++ action="store_const", ++ dest="ci_mode", ++ const="fast", ++ help="Add test arguments for GitHub Actions", ++ ) ++ group.add_argument( ++ "--slow-ci", ++ action="store_const", ++ dest="ci_mode", ++ const="slow", ++ help="Add test arguments for buildbots", ++ ) ++ ++ for subcommand in [configure_build, configure_host, build, ci]: ++ subcommand.add_argument( ++ "args", nargs="*", help="Extra arguments to pass to `configure`" ++ ) ++ ++ return parser.parse_args() ++ ++ ++def print_called_process_error(e: subprocess.CalledProcessError) -> None: ++ for stream_name in ["stdout", "stderr"]: ++ content = getattr(e, stream_name) ++ stream = getattr(sys, stream_name) ++ if content: ++ stream.write(content) ++ if not content.endswith("\n"): ++ stream.write("\n") ++ ++ # shlex uses single quotes, so we surround the command with double quotes. ++ print( ++ f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}' ++ ) ++ ++ ++def main() -> None: ++ # Handle SIGTERM the same way as SIGINT. This ensures that if we're ++ # terminated by the buildbot worker, we'll make an attempt to clean up our ++ # subprocesses. ++ def signal_handler(*args): ++ os.kill(os.getpid(), signal.SIGINT) ++ ++ signal.signal(signal.SIGTERM, signal_handler) ++ ++ # Process command line arguments ++ context = parse_args() ++ ++ # Set the CROSS_BUILD_DIR if an argument was provided ++ if context.cross_build_dir: ++ global CROSS_BUILD_DIR ++ CROSS_BUILD_DIR = context.cross_build_dir.resolve() ++ ++ dispatch: dict[str, Callable] = { ++ "clean": clean, ++ "configure-build": configure_build_python, ++ "make-build": make_build_python, ++ "configure-host": configure_host_python, ++ "make-host": make_host_python, ++ "package": package, ++ "build": build, ++ "test": test, ++ "ci": ci, ++ } ++ ++ try: ++ dispatch[context.subcommand](context) ++ except CalledProcessError as e: ++ print() ++ print_called_process_error(e) ++ sys.exit(1) ++ except RuntimeError as e: ++ print() ++ print(e) ++ sys.exit(2) ++ ++ ++if __name__ == "__main__": ++ # Under the buildbot, stdout is not a TTY, but we must still flush after ++ # every line to make sure our output appears in the correct order relative ++ # to the output of our subprocesses. ++ for stream in [sys.stdout, sys.stderr]: ++ stream.reconfigure(line_buffering=True) ++ ++ main() +diff --git a/Platforms/Apple/iOS/README.md b/Platforms/Apple/iOS/README.md +new file mode 100644 +index 00000000000..faeeead1df0 +--- /dev/null ++++ b/Platforms/Apple/iOS/README.md +@@ -0,0 +1,339 @@ ++# Python on iOS README ++ ++**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).** ++ ++This document provides a quick overview of some iOS specific features in the ++Python distribution. ++ ++These instructions are only needed if you're planning to compile Python for iOS ++yourself. Most users should *not* need to do this. If you're looking to ++experiment with writing an iOS app in Python, tools such as [BeeWare's ++Briefcase](https://briefcase.readthedocs.io) and [Kivy's ++Buildozer](https://buildozer.readthedocs.io) will provide a much more ++approachable user experience. ++ ++## Compilers for building on iOS ++ ++Building for iOS requires the use of Apple's Xcode tooling. It is strongly ++recommended that you use the most recent stable release of Xcode. This will ++require the use of the most (or second-most) recently released macOS version, ++as Apple does not maintain Xcode for older macOS versions. The Xcode Command ++Line Tools are not sufficient for iOS development; you need a *full* Xcode ++install. ++ ++If you want to run your code on the iOS simulator, you'll also need to install ++an iOS Simulator Platform. You should be prompted to select an iOS Simulator ++Platform when you first run Xcode. Alternatively, you can add an iOS Simulator ++Platform by selecting an open the Platforms tab of the Xcode Settings panel. ++ ++## Building Python on iOS ++ ++### ABIs and Architectures ++ ++iOS apps can be deployed on physical devices, and on the iOS simulator. Although ++the API used on these devices is identical, the ABI is different - you need to ++link against different libraries for an iOS device build (`iphoneos`) or an ++iOS simulator build (`iphonesimulator`). ++ ++Apple uses the `XCframework` format to allow specifying a single dependency ++that supports multiple ABIs. An `XCframework` is a wrapper around multiple ++ABI-specific frameworks that share a common API. ++ ++iOS can also support different CPU architectures within each ABI. At present, ++there is only a single supported architecture on physical devices - ARM64. ++However, the *simulator* supports 2 architectures - ARM64 (for running on Apple ++Silicon machines), and x86_64 (for running on older Intel-based machines). ++ ++To support multiple CPU architectures on a single platform, Apple uses a "fat ++binary" format - a single physical file that contains support for multiple ++architectures. It is possible to compile and use a "thin" single architecture ++version of a binary for testing purposes; however, the "thin" binary will not be ++portable to machines using other architectures. ++ ++### Building a multi-architecture iOS XCframework ++ ++The `Platforms/Apple` subfolder of the Python repository acts as a build script that ++can be used to coordinate the compilation of a complete iOS XCframework. To use ++it, run:: ++ ++ python Platforms/Apple build iOS ++ ++This will: ++ ++* Configure and compile a version of Python to run on the build machine ++* Download pre-compiled binary dependencies for each platform ++* Configure and build a `Python.framework` for each required architecture and ++ iOS SDK ++* Merge the multiple `Python.framework` folders into a single `Python.xcframework` ++* Produce a `.tar.gz` archive in the `cross-build/dist` folder containing ++ the `Python.xcframework`, plus a copy of the Testbed app pre-configured to ++ use the XCframework. ++ ++The `Platforms/Apple` build script has other entry points that will perform the ++individual parts of the overall `build` target, plus targets to test the ++build, clean the `cross-build` folder of iOS build products, and perform a ++complete "build and test" CI run. The `--clean` flag can also be used on ++individual commands to ensure that a stale build product are removed before ++building. ++ ++### Building a single-architecture framework ++ ++If you're using the `Platforms/Apple` build script, you won't need to build ++individual frameworks. However, if you do need to manually configure an iOS ++Python build for a single framework, the following options are available. ++ ++#### iOS specific arguments to configure ++ ++* `--enable-framework[=DIR]` ++ ++ This argument specifies the location where the Python.framework will be ++ installed. If `DIR` is not specified, the framework will be installed into ++ a subdirectory of the `iOS/Frameworks` folder. ++ ++ This argument *must* be provided when configuring iOS builds. iOS does not ++ support non-framework builds. ++ ++* `--with-framework-name=NAME` ++ ++ Specify the name for the Python framework; defaults to `Python`. ++ ++ > [!NOTE] ++ > Unless you know what you're doing, changing the name of the Python ++ > framework on iOS is not advised. If you use this option, you won't be able ++ > to run the `Platforms/Apple` build script without making significant manual ++ > alterations, and you won't be able to use any binary packages unless you ++ > compile them yourself using your own framework name. ++ ++#### Building Python for iOS ++ ++The Python build system will create a `Python.framework` that supports a ++*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a ++framework to contain non-library content, so the iOS build will produce a ++`bin` and `lib` folder in the same output folder as `Python.framework`. ++The `lib` folder will be needed at runtime to support the Python library. ++ ++If you want to use Python in a real iOS project, you need to produce multiple ++`Python.framework` builds, one for each ABI and architecture. iOS builds of ++Python *must* be constructed as framework builds. To support this, you must ++provide the `--enable-framework` flag when configuring the build. The build ++also requires the use of cross-compilation. The minimal commands for building ++Python for the ARM64 iOS simulator will look something like: ++``` ++export PATH="$(pwd)/Platforms/Apple/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" ++./configure \ ++ --enable-framework \ ++ --host=arm64-apple-ios-simulator \ ++ --build=arm64-apple-darwin \ ++ --with-build-python=/path/to/python.exe ++make ++make install ++``` ++ ++In this invocation: ++ ++* `Platforms/Apple/iOS/Resources/bin` has been added to the path, providing some shims for the ++ compilers and linkers needed by the build. Xcode requires the use of `xcrun` ++ to invoke compiler tooling. However, if `xcrun` is pre-evaluated and the ++ result passed to `configure`, these results can embed user- and ++ version-specific paths into the sysconfig data, which limits the portability ++ of the compiled Python. Alternatively, if `xcrun` is used *as* the compiler, ++ it requires that compiler variables like `CC` include spaces, which can ++ cause significant problems with many C configuration systems which assume that ++ `CC` will be a single executable. ++ ++ To work around this problem, the `Platforms/Apple/iOS/Resources/bin` folder contains some ++ wrapper scripts that present as simple compilers and linkers, but wrap ++ underlying calls to `xcrun`. This allows configure to use a `CC` ++ definition without spaces, and without user- or version-specific paths, while ++ retaining the ability to adapt to the local Xcode install. These scripts are ++ included in the `bin` directory of an iOS install. ++ ++ These scripts will, by default, use the currently active Xcode installation. ++ If you want to use a different Xcode installation, you can use ++ `xcode-select` to set a new default Xcode globally, or you can use the ++ `DEVELOPER_DIR` environment variable to specify an Xcode install. The ++ scripts will use the default `iphoneos`/`iphonesimulator` SDK version for ++ the select Xcode install; if you want to use a different SDK, you can set the ++ `IOS_SDK_VERSION` environment variable. (e.g, setting ++ `IOS_SDK_VERSION=17.1` would cause the scripts to use the `iphoneos17.1` ++ and `iphonesimulator17.1` SDKs, regardless of the Xcode default.) ++ ++ The path has also been cleared of any user customizations. A common source of ++ bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS ++ build. Resetting the path to a known "bare bones" value is the easiest way to ++ avoid these problems. ++ ++* `--host` is the architecture and ABI that you want to build, in GNU compiler ++ triple format. This will be one of: ++ ++ - `arm64-apple-ios` for ARM64 iOS devices. ++ - `arm64-apple-ios-simulator` for the iOS simulator running on Apple ++ Silicon devices. ++ - `x86_64-apple-ios-simulator` for the iOS simulator running on Intel ++ devices. ++ ++* `--build` is the GNU compiler triple for the machine that will be running ++ the compiler. This is one of: ++ ++ - `arm64-apple-darwin` for Apple Silicon devices. ++ - `x86_64-apple-darwin` for Intel devices. ++ ++* `/path/to/python.exe` is the path to a Python binary on the machine that ++ will be running the compiler. This is needed because the Python compilation ++ process involves running some Python code. On a normal desktop build of ++ Python, you can compile a python interpreter and then use that interpreter to ++ run Python code. However, the binaries produced for iOS won't run on macOS, so ++ you need to provide an external Python interpreter. This interpreter must be ++ the same version as the Python that is being compiled. To be completely safe, ++ this should be the *exact* same commit hash. However, the longer a Python ++ release has been stable, the more likely it is that this constraint can be ++ relaxed - the same micro version will often be sufficient. ++ ++* The `install` target for iOS builds is slightly different to other ++ platforms. On most platforms, `make install` will install the build into ++ the final runtime location. This won't be the case for iOS, as the final ++ runtime location will be on a physical device. ++ ++ However, you still need to run the `install` target for iOS builds, as it ++ performs some final framework assembly steps. The location specified with ++ `--enable-framework` will be the location where `make install` will ++ assemble the complete iOS framework. This completed framework can then ++ be copied and relocated as required. ++ ++For a full CPython build, you also need to specify the paths to iOS builds of ++the binary libraries that CPython depends on (such as XZ, LibFFI and OpenSSL). ++This can be done by defining library specific environment variables (such as ++`LIBLZMA_CFLAGS`, `LIBLZMA_LIBS`), and the `--with-openssl` configure ++option. Versions of these libraries pre-compiled for iOS can be found in [this ++repository](https://github.com/beeware/cpython-apple-source-deps/releases). ++LibFFI is especially important, as many parts of the standard library ++(including the `platform`, `sysconfig` and `webbrowser` modules) require ++the use of the `ctypes` module at runtime. ++ ++By default, Python will be compiled with an iOS deployment target (i.e., the ++minimum supported iOS version) of 13.0. To specify a different deployment ++target, provide the version number as part of the `--host` argument - for ++example, `--host=arm64-apple-ios15.4-simulator` would compile an ARM64 ++simulator build with a deployment target of 15.4. ++ ++## Testing Python on iOS ++ ++### Testing a multi-architecture framework ++ ++Once you have a built an XCframework, you can test that framework by running: ++ ++ $ python Platforms/Apple test iOS ++ ++This test will attempt to find an "SE-class" simulator (i.e., an iPhone SE, or ++iPhone 16e, or similar), and run the test suite on the most recent version of ++iOS that is available. You can specify a simulator using the `--simulator` ++command line argument, providing the name of the simulator (e.g., `--simulator ++'iPhone 16 Pro'`). You can also use this argument to control the OS version used ++for testing; `--simulator 'iPhone 16 Pro,OS=18.2'` would attempt to run the ++tests on an iPhone 16 Pro running iOS 18.2. ++ ++If the test runner is executed on GitHub Actions, the `GITHUB_ACTIONS` ++environment variable will be exposed to the iOS process at runtime. ++ ++### Testing a single-architecture framework ++ ++The `Platforms/Apple/testbed` folder that contains an Xcode project that is able to run ++the Python test suite on Apple platforms. This project converts the Python test ++suite into a single test case in Xcode's XCTest framework. The single XCTest ++passes if the test suite passes. ++ ++To run the test suite, configure a Python build for an iOS simulator (i.e., ++`--host=arm64-apple-ios-simulator` or `--host=x86_64-apple-ios-simulator` ++), specifying a framework build (i.e. `--enable-framework`). Ensure that your ++`PATH` has been configured to include the `Platforms/Apple/iOS/Resources/bin` folder and ++exclude any non-iOS tools, then run: ++``` ++make all ++make install ++make testios ++``` ++ ++This will: ++ ++* Build an iOS framework for your chosen architecture; ++* Finalize the single-platform framework; ++* Make a clean copy of the testbed project; ++* Install the Python iOS framework into the copy of the testbed project; and ++* Run the test suite on an "entry-level device" simulator (i.e., an iPhone SE, ++ iPhone 16e, or a similar). ++ ++On success, the test suite will exit and report successful completion of the ++test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15 ++minutes to run; a couple of extra minutes is required to compile the testbed ++project, and then boot and prepare the iOS simulator. ++ ++### Debugging test failures ++ ++Running `python Platforms/Apple test iOS` generates a standalone version of the ++`Platforms/Apple/testbed` project, and runs the full test suite. It does this using ++`Platforms/Apple/testbed` itself - the folder is an executable module that can be used ++to create and run a clone of the testbed project. The standalone version of the ++testbed will be created in a directory named ++`cross-build/iOS-testbed.`. ++ ++You can generate your own standalone testbed instance by running: ++``` ++python cross-build/iOS/testbed clone my-testbed ++``` ++ ++In this invocation, `my-testbed` is the name of the folder for the new ++testbed clone. ++ ++If you've built your own XCframework, or you only want to test a single architecture, ++you can construct a standalone testbed instance by running: ++``` ++python Platforms/Apple/testbed clone --platform iOS --framework my-testbed ++``` ++ ++The framework path can be the path path to a `Python.xcframework`, or the ++path to a folder that contains a single-platform `Python.framework`. ++ ++You can then use the `my-testbed` folder to run the Python test suite, ++passing in any command line arguments you may require. For example, if you're ++trying to diagnose a failure in the `os` module, you might run: ++``` ++python my-testbed run -- test -W test_os ++``` ++ ++This is the equivalent of running `python -m test -W test_os` on a desktop ++Python build. Any arguments after the `--` will be passed to testbed as if ++they were arguments to `python -m` on a desktop machine. ++ ++### Testing in Xcode ++ ++You can also open the testbed project in Xcode by running: ++``` ++open my-testbed/iOSTestbed.xcodeproj ++``` ++ ++This will allow you to use the full Xcode suite of tools for debugging. ++ ++The arguments used to run the test suite are defined as part of the test plan. ++To modify the test plan, select the test plan node of the project tree (it ++should be the first child of the root node), and select the "Configurations" ++tab. Modify the "Arguments Passed On Launch" value to change the testing ++arguments. ++ ++The test plan also disables parallel testing, and specifies the use of the ++`Testbed.lldbinit` file for providing configuration of the debugger. The ++default debugger configuration disables automatic breakpoints on the ++`SIGINT`, `SIGUSR1`, `SIGUSR2`, and `SIGXFSZ` signals. ++ ++### Testing on an iOS device ++ ++To test on an iOS device, the app needs to be signed with known developer ++credentials. To obtain these credentials, you must have an iOS Developer ++account, and your Xcode install will need to be logged into your account (see ++the Accounts tab of the Preferences dialog). ++ ++Once the project is open, and you're signed into your Apple Developer account, ++select the root node of the project tree (labeled "iOSTestbed"), then the ++"Signing & Capabilities" tab in the details page. Select a development team ++(this will likely be your own name), and plug in a physical device to your ++macOS machine with a USB cable. You should then be able to select your physical ++device from the list of targets in the pulldown in the Xcode titlebar. +diff --git a/Platforms/Apple/iOS/Resources/Info.plist.in b/Platforms/Apple/iOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..26ef7a95de4 +--- /dev/null ++++ b/Platforms/Apple/iOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString + Python Runtime and Library + CFBundleIdentifier + @PYTHONFRAMEWORKIDENTIFIER@ @@ -1994,106 +2098,140 @@ index c3e261ecd9e..26ef7a95de4 100644 + CFBundleSignature + ???? + CFBundleVersion -+ 1 ++ %VERSION% + CFBundleSupportedPlatforms + -+ tvOS ++ iPhoneOS + + MinimumOSVersion -+ @TVOS_DEPLOYMENT_TARGET@ ++ @IPHONEOS_DEPLOYMENT_TARGET@ + + +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-ar b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-ar +new file mode 100755 +index 00000000000..3cf3eb21874 --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-ar ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-ar @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang +new file mode 100755 +index 00000000000..f50d5b5142f --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ +new file mode 100755 +index 00000000000..0794731d7dc --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-clang++ ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-clang++ @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-cpp b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-cpp +new file mode 100755 +index 00000000000..24fa1506bab --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-cpp ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-cpp @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos -E "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar +new file mode 100755 +index 00000000000..b836b6db902 --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang +new file mode 100755 +index 00000000000..4891a00876e --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ +new file mode 100755 +index 00000000000..58b2a5f6f18 --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp +new file mode 100755 +index 00000000000..c9df94e8b7c --- /dev/null -+++ b/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos-simulator -E "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip +new file mode 100755 +index 00000000000..fd59d309b73 --- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-strip b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-strip +new file mode 100755 +index 00000000000..75e823a3d02 --- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang ++++ b/Platforms/Apple/iOS/Resources/bin/arm64-apple-ios-strip @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar +new file mode 100755 +index 00000000000..b836b6db902 --- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ ++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos-simulator "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang +new file mode 100755 +index 00000000000..f4739a7b945 --- /dev/null -+++ b/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp ++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos-simulator -E "$@" ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ +new file mode 100755 +index 00000000000..c348ae4c103 --- /dev/null -+++ b/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 -+ -+ ++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +new file mode 100755 +index 00000000000..6d7f8084c9f --- /dev/null -+++ b/tvOS/Resources/pyconfig.h ++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip +new file mode 100755 +index 00000000000..c5cfb289291 +--- /dev/null ++++ b/Platforms/Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@" +diff --git a/Platforms/Apple/iOS/Resources/pyconfig.h b/Platforms/Apple/iOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..4acff2c6051 +--- /dev/null ++++ b/Platforms/Apple/iOS/Resources/pyconfig.h @@ -0,0 +1,7 @@ +#ifdef __arm64__ +#include "pyconfig-arm64.h" @@ -2102,119 +2240,12 @@ index c3e261ecd9e..26ef7a95de4 100644 +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif +diff --git a/Platforms/Apple/testbed/Python.xcframework/Info.plist b/Platforms/Apple/testbed/Python.xcframework/Info.plist +new file mode 100644 +index 00000000000..0587f4735f7 --- /dev/null -+++ b/visionOS/Resources/Info.plist.in -@@ -0,0 +1,34 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleExecutable -+ Python -+ CFBundleGetInfoString -+ Python Runtime and Library -+ CFBundleIdentifier -+ @PYTHONFRAMEWORKIDENTIFIER@ -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleName -+ Python -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ %VERSION% -+ CFBundleLongVersionString -+ %VERSION%, (c) 2001-2023 Python Software Foundation. -+ CFBundleSignature -+ ???? -+ CFBundleVersion -+ %VERSION% -+ CFBundleSupportedPlatforms -+ -+ XROS -+ -+ MinimumOSVersion -+ @XROS_DEPLOYMENT_TARGET@ -+ -+ ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xros${XROS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@" ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xros${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@" ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} -E "$@" ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xrsimulator${XROS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-clang -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@" ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@" ---- /dev/null -+++ b/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk xrsimulator clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator -E "$@" ---- /dev/null -+++ b/visionOS/Resources/dylib-Info-template.plist -@@ -0,0 +1,30 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ XROS -+ -+ CFBundleVersion -+ 1 -+ MinimumOSVersion -+ 2.0 -+ UIDeviceFamily -+ -+ 7 -+ -+ -+ ---- /dev/null -+++ b/visionOS/testbed/Python.xcframework/Info.plist -@@ -0,0 +1,43 @@ ++++ b/Platforms/Apple/testbed/Python.xcframework/Info.plist +@@ -0,0 +1,136 @@ + + + @@ -2225,7 +2256,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + BinaryPath + Python.framework/Python + LibraryIdentifier -+ xros-arm64-simulator ++ ios-arm64 + LibraryPath + Python.framework + SupportedArchitectures @@ -2233,300 +2264,891 @@ index c3e261ecd9e..26ef7a95de4 100644 + arm64 + + SupportedPlatform -+ xros -+ SupportedPlatformVariant -+ simulator ++ ios + + + BinaryPath + Python.framework/Python + LibraryIdentifier -+ xros-arm64 ++ ios-arm64_x86_64-simulator + LibraryPath + Python.framework + SupportedArchitectures + + arm64 ++ x86_64 + + SupportedPlatform -+ xros ++ ios ++ SupportedPlatformVariant ++ simulator + -+ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ tvos-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ tvos ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ tvos-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ tvos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ xros-arm64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ xros ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ xros-arm64 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ ++ SupportedPlatform ++ xros ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_x86_64-simulator ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64 ++ x86_64 ++ ++ SupportedPlatform ++ watchos ++ SupportedPlatformVariant ++ simulator ++ ++ ++ BinaryPath ++ Python.framework/Python ++ LibraryIdentifier ++ watchos-arm64_32 ++ LibraryPath ++ Python.framework ++ SupportedArchitectures ++ ++ arm64_32 ++ ++ SupportedPlatform ++ watchos ++ ++ + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..d6caa01c1e4 --- /dev/null -+++ b/visionOS/testbed/Python.xcframework/xros-arm64-simulator/README -@@ -0,0 +1,4 @@ -+This directory is intentionally empty. ++++ b/Platforms/Apple/testbed/Python.xcframework/build/iOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ iPhoneOS ++ ++ MinimumOSVersion ++ 13.0 ++ CFBundleVersion ++ 1 ++ ++ +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..a20d476fa7b +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/build/tvOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ 9.0 ++ CFBundleVersion ++ 1 ++ ++ +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/utils.sh b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh +new file mode 100755 +index 00000000000..2d3b9320b3c +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/build/utils.sh +@@ -0,0 +1,180 @@ ++# Utility methods for use in an Xcode project. ++# ++# An iOS XCframework cannot include any content other than the library binary ++# and relevant metadata. However, Python requires a standard library at runtime. ++# Therefore, it is necessary to add a build step to an Xcode app target that ++# processes the standard library and puts the content into the final app. ++# ++# In general, these tools will be invoked after bundle resources have been ++# copied into the app, but before framework embedding (and signing). ++# ++# The following is an example script, assuming that: ++# * Python.xcframework is in the root of the project ++# * There is an `app` folder that contains the app code ++# * There is an `app_packages` folder that contains installed Python packages. ++# ----- ++# set -e ++# source $PROJECT_DIR/Python.xcframework/build/build_utils.sh ++# install_python Python.xcframework app app_packages ++# ----- ++ ++# Copy the standard library from the XCframework into the app bundle. ++# ++# Accepts one argument: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. ++install_stdlib() { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ ++ mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" ++ if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then ++ echo "Installing Python modules for iOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/ios-arm64-simulator" ]; then ++ SLICE_FOLDER="ios-arm64-simulator" ++ else ++ SLICE_FOLDER="ios-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-iphoneos" ]; then ++ echo "Installing Python modules for iOS Device" ++ SLICE_FOLDER="ios-arm64" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvsimulator" ]; then ++ echo "Installing Python modules for tvOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/tvos-arm64-simulator" ]; then ++ SLICE_FOLDER="tvos-arm64-simulator" ++ else ++ SLICE_FOLDER="tvos-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-appletvos" ]; then ++ echo "Installing Python modules for tvOS Device" ++ SLICE_FOLDER="tvos-arm64" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchsimulator" ]; then ++ echo "Installing Python modules for watchOS Simulator" ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/watchos-arm64-simulator" ]; then ++ SLICE_FOLDER="watchos-arm64-simulator" ++ else ++ SLICE_FOLDER="watchos-arm64_x86_64-simulator" ++ fi ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-watchos" ]; then ++ echo "Installing Python modules for watchOS Device" ++ SLICE_FOLDER="watchos-arm64" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xrsimulator" ]; then ++ echo "Installing Python modules for visionOS Simulator" ++ SLICE_FOLDER="xros-arm64-simulator" ++ elif [ "$EFFECTIVE_PLATFORM_NAME" = "-xros" ]; then ++ echo "Installing Python modules for visionOS Device" ++ SLICE_FOLDER="xros-arm64" ++ else ++ echo "Unsupported platform name $EFFECTIVE_PLATFORM_NAME" ++ exit 1 ++ fi ++ ++ # If the XCframework has a shared lib folder, then it's a full framework. ++ # Copy both the common and slice-specific part of the lib directory. ++ # Otherwise, it's a single-arch framework; use the "full" lib folder. ++ # Don't include any libpython symlink; that can't be included at runtime ++ if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then ++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib' ++ rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib' ++ else ++ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib' ++ fi ++} + -+It should be used as a target for `--enable-framework` when compiling an visionOS simulator -+build for testing purposes (either x86_64 or ARM64). ++# Convert a single .so library into a framework that iOS can load. ++# ++# Accepts three arguments: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. ++# 2. The base path, relative to the installed location in the app bundle, that ++# needs to be processed. Any .so file found in this path (or a subdirectory ++# of it) will be processed. ++# 2. The full path to a single .so file to process. This path should include ++# the base path. ++install_dylib () { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ INSTALL_BASE=$2 ++ FULL_EXT=$3 ++ ++ # The name of the extension file ++ EXT=$(basename "$FULL_EXT") ++ # The name and location of the module ++ MODULE_PATH=$(dirname "$FULL_EXT") ++ MODULE_NAME=$(echo $EXT | cut -d "." -f 1) ++ # The location of the extension file, relative to the bundle ++ RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} ++ # The path to the extension file, relative to the install base ++ PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} ++ # The full dotted name of the extension module, constructed from the file path. ++ FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); ++ # A bundle identifier; not actually used, but required by Xcode framework packaging ++ FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") ++ # The name of the framework folder. ++ FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" ++ ++ # If the framework folder doesn't exist, create it. ++ if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then ++ echo "Creating framework for $RELATIVE_EXT" ++ mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ++ cp "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/build/$PLATFORM_FAMILY_NAME-dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" ++ fi ++ ++ echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ # Create a placeholder .fwork file where the .so was ++ echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork ++ # Create a back reference to the .so file location in the framework ++ echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" ++ ++ # If the framework provides an xcprivacy file, install it. ++ if [ -e "$MODULE_PATH/$MODULE_NAME.xcprivacy" ]; then ++ echo "Installing XCPrivacy file for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" ++ XCPRIVACY_FILE="$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/PrivacyInfo.xcprivacy" ++ if [ -e "$XCPRIVACY_FILE" ]; then ++ rm -rf "$XCPRIVACY_FILE" ++ fi ++ mv "$MODULE_PATH/$MODULE_NAME.xcprivacy" "$XCPRIVACY_FILE" ++ fi ++ ++ echo "Signing framework as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." ++ /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ++} ++ ++# Process all the dynamic libraries in a path into Framework format. ++# ++# Accepts two arguments: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. ++# 2. The base path, relative to the installed location in the app bundle, that ++# needs to be processed. Any .so file found in this path (or a subdirectory ++# of it) will be processed. ++process_dylibs () { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ LIB_PATH=$2 ++ find "$CODESIGNING_FOLDER_PATH/$LIB_PATH" -name "*.so" | while read FULL_EXT; do ++ install_dylib $PYTHON_XCFRAMEWORK_PATH "$LIB_PATH/" "$FULL_EXT" ++ done ++} ++ ++# The entry point for post-processing a Python XCframework. ++# ++# Accepts 1 or more arguments: ++# 1. The path, relative to the root of the Xcode project, where the Python ++# XCframework can be found. If the XCframework is in the root of the project, ++# 2+. The path of a package, relative to the root of the packaged app, that contains ++# library content that should be processed for binary libraries. ++install_python() { ++ PYTHON_XCFRAMEWORK_PATH=$1 ++ shift ++ ++ install_stdlib $PYTHON_XCFRAMEWORK_PATH ++ PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib" | grep -E "^python3\.\d+$") ++ echo "Install Python $PYTHON_VER standard library extension modules..." ++ process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload ++ ++ for package_path in $@; do ++ echo "Installing $package_path extension modules ..." ++ process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path ++ done ++} +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..6f8c0bc2095 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/build/watchOS-dylib-Info-template.plist +@@ -0,0 +1,26 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ watchOS ++ ++ MinimumOSVersion ++ 4.0 ++ CFBundleVersion ++ 1 ++ ++ +diff --git a/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist +new file mode 100644 +index 00000000000..e91673b4fbc +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/build/xrOS-dylib-Info-template.plist +@@ -0,0 +1,30 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleExecutable ++ ++ CFBundleIdentifier ++ ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSupportedPlatforms ++ ++ XROS ++ ++ CFBundleVersion ++ 1 ++ MinimumOSVersion ++ 2.0 ++ UIDeviceFamily ++ ++ 7 ++ ++ ++ +diff --git a/Platforms/Apple/testbed/Python.xcframework/ios-arm64/README b/Platforms/Apple/testbed/Python.xcframework/ios-arm64/README +new file mode 100644 +index 00000000000..c1b076d12cd --- /dev/null -+++ b/visionOS/testbed/Python.xcframework/xros-arm64/README ++++ b/Platforms/Apple/testbed/Python.xcframework/ios-arm64/README @@ -0,0 +1,4 @@ +This directory is intentionally empty. + -+It should be used as a target for `--enable-framework` when compiling an visionOS on-device ++It should be used as a target for `--enable-framework` when compiling an iOS on-device +build for testing purposes. +diff --git a/Platforms/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..ae334e5d769 --- /dev/null -+++ b/visionOS/testbed/__main__.py -@@ -0,0 +1,512 @@ -+import argparse -+import asyncio -+import fcntl -+import json -+import os -+import plistlib -+import re -+import shutil -+import subprocess -+import sys -+import tempfile -+from contextlib import asynccontextmanager -+from datetime import datetime -+from pathlib import Path -+ -+ -+DECODE_ARGS = ("UTF-8", "backslashreplace") ++++ b/Platforms/Apple/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+# The system log prefixes each line: -+# 2025-01-17 16:14:29.090 Df visionOSTestbed[23987:1fd393b4] (Python) ... -+# 2025-01-17 16:14:29.090 E visionOSTestbed[23987:1fd393b4] (Python) ... ++It should be used as a target for `--enable-framework` when compiling an iOS simulator ++build for testing purposes (either x86_64 or ARM64). +diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README +new file mode 100644 +index 00000000000..ebd648d04bd +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+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+visionOSTestbed\[\d+:\w+\]" # Process/thread ID -+ r"\s+\(Python\)\s" # Logger name -+) ++It should be used as a target for `--enable-framework` when compiling a tvOS ++on-device build for testing purposes. +diff --git a/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..f8163468d8c +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/tvos-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + ++It should be used as a target for `--enable-framework` when compiling a tvOS ++simulator build for testing purposes (either x86_64 or ARM64). +diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README +new file mode 100644 +index 00000000000..696af231df3 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_32/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+# Work around a bug involving sys.exit and TaskGroups -+# (https://github.com/python/cpython/issues/101515). -+def exit(*args): -+ raise MySystemExit(*args) ++It should be used as a target for `--enable-framework` when compiling a watchOS on-device ++build for testing purposes. +diff --git a/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README +new file mode 100644 +index 00000000000..d38e1e98276 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/watchos-arm64_x86_64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + ++It should be used as a target for `--enable-framework` when compiling a watchOS ++simulator build for testing purposes (either x86_64 or ARM64). +diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README +new file mode 100644 +index 00000000000..7bb0ad4b6ba +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64-simulator/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + -+class MySystemExit(Exception): -+ pass ++It should be used as a target for `--enable-framework` when compiling an visionOS simulator ++build for testing purposes (either x86_64 or ARM64). +diff --git a/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README +new file mode 100644 +index 00000000000..2fc2112e477 +--- /dev/null ++++ b/Platforms/Apple/testbed/Python.xcframework/xros-arm64/README +@@ -0,0 +1,4 @@ ++This directory is intentionally empty. + ++It should be used as a target for `--enable-framework` when compiling an visionOS on-device ++build for testing purposes. +diff --git a/Platforms/Apple/testbed/Testbed.lldbinit b/Platforms/Apple/testbed/Testbed.lldbinit +new file mode 100644 +index 00000000000..4cf00dd0f9d +--- /dev/null ++++ b/Platforms/Apple/testbed/Testbed.lldbinit +@@ -0,0 +1,4 @@ ++process handle SIGINT -n true -p true -s false ++process handle SIGUSR1 -n true -p true -s false ++process handle SIGUSR2 -n true -p true -s false ++process handle SIGXFSZ -n true -p true -s false +diff --git a/Platforms/Apple/testbed/TestbedTests/TestbedTests.m b/Platforms/Apple/testbed/TestbedTests/TestbedTests.m +new file mode 100644 +index 00000000000..f7788c47f2c +--- /dev/null ++++ b/Platforms/Apple/testbed/TestbedTests/TestbedTests.m +@@ -0,0 +1,200 @@ ++#import ++#import + -+class SimulatorLock: -+ # An fcntl-based filesystem lock that can be used to ensure that -+ def __init__(self, timeout): -+ self.filename = Path(tempfile.gettempdir()) / "python-visionos-testbed" -+ self.timeout = timeout ++@interface TestbedTests : XCTestCase + -+ self.fd = None ++@end + -+ async def acquire(self): -+ # Ensure the lockfile exists -+ self.filename.touch(exist_ok=True) ++@implementation TestbedTests + -+ # Try `timeout` times to acquire the lock file, with a 1 second pause -+ # between each attempt. Report status every 10 seconds. -+ for i in range(0, self.timeout): -+ try: -+ fd = os.open(self.filename, os.O_RDWR | os.O_TRUNC, 0o644) -+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) -+ except OSError: -+ os.close(fd) -+ if i % 10 == 0: -+ print("... waiting", flush=True) -+ await asyncio.sleep(1) -+ else: -+ self.fd = fd -+ return + -+ # If we reach the end of the loop, we've exceeded the allowed number of -+ # attempts. -+ raise ValueError("Unable to obtain lock on visionOS simulator creation") -+ -+ def release(self): -+ # If a lock is held, release it. -+ if self.fd is not None: -+ # Release the lock. -+ fcntl.flock(self.fd, fcntl.LOCK_UN) -+ os.close(self.fd) -+ self.fd = None -+ -+ -+# All subprocesses are executed through this context manager so that no matter -+# what happens, they can always be cancelled from another task, and they will -+# always be cleaned up on exit. -+@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() ++- (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; + -+ # 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) ++ NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + ++ // Set some other common environment indicators to disable color, as the ++ // Xcode log can't display color. Stdout will report that it is *not* a ++ // TTY. ++ setenv("NO_COLOR", "1", true); ++ setenv("PYTHON_COLORS", "0", true); + -+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), -+ ) ++ if (getenv("GITHUB_ACTIONS")) { ++ NSLog(@"Running in a GitHub Actions environment"); ++ } ++ // Arguments to pass into the test suite runner. ++ // argv[0] must identify the process; any subsequent arg ++ // will be handled as if it were an argument to `python -m test` ++ // The processInfo arguments contain the binary that is running, ++ // followed by the arguments defined in the test plan. This means: ++ // run_module = test_args[1] ++ // argv = ["Testbed"] + test_args[2:] ++ test_args = [[NSProcessInfo processInfo] arguments]; ++ if (test_args == NULL) { ++ NSLog(@"Unable to identify test arguments."); ++ } ++ NSLog(@"Test arguments: %@", test_args); ++ argv = malloc(sizeof(char *) * ([test_args count] - 1)); ++ argv[0] = "Testbed"; ++ for (int i = 1; i < [test_args count] - 1; i++) { ++ argv[i] = [[test_args objectAtIndex:i+1] UTF8String]; ++ } + ++ // Generate an isolated Python configuration. ++ NSLog(@"Configuring isolated Python..."); ++ PyPreConfig_InitIsolatedConfig(&preconfig); ++ PyConfig_InitIsolatedConfig(&config); + -+# 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 visionOS simulators -+ return [ -+ simulator["udid"] -+ for runtime, simulators in json_data["devices"].items() -+ for simulator in simulators -+ if runtime.split(".")[-1].startswith("xrOS") and simulator["state"] == "Booted" -+ ] -+ except subprocess.CalledProcessError as e: -+ # If there's no ~/Library/Developer/XCTestDevices folder (which is the -+ # case on fresh installs, and in some CI environments), `simctl list` -+ # returns error code 1, rather than an empty list. Handle that case, -+ # but raise all other errors. -+ if e.returncode == 1: -+ return [] -+ else: -+ raise -+ -+ -+async def find_device(initial_devices, lock): -+ 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}", flush=True) -+ lock.release() -+ return udid -+ else: -+ exit(f"Found more than one new device: {new_devices}") ++ // Configure the Python interpreter: ++ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. ++ // See https://docs.python.org/3/library/os.html#python-utf-8-mode. ++ preconfig.utf8_mode = 1; ++ // Use the system logger for stdout/err ++ config.use_system_logger = 1; ++ // Don't buffer stdio. We want output to appears in the log immediately ++ config.buffered_stdio = 0; ++ // Don't write bytecode; we can't modify the app bundle ++ // after it has been signed. ++ config.write_bytecode = 0; ++ // Ensure that signal handlers are installed ++ config.install_signal_handlers = 1; ++ // Run the test module. ++ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:1] UTF8String], NULL); ++ // For debugging - enable verbose mode. ++ // config.verbose = 1; ++ ++ NSLog(@"Pre-initializing Python runtime..."); ++ status = Py_PreInitialize(&preconfig); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } + ++ // Set the home for the Python interpreter ++ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; ++ NSLog(@"PythonHome: %@", python_home); ++ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); ++ status = PyConfig_SetString(&config, &config.home, wtmp_str); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ PyMem_RawFree(wtmp_str); + -+async def log_stream_task(initial_devices, lock): -+ # Wait up to 5 minutes for the build to complete and the simulator to boot. -+ udid = await asyncio.wait_for(find_device(initial_devices, lock), 5 * 60) ++ // 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; ++ } + -+ # Stream the visionOS 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 "/visionOSTestbedTests.xctest/visionOSTestbedTests"' -+ ' OR senderImagePath ENDSWITH "/Python.framework/Python"' -+ ), -+ ] ++ 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; ++ } + -+ 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 visionOS 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() ++ NSLog(@"Initializing Python runtime..."); ++ status = Py_InitializeFromConfig(&config); ++ if (PyStatus_Exception(status)) { ++ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg); ++ PyConfig_Clear(&config); ++ return; ++ } ++ ++ // Add app_packages as a site directory. This both adds to sys.path, ++ // and ensures that any .pth files in that directory will be executed. ++ site_module = PyImport_ImportModule("site"); ++ if (site_module == NULL) { ++ XCTFail(@"Could not import site module"); ++ return; ++ } ++ ++ site_addsitedir_attr = PyObject_GetAttrString(site_module, "addsitedir"); ++ if (site_addsitedir_attr == NULL || !PyCallable_Check(site_addsitedir_attr)) { ++ XCTFail(@"Could not access site.addsitedir"); ++ return; ++ } ++ ++ path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil]; ++ NSLog(@"App packages path: %@", path); ++ wtmp_str = Py_DecodeLocale([path UTF8String], NULL); ++ app_packages_path = PyUnicode_FromWideChar(wtmp_str, wcslen(wtmp_str)); ++ if (app_packages_path == NULL) { ++ XCTFail(@"Could not convert app_packages path to unicode"); ++ return; ++ } ++ PyMem_RawFree(wtmp_str); ++ ++ method_args = Py_BuildValue("(O)", app_packages_path); ++ if (method_args == NULL) { ++ XCTFail(@"Could not create arguments for site.addsitedir"); ++ return; ++ } ++ ++ result = PyObject_CallObject(site_addsitedir_attr, method_args); ++ if (result == NULL) { ++ XCTFail(@"Could not add app_packages directory using site.addsitedir"); ++ return; ++ } ++ ++ // Add test code to sys.path ++ sys_module = PyImport_ImportModule("sys"); ++ if (sys_module == NULL) { ++ XCTFail(@"Could not import sys module"); ++ return; ++ } + ++ sys_path_attr = PyObject_GetAttrString(sys_module, "path"); ++ if (sys_path_attr == NULL) { ++ XCTFail(@"Could not access sys.path"); ++ return; ++ } ++ ++ path = [NSString stringWithFormat:@"%@/app", resourcePath, nil]; ++ NSLog(@"App path: %@", path); ++ wtmp_str = Py_DecodeLocale([path UTF8String], NULL); ++ failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String])); ++ if (failed) { ++ XCTFail(@"Unable to add app to sys.path"); ++ return; ++ } ++ PyMem_RawFree(wtmp_str); ++ ++ // Ensure the working directory is the app folder. ++ chdir([path UTF8String]); ++ ++ // Start the test suite. Print a separator to differentiate Python startup logs from app logs ++ NSLog(@"---------------------------------------------------------------------------"); ++ ++ exit_code = Py_RunMain(); ++ XCTAssertEqual(exit_code, 0, @"Test suite did not pass"); ++ ++ NSLog(@"---------------------------------------------------------------------------"); ++ ++ Py_Finalize(); ++} ++ ++ ++@end +diff --git a/Platforms/Apple/testbed/__main__.py b/Platforms/Apple/testbed/__main__.py +new file mode 100644 +index 00000000000..3f5b570a195 +--- /dev/null ++++ b/Platforms/Apple/testbed/__main__.py +@@ -0,0 +1,456 @@ ++import argparse ++import json ++import os ++import re ++import shlex ++import shutil ++import subprocess ++import sys ++from pathlib import Path ++ ++TEST_SLICES = { ++ "iOS": "ios-arm64_x86_64-simulator", ++ "tvOS": "tvos-arm64_x86_64-simulator", ++ "visionOS": "xros-arm64-simulator", ++ "watchOS": "watchos-arm64_x86_64-simulator", ++} ++ ++DECODE_ARGS = ("UTF-8", "backslashreplace") ++ ++# The system log prefixes each line: ++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ... ++# 2025-01-17 16:14:29.093742+0800 iOSTestbed[23987:1fd393b4] ... ++ ++LOG_PREFIX_REGEX = re.compile( ++ r"^\d{4}-\d{2}-\d{2}" # YYYY-MM-DD ++ r"\s+\d+:\d{2}:\d{2}\.\d+\+\d{4}" # HH:MM:SS.ssssss+ZZZZ ++ r"\s+.*Testbed\[\d+:\w+\] " # Process/thread ID ++) ++ ++ ++# Select a simulator device to use. ++def select_simulator_device(platform): ++ # List the testing simulators, in JSON format ++ raw_json = subprocess.check_output(["xcrun", "simctl", "list", "-j"]) ++ json_data = json.loads(raw_json) ++ ++ if platform == "iOS": ++ # Any iOS device will do; we'll look for "SE" devices - but the name ++ # isn't consistent over time. Older Xcode versions will use "iPhone SE ++ # (Nth generation)"; As of 2025, they've started using "iPhone 16e". ++ # ++ # When Xcode is updated after a new release, new devices will be ++ # available and old ones will be dropped from the set available on the ++ # latest iOS version. Select the one with the highest minimum runtime ++ # version - this is an indicator of the "newest" released device, which ++ # should always be supported on the "most recent" iOS version. ++ se_simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "iPhone" ++ and ( ++ ( ++ "iPhone " in devicetype["name"] ++ and devicetype["name"].endswith("e") ++ ) ++ or "iPhone SE " in devicetype["name"] ++ ) ++ ) ++ simulator = se_simulators[-1][1] ++ elif platform == "tvOS": ++ # Find the most recent tvOS release. ++ simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple TV" ++ ) ++ simulator = simulators[-1][1] ++ elif platform == "visionOS": ++ # Find the most recent visionOS release. ++ simulators = sorted( ++ (devicetype["minRuntimeVersion"], devicetype["name"]) ++ for devicetype in json_data["devicetypes"] ++ if devicetype["productFamily"] == "Apple Vision" ++ ) ++ simulator = simulators[-1][1] ++ elif platform == "watchOS": ++ raise NotImplementedError("Don't know how to launch watchOS (yet)") ++ else: ++ raise ValueError(f"Unknown platform {platform}") ++ ++ return simulator + -+async def xcode_test(location, simulator, verbose): -+ # Run the test suite on the named simulator -+ print("Starting xcodebuild...", flush=True) ++ ++def xcode_test(location: Path, platform: str, simulator: str, verbose: bool): ++ # Build and run the test suite on the named simulator. + args = [ -+ "xcodebuild", -+ "test", + "-project", -+ str(location / "visionOSTestbed.xcodeproj"), ++ str(location / f"{platform}Testbed.xcodeproj"), + "-scheme", -+ "visionOSTestbed", ++ f"{platform}Testbed", + "-destination", -+ f"platform=visionOS Simulator,name={simulator}", -+ "-resultBundlePath", -+ str(location / f"{datetime.now():%Y%m%d-%H%M%S}.xcresult"), ++ f"platform={platform} Simulator,name={simulator}", + "-derivedDataPath", + str(location / "DerivedData"), + ] -+ if not verbose: -+ args += ["-quiet"] ++ verbosity_args = [] if verbose else ["-quiet"] ++ ++ print("Building test project...") ++ subprocess.run( ++ ["xcodebuild", "build-for-testing"] + args + verbosity_args, ++ check=True, ++ ) + -+ async with async_process( -+ *args, ++ # 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, -+ ) as process: -+ while line := (await process.stdout.readline()).decode(*DECODE_ARGS): -+ sys.stdout.write(line) -+ sys.stdout.flush() ++ env=test_env, ++ ) ++ while line := (process.stdout.readline()).decode(*DECODE_ARGS): ++ # Strip the timestamp/process prefix from each log line ++ line = LOG_PREFIX_REGEX.sub("", line) ++ sys.stdout.write(line) ++ sys.stdout.flush() ++ ++ status = process.wait(timeout=5) ++ exit(status) ++ + -+ status = await asyncio.wait_for(process.wait(), timeout=1) -+ exit(status) ++def copy(src, tgt): ++ """An all-purpose copy. ++ ++ If src is a file, it is copied. If src is a symlink, it is copied *as a ++ symlink*. If src is a directory, the full tree is duplicated, with symlinks ++ being preserved. ++ """ ++ if src.is_file() or src.is_symlink(): ++ shutil.copyfile(src, tgt, follow_symlinks=False) ++ else: ++ shutil.copytree(src, tgt, symlinks=True) + + +def clone_testbed( + source: Path, + target: Path, + framework: Path, ++ platform: str, + apps: list[Path], +) -> None: + if target.exists(): @@ -2535,11 +3157,11 @@ index c3e261ecd9e..26ef7a95de4 100644 + + if framework is None: + if not ( -+ source / "Python.xcframework/xros-arm64-simulator/bin" ++ source / "Python.xcframework" / TEST_SLICES[platform] / "bin" + ).is_dir(): + print( + f"The testbed being cloned ({source}) does not contain " -+ f"a simulator framework. Re-run with --framework" ++ "a framework with slices. Re-run with --framework" + ) + sys.exit(11) + else: @@ -2557,77 +3179,90 @@ index c3e261ecd9e..26ef7a95de4 100644 + sys.exit(13) + + print("Cloning testbed project:") -+ print(f" Cloning {source}...", end="", flush=True) -+ shutil.copytree(source, target, symlinks=True) ++ 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" -+ sim_framework_path = xc_framework_path / "xros-arm64-simulator" ++ test_framework_path = xc_framework_path / TEST_SLICES[platform] + 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) ++ print(" Installing XCFramework...", end="") + xc_framework_path.symlink_to( + framework.relative_to(xc_framework_path.parent, walk_up=True) + ) + print(" done") + else: -+ print(" Installing simulator framework...", end="", flush=True) -+ if sim_framework_path.is_dir(): -+ shutil.rmtree(sim_framework_path) ++ 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: -+ sim_framework_path.unlink(missing_ok=True) -+ sim_framework_path.symlink_to( -+ framework.relative_to(sim_framework_path.parent, walk_up=True) ++ test_framework_path.unlink(missing_ok=True) ++ test_framework_path.symlink_to( ++ framework.relative_to(test_framework_path.parent, walk_up=True) + ) + print(" done") + else: ++ copy(orig_xc_framework_path, xc_framework_path) ++ + if ( + xc_framework_path.is_symlink() + and not xc_framework_path.readlink().is_absolute() + ): + # XCFramework is a relative symlink. Rewrite the symlink relative + # to the new location. -+ print(" Rewriting symlink to XCframework...", end="", flush=True) -+ orig_xc_framework_path = ( -+ source -+ / xc_framework_path.readlink() ++ 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( -+ orig_xc_framework_path.relative_to( ++ resolved_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() ++ test_framework_path.is_symlink() ++ and not test_framework_path.readlink().is_absolute() + ): -+ print(" Rewriting symlink to simulator framework...", end="", flush=True) ++ print(" Rewriting symlink to simulator framework...", end="") + # 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() ++ orig_test_framework_path = ( ++ source / "Python.XCframework" / test_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 ++ test_framework_path.unlink() ++ test_framework_path.symlink_to( ++ orig_test_framework_path.relative_to( ++ test_framework_path.parent, walk_up=True + ) + ) + print(" done") + else: -+ print(" Using pre-existing visionOS framework.") ++ print(" Using pre-existing Python framework.") + + for app_src in apps: -+ print(f" Installing app {app_src.name!r}...", end="", flush=True) -+ app_target = target / f"visionOSTestbed/app/{app_src.name}" ++ 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) @@ -2636,65 +3271,65 @@ index c3e261ecd9e..26ef7a95de4 100644 + print(f"Successfully cloned testbed: {target.resolve()}") + + -+def update_plist(testbed_path, args): -+ # Add the test runner arguments to the testbed's Info.plist file. -+ info_plist = testbed_path / "visionOSTestbed" / "visionOSTestbed-Info.plist" -+ with info_plist.open("rb") as f: -+ info = plistlib.load(f) ++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) + -+ info["TestArgs"] = args ++ test_plan["defaultOptions"]["commandLineArgumentEntries"] = [ ++ {"argument": shlex.quote(arg)} for arg in args ++ ] + -+ with info_plist.open("wb") as f: -+ plistlib.dump(info, f) ++ with test_plan_path.open("w", encoding="utf-8") as f: ++ json.dump(test_plan, f, indent=2) + + -+async def run_testbed(simulator: str, args: list[str], verbose: bool=False): ++def run_testbed( ++ platform: str, ++ simulator: str | None, ++ args: list[str], ++ verbose: bool = False, ++): + location = Path(__file__).parent -+ print("Updating plist...", end="", flush=True) -+ update_plist(location, args) -+ print(" done.", flush=True) -+ -+ # We need to get an exclusive lock on simulator creation, to avoid issues -+ # with multiple simulators starting and being unable to tell which -+ # simulator is due to which testbed instance. See -+ # https://github.com/python/cpython/issues/130294 for details. Wait up to -+ # 10 minutes for a simulator to boot. -+ print("Obtaining lock on simulator creation...", flush=True) -+ simulator_lock = SimulatorLock(timeout=10*60) -+ await simulator_lock.acquire() -+ print("Simulator lock acquired.", flush=True) -+ -+ # Get the list of devices that are booted at the start of the test run. -+ # The simulator started by the test suite will be detected as the new -+ # entry that appears on the device list. -+ initial_devices = await list_devices() -+ -+ try: -+ async with asyncio.TaskGroup() as tg: -+ tg.create_task(log_stream_task(initial_devices, simulator_lock)) -+ tg.create_task(xcode_test(location, simulator=simulator, verbose=verbose)) -+ except* MySystemExit as e: -+ raise SystemExit(*e.exceptions[0].args) from None -+ except* subprocess.CalledProcessError as e: -+ # Extract it from the ExceptionGroup so it can be handled by `main`. -+ raise e.exceptions[0] -+ finally: -+ simulator_lock.release() ++ print("Updating test plan...", end="") ++ update_test_plan(location, platform, args) ++ print(" done.") ++ ++ if simulator is None: ++ simulator = select_simulator_device(platform) ++ print(f"Running test on {simulator}") ++ ++ xcode_test( ++ location, ++ platform=platform, ++ simulator=simulator, ++ verbose=verbose, ++ ) + + +def main(): ++ # Look for directories like `iOSTestbed` as an indicator of the platforms ++ # that the testbed folder supports. The original source testbed can support ++ # many platforms, but when cloned, only one platform is preserved. ++ available_platforms = [ ++ platform ++ for platform in ["iOS", "tvOS", "visionOS", "watchOS"] ++ if (Path(__file__).parent / f"{platform}Testbed").is_dir() ++ ] ++ + parser = argparse.ArgumentParser( + description=( -+ "Manages the process of testing a Python project in the visionOS simulator." ++ "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 an visionOS Python framework and" ++ "Clone the testbed project, copying in a Python framework and" + "any specified application code." + ), + help="Clone a testbed project to a new location.", @@ -2707,6 +3342,13 @@ index c3e261ecd9e..26ef7a95de4 100644 + ), + ) + 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", @@ -2720,21 +3362,37 @@ index c3e261ecd9e..26ef7a95de4 100644 + + run = subcommands.add_parser( + "run", -+ usage="%(prog)s [-h] [--simulator SIMULATOR] -- [ ...]", ++ usage=( ++ "%(prog)s [-h] [--simulator SIMULATOR] -- " ++ " [ ...]" ++ ), + description=( + "Run a testbed project. The arguments provided after `--` will be " -+ "passed to the running visionOS process as if they were arguments to " ++ "passed to the running test process as if they were arguments to " + "`python -m`." + ), + help="Run a testbed project", + ) + 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", -+ default="Apple Vision Pro", -+ help="The name of the simulator to use (default: 'Apple Vision Pro')", ++ 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", ++ "-v", ++ "--verbose", + action="store_true", + help="Enable verbose output", + ) @@ -2753,30 +3411,38 @@ index c3e261ecd9e..26ef7a95de4 100644 + clone_testbed( + source=Path(__file__).parent.resolve(), + target=Path(context.location).resolve(), -+ framework=Path(context.framework).resolve() if context.framework else None, ++ 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/xros-arm64-simulator/bin" ++ Path(__file__).parent ++ / "Python.xcframework" ++ / TEST_SLICES[context.platform] ++ / "bin" + ).is_dir(): + print( -+ f"Testbed does not contain a compiled visionOS framework. Use " -+ f"`python {sys.argv[0]} clone ...` to create a runnable " -+ f"clone of this testbed." ++ "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) + -+ asyncio.run( -+ run_testbed( -+ simulator=context.simulator, -+ verbose=context.verbose, -+ args=test_args, -+ ) ++ run_testbed( ++ platform=context.platform, ++ simulator=context.simulator, ++ verbose=context.verbose, ++ args=test_args, + ) + else: -+ print(f"Must specify test arguments (e.g., {sys.argv[0]} run -- test)") ++ print( ++ "Must specify test arguments " ++ f"(e.g., {sys.argv[0]} run -- test)" ++ ) + print() + parser.print_help(sys.stderr) + sys.exit(21) @@ -2786,10 +3452,18 @@ index c3e261ecd9e..26ef7a95de4 100644 + + +if __name__ == "__main__": ++ # Under the buildbot, stdout is not a TTY, but we must still flush after ++ # every line to make sure our output appears in the correct order relative ++ # to the output of our subprocesses. ++ for stream in [sys.stdout, sys.stderr]: ++ stream.reconfigure(line_buffering=True) + main() +diff --git a/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +new file mode 100644 +index 00000000000..f8835a3bc58 --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed.xcodeproj/project.pbxproj -@@ -0,0 +1,581 @@ ++++ b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,557 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; @@ -2801,15 +3475,15 @@ index c3e261ecd9e..26ef7a95de4 100644 +/* 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 /* visionOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* visionOSTestbedTests.m */; }; -+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; }; ++ 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 */; }; -+ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; -+ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ @@ -2829,7 +3503,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + dstPath = ""; + dstSubfolderSpec = 10; + files = ( -+ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */, ++ 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; @@ -2840,7 +3514,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + dstPath = ""; + dstSubfolderSpec = 10; + files = ( -+ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */, ++ 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; @@ -2848,18 +3522,19 @@ index c3e261ecd9e..26ef7a95de4 100644 +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ -+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = visionOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 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 /* visionOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = visionOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -+ 607A66312B0EFA3A0010BFC8 /* visionOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = visionOSTestbedTests.m; sourceTree = ""; }; -+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = ""; }; -+ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "visionOSTestbed-Info.plist"; 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 = ""; }; -+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = iOSTestbed.xctestplan; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ @@ -2867,7 +3542,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( -+ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; @@ -2875,7 +3550,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( -+ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; @@ -2885,9 +3560,10 @@ index c3e261ecd9e..26ef7a95de4 100644 + 607A66092B0EFA380010BFC8 = { + isa = PBXGroup; + children = ( -+ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */, -+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, -+ 607A66302B0EFA3A0010BFC8 /* visionOSTestbedTests */, ++ 60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */, ++ 607A664A2B0EFB310010BFC8 /* Python.xcframework */, ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */, ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, + 607A66132B0EFA380010BFC8 /* Products */, + 607A664F2B0EFFE00010BFC8 /* Frameworks */, + ); @@ -2896,33 +3572,33 @@ index c3e261ecd9e..26ef7a95de4 100644 + 607A66132B0EFA380010BFC8 /* Products */ = { + isa = PBXGroup; + children = ( -+ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */, -+ 607A662D2B0EFA3A0010BFC8 /* visionOSTestbedTests.xctest */, ++ 607A66122B0EFA380010BFC8 /* iOSTestbed.app */, ++ 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; -+ 607A66142B0EFA380010BFC8 /* visionOSTestbed */ = { ++ 607A66142B0EFA380010BFC8 /* iOSTestbed */ = { + isa = PBXGroup; + children = ( + 608619552CB7819B00F46182 /* app */, + 608619532CB77BA900F46182 /* app_packages */, -+ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */, -+ 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */, ++ 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */, + 607A66152B0EFA380010BFC8 /* AppDelegate.h */, + 607A66162B0EFA380010BFC8 /* AppDelegate.m */, + 607A66212B0EFA390010BFC8 /* Assets.xcassets */, ++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */, + 607A66272B0EFA390010BFC8 /* main.m */, + ); -+ path = visionOSTestbed; ++ path = iOSTestbed; + sourceTree = ""; + }; -+ 607A66302B0EFA3A0010BFC8 /* visionOSTestbedTests */ = { ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { + isa = PBXGroup; + children = ( -+ 607A66312B0EFA3A0010BFC8 /* visionOSTestbedTests.m */, ++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */, + ); -+ path = visionOSTestbedTests; ++ path = TestbedTests; + sourceTree = ""; + }; + 607A664F2B0EFFE00010BFC8 /* Frameworks */ = { @@ -2935,29 +3611,28 @@ index c3e261ecd9e..26ef7a95de4 100644 +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ -+ 607A66112B0EFA380010BFC8 /* visionOSTestbed */ = { ++ 607A66112B0EFA380010BFC8 /* iOSTestbed */ = { + isa = PBXNativeTarget; -+ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */; ++ 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 */, ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */, + 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); -+ name = visionOSTestbed; ++ name = iOSTestbed; + productName = iOSTestbed; -+ productReference = 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */; ++ productReference = 607A66122B0EFA380010BFC8 /* iOSTestbed.app */; + productType = "com.apple.product-type.application"; + }; -+ 607A662C2B0EFA3A0010BFC8 /* visionOSTestbedTests */ = { ++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */ = { + isa = PBXNativeTarget; -+ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbedTests" */; ++ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */; + buildPhases = ( + 607A66292B0EFA3A0010BFC8 /* Sources */, + 607A662A2B0EFA3A0010BFC8 /* Frameworks */, @@ -2969,9 +3644,9 @@ index c3e261ecd9e..26ef7a95de4 100644 + dependencies = ( + 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, + ); -+ name = visionOSTestbedTests; ++ name = iOSTestbedTests; + productName = iOSTestbedTests; -+ productReference = 607A662D2B0EFA3A0010BFC8 /* visionOSTestbedTests.xctest */; ++ productReference = 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ @@ -2992,7 +3667,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + }; + }; + }; -+ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */; ++ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; @@ -3005,8 +3680,8 @@ index c3e261ecd9e..26ef7a95de4 100644 + projectDirPath = ""; + projectRoot = ""; + targets = ( -+ 607A66112B0EFA380010BFC8 /* visionOSTestbed */, -+ 607A662C2B0EFA3A0010BFC8 /* visionOSTestbedTests */, ++ 607A66112B0EFA380010BFC8 /* iOSTestbed */, ++ 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */, + ); + }; +/* End PBXProject section */ @@ -3016,7 +3691,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( -+ 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */, ++ 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */, + 608619562CB7819B00F46182 /* app in Resources */, + 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, + 608619542CB77BA900F46182 /* app_packages in Resources */, @@ -3033,7 +3708,7 @@ index c3e261ecd9e..26ef7a95de4 100644 +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ -+ 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = { ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; @@ -3043,45 +3718,25 @@ index c3e261ecd9e..26ef7a95de4 100644 + ); + inputPaths = ( + ); -+ name = "Install Target Specific Python Standard Library"; ++ name = "Process Python libraries"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; -+ shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-xrsimulator\" ]; then\n echo \"Installing Python modules for xrOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/xros-arm64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for xrOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/xros-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n"; ++ shellScript = "set -e\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\ninstall_python Python.xcframework app app_packages\n"; + showEnvVarsInLog = 0; + }; -+ 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = { -+ isa = PBXShellScriptBuildPhase; -+ alwaysOutOfDate = 1; ++/* End PBXShellScriptBuildPhase section */ ++ ++/* Begin PBXSourcesBuildPhase section */ ++ 607A660E2B0EFA380010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; + 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 */ -+ -+/* Begin PBXSourcesBuildPhase section */ -+ 607A660E2B0EFA380010BFC8 /* Sources */ = { -+ isa = PBXSourcesBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, -+ 607A66282B0EFA390010BFC8 /* main.m in Sources */, ++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, ++ 607A66282B0EFA390010BFC8 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; @@ -3089,7 +3744,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( -+ 607A66322B0EFA3A0010BFC8 /* visionOSTestbedTests.m in Sources */, ++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; @@ -3098,11 +3753,22 @@ index c3e261ecd9e..26ef7a95de4 100644 +/* Begin PBXTargetDependency section */ + 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; -+ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; ++ target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; + targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + ++/* Begin PBXVariantGroup section */ ++ 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = { ++ isa = PBXVariantGroup; ++ children = ( ++ 607A66242B0EFA390010BFC8 /* Base */, ++ ); ++ name = LaunchScreen.storyboard; ++ sourceTree = ""; ++ }; ++/* End PBXVariantGroup section */ ++ +/* Begin XCBuildConfiguration section */ + 607A663F2B0EFA3A0010BFC8 /* Debug */ = { + isa = XCBuildConfiguration; @@ -3156,12 +3822,12 @@ index c3e261ecd9e..26ef7a95de4 100644 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ 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 = xros; ++ SDKROOT = iphoneos; + }; + name = Debug; + }; @@ -3211,11 +3877,11 @@ index c3e261ecd9e..26ef7a95de4 100644 + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; -+ SDKROOT = xros; ++ SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; @@ -3231,26 +3897,22 @@ index c3e261ecd9e..26ef7a95de4 100644 + DEVELOPMENT_TEAM = ""; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; ++ 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.visionOSTestbed; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 7; -+ XROS_DEPLOYMENT_TARGET = 2.0; ++ TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; @@ -3266,26 +3928,22 @@ index c3e261ecd9e..26ef7a95de4 100644 + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; ++ 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.visionOSTestbed; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; -+ TARGETED_DEVICE_FAMILY = 7; -+ XROS_DEPLOYMENT_TARGET = 2.0; ++ TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; @@ -3299,17 +3957,13 @@ index c3e261ecd9e..26ef7a95de4 100644 + DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbedTests; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 7; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; + }; + name = Debug; + }; @@ -3323,24 +3977,20 @@ index c3e261ecd9e..26ef7a95de4 100644 + DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; -+ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; -+ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbedTests; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; -+ SUPPORTED_PLATFORMS = "xros xrsimulator"; -+ SUPPORTS_MACCATALYST = NO; -+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; -+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; -+ TARGETED_DEVICE_FAMILY = 7; -+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ TARGETED_DEVICE_FAMILY = "1,2"; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ -+ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */ = { ++ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607A663F2B0EFA3A0010BFC8 /* Debug */, @@ -3349,7 +3999,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; -+ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */ = { ++ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607A66422B0EFA3A0010BFC8 /* Debug */, @@ -3358,7 +4008,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; -+ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbedTests" */ = { ++ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607A66452B0EFA3A0010BFC8 /* Debug */, @@ -3371,12 +4021,170 @@ index c3e261ecd9e..26ef7a95de4 100644 + }; + rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; +} +diff --git a/Platforms/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme +new file mode 100644 +index 00000000000..3c330a4152b +--- /dev/null ++++ b/Platforms/Apple/testbed/iOSTestbed.xcodeproj/xcshareddata/xcschemes/iOSTestbed.xcscheme +@@ -0,0 +1,97 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/iOSTestbed.xctestplan b/Platforms/Apple/testbed/iOSTestbed.xctestplan +new file mode 100644 +index 00000000000..0c4ab9eb2ba +--- /dev/null ++++ b/Platforms/Apple/testbed/iOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", ++ "name" : "Test Scheme Action", ++ "options" : { ++ ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:iOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "iOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:iOSTestbed.xcodeproj", ++ "identifier" : "607A662C2B0EFA3A0010BFC8", ++ "name" : "iOSTestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +diff --git a/Platforms/Apple/testbed/iOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.h +new file mode 100644 +index 00000000000..f695b3b5efc --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/AppDelegate.h ++++ b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.h @@ -0,0 +1,11 @@ +// +// AppDelegate.h -+// visionOSTestbed ++// iOSTestbed +// + +#import @@ -3385,12 +4193,15 @@ index c3e261ecd9e..26ef7a95de4 100644 + + +@end +diff --git a/Platforms/Apple/testbed/iOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..e5085399d0c --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/AppDelegate.m ++++ b/Platforms/Apple/testbed/iOSTestbed/AppDelegate.m @@ -0,0 +1,19 @@ +// +// AppDelegate.m -+// visionOSTestbed ++// iOSTestbed +// + +#import "AppDelegate.h" @@ -3407,8 +4218,11 @@ index c3e261ecd9e..26ef7a95de4 100644 +} + +@end +diff --git a/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +new file mode 100644 +index 00000000000..eb878970081 --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json ++++ b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ @@ -3421,8 +4235,11 @@ index c3e261ecd9e..26ef7a95de4 100644 + "version" : 1 + } +} +diff --git a/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +new file mode 100644 +index 00000000000..13613e3ee1a --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json ++++ b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ @@ -3437,8 +4254,11 @@ index c3e261ecd9e..26ef7a95de4 100644 + "version" : 1 + } +} +diff --git a/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json +new file mode 100644 +index 00000000000..73c00596a7f --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/Assets.xcassets/Contents.json ++++ b/Platforms/Apple/testbed/iOSTestbed/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { @@ -3446,81 +4266,53 @@ index c3e261ecd9e..26ef7a95de4 100644 + "version" : 1 + } +} +diff --git a/Platforms/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard b/Platforms/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard +new file mode 100644 +index 00000000000..5daafe73a86 +--- /dev/null ++++ b/Platforms/Apple/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,9 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/iOSTestbed/app/README b/Platforms/Apple/testbed/iOSTestbed/app/README +new file mode 100644 +index 00000000000..46c0e8e2a29 --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/app/README ++++ b/Platforms/Apple/testbed/iOSTestbed/app/README @@ -0,0 +1,7 @@ +This folder can contain any Python application code. + +During the build, any binary modules found in this folder will be processed into -+iOS Framework form. ++Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH, and will be the +working directory for the test suite. +diff --git a/Platforms/Apple/testbed/iOSTestbed/app_packages/README b/Platforms/Apple/testbed/iOSTestbed/app_packages/README +new file mode 100644 +index 00000000000..02c2beccfbd --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/app_packages/README ++++ b/Platforms/Apple/testbed/iOSTestbed/app_packages/README @@ -0,0 +1,7 @@ +This folder can be a target for installing any Python dependencies needed by the +test suite. + +During the build, any binary modules found in this folder will be processed into -+iOS Framework form. ++Framework form. + +When the test suite runs, this folder will be on the PYTHONPATH. +diff --git a/Platforms/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist b/Platforms/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist +new file mode 100644 +index 00000000000..fea45e1fad6 --- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/dylib-Info-template.plist -@@ -0,0 +1,30 @@ -+ -+ -+ -+ -+ CFBundleDevelopmentRegion -+ en -+ CFBundleInfoDictionaryVersion -+ 6.0 -+ CFBundleExecutable -+ -+ CFBundleIdentifier -+ -+ CFBundlePackageType -+ FMWK -+ CFBundleShortVersionString -+ 1.0 -+ CFBundleSupportedPlatforms -+ -+ XROS -+ -+ CFBundleVersion -+ 1 -+ MinimumOSVersion -+ 2.0 -+ UIDeviceFamily -+ -+ 7 -+ -+ -+ ---- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/main.m -@@ -0,0 +1,16 @@ -+// -+// main.m -+// visionOSTestbed -+// -+ -+#import -+#import "AppDelegate.h" -+ -+int main(int argc, char * argv[]) { -+ NSString * appDelegateClassName; -+ @autoreleasepool { -+ appDelegateClassName = NSStringFromClass([AppDelegate class]); -+ -+ return UIApplicationMain(argc, argv, nil, appDelegateClassName); -+ } -+} ---- /dev/null -+++ b/visionOS/testbed/visionOSTestbed/visionOSTestbed-Info.plist -@@ -0,0 +1,56 @@ ++++ b/Platforms/Apple/testbed/iOSTestbed/iOSTestbed-Info.plist +@@ -0,0 +1,52 @@ + + + @@ -3532,7 +4324,7 @@ index c3e261ecd9e..26ef7a95de4 100644 + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier -+ org.python.visionOSTestbed ++ org.python.iOSTestbed + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName @@ -3545,23 +4337,12 @@ index c3e261ecd9e..26ef7a95de4 100644 + ???? + CFBundleVersion + 1 -+ TestArgs -+ -+ test -+ -uall -+ --single-process -+ --rerun -+ -W -+ -+ UIApplicationSceneManifest -+ -+ UIApplicationSupportsMultipleScenes -+ -+ UISceneConfigurations -+ -+ ++ LSRequiresIPhoneOS ++ + UIRequiresFullScreen + ++ UILaunchStoryboardName ++ Launch Screen + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait @@ -3575,421 +4356,3848 @@ index c3e261ecd9e..26ef7a95de4 100644 + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ + + +diff --git a/Platforms/Apple/testbed/iOSTestbed/main.m b/Platforms/Apple/testbed/iOSTestbed/main.m +new file mode 100644 +index 00000000000..e32bd78c9b4 --- /dev/null -+++ b/visionOS/testbed/visionOSTestbedTests/visionOSTestbedTests.m -@@ -0,0 +1,162 @@ -+#import -+#import -+ -+@interface visionOSTestbedTests : XCTestCase ++++ b/Platforms/Apple/testbed/iOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// iOSTestbed ++// + -+@end ++#import ++#import "AppDelegate.h" + -+@implementation visionOSTestbedTests ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); + ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj +new file mode 100644 +index 00000000000..5ddfba882f4 +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,505 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 77; ++ objects = { + -+- (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; ++/* 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 */ + -+ NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; ++/* Begin PBXContainerItemProxy section */ ++ EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = EE989E462DCD6E780036B268 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = EE989E4D2DCD6E780036B268; ++ remoteInfo = tvOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ + -+ // 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); ++/* 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 */ + -+ // 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] = "visionOSTestbed"; -+ for (int i = 1; i < [test_args count]; i++) { -+ argv[i] = [[test_args objectAtIndex:i] UTF8String]; -+ } -+ NSLog(@"Test command: %@", test_args); ++/* 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 */ + -+ // Generate an isolated Python configuration. -+ NSLog(@"Configuring isolated Python..."); -+ PyPreConfig_InitIsolatedConfig(&preconfig); -+ PyConfig_InitIsolatedConfig(&config); ++/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ++ 6077B37F2E81892A00E3D6A3 /* Exceptions for "tvOSTestbed" folder in "tvOSTestbed" target */ = { ++ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; ++ membershipExceptions = ( ++ "tvOSTestbed-Info.plist", ++ ); ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ }; ++/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + -+ // Configure the Python interpreter: -+ // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. -+ // See https://docs.python.org/3/library/os.html#python-utf-8-mode. -+ preconfig.utf8_mode = 1; -+ // Use the system logger for stdout/err -+ config.use_system_logger = 1; -+ // Don't buffer stdio. We want output to appears in the log immediately -+ config.buffered_stdio = 0; -+ // Don't write bytecode; we can't modify the app bundle -+ // after it has been signed. -+ config.write_bytecode = 0; -+ // Ensure that signal handlers are installed -+ config.install_signal_handlers = 1; -+ // Run the test module. -+ config.run_module = Py_DecodeLocale([[test_args objectAtIndex:0] UTF8String], NULL); -+ // For debugging - enable verbose mode. -+ // config.verbose = 1; ++/* 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 */ + -+ NSLog(@"Pre-initializing Python runtime..."); -+ status = Py_PreInitialize(&preconfig); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } -+ -+ // Set the home for the Python interpreter -+ python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; -+ NSLog(@"PythonHome: %@", python_home); -+ wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); -+ status = PyConfig_SetString(&config, &config.home, wtmp_str); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } -+ PyMem_RawFree(wtmp_str); -+ -+ // Read the site config -+ status = PyConfig_Read(&config); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to read site config: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } -+ -+ NSLog(@"Configure argc/argv..."); -+ status = PyConfig_SetBytesArgv(&config, [test_args count], (char**) argv); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to configure argc/argv: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } -+ -+ NSLog(@"Initializing Python runtime..."); -+ status = Py_InitializeFromConfig(&config); -+ if (PyStatus_Exception(status)) { -+ XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg); -+ PyConfig_Clear(&config); -+ return; -+ } -+ -+ 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; -+ } -+ -+ // 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); -+ -+ 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/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`` ++/* 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 */ + -+ Specify the name for the python framework, defaults to ``Python``. ++/* 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 */ + -+Building and using Python on watchOS -+==================================== ++/* 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 */ + -+ABIs and Architectures -+---------------------- ++/* Begin PBXResourcesBuildPhase section */ ++ EE989E4C2DCD6E780036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E632DCD6E7A0036B268 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ + -+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. ++/* Begin PBXShellScriptBuildPhase section */ ++ EE7C8A222DCD70F4003206DB /* Process Python libraries */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Process Python libraries"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ }; ++/* End PBXShellScriptBuildPhase section */ + -+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.) ++/* Begin PBXSourcesBuildPhase section */ ++ EE989E4A2DCD6E780036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ EE989E612DCD6E7A0036B268 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ + -+To support multiple CPU architectures on a single platform, Apple uses a "fat -+binary" format - a single physical file that contains support for multiple -+architectures. ++/* Begin PBXTargetDependency section */ ++ EE989E672DCD6E7A0036B268 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = EE989E4D2DCD6E780036B268 /* tvOSTestbed */; ++ targetProxy = EE989E662DCD6E7A0036B268 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ + -+How do I build Python for watchOS? -+------------------------------- ++/* 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 */ + -+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: ++/* Begin XCConfigurationList section */ ++ EE989E492DCD6E780036B268 /* Build configuration list for PBXProject "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E772DCD6E7A0036B268 /* Debug */, ++ EE989E782DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E792DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "tvOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7A2DCD6E7A0036B268 /* Debug */, ++ EE989E7B2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ EE989E7C2DCD6E7A0036B268 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ EE989E7D2DCD6E7A0036B268 /* Debug */, ++ EE989E7E2DCD6E7A0036B268 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = EE989E462DCD6E780036B268 /* Project object */; ++} +diff --git a/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme +new file mode 100644 +index 00000000000..c3f3f894a1f +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed.xcodeproj/xcshareddata/xcschemes/tvOSTestbed.xcscheme +@@ -0,0 +1,97 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/tvOSTestbed.xctestplan b/Platforms/Apple/testbed/tvOSTestbed.xctestplan +new file mode 100644 +index 00000000000..f996facc178 +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "F5A95CE4-1ADE-4A6E-A0E1-CDBAE26DF0C5", ++ "name" : "Test Scheme Action", ++ "options" : { + -+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. ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "tvOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:tvOSTestbed.xcodeproj", ++ "identifier" : "EE989E642DCD6E7A0036B268", ++ "name" : "TestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +\ No newline at end of file +diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h +new file mode 100644 +index 00000000000..112c9ed64b8 +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// tvOSTestbed ++// + -+watchOS builds of Python *must* be constructed as framework builds. To support this, -+you must provide the ``--enable-framework`` flag when configuring the build. ++#import + -+The build also requires the use of cross-compilation. The commands for building -+Python for watchOS will look somethign like:: ++@interface AppDelegate : UIResponder + -+ $ ./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: ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..bd91fb2d7d6 +--- /dev/null ++++ b/Platforms/Apple/testbed/tvOSTestbed/AppDelegate.m +@@ -0,0 +1,19 @@ ++// ++// AppDelegate.m ++// tvOSTestbed ++// + -+* ``/path/to/install`` is the location where the final Python.framework will be -+ output. ++#import "AppDelegate.h" + -+* ``--host`` is the architecture and ABI that you want to build, in GNU compiler -+ triple format. This will be one of: ++@interface AppDelegate () + -+ - ``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. ++@end + -+* ``--build`` is the GNU compiler triple for the machine that will be running -+ the compiler. This is one of: ++@implementation AppDelegate + -+ - ``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. ++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ++ return YES; ++} + -+Using a framework-based Python on watchOS -+====================================== ---- /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@ -+ -+ ---- /dev/null -+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/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-simulator "$@" ---- /dev/null -+++ b/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-simulator "$@" ---- /dev/null -+++ b/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator clang -target arm64-apple-watchos-simulator -E "$@" ---- /dev/null -+++ b/watchOS/Resources/bin/arm64_32-apple-watchos-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@" ---- /dev/null -+++ b/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 "$@" ---- /dev/null -+++ b/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 "$@" ---- /dev/null -+++ b/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 -E "$@" ++@end +diff --git a/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard +new file mode 100644 +index 00000000000..660ba53de4f --- /dev/null -+++ b/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar -@@ -0,0 +1,2 @@ -+#!/bin/bash -+xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" ++++ b/Platforms/Apple/testbed/tvOSTestbed/Base.lproj/LaunchScreen.storyboard +@@ -0,0 +1,24 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/tvOSTestbed/app/README b/Platforms/Apple/testbed/tvOSTestbed/app/README +new file mode 100644 +index 00000000000..46c0e8e2a29 --- /dev/null -+++ b/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-simulator "$@" ++++ b/Platforms/Apple/testbed/tvOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. ++ ++During the build, any binary modules found in this folder will be processed into ++Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH, and will be the ++working directory for the test suite. +diff --git a/Platforms/Apple/testbed/tvOSTestbed/app_packages/README b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README +new file mode 100644 +index 00000000000..02c2beccfbd --- /dev/null -+++ b/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-simulator "$@" ++++ b/Platforms/Apple/testbed/tvOSTestbed/app_packages/README +@@ -0,0 +1,7 @@ ++This folder can be a target for installing any Python dependencies needed by the ++test suite. ++ ++During the build, any binary modules found in this folder will be processed into ++Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH. +diff --git a/Platforms/Apple/testbed/tvOSTestbed/main.m b/Platforms/Apple/testbed/tvOSTestbed/main.m +new file mode 100644 +index 00000000000..d5808fbb933 --- /dev/null -+++ b/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-simulator -E "$@" ++++ b/Platforms/Apple/testbed/tvOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// tvOSTestbed ++// ++ ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +diff --git a/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist b/Platforms/Apple/testbed/tvOSTestbed/tvOSTestbed-Info.plist +new file mode 100644 +index 00000000000..f08f6098999 --- /dev/null -+++ b/watchOS/Resources/dylib-Info-template.plist -@@ -0,0 +1,26 @@ ++++ b/Platforms/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 -+ CFBundleSupportedPlatforms -+ -+ watchOS -+ -+ MinimumOSVersion -+ 4.0 ++ CFBundleSignature ++ ???? + CFBundleVersion + 1 ++ LSRequiresIPhoneOS ++ ++ UIRequiresFullScreen ++ ++ UILaunchStoryboardName ++ Launch Screen ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ + + +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj +new file mode 100644 +index 00000000000..0fd5df716b1 --- /dev/null -+++ b/watchOS/Resources/pyconfig.h -@@ -0,0 +1,11 @@ -+#ifdef __arm64__ -+# ifdef __LP64__ -+#include "pyconfig-arm64.h" -+# else -+#include "pyconfig-arm64_32.h" -+# endif -+#endif ++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/project.pbxproj +@@ -0,0 +1,558 @@ ++// !$*UTF8*$! ++{ ++ archiveVersion = 1; ++ classes = { ++ }; ++ objectVersion = 56; ++ objects = { + -+#ifdef __x86_64__ -+#include "pyconfig-x86_64.h" -+#endif ++/* Begin PBXBuildFile section */ ++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; }; ++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; }; ++ 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; }; ++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */; }; ++ 608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; }; ++ 608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; }; ++ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ++ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; ++ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */; }; ++/* End PBXBuildFile section */ ++ ++/* Begin PBXContainerItemProxy section */ ++ 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { ++ isa = PBXContainerItemProxy; ++ containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; ++ proxyType = 1; ++ remoteGlobalIDString = 607A66112B0EFA380010BFC8; ++ remoteInfo = iOSTestbed; ++ }; ++/* End PBXContainerItemProxy section */ ++ ++/* Begin PBXCopyFilesBuildPhase section */ ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ EEB367CF2DADF5D300B9A1D7 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { ++ isa = PBXCopyFilesBuildPhase; ++ buildActionMask = 2147483647; ++ dstPath = ""; ++ dstSubfolderSpec = 10; ++ files = ( ++ EEB367CE2DADF5C900B9A1D7 /* Python.xcframework in Embed Frameworks */, ++ ); ++ name = "Embed Frameworks"; ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXCopyFilesBuildPhase section */ ++ ++/* Begin PBXFileReference section */ ++ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = visionOSTestbed.xctestplan; sourceTree = ""; }; ++ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = visionOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; ++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; ++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; ++ 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; ++ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestbedTests.m; sourceTree = ""; }; ++ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "visionOSTestbed-Info.plist"; sourceTree = ""; }; ++ 608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = ""; }; ++ 608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; }; ++ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; ++/* End PBXFileReference section */ ++ ++/* Begin PBXFrameworksBuildPhase section */ ++ 607A660F2B0EFA380010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EEE9C80D2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { ++ isa = PBXFrameworksBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ EEE9C80E2DAB5ECA0056F8C6 /* Python.xcframework in Frameworks */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXFrameworksBuildPhase section */ ++ ++/* Begin PBXGroup section */ ++ 607A66092B0EFA380010BFC8 = { ++ isa = PBXGroup; ++ children = ( ++ 6077B3D62E82E60C00E3D6A3 /* visionOSTestbed.xctestplan */, ++ EEE9C80C2DAB5ECA0056F8C6 /* Python.xcframework */, ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */, ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */, ++ 607A66132B0EFA380010BFC8 /* Products */, ++ 607A664F2B0EFFE00010BFC8 /* Frameworks */, ++ ); ++ sourceTree = ""; ++ }; ++ 607A66132B0EFA380010BFC8 /* Products */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */, ++ 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */, ++ ); ++ name = Products; ++ sourceTree = ""; ++ }; ++ 607A66142B0EFA380010BFC8 /* visionOSTestbed */ = { ++ isa = PBXGroup; ++ children = ( ++ 608619552CB7819B00F46182 /* app */, ++ 608619532CB77BA900F46182 /* app_packages */, ++ 607A66592B0F08600010BFC8 /* visionOSTestbed-Info.plist */, ++ 607A66152B0EFA380010BFC8 /* AppDelegate.h */, ++ 607A66162B0EFA380010BFC8 /* AppDelegate.m */, ++ 607A66212B0EFA390010BFC8 /* Assets.xcassets */, ++ 607A66272B0EFA390010BFC8 /* main.m */, ++ ); ++ path = visionOSTestbed; ++ sourceTree = ""; ++ }; ++ 607A66302B0EFA3A0010BFC8 /* TestbedTests */ = { ++ isa = PBXGroup; ++ children = ( ++ 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */, ++ ); ++ path = TestbedTests; ++ sourceTree = ""; ++ }; ++ 607A664F2B0EFFE00010BFC8 /* Frameworks */ = { ++ isa = PBXGroup; ++ children = ( ++ ); ++ name = Frameworks; ++ sourceTree = ""; ++ }; ++/* End PBXGroup section */ ++ ++/* Begin PBXNativeTarget section */ ++ 607A66112B0EFA380010BFC8 /* visionOSTestbed */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */; ++ buildPhases = ( ++ 607A660E2B0EFA380010BFC8 /* Sources */, ++ 607A660F2B0EFA380010BFC8 /* Frameworks */, ++ 607A66102B0EFA380010BFC8 /* Resources */, ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */, ++ 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ ); ++ name = visionOSTestbed; ++ productName = iOSTestbed; ++ productReference = 607A66122B0EFA380010BFC8 /* visionOSTestbed.app */; ++ productType = "com.apple.product-type.application"; ++ }; ++ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */ = { ++ isa = PBXNativeTarget; ++ buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */; ++ buildPhases = ( ++ 607A66292B0EFA3A0010BFC8 /* Sources */, ++ 607A662A2B0EFA3A0010BFC8 /* Frameworks */, ++ 607A662B2B0EFA3A0010BFC8 /* Resources */, ++ 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, ++ ); ++ buildRules = ( ++ ); ++ dependencies = ( ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, ++ ); ++ name = TestbedTests; ++ productName = iOSTestbedTests; ++ productReference = 607A662D2B0EFA3A0010BFC8 /* TestbedTests.xctest */; ++ productType = "com.apple.product-type.bundle.unit-test"; ++ }; ++/* End PBXNativeTarget section */ ++ ++/* Begin PBXProject section */ ++ 607A660A2B0EFA380010BFC8 /* Project object */ = { ++ isa = PBXProject; ++ attributes = { ++ BuildIndependentTargetsInParallel = 1; ++ LastUpgradeCheck = 1500; ++ TargetAttributes = { ++ 607A66112B0EFA380010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; ++ }; ++ 607A662C2B0EFA3A0010BFC8 = { ++ CreatedOnToolsVersion = 15.0.1; ++ TestTargetID = 607A66112B0EFA380010BFC8; ++ }; ++ }; ++ }; ++ buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */; ++ compatibilityVersion = "Xcode 14.0"; ++ developmentRegion = en; ++ hasScannedForEncodings = 0; ++ knownRegions = ( ++ en, ++ Base, ++ ); ++ mainGroup = 607A66092B0EFA380010BFC8; ++ productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; ++ projectDirPath = ""; ++ projectRoot = ""; ++ targets = ( ++ 607A66112B0EFA380010BFC8 /* visionOSTestbed */, ++ 607A662C2B0EFA3A0010BFC8 /* TestbedTests */, ++ ); ++ }; ++/* End PBXProject section */ ++ ++/* Begin PBXResourcesBuildPhase section */ ++ 607A66102B0EFA380010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 608619562CB7819B00F46182 /* app in Resources */, ++ 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, ++ 608619542CB77BA900F46182 /* app_packages in Resources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A662B2B0EFA3A0010BFC8 /* Resources */ = { ++ isa = PBXResourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXResourcesBuildPhase section */ ++ ++/* Begin PBXShellScriptBuildPhase section */ ++ 607A66552B0F061D0010BFC8 /* Process Python libraries */ = { ++ isa = PBXShellScriptBuildPhase; ++ alwaysOutOfDate = 1; ++ buildActionMask = 2147483647; ++ files = ( ++ ); ++ inputFileListPaths = ( ++ ); ++ inputPaths = ( ++ ); ++ name = "Process Python libraries"; ++ outputFileListPaths = ( ++ ); ++ outputPaths = ( ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ shellPath = /bin/sh; ++ shellScript = "set -e\n\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\n\ninstall_python Python.xcframework app app_packages\n"; ++ showEnvVarsInLog = 0; ++ }; ++/* End PBXShellScriptBuildPhase section */ ++ ++/* Begin PBXSourcesBuildPhase section */ ++ 607A660E2B0EFA380010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, ++ 607A66282B0EFA390010BFC8 /* main.m in Sources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++ 607A66292B0EFA3A0010BFC8 /* Sources */ = { ++ isa = PBXSourcesBuildPhase; ++ buildActionMask = 2147483647; ++ files = ( ++ 607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */, ++ ); ++ runOnlyForDeploymentPostprocessing = 0; ++ }; ++/* End PBXSourcesBuildPhase section */ ++ ++/* Begin PBXTargetDependency section */ ++ 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { ++ isa = PBXTargetDependency; ++ target = 607A66112B0EFA380010BFC8 /* visionOSTestbed */; ++ targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; ++ }; ++/* End PBXTargetDependency section */ ++ ++/* Begin XCBuildConfiguration section */ ++ 607A663F2B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = dwarf; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_DYNAMIC_NO_PIC = NO; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_OPTIMIZATION_LEVEL = 0; ++ GCC_PREPROCESSOR_DEFINITIONS = ( ++ "DEBUG=1", ++ "$(inherited)", ++ ); ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; ++ MTL_FAST_MATH = YES; ++ ONLY_ACTIVE_ARCH = YES; ++ SDKROOT = xros; ++ }; ++ name = Debug; ++ }; ++ 607A66402B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ALWAYS_SEARCH_USER_PATHS = NO; ++ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ++ CLANG_ANALYZER_NONNULL = YES; ++ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; ++ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; ++ CLANG_ENABLE_MODULES = YES; ++ CLANG_ENABLE_OBJC_ARC = YES; ++ CLANG_ENABLE_OBJC_WEAK = YES; ++ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; ++ CLANG_WARN_BOOL_CONVERSION = YES; ++ CLANG_WARN_COMMA = YES; ++ CLANG_WARN_CONSTANT_CONVERSION = YES; ++ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; ++ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; ++ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; ++ CLANG_WARN_EMPTY_BODY = YES; ++ CLANG_WARN_ENUM_CONVERSION = YES; ++ CLANG_WARN_INFINITE_RECURSION = YES; ++ CLANG_WARN_INT_CONVERSION = YES; ++ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; ++ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; ++ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; ++ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; ++ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; ++ CLANG_WARN_STRICT_PROTOTYPES = YES; ++ CLANG_WARN_SUSPICIOUS_MOVE = YES; ++ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; ++ CLANG_WARN_UNREACHABLE_CODE = YES; ++ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ++ COPY_PHASE_STRIP = NO; ++ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ++ ENABLE_NS_ASSERTIONS = NO; ++ ENABLE_STRICT_OBJC_MSGSEND = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = YES; ++ GCC_C_LANGUAGE_STANDARD = gnu17; ++ GCC_NO_COMMON_BLOCKS = YES; ++ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; ++ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; ++ GCC_WARN_UNDECLARED_SELECTOR = YES; ++ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; ++ GCC_WARN_UNUSED_FUNCTION = YES; ++ GCC_WARN_UNUSED_VARIABLE = YES; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; ++ MTL_ENABLE_DEBUG_INFO = NO; ++ MTL_FAST_MATH = YES; ++ SDKROOT = xros; ++ VALIDATE_PRODUCT = YES; ++ }; ++ name = Release; ++ }; ++ 607A66422B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 7; ++ XROS_DEPLOYMENT_TARGET = 2.0; ++ }; ++ name = Debug; ++ }; ++ 607A66432B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ++ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = ""; ++ ENABLE_TESTABILITY = YES; ++ ENABLE_USER_SCRIPT_SANDBOXING = NO; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ INFOPLIST_FILE = "visionOSTestbed/visionOSTestbed-Info.plist"; ++ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; ++ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; ++ INFOPLIST_KEY_UIMainStoryboardFile = Main; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; ++ LD_RUNPATH_SEARCH_PATHS = ( ++ "$(inherited)", ++ "@executable_path/Frameworks", ++ ); ++ MARKETING_VERSION = 3.13.0a1; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.visionOSTestbed; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = YES; ++ TARGETED_DEVICE_FAMILY = 7; ++ XROS_DEPLOYMENT_TARGET = 2.0; ++ }; ++ name = Release; ++ }; ++ 607A66452B0EFA3A0010BFC8 /* Debug */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 7; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ }; ++ name = Debug; ++ }; ++ 607A66462B0EFA3A0010BFC8 /* Release */ = { ++ isa = XCBuildConfiguration; ++ buildSettings = { ++ BUNDLE_LOADER = "$(TEST_HOST)"; ++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; ++ CODE_SIGN_STYLE = Automatic; ++ CURRENT_PROJECT_VERSION = 1; ++ DEVELOPMENT_TEAM = 3HEZE76D99; ++ GENERATE_INFOPLIST_FILE = YES; ++ HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; ++ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ++ MARKETING_VERSION = 1.0; ++ PRODUCT_BUNDLE_IDENTIFIER = org.python.TestbedTests; ++ PRODUCT_NAME = "$(TARGET_NAME)"; ++ SUPPORTED_PLATFORMS = "xros xrsimulator"; ++ SUPPORTS_MACCATALYST = NO; ++ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; ++ SWIFT_EMIT_LOC_STRINGS = NO; ++ TARGETED_DEVICE_FAMILY = 7; ++ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/visionOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/visionOSTestbed"; ++ }; ++ name = Release; ++ }; ++/* End XCBuildConfiguration section */ ++ ++/* Begin XCConfigurationList section */ ++ 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "visionOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A663F2B0EFA3A0010BFC8 /* Debug */, ++ 607A66402B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "visionOSTestbed" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66422B0EFA3A0010BFC8 /* Debug */, ++ 607A66432B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++ 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "TestbedTests" */ = { ++ isa = XCConfigurationList; ++ buildConfigurations = ( ++ 607A66452B0EFA3A0010BFC8 /* Debug */, ++ 607A66462B0EFA3A0010BFC8 /* Release */, ++ ); ++ defaultConfigurationIsVisible = 0; ++ defaultConfigurationName = Release; ++ }; ++/* End XCConfigurationList section */ ++ }; ++ rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme +new file mode 100644 +index 00000000000..b8397ea7cf1 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed.xcodeproj/xcshareddata/xcschemes/visionOSTestbed.xcscheme +@@ -0,0 +1,97 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/Platforms/Apple/testbed/visionOSTestbed.xctestplan b/Platforms/Apple/testbed/visionOSTestbed.xctestplan +new file mode 100644 +index 00000000000..73a2be8c22a +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed.xctestplan +@@ -0,0 +1,46 @@ ++{ ++ "configurations" : [ ++ { ++ "id" : "C17FA044-0B70-48CA-AFF8-BC252081002F", ++ "name" : "Test Scheme Action", ++ "options" : { ++ ++ } ++ } ++ ], ++ "defaultOptions" : { ++ "commandLineArgumentEntries" : [ ++ { ++ "argument" : "test" ++ }, ++ { ++ "argument" : "-uall" ++ }, ++ { ++ "argument" : "--single-process" ++ }, ++ { ++ "argument" : "--rerun" ++ }, ++ { ++ "argument" : "-W" ++ } ++ ], ++ "targetForVariableExpansion" : { ++ "containerPath" : "container:visionOSTestbed.xcodeproj", ++ "identifier" : "607A66112B0EFA380010BFC8", ++ "name" : "visionOSTestbed" ++ } ++ }, ++ "testTargets" : [ ++ { ++ "parallelizable" : false, ++ "target" : { ++ "containerPath" : "container:visionOSTestbed.xcodeproj", ++ "identifier" : "607A662C2B0EFA3A0010BFC8", ++ "name" : "TestbedTests" ++ } ++ } ++ ], ++ "version" : 1 ++} +\ No newline at end of file +diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h +new file mode 100644 +index 00000000000..8fe7ec8e64e +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.h +@@ -0,0 +1,11 @@ ++// ++// AppDelegate.h ++// visionOSTestbed ++// ++ ++#import ++ ++@interface AppDelegate : UIResponder ++ ++ ++@end +diff --git a/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m +new file mode 100644 +index 00000000000..b3ff14f7255 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/AppDelegate.m +@@ -0,0 +1,19 @@ ++// ++// AppDelegate.m ++// visionOSTestbed ++// ++ ++#import "AppDelegate.h" ++ ++@interface AppDelegate () ++ ++@end ++ ++@implementation AppDelegate ++ ++ ++- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ++ return YES; ++} ++ ++@end +diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +new file mode 100644 +index 00000000000..eb878970081 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json +@@ -0,0 +1,11 @@ ++{ ++ "colors" : [ ++ { ++ "idiom" : "universal" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +new file mode 100644 +index 00000000000..13613e3ee1a +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json +@@ -0,0 +1,13 @@ ++{ ++ "images" : [ ++ { ++ "idiom" : "universal", ++ "platform" : "ios", ++ "size" : "1024x1024" ++ } ++ ], ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json +new file mode 100644 +index 00000000000..73c00596a7f +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/Assets.xcassets/Contents.json +@@ -0,0 +1,6 @@ ++{ ++ "info" : { ++ "author" : "xcode", ++ "version" : 1 ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/app/README b/Platforms/Apple/testbed/visionOSTestbed/app/README +new file mode 100644 +index 00000000000..af22c685f87 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/app/README +@@ -0,0 +1,7 @@ ++This folder can contain any Python application code. ++ ++During the build, any binary modules found in this folder will be processed into ++iOS Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH, and will be the ++working directory for the test suite. +diff --git a/Platforms/Apple/testbed/visionOSTestbed/app_packages/README b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README +new file mode 100644 +index 00000000000..42d7fdeb813 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/app_packages/README +@@ -0,0 +1,7 @@ ++This folder can be a target for installing any Python dependencies needed by the ++test suite. ++ ++During the build, any binary modules found in this folder will be processed into ++iOS Framework form. ++ ++When the test suite runs, this folder will be on the PYTHONPATH. +diff --git a/Platforms/Apple/testbed/visionOSTestbed/main.m b/Platforms/Apple/testbed/visionOSTestbed/main.m +new file mode 100644 +index 00000000000..2bb491f25c8 +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/main.m +@@ -0,0 +1,16 @@ ++// ++// main.m ++// visionOSTestbed ++// ++ ++#import ++#import "AppDelegate.h" ++ ++int main(int argc, char * argv[]) { ++ NSString * appDelegateClassName; ++ @autoreleasepool { ++ appDelegateClassName = NSStringFromClass([AppDelegate class]); ++ ++ return UIApplicationMain(argc, argv, nil, appDelegateClassName); ++ } ++} +diff --git a/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist +new file mode 100644 +index 00000000000..fce9298555d +--- /dev/null ++++ b/Platforms/Apple/testbed/visionOSTestbed/visionOSTestbed-Info.plist +@@ -0,0 +1,56 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleDisplayName ++ ${PRODUCT_NAME} ++ CFBundleExecutable ++ ${EXECUTABLE_NAME} ++ CFBundleIdentifier ++ org.python.visionOSTestbed ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ ${PRODUCT_NAME} ++ CFBundlePackageType ++ APPL ++ CFBundleShortVersionString ++ 1.0 ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ TestArgs ++ ++ test ++ -uall ++ --single-process ++ --rerun ++ -W ++ ++ UIApplicationSceneManifest ++ ++ UIApplicationSupportsMultipleScenes ++ ++ UISceneConfigurations ++ ++ ++ UIRequiresFullScreen ++ ++ UISupportedInterfaceOrientations ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ UISupportedInterfaceOrientations~ipad ++ ++ UIInterfaceOrientationPortrait ++ UIInterfaceOrientationPortraitUpsideDown ++ UIInterfaceOrientationLandscapeLeft ++ UIInterfaceOrientationLandscapeRight ++ ++ ++ +diff --git a/Platforms/Apple/tvOS/README.rst b/Platforms/Apple/tvOS/README.rst +new file mode 100644 +index 00000000000..1f793252caf +--- /dev/null ++++ b/Platforms/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 ++====================================== +diff --git a/Platforms/Apple/tvOS/Resources/Info.plist.in b/Platforms/Apple/tvOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..ab3050804b8 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2024 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ 1 ++ CFBundleSupportedPlatforms ++ ++ tvOS ++ ++ MinimumOSVersion ++ @TVOS_DEPLOYMENT_TARGET@ ++ ++ +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar +new file mode 100755 +index 00000000000..e302748a13c +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang +new file mode 100755 +index 00000000000..7fb6d3d901c +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ +new file mode 100755 +index 00000000000..33bfb1367c3 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp +new file mode 100755 +index 00000000000..641c1bc8d18 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvos${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar +new file mode 100755 +index 00000000000..87ef5015aae +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang +new file mode 100755 +index 00000000000..c8719cb0318 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ +new file mode 100755 +index 00000000000..e3f0e720f7f +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp +new file mode 100755 +index 00000000000..f9a37b72a61 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip +new file mode 100755 +index 00000000000..a8cce95233e +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip +new file mode 100755 +index 00000000000..ee1d2b95ff1 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/arm64-apple-tvos-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk iphoneos${TVOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +new file mode 100755 +index 00000000000..87ef5015aae +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang +new file mode 100755 +index 00000000000..ea0cc26cbd9 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ +new file mode 100755 +index 00000000000..f18f3603169 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang++ -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp +new file mode 100755 +index 00000000000..b98054d1ce2 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} clang -target x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip +new file mode 100755 +index 00000000000..f6a884b4aef +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/bin/x86_64-apple-tvos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk appletvsimulator${TVOS_SDK_VERSION} strip -arch x86_64 "$@" +diff --git a/Platforms/Apple/tvOS/Resources/pyconfig.h b/Platforms/Apple/tvOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..4acff2c6051 +--- /dev/null ++++ b/Platforms/Apple/tvOS/Resources/pyconfig.h +@@ -0,0 +1,7 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif ++ ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +diff --git a/Platforms/Apple/visionOS/Resources/Info.plist.in b/Platforms/Apple/visionOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..2679440da67 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2023 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ %VERSION% ++ CFBundleSupportedPlatforms ++ ++ XROS ++ ++ MinimumOSVersion ++ @XROS_DEPLOYMENT_TARGET@ ++ ++ +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-ar b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-ar +new file mode 100755 +index 00000000000..9fd78a205f3 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xros${XROS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang +new file mode 100755 +index 00000000000..9a1a757cbd0 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ +new file mode 100755 +index 00000000000..f64fcfc11cd +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xros${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp +new file mode 100755 +index 00000000000..d6492eff052 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xros${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar +new file mode 100755 +index 00000000000..b202330fb5d +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xrsimulator${XROS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang +new file mode 100755 +index 00000000000..87b0aba0751 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ +new file mode 100755 +index 00000000000..c89d48f1cb8 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang++ -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp +new file mode 100755 +index 00000000000..ee11e018ae0 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk xrsimulator${XROS_SDK_VERSION} clang -target arm64-apple-xros${XROS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip +new file mode 100644 +index 00000000000..376aff61a23 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk xrsimulator${XROS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-strip b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-strip +new file mode 100644 +index 00000000000..2de3ba5d31c +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/bin/arm64-apple-xros-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk xros${XROS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/visionOS/Resources/pyconfig.h b/Platforms/Apple/visionOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..2a433078400 +--- /dev/null ++++ b/Platforms/Apple/visionOS/Resources/pyconfig.h +@@ -0,0 +1,3 @@ ++#ifdef __arm64__ ++#include "pyconfig-arm64.h" ++#endif +diff --git a/Platforms/Apple/watchOS/README.rst b/Platforms/Apple/watchOS/README.rst +new file mode 100644 +index 00000000000..35221478452 +--- /dev/null ++++ b/Platforms/Apple/watchOS/README.rst +@@ -0,0 +1,108 @@ ++======================== ++Python on watchOS README ++======================== ++ ++: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 ++====================================== +diff --git a/Platforms/Apple/watchOS/Resources/Info.plist.in b/Platforms/Apple/watchOS/Resources/Info.plist.in +new file mode 100644 +index 00000000000..e83ddfd2a43 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/Info.plist.in +@@ -0,0 +1,34 @@ ++ ++ ++ ++ ++ CFBundleDevelopmentRegion ++ en ++ CFBundleExecutable ++ Python ++ CFBundleGetInfoString ++ Python Runtime and Library ++ CFBundleIdentifier ++ @PYTHONFRAMEWORKIDENTIFIER@ ++ CFBundleInfoDictionaryVersion ++ 6.0 ++ CFBundleName ++ Python ++ CFBundlePackageType ++ FMWK ++ CFBundleShortVersionString ++ %VERSION% ++ CFBundleLongVersionString ++ %VERSION%, (c) 2001-2023 Python Software Foundation. ++ CFBundleSignature ++ ???? ++ CFBundleVersion ++ %VERSION% ++ CFBundleSupportedPlatforms ++ ++ watchOS ++ ++ MinimumOSVersion ++ @WATCHOS_DEPLOYMENT_TARGET@ ++ ++ +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar +new file mode 100755 +index 00000000000..dda2b211bd5 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang +new file mode 100755 +index 00000000000..fe834d3efe4 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ +new file mode 100755 +index 00000000000..757f3a26d8f +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp +new file mode 100755 +index 00000000000..fdb57d9e010 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator clang -target arm64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip +new file mode 100755 +index 00000000000..e28e3f7597a +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip +new file mode 100755 +index 00000000000..efe5a1260ad +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64-apple-watchos-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} strip -arch arm64 "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar +new file mode 100755 +index 00000000000..029f9a32073 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang +new file mode 100755 +index 00000000000..285036d4010 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ +new file mode 100755 +index 00000000000..c8f60ebec51 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang++ -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp +new file mode 100755 +index 00000000000..b411fc25aa4 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/arm64_32-apple-watchos-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchos${WATCHOS_SDK_VERSION} clang -target arm64_32-apple-watchos${WATCHOS_DEPLOYMENT_TARGET} -E "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar +new file mode 100755 +index 00000000000..dda2b211bd5 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-ar +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} ar "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang +new file mode 100755 +index 00000000000..4776b9b5348 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ +new file mode 100755 +index 00000000000..e9b0c5f4b87 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-clang++ +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang++ -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp +new file mode 100755 +index 00000000000..d3b821c5f7f +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-cpp +@@ -0,0 +1,2 @@ ++#!/bin/bash ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} clang -target x86_64-apple-watchos${WATCHOS_DEPLOYMENT_TARGET}-simulator -E "$@" +diff --git a/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip +new file mode 100755 +index 00000000000..105c78281f9 +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/bin/x86_64-apple-watchos-simulator-strip +@@ -0,0 +1,2 @@ ++#!/bin/sh ++xcrun --sdk watchsimulator${WATCHOS_SDK_VERSION} strip -arch x86_64 "$@" +diff --git a/Platforms/Apple/watchOS/Resources/pyconfig.h b/Platforms/Apple/watchOS/Resources/pyconfig.h +new file mode 100644 +index 00000000000..f842b987b2e +--- /dev/null ++++ b/Platforms/Apple/watchOS/Resources/pyconfig.h +@@ -0,0 +1,11 @@ ++#ifdef __arm64__ ++# ifdef __LP64__ ++#include "pyconfig-arm64.h" ++# else ++#include "pyconfig-arm64_32.h" ++# endif ++#endif ++ ++#ifdef __x86_64__ ++#include "pyconfig-x86_64.h" ++#endif +diff --git a/config.sub b/config.sub +index 1bb6a05dc11..49febd56a37 100755 +--- a/config.sub ++++ b/config.sub +@@ -1743,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* | tvos* | watchos* \ ++ | os9* | macos* | osx* | ios* | tvos* | watchos* | xros* \ + | mpw* | magic* | mmixware* | mon960* | lnews* \ + | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ + | aos* | aros* | cloudabi* | sortix* | twizzler* \ +@@ -1867,7 +1867,7 @@ + ;; + *-eabi*- | *-gnueabi*-) + ;; +- ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) ++ ios*-simulator- | tvos*-simulator- | watchos*-simulator- | xros*-simulator-) + ;; + none--*) + # None (no kernel, i.e. freestanding / bare metal), +diff --git a/configure b/configure +index 2088290f0ee..d81f302d6aa 100755 +--- a/configure ++++ b/configure +@@ -984,6 +984,10 @@ + CFLAGS + CC + HAS_XCRUN ++EXPORT_XROS_DEPLOYMENT_TARGET ++WATCHOS_DEPLOYMENT_TARGET ++XROS_DEPLOYMENT_TARGET ++TVOS_DEPLOYMENT_TARGET + IPHONEOS_DEPLOYMENT_TARGET + EXPORT_MACOSX_DEPLOYMENT_TARGET + CONFIGURE_MACOSX_DEPLOYMENT_TARGET +@@ -4118,6 +4122,15 @@ + *-apple-ios*) + ac_sys_system=iOS + ;; ++ *-apple-tvos*) ++ ac_sys_system=tvOS ++ ;; ++ *-apple-xros*) ++ ac_sys_system=visionOS ++ ;; ++ *-apple-watchos*) ++ ac_sys_system=watchOS ++ ;; + *-*-darwin*) + ac_sys_system=Darwin + ;; +@@ -4199,7 +4212,7 @@ + # On cross-compile builds, configure will look for a host-specific compiler by + # prepending the user-provided host triple to the required binary name. + # +-# On iOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", ++# On iOS/tvOS/visionOS/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 +@@ -4214,6 +4227,17 @@ + aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; + aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; + x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; ++ ++ 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 ;; ++ ++ aarch64-apple-xros*-simulator) AR=arm64-apple-xros-simulator-ar ;; ++ aarch64-apple-xros*) AR=arm64-apple-xros-ar ;; ++ ++ 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 +@@ -4222,6 +4246,17 @@ + aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; + aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; + x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++ ++ 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-xros*-simulator) CC=arm64-apple-xros-simulator-clang ;; ++ aarch64-apple-xros*) CC=arm64-apple-xros-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 +@@ -4230,6 +4265,17 @@ + aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; + aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; + x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++ ++ 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 ;; ++ ++ aarch64-apple-xros*-simulator) CPP=arm64-apple-xros-simulator-cpp ;; ++ aarch64-apple-xros*) CPP=arm64-apple-xros-cpp ;; ++ ++ 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 +@@ -4238,6 +4284,17 @@ + aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; + aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; + x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; ++ ++ 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++ ;; ++ ++ aarch64-apple-xros*-simulator) CXX=arm64-apple-xros-simulator-clang++ ;; ++ aarch64-apple-xros*) CXX=arm64-apple-xros-clang++ ;; ++ ++ 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 +@@ -4360,8 +4417,11 @@ + case $enableval in + yes) + case $ac_sys_system in +- Darwin) enableval=/Library/Frameworks ;; +- iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; ++ Darwin) enableval=/Library/Frameworks ;; ++ iOS) enableval=Platforms/Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; ++ tvOS) enableval=Platforms/Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;; ++ visionOS) enableval=Platforms/Apple/visionOS/Frameworks/\$\(MULTIARCH\) ;; ++ watchOS) enableval=Platforms/Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;; + *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 + esac + esac +@@ -4370,6 +4430,9 @@ + no) + 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 ;; ++ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;; ++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework +@@ -4472,9 +4535,54 @@ + + prefix=$PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" +- RESSRCDIR=Apple/iOS/Resources ++ RESSRCDIR=Platforms/Apple/iOS/Resources ++ ++ ac_config_files="$ac_config_files Platforms/Apple/iOS/Resources/Info.plist" ++ ++ ;; ++ tvOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Platforms/Apple/tvOS/Resources ++ ++ ac_config_files="$ac_config_files Platforms/Apple/tvOS/Resources/Info.plist" ++ ++ ;; ++ visionOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Platforms/Apple/visionOS/Resources ++ ++ ac_config_files="$ac_config_files Platforms/Apple/visionOS/Resources/Info.plist" ++ ++ ;; ++ watchOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Platforms/Apple/watchOS/Resources + +- ac_config_files="$ac_config_files Apple/iOS/Resources/Info.plist" ++ ac_config_files="$ac_config_files Platforms/Apple/watchOS/Resources/Info.plist" + + ;; + *) +@@ -4487,6 +4595,9 @@ + e) + 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 ;; ++ visionOS) as_fn_error $? "visionOS builds must use --enable-framework" "$LINENO" 5 ;; ++ watchOS) as_fn_error $? "watchOS builds must use --enable-framework" "$LINENO" 5 ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework +@@ -4541,8 +4652,8 @@ + case "$withval" in + yes) + case $ac_sys_system in +- Darwin|iOS) +- # iOS is able to share the macOS patch ++ Darwin|iOS|tvOS|visionOS|watchOS) ++ # iOS/tvOS/visionOS/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 ;; +@@ -4560,8 +4671,8 @@ + else case e in #( + e) + case $ac_sys_system in +- iOS) +- # Always apply the compliance patch on iOS; we can use the macOS patch ++ iOS|tvOS|visionOS|watchOS) ++ # Always apply the compliance patch on iOS/tvOS/visionOS/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; } +@@ -4579,6 +4690,8 @@ + + + ++EXPORT_XROS_DEPLOYMENT_TARGET='#' ++ + + if test "$cross_compiling" = yes; then + case "$host" in +@@ -4616,6 +4729,78 @@ + ;; + esac + ;; ++ *-apple-tvos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # 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; } ++ ++ 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-xros*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # XROS_DEPLOYMENT_TARGET is the minimum supported visionOS version ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking visionOS deployment target" >&5 ++printf %s "checking visionOS deployment target... " >&6; } ++ XROS_DEPLOYMENT_TARGET=${_host_os:8} ++ XROS_DEPLOYMENT_TARGET=${XROS_DEPLOYMENT_TARGET:=2.0} ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $XROS_DEPLOYMENT_TARGET" >&5 ++printf "%s\n" "$XROS_DEPLOYMENT_TARGET" >&6; } ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking exporting flag of visionOS deployment target" >&5 ++printf %s "checking exporting flag of visionOS deployment target... " >&6; } ++ export XROS_DEPLOYMENT_TARGET ++ EXPORT_XROS_DEPLOYMENT_TARGET='' ++ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $EXPORT_XROS_DEPLOYMENT_TARGET" >&5 ++printf "%s\n" "$EXPORT_XROS_DEPLOYMENT_TARGET" >&6; } ++ ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${XROS_DEPLOYMENT_TARGET}-arm64-xr${_host_device} ++ ;; ++ *) ++ _host_ident=${XROS_DEPLOYMENT_TARGET}-$host_cpu-xr${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-watchos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # 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; } ++ ++ 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 ++ ;; + *-*-darwin*) + case "$host_cpu" in + arm*) +@@ -4706,9 +4891,15 @@ + define_xopen_source=no;; + Darwin/[12][0-9].*) + define_xopen_source=no;; +- # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. ++ # On iOS/tvOS/visionOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; ++ tvOS/*) ++ define_xopen_source=no;; ++ visionOS/*) ++ 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) +@@ -4771,7 +4962,14 @@ + CONFIGURE_MACOSX_DEPLOYMENT_TARGET= + EXPORT_MACOSX_DEPLOYMENT_TARGET='#' + +-# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple. ++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / ++# XROS_DEPLOYMENT_TARGET / WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. ++ ++ ++ ++ ++ ++# XROS_DEPLOYMENT_TARGET should get exported + + + # checks for alternative programs +@@ -4812,6 +5010,16 @@ + as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" + as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}" + ;; #( ++ tvOS) : ++ ++ 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 +@@ -7181,6 +7389,12 @@ + MULTIARCH="" ;; #( + iOS) : + MULTIARCH="" ;; #( ++ tvOS) : ++ MULTIARCH="" ;; #( ++ visionOS) : ++ MULTIARCH="" ;; #( ++ watchOS) : ++ MULTIARCH="" ;; #( + FreeBSD*) : + MULTIARCH="" ;; #( + *) : +@@ -7201,7 +7415,7 @@ + printf "%s\n" "$MULTIARCH" >&6; } + + case $ac_sys_system in #( +- iOS) : ++ iOS|tvOS|visionOS|watchOS) : + SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( + *) : + SOABI_PLATFORM=$PLATFORM_TRIPLET +@@ -7264,6 +7478,18 @@ + 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-xros*-simulator/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-xros*/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ aarch64-apple-watchos*-simulator/clang) : ++ PY_SUPPORT_TIER=3 ;; #( ++ arm64_32-apple-watchos*/clang) : ++ PY_SUPPORT_TIER=3 ;; #( + aarch64-*-linux-android/clang) : + PY_SUPPORT_TIER=3 ;; #( + x86_64-*-linux-android/clang) : +@@ -7702,7 +7928,7 @@ + case $ac_sys_system in + Darwin) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; +- iOS) ++ iOS|tvOS|visionOS|watchOS) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; + *) + as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; +@@ -7768,7 +7994,7 @@ + BLDLIBRARY='-L. -lpython$(LDVERSION)' + RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} + ;; +- iOS) ++ iOS|tvOS|visionOS|watchOS) + LDLIBRARY='libpython$(LDVERSION).dylib' + ;; + AIX*) +@@ -13677,7 +13903,7 @@ + BLDSHARED="$LDSHARED" + fi + ;; +- iOS/*) ++ iOS/*|tvOS/*|visionOS/*|watchOS/*) + LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + BLDSHARED="$LDSHARED" +@@ -13810,7 +14036,7 @@ + Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; + Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; + # -u libsys_s pulls in all symbols in libsys +- Darwin/*|iOS/*) ++ Darwin/*|iOS/*|tvOS/*|visionOS/*|watchOS/*) + LINKFORSHARED="$extra_undefs -framework CoreFoundation" + + # Issue #18075: the default maximum stack size (8MBytes) is too +@@ -13834,7 +14060,7 @@ + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + fi + LINKFORSHARED="$LINKFORSHARED" +- elif test $ac_sys_system = "iOS"; then ++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi + ;; +@@ -15611,7 +15837,7 @@ + + ctypes_malloc_closure=yes + ;; #( +- iOS) : ++ iOS|tvOS|visionOS|watchOS) : + + ctypes_malloc_closure=yes + ;; #( +@@ -19375,12 +19601,6 @@ + then : + printf "%s\n" "#define HAVE_DUP 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 +@@ -19441,18 +19661,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 +@@ -19879,24 +20087,6 @@ + then : + printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h + +-fi +-ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn" +-if test "x$ac_cv_func_posix_spawn" = xyes +-then : +- printf "%s\n" "#define HAVE_POSIX_SPAWN 1" >>confdefs.h +- +-fi +-ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp" +-if test "x$ac_cv_func_posix_spawnp" = xyes +-then : +- printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h +- +-fi +-ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" +-if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes +-then : +- printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h +- + fi + ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread" + if test "x$ac_cv_func_pread" = xyes +@@ -20215,12 +20405,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 +@@ -20489,11 +20673,11 @@ + + fi + +-# iOS defines some system methods that can be linked (so they are ++# iOS/tvOS/visionOS/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" ; then ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" ; then + ac_fn_c_check_func "$LINENO" "dup3" "ac_cv_func_dup3" + if test "x$ac_cv_func_dup3" = xyes + then : +@@ -20527,6 +20711,53 @@ + + fi + ++# tvOS/watchOS have some additional methods that can be found, but not used. ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ ac_fn_c_check_func "$LINENO" "execv" "ac_cv_func_execv" ++if test "x$ac_cv_func_execv" = xyes ++then : ++ printf "%s\n" "#define HAVE_EXECV 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "fork" "ac_cv_func_fork" ++if test "x$ac_cv_func_fork" = xyes ++then : ++ printf "%s\n" "#define HAVE_FORK 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "fork1" "ac_cv_func_fork1" ++if test "x$ac_cv_func_fork1" = xyes ++then : ++ printf "%s\n" "#define HAVE_FORK1 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn" ++if test "x$ac_cv_func_posix_spawn" = xyes ++then : ++ printf "%s\n" "#define HAVE_POSIX_SPAWN 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp" ++if test "x$ac_cv_func_posix_spawnp" = xyes ++then : ++ printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" ++if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes ++then : ++ printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h ++ ++fi ++ac_fn_c_check_func "$LINENO" "sigaltstack" "ac_cv_func_sigaltstack" ++if test "x$ac_cv_func_sigaltstack" = xyes ++then : ++ printf "%s\n" "#define HAVE_SIGALTSTACK 1" >>confdefs.h ++ ++fi ++ ++fi ++ + { 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} +@@ -23998,7 +24229,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 : +@@ -24112,7 +24344,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 : +@@ -24295,6 +24527,7 @@ + fi + + done ++fi + + # check for long file support functions + ac_fn_c_check_func "$LINENO" "fseek64" "ac_cv_func_fseek64" +@@ -24560,10 +24793,10 @@ + + done + +-# On Android and iOS, clock_settime can be linked (so it is found by ++# On Android, iOS, tvOS, visionOS, 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" != "Linux-android" && test "$ac_sys_system" != "iOS" ++if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" + then + + for ac_func in clock_settime +@@ -24880,7 +25113,7 @@ + e) if test "$cross_compiling" = yes + then : + +-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then ++if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "visionOS" || test "$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" +@@ -26902,8 +27135,8 @@ + LIBPYTHON="\$(BLDLIBRARY)" + fi + +-# On iOS the shared libraries must be linked with the Python framework +-if test "$ac_sys_system" = "iOS"; then ++# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework ++if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "visionOS" -o $ac_sys_system = "watchOS"; then + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" + fi + +@@ -29669,7 +29902,7 @@ + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 + printf "%s\n" "$as_me: checking for device files" >&6;} + +-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then ++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS" ; then + ac_cv_file__dev_ptmx=no + ac_cv_file__dev_ptc=no + else +@@ -30222,7 +30455,7 @@ + with_ensurepip=no ;; #( + WASI) : + with_ensurepip=no ;; #( +- iOS) : ++ iOS|tvOS|visionOS|watchOS) : + with_ensurepip=no ;; #( + *) : + with_ensurepip=upgrade +@@ -31172,6 +31405,9 @@ + NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268 + Darwin) _PYTHREAD_NAME_MAXLEN=63;; + iOS) _PYTHREAD_NAME_MAXLEN=63;; ++ tvOS) _PYTHREAD_NAME_MAXLEN=63;; ++ visionOS) _PYTHREAD_NAME_MAXLEN=63;; ++ watchOS) _PYTHREAD_NAME_MAXLEN=63;; + FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 + OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 + *) _PYTHREAD_NAME_MAXLEN=;; +@@ -31203,7 +31439,7 @@ + ;; #( + Darwin) : + ;; #( +- iOS) : ++ iOS|tvOS|visionOS|watchOS) : + + + +@@ -35370,7 +35606,10 @@ + "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;; + "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; + "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; +- "Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Apple/iOS/Resources/Info.plist" ;; ++ "Platforms/Apple/iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/iOS/Resources/Info.plist" ;; ++ "Platforms/Apple/tvOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/tvOS/Resources/Info.plist" ;; ++ "Platforms/Apple/visionOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/visionOS/Resources/Info.plist" ;; ++ "Platforms/Apple/watchOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES Platforms/Apple/watchOS/Resources/Info.plist" ;; + "Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;; + "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; + "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; +diff --git a/configure.ac b/configure.ac +index aed14946731..3fa418c611f 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -330,6 +330,15 @@ + *-apple-ios*) + ac_sys_system=iOS + ;; ++ *-apple-tvos*) ++ ac_sys_system=tvOS ++ ;; ++ *-apple-xros*) ++ ac_sys_system=visionOS ++ ;; ++ *-apple-watchos*) ++ ac_sys_system=watchOS ++ ;; + *-*-darwin*) + ac_sys_system=Darwin + ;; +@@ -405,7 +414,7 @@ + # On cross-compile builds, configure will look for a host-specific compiler by + # prepending the user-provided host triple to the required binary name. + # +-# On iOS, this results in binaries like "arm64-apple-ios13.0-simulator-gcc", ++# On iOS/tvOS/visionOS/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 +@@ -420,6 +429,17 @@ + aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; + aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; + x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; ++ ++ 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 ;; ++ ++ aarch64-apple-xros*-simulator) AR=arm64-apple-xros-simulator-ar ;; ++ aarch64-apple-xros*) AR=arm64-apple-xros-ar ;; ++ ++ 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 +@@ -428,6 +448,17 @@ + aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; + aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; + x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; ++ ++ 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-xros*-simulator) CC=arm64-apple-xros-simulator-clang ;; ++ aarch64-apple-xros*) CC=arm64-apple-xros-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 +@@ -436,6 +467,17 @@ + aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; + aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; + x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; ++ ++ 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 ;; ++ ++ aarch64-apple-xros*-simulator) CPP=arm64-apple-xros-simulator-cpp ;; ++ aarch64-apple-xros*) CPP=arm64-apple-xros-cpp ;; ++ ++ 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 +@@ -444,6 +486,17 @@ + aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang++ ;; + aarch64-apple-ios*) CXX=arm64-apple-ios-clang++ ;; + x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang++ ;; ++ ++ 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++ ;; ++ ++ aarch64-apple-xros*-simulator) CXX=arm64-apple-xros-simulator-clang++ ;; ++ aarch64-apple-xros*) CXX=arm64-apple-xros-clang++ ;; ++ ++ 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 +@@ -558,8 +611,11 @@ + case $enableval in + yes) + case $ac_sys_system in +- Darwin) enableval=/Library/Frameworks ;; +- iOS) enableval=Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; ++ Darwin) enableval=/Library/Frameworks ;; ++ iOS) enableval=Platforms/Apple/iOS/Frameworks/\$\(MULTIARCH\) ;; ++ tvOS) enableval=Platforms/Apple/tvOS/Frameworks/\$\(MULTIARCH\) ;; ++ visionOS) enableval=Platforms/Apple/visionOS/Frameworks/\$\(MULTIARCH\) ;; ++ watchOS) enableval=Platforms/Apple/watchOS/Frameworks/\$\(MULTIARCH\) ;; + *) AC_MSG_ERROR([Unknown platform for framework build]) + esac + esac +@@ -568,6 +624,9 @@ + no) + 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]) ;; ++ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;; ++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework +@@ -666,9 +725,51 @@ + + prefix=$PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" +- RESSRCDIR=Apple/iOS/Resources ++ RESSRCDIR=Platforms/Apple/iOS/Resources + +- AC_CONFIG_FILES([Apple/iOS/Resources/Info.plist]) ++ AC_CONFIG_FILES([Platforms/Apple/iOS/Resources/Info.plist]) ++ ;; ++ tvOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Platforms/Apple/tvOS/Resources ++ ++ AC_CONFIG_FILES([Platforms/Apple/tvOS/Resources/Info.plist]) ++ ;; ++ visionOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Platforms/Apple/visionOS/Resources ++ ++ AC_CONFIG_FILES([Platforms/Apple/visionOS/Resources/Info.plist]) ++ ;; ++ watchOS) : ++ FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" ++ FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " ++ FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" ++ FRAMEWORKPYTHONW= ++ INSTALLTARGETS="libinstall inclinstall sharedinstall" ++ ++ prefix=$PYTHONFRAMEWORKPREFIX ++ PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" ++ RESSRCDIR=Platforms/Apple/watchOS/Resources ++ ++ AC_CONFIG_FILES([Platforms/Apple/watchOS/Resources/Info.plist]) + ;; + *) + AC_MSG_ERROR([Unknown platform for framework build]) +@@ -678,6 +779,9 @@ + ],[ + case $ac_sys_system in + iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; ++ tvOS) AC_MSG_ERROR([tvOS builds must use --enable-framework]) ;; ++ visionOS) AC_MSG_ERROR([visionOS builds must use --enable-framework]) ;; ++ watchOS) AC_MSG_ERROR([watchOS builds must use --enable-framework]) ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework +@@ -730,8 +834,8 @@ + case "$withval" in + yes) + case $ac_sys_system in +- Darwin|iOS) +- # iOS is able to share the macOS patch ++ Darwin|iOS|tvOS|visionOS|watchOS) ++ # iOS/tvOS/visionOS/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]) ;; +@@ -745,8 +849,8 @@ + esac + ],[ + case $ac_sys_system in +- iOS) +- # Always apply the compliance patch on iOS; we can use the macOS patch ++ iOS|tvOS|visionOS|watchOS) ++ # Always apply the compliance patch on iOS/tvOS/visionOS/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]) + ;; +@@ -759,6 +863,8 @@ + ]) + AC_SUBST([APP_STORE_COMPLIANCE_PATCH]) + ++EXPORT_XROS_DEPLOYMENT_TARGET='#' ++ + AC_SUBST([_PYTHON_HOST_PLATFORM]) + if test "$cross_compiling" = yes; then + case "$host" in +@@ -794,6 +900,70 @@ + ;; + esac + ;; ++ *-apple-tvos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # 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]) ++ ++ 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-xros*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # XROS_DEPLOYMENT_TARGET is the minimum supported visionOS version ++ AC_MSG_CHECKING([visionOS deployment target]) ++ XROS_DEPLOYMENT_TARGET=${_host_os:8} ++ XROS_DEPLOYMENT_TARGET=${XROS_DEPLOYMENT_TARGET:=2.0} ++ AC_MSG_RESULT([$XROS_DEPLOYMENT_TARGET]) ++ AC_MSG_CHECKING([exporting flag of visionOS deployment target]) ++ export XROS_DEPLOYMENT_TARGET ++ EXPORT_XROS_DEPLOYMENT_TARGET='' ++ AC_MSG_RESULT([$EXPORT_XROS_DEPLOYMENT_TARGET]) ++ ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=${XROS_DEPLOYMENT_TARGET}-arm64-xr${_host_device} ++ ;; ++ *) ++ _host_ident=${XROS_DEPLOYMENT_TARGET}-$host_cpu-xr${_host_device} ++ ;; ++ esac ++ ;; ++ *-apple-watchos*) ++ _host_os=`echo $host | cut -d '-' -f3` ++ _host_device=`echo $host | cut -d '-' -f4` ++ _host_device=${_host_device:=os} ++ ++ # 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]) ++ ++ 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 ++ ;; + *-*-darwin*) + case "$host_cpu" in + arm*) +@@ -883,9 +1053,15 @@ + define_xopen_source=no;; + Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) + define_xopen_source=no;; +- # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. ++ # On iOS/tvOS/visionOS/watchOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; ++ tvOS/*) ++ define_xopen_source=no;; ++ visionOS/*) ++ 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) +@@ -944,8 +1120,15 @@ + CONFIGURE_MACOSX_DEPLOYMENT_TARGET= + EXPORT_MACOSX_DEPLOYMENT_TARGET='#' + +-# Record the value of IPHONEOS_DEPLOYMENT_TARGET enforced by the selected host triple. ++# Record the value of IPHONEOS_DEPLOYMENT_TARGET / TVOS_DEPLOYMENT_TARGET / ++# XROS_DEPLOYMENT_TARGET / WATCHOS_DEPLOYMENT_TARGET enforced by the selected host triple. + AC_SUBST([IPHONEOS_DEPLOYMENT_TARGET]) ++AC_SUBST([TVOS_DEPLOYMENT_TARGET]) ++AC_SUBST([XROS_DEPLOYMENT_TARGET]) ++AC_SUBST([WATCHOS_DEPLOYMENT_TARGET]) ++ ++# XROS_DEPLOYMENT_TARGET should get exported ++AC_SUBST([EXPORT_XROS_DEPLOYMENT_TARGET]) + + # checks for alternative programs + +@@ -979,11 +1162,19 @@ + ], + ) + +-dnl Add the compiler flag for the iOS minimum supported OS version. ++dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS ++dnl version. visionOS doesn't use an explicit -mxros-version-min option - ++dnl it encodes the min version into the target triple. + 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}"]) + ], + ) + +@@ -1172,6 +1363,9 @@ + AS_CASE([$ac_sys_system], + [Darwin*], [MULTIARCH=""], + [iOS], [MULTIARCH=""], ++ [tvOS], [MULTIARCH=""], ++ [visionOS], [MULTIARCH=""], ++ [watchOS], [MULTIARCH=""], + [FreeBSD*], [MULTIARCH=""], + [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] + ) +@@ -1193,7 +1387,7 @@ + dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of + dnl the PLATFORM_TRIPLET that will be used in binary module extensions. + AS_CASE([$ac_sys_system], +- [iOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], ++ [iOS|tvOS|visionOS|watchOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], + [SOABI_PLATFORM=$PLATFORM_TRIPLET] + ) + +@@ -1228,16 +1422,22 @@ + [wasm32-unknown-wasip1/clang], [PY_SUPPORT_TIER=2], dnl WebAssembly System Interface preview1, clang + [x86_64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], dnl Linux on AMD64, any vendor, glibc, clang + +- [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC +- [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc +- [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang +- [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc +- [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-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64 +- [x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64 +- [wasm32-*-emscripten/emcc], [PY_SUPPORT_TIER=3], dnl Emscripten ++ [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC ++ [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc ++ [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang ++ [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc ++ [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-xros*-simulator/clang], [PY_SUPPORT_TIER=3], dnl visionOS Simulator on arm64 ++ [aarch64-apple-xros*/clang], [PY_SUPPORT_TIER=3], dnl visionOS 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 ++ [aarch64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on ARM64 ++ [x86_64-*-linux-android/clang], [PY_SUPPORT_TIER=3], dnl Android on AMD64 ++ [wasm32-*-emscripten/emcc], [PY_SUPPORT_TIER=3], dnl Emscripten + + [PY_SUPPORT_TIER=0] + ) +@@ -1545,7 +1745,7 @@ + case $ac_sys_system in + Darwin) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; +- iOS) ++ iOS|tvOS|visionOS|watchOS) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; + *) + AC_MSG_ERROR([Unknown platform for framework build]);; +@@ -1610,7 +1810,7 @@ + BLDLIBRARY='-L. -lpython$(LDVERSION)' + RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} + ;; +- iOS) ++ iOS|tvOS|visionOS|watchOS) + LDLIBRARY='libpython$(LDVERSION).dylib' + ;; + AIX*) +@@ -3502,7 +3702,7 @@ + BLDSHARED="$LDSHARED" + fi + ;; +- iOS/*) ++ iOS/*|tvOS/*|visionOS/*|watchOS/*) + LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + BLDSHARED="$LDSHARED" +@@ -3626,7 +3826,7 @@ + Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; + Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; + # -u libsys_s pulls in all symbols in libsys +- Darwin/*|iOS/*) ++ Darwin/*|iOS/*|tvOS/*|visionOS/*|watchOS/*) + LINKFORSHARED="$extra_undefs -framework CoreFoundation" + + # Issue #18075: the default maximum stack size (8MBytes) is too +@@ -3650,7 +3850,7 @@ + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + fi + LINKFORSHARED="$LINKFORSHARED" +- elif test $ac_sys_system = "iOS"; then ++ elif test "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi + ;; +@@ -4138,7 +4338,7 @@ + dnl when do we need USING_APPLE_OS_LIBFFI? + ctypes_malloc_closure=yes + ], +- [iOS], [ ++ [iOS|tvOS|visionOS|watchOS], [ + ctypes_malloc_closure=yes + ], + [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] +@@ -5264,9 +5464,9 @@ + # checks for library functions + AC_CHECK_FUNCS([ \ + accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \ +- copy_file_range ctermid dladdr dup execv explicit_bzero explicit_memset \ ++ copy_file_range ctermid dladdr dup explicit_bzero explicit_memset \ + faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ +- fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ ++ fpathconf fstatat ftime ftruncate futimens futimes futimesat \ + gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \ + getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin getlogin_r \ + getpeername getpgid getpid getppid getpriority _getpty \ +@@ -5274,8 +5474,7 @@ + getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ + lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ + mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ +- plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ +- posix_spawn_file_actions_addclosefrom_np \ ++ plock poll posix_fadvise posix_fallocate posix_openpt \ + pread preadv preadv2 process_vm_readv \ + pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ + pthread_kill pthread_get_name_np pthread_getname_np pthread_set_name_np \ +@@ -5285,7 +5484,7 @@ + sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ + sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ + setitimer setlocale setpgid setpgrp setpriority setregid setresgid \ +- 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 tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ +@@ -5300,14 +5499,22 @@ + AC_CHECK_FUNCS([lchmod]) + fi + +-# iOS defines some system methods that can be linked (so they are ++# iOS/tvOS/visionOS/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" ; then ++if test "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" ; then + AC_CHECK_FUNCS([dup3 getentropy getgroups pipe2 system]) + fi + ++# tvOS/watchOS have some additional methods that can be found, but not used. ++if test "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "watchOS" ; then ++ AC_CHECK_FUNCS([ \ ++ execv fork fork1 posix_spawn posix_spawnp posix_spawn_file_actions_addclosefrom_np \ ++ sigaltstack \ ++ ]) ++fi ++ + AC_CHECK_DECL([dirfd], + [AC_DEFINE([HAVE_DIRFD], [1], + [Define if you have the 'dirfd' function or macro.])], +@@ -5600,20 +5807,22 @@ + [@%:@include ]) + + # check for openpty, login_tty, and forkpty +- +-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]) +@@ -5652,10 +5861,10 @@ + ]) + ]) + +-# On Android and iOS, clock_settime can be linked (so it is found by ++# On Android, iOS, tvOS, visionOS, 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" != "Linux-android" && test "$ac_sys_system" != "iOS" ++if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "iOS" -a "$ac_sys_system" != "tvOS" -a "$ac_sys_system" != "visionOS" -a "$ac_sys_system" != "watchOS" + then + AC_CHECK_FUNCS([clock_settime], [], [ + AC_CHECK_LIB([rt], [clock_settime], [ +@@ -5813,7 +6022,7 @@ + [ac_cv_buggy_getaddrinfo=no], + [ac_cv_buggy_getaddrinfo=yes], + [ +-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then ++if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS" || test "$ac_sys_system" = "tvOS" || test "$ac_sys_system" = "visionOS" || test "$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" +@@ -6406,8 +6615,8 @@ + LIBPYTHON="\$(BLDLIBRARY)" + fi + +-# On iOS the shared libraries must be linked with the Python framework +-if test "$ac_sys_system" = "iOS"; then ++# On iOS/tvOS/watchOS the shared libraries must be linked with the Python framework ++if test "$ac_sys_system" = "iOS" -o $ac_sys_system = "tvOS" -o $ac_sys_system = "visionOS" -o $ac_sys_system = "watchOS"; then + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" + fi + +@@ -7015,7 +7224,7 @@ + dnl NOTE: Inform user how to proceed with files when cross compiling. + dnl Some cross-compile builds are predictable; they won't ever + dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. +-if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then ++if test "$ac_sys_system" = "Linux-android" -o "$ac_sys_system" = "iOS" -o "$ac_sys_system" = "tvOS" -o "$ac_sys_system" = "visionOS" -o "$ac_sys_system" = "watchOS" ; then + ac_cv_file__dev_ptmx=no + ac_cv_file__dev_ptc=no + else +@@ -7343,7 +7552,7 @@ + AS_CASE([$ac_sys_system], + [Emscripten], [with_ensurepip=no], + [WASI], [with_ensurepip=no], +- [iOS], [with_ensurepip=no], ++ [iOS|tvOS|visionOS|watchOS], [with_ensurepip=no], + [with_ensurepip=upgrade] + ) + ]) +@@ -7731,6 +7940,9 @@ + NetBSD*) _PYTHREAD_NAME_MAXLEN=15;; # gh-131268 + Darwin) _PYTHREAD_NAME_MAXLEN=63;; + iOS) _PYTHREAD_NAME_MAXLEN=63;; ++ tvOS) _PYTHREAD_NAME_MAXLEN=63;; ++ visionOS) _PYTHREAD_NAME_MAXLEN=63;; ++ watchOS) _PYTHREAD_NAME_MAXLEN=63;; + FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 + OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 + *) _PYTHREAD_NAME_MAXLEN=;; +@@ -7755,7 +7967,7 @@ + [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])], + dnl The _scproxy module is available on macOS + [Darwin], [], +- [iOS], [ ++ [iOS|tvOS|visionOS|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