├── .github ├── dependabot.yml └── workflows │ ├── ci.yaml │ ├── publish.yaml │ └── release.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.rst ├── USAGE.md └── patch ├── Python ├── OpenSSL.xcprivacy ├── Python.patch ├── _cross_target.py.tmpl ├── _cross_venv.py ├── app-store-compliance.patch ├── diff.exclude ├── make_cross_venv.py ├── module.modulemap.prefix ├── release.iOS.exclude ├── release.macOS.exclude ├── release.tvOS.exclude ├── release.visionOS.exclude ├── release.watchOS.exclude └── sitecustomize.py.tmpl └── make-relocatable.sh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | # Check for updates on Sunday, 8PM UTC 8 | interval: "weekly" 9 | day: "sunday" 10 | time: "20:00" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | workflow_call: 5 | inputs: 6 | build-number: 7 | description: "The build number to add to the built package" 8 | default: "custom" 9 | type: "string" 10 | outputs: 11 | PYTHON_VER: 12 | description: "The Python major.minor version." 13 | value: ${{ jobs.config.outputs.PYTHON_VER }} 14 | PYTHON_VERSION: 15 | description: "The full Python version." 16 | value: ${{ jobs.config.outputs.PYTHON_VERSION }} 17 | BZIP2_VERSION: 18 | description: "The BZip2 version used for the build." 19 | value: ${{ jobs.config.outputs.BZIP2_VERSION }} 20 | LIBFFI_VERSION: 21 | description: "The libFFI version used for the build." 22 | value: ${{ jobs.config.outputs.LIBFFI_VERSION }} 23 | MPDECIMAL_VERSION: 24 | description: "The mpdecimal version used for the build." 25 | value: ${{ jobs.config.outputs.MPDECIMAL_VERSION }} 26 | OPENSSL_VERSION: 27 | description: "The OpenSSL version used for the build." 28 | value: ${{ jobs.config.outputs.OPENSSL_VERSION }} 29 | XZ_VERSION: 30 | description: "The XZ version used for the build." 31 | value: ${{ jobs.config.outputs.XZ_VERSION }} 32 | 33 | env: 34 | FORCE_COLOR: "1" 35 | 36 | defaults: 37 | run: 38 | shell: bash 39 | 40 | # Cancel active CI runs for a PR before starting another run 41 | concurrency: 42 | group: ${{ github.ref }} 43 | cancel-in-progress: true 44 | 45 | jobs: 46 | config: 47 | runs-on: macOS-latest 48 | outputs: 49 | PYTHON_VER: ${{ steps.extract.outputs.PYTHON_VER }} 50 | PYTHON_VERSION: ${{ steps.extract.outputs.PYTHON_VERSION }} 51 | BUILD_NUMBER: ${{ steps.extract.outputs.BUILD_NUMBER }} 52 | BZIP2_VERSION: ${{ steps.extract.outputs.BZIP2_VERSION }} 53 | LIBFFI_VERSION: ${{ steps.extract.outputs.LIBFFI_VERSION }} 54 | MPDECIMAL_VERSION: ${{ steps.extract.outputs.MPDECIMAL_VERSION }} 55 | OPENSSL_VERSION: ${{ steps.extract.outputs.OPENSSL_VERSION }} 56 | XZ_VERSION: ${{ steps.extract.outputs.XZ_VERSION }} 57 | 58 | steps: 59 | - uses: actions/checkout@v4.1.7 60 | 61 | - name: Extract config variables 62 | id: extract 63 | run: | 64 | PYTHON_VER=$(make config | grep "PYTHON_VER=" | cut -d "=" -f 2) 65 | PYTHON_VERSION=$(make config | grep "PYTHON_VERSION=" | cut -d "=" -f 2) 66 | BZIP2_VERSION=$(make config | grep "BZIP2_VERSION=" | cut -d "=" -f 2) 67 | LIBFFI_VERSION=$(make config | grep "LIBFFI_VERSION=" | cut -d "=" -f 2) 68 | MPDECIMAL_VERSION=$(make config | grep "MPDECIMAL_VERSION=" | cut -d "=" -f 2) 69 | OPENSSL_VERSION=$(make config | grep "OPENSSL_VERSION=" | cut -d "=" -f 2) 70 | XZ_VERSION=$(make config | grep "XZ_VERSION=" | cut -d "=" -f 2) 71 | if [ -z "${{ inputs.build-number }}" ]; then 72 | BUILD_NUMBER=custom 73 | else 74 | BUILD_NUMBER=${{ inputs.build-number }} 75 | fi 76 | 77 | echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} 78 | echo "PYTHON_VERSION=${PYTHON_VERSION}" | tee -a ${GITHUB_OUTPUT} 79 | echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} 80 | echo "BZIP2_VERSION=${BZIP2_VERSION}" | tee -a ${GITHUB_OUTPUT} 81 | echo "LIBFFI_VERSION=${LIBFFI_VERSION}" | tee -a ${GITHUB_OUTPUT} 82 | echo "MPDECIMAL_VERSION=${MPDECIMAL_VERSION}" | tee -a ${GITHUB_OUTPUT} 83 | echo "OPENSSL_VERSION=${OPENSSL_VERSION}" | tee -a ${GITHUB_OUTPUT} 84 | echo "XZ_VERSION=${XZ_VERSION}" | tee -a ${GITHUB_OUTPUT} 85 | 86 | build: 87 | runs-on: macOS-latest 88 | needs: [ config ] 89 | strategy: 90 | fail-fast: false 91 | matrix: 92 | target: ['macOS', 'iOS', 'tvOS', 'watchOS', 'visionOS'] 93 | 94 | steps: 95 | - uses: actions/checkout@v4.1.7 96 | 97 | - name: Set up Python 98 | uses: actions/setup-python@v5.6.0 99 | with: 100 | # Appending -dev ensures that we can always build the dev release. 101 | # It's a no-op for versions that have been published. 102 | python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev 103 | # Ensure that we *always* use the latest build, not a cached version. 104 | # It's an edge case, but when a new alpha is released, we need to use it ASAP. 105 | check-latest: true 106 | 107 | - name: Build ${{ matrix.target }} 108 | run: | 109 | # Do the build for the requested target. 110 | make ${{ matrix.target }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} 111 | 112 | - name: Upload build artefacts 113 | uses: actions/upload-artifact@v4.6.2 114 | with: 115 | name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz 116 | path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz 117 | 118 | briefcase-testbed: 119 | name: Briefcase testbed (${{ matrix.target }}) 120 | runs-on: macOS-latest 121 | needs: [ config, build ] 122 | strategy: 123 | fail-fast: false 124 | matrix: 125 | target: ["macOS", "iOS"] 126 | include: 127 | - briefcase-run-args: 128 | 129 | - target: iOS 130 | briefcase-run-args: ' -d "iPhone SE (3rd generation)"' 131 | 132 | steps: 133 | - uses: actions/checkout@v4.1.7 134 | 135 | - name: Get build artifact 136 | uses: actions/download-artifact@v4.3.0 137 | with: 138 | pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz 139 | path: dist 140 | merge-multiple: true 141 | 142 | - name: Set up Python 143 | uses: actions/setup-python@v5.6.0 144 | with: 145 | # Appending -dev ensures that we can always build the dev release. 146 | # It's a no-op for versions that have been published. 147 | python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev 148 | # Ensure that we *always* use the latest build, not a cached version. 149 | # It's an edge case, but when a new alpha is released, we need to use it ASAP. 150 | check-latest: true 151 | 152 | - uses: actions/checkout@v4.1.7 153 | with: 154 | repository: beeware/Python-support-testbed 155 | path: Python-support-testbed 156 | 157 | - name: Install dependencies 158 | run: | 159 | # Use the development version of Briefcase 160 | python -m pip install git+https://github.com/beeware/briefcase.git 161 | 162 | - name: Run support testbed check 163 | timeout-minutes: 10 164 | working-directory: Python-support-testbed 165 | run: briefcase run ${{ matrix.target }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\' 166 | 167 | cpython-testbed: 168 | name: CPython testbed (${{ matrix.target }}) 169 | runs-on: macOS-latest 170 | needs: [ config, build ] 171 | strategy: 172 | fail-fast: false 173 | matrix: 174 | target: ["iOS", "visionOS"] 175 | 176 | steps: 177 | - uses: actions/checkout@v4.1.7 178 | 179 | - name: Get build artifact 180 | uses: actions/download-artifact@v4.3.0 181 | with: 182 | pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz 183 | path: dist 184 | merge-multiple: true 185 | 186 | - name: Set up Python 187 | uses: actions/setup-python@v5.6.0 188 | with: 189 | # Appending -dev ensures that we can always build the dev release. 190 | # It's a no-op for versions that have been published. 191 | python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev 192 | # Ensure that we *always* use the latest build, not a cached version. 193 | # It's an edge case, but when a new alpha is released, we need to use it ASAP. 194 | check-latest: true 195 | 196 | - name: Unpack support package 197 | run: | 198 | mkdir support 199 | cd support 200 | tar zxvf ../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz 201 | 202 | - name: Run CPython testbed 203 | timeout-minutes: 10 204 | working-directory: support 205 | run: | 206 | # Run a representative subset of CPython core tests: 207 | # - test_builtin as a test of core language tools 208 | # - test_grammar as a test of core language features 209 | # - test_os as a test of system library calls 210 | # - test_bz2 as a simple test of third party libraries 211 | # - test_ctypes as a test of FFI 212 | python -m testbed run -- test --single-process --rerun -W test_builtin test_grammar test_os test_bz2 test_ctypes 213 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish support package 2 | 3 | on: 4 | release: 5 | types: published 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Set up Python environment 14 | uses: actions/setup-python@v5.6.0 15 | with: 16 | python-version: "3.X" 17 | 18 | - name: Set build variables 19 | id: build-vars 20 | env: 21 | TAG_NAME: ${{ github.ref }} 22 | run: | 23 | TAG=$(basename $TAG_NAME) 24 | PYTHON_VER="${TAG%-*}" 25 | BUILD_NUMBER="${TAG#*-}" 26 | 27 | echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} 28 | echo "PYTHON_VER=${PYTHON_VER}" | tee -a ${GITHUB_OUTPUT} 29 | echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} 30 | 31 | - name: Update Release Asset to S3 32 | env: 33 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 34 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 35 | run: | 36 | python -m pip install -U pip 37 | python -m pip install -U setuptools 38 | python -m pip install awscli 39 | # macOS build 40 | 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 41 | 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 42 | # iOS build 43 | 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 44 | 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 45 | # tvOS build 46 | 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 47 | 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 48 | # watchOS build 49 | 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 50 | 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 51 | # visionOS build 52 | 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 53 | 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 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # This is the DEV workflow. 2 | # Run this Action on creating a new tag matching "-b" 3 | # e.g., 3.7-b1 4 | name: Build support package 5 | on: 6 | push: 7 | tags: 8 | - '*-b*' 9 | 10 | jobs: 11 | config: 12 | name: Build vars 13 | runs-on: macOS-latest 14 | outputs: 15 | TAG: ${{ steps.build-vars.outputs.TAG }} 16 | BUILD_NUMBER: ${{ steps.build-vars.outputs.BUILD_NUMBER }} 17 | 18 | steps: 19 | - name: Set Build Variables 20 | id: build-vars 21 | env: 22 | TAG_NAME: ${{ github.ref }} 23 | run: | 24 | export TAG=$(basename $TAG_NAME) 25 | export BUILD_NUMBER="${TAG#*-}" 26 | 27 | echo "TAG=${TAG}" | tee -a ${GITHUB_OUTPUT} 28 | echo "BUILD_NUMBER=${BUILD_NUMBER}" | tee -a ${GITHUB_OUTPUT} 29 | 30 | ci: 31 | name: CI 32 | needs: [ config ] 33 | uses: ./.github/workflows/ci.yaml 34 | with: 35 | build-number: ${{ needs.config.outputs.BUILD_NUMBER }} 36 | 37 | make-release: 38 | name: Make Release 39 | runs-on: ubuntu-latest 40 | needs: [ config, ci ] 41 | steps: 42 | - name: Get build artifacts 43 | uses: actions/download-artifact@v4.3.0 44 | with: 45 | pattern: Python-* 46 | path: dist 47 | merge-multiple: true 48 | 49 | - name: Create Release 50 | uses: ncipollo/release-action@v1.16.0 51 | with: 52 | name: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} 53 | tag: ${{ needs.ci.outputs.PYTHON_VER }}-${{ needs.config.outputs.BUILD_NUMBER }} 54 | draft: true 55 | body: | 56 | Build ${{ needs.config.outputs.BUILD_NUMBER }} of the BeeWare support package for Python ${{ needs.ci.outputs.PYTHON_VER }}. 57 | 58 | Includes: 59 | * Python ${{ needs.ci.outputs.PYTHON_VERSION }} 60 | * BZip2 ${{ needs.ci.outputs.BZIP2_VERSION }} 61 | * libFFI ${{ needs.ci.outputs.LIBFFI_VERSION }} 62 | * mpdecimal ${{ needs.ci.outputs.MPDECIMAL_VERSION }} 63 | * OpenSSL ${{ needs.ci.outputs.OPENSSL_VERSION }} 64 | * XZ ${{ needs.ci.outputs.XZ_VERSION }} 65 | artifacts: "dist/*" 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swo 2 | *.swp 3 | .envrc 4 | .vscode/ 5 | build 6 | dist 7 | downloads 8 | install 9 | local 10 | support 11 | *.dist-info 12 | __pycache__ 13 | tests/testbed/macOS 14 | tests/testbed/iOS 15 | *.log 16 | *.gz 17 | *.DS_Store 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | BeeWare <3's contributions! 4 | 5 | Please be aware that BeeWare operates under a [Code of 6 | Conduct](https://beeware.org/community/behavior/code-of-conduct/). 7 | 8 | See [CONTRIBUTING to BeeWare](https://beeware.org/contributing) for general 9 | project contribution guidelines. 10 | 11 | Unless a fix is version specific, PRs should genereally be made against the 12 | `main` branch of this repo, targeting the current development version of Python. 13 | Project maintainers will manage the process of backporting changes to older 14 | Python versions. 15 | 16 | ## Changes to `Python.patch` 17 | 18 | Additional handling is required if you need to make modifications to the patch 19 | applied to Python sources (`patch/Python/Python.patch`). 20 | 21 | Any iOS or macOS-specific changes should be submitted to the [upstream CPython 22 | repository](https://github.com/python/cpython). macOS and iOS are both 23 | officially supported Python platforms, and the code distributed by this project 24 | for those platforms is unmodified from the official repository. 25 | 26 | Changes to to support other platforms can be included in a PR for this repo, but 27 | they must also be submitted as a pull request against the `MAJOR.MINOR-patched` 28 | branch on [the `freakboy3742` fork of the CPython 29 | repo](https://github.com/freakboy3742/cpython). This is required to ensure that 30 | any contributed changes can be easily reproduced in future patches as more 31 | changes are made. 32 | 33 | Note that the `MAJOR.MINOR-patched` branch of that fork is maintained in the format 34 | of a *patch tree*, which is a branch that consists of an entirely linear sequence of 35 | commits applied on top of another branch (in the case of the fork, `MAJOR.MINOR`), 36 | each of which adds a significant new feature. Therefore, a bug fix for an existing commit 37 | in the patch tree *will* be merged when appropriate, but its changes will get combined 38 | with that existing commit that adds the feature. A feature addition PR will be squashed 39 | into a single, new commit, and then put on top of the patch tree. 40 | 41 | This also means that if another contributor gets a pull request merged into 42 | `MAJOR.MINOR-patched`, you must *rebase* your changes on top of the updated 43 | `MAJOR.MINOR-patched` branch, as opposed to *merging* `MAJOR.MINOR-patched` into your 44 | branch, since the "history" of a patch tree is likely to change in a way that is 45 | incompatible with merge commits. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Russell Keith-Magee. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Useful targets: 3 | # - all - build everything 4 | # - macOS - build everything for macOS 5 | # - iOS - build everything for iOS 6 | # - tvOS - build everything for tvOS 7 | # - watchOS - build everything for watchOS 8 | # - visionOS - build everything for visionOS 9 | 10 | # Current directory 11 | PROJECT_DIR=$(shell pwd) 12 | 13 | BUILD_NUMBER=custom 14 | 15 | # Version of packages that will be compiled by this meta-package 16 | # PYTHON_VERSION is the full version number (e.g., 3.10.0b3) 17 | # PYTHON_PKG_VERSION is the version number with binary package releases to use 18 | # for macOS binaries. This will be less than PYTHON_VERSION towards the end 19 | # of a release cycle, as official binaries won't be published. 20 | # PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0) 21 | # PYTHON_VER is the major/minor version (e.g., 3.10) 22 | PYTHON_VERSION=3.14.0b2 23 | PYTHON_PKG_VERSION=$(PYTHON_VERSION) 24 | PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+") 25 | PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+") 26 | PYTHON_VER=$(basename $(PYTHON_VERSION)) 27 | 28 | # The binary releases of dependencies, published at: 29 | # https://github.com/beeware/cpython-apple-source-deps/releases 30 | BZIP2_VERSION=1.0.8-2 31 | LIBFFI_VERSION=3.4.7-2 32 | MPDECIMAL_VERSION=4.0.0-2 33 | OPENSSL_VERSION=3.0.16-2 34 | XZ_VERSION=5.6.4-2 35 | 36 | # Supported OS 37 | OS_LIST=macOS iOS tvOS watchOS visionOS 38 | 39 | CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar 40 | 41 | # macOS targets 42 | TARGETS-macOS=macosx.x86_64 macosx.arm64 43 | TRIPLE_OS-macOS=macos 44 | VERSION_MIN-macOS=11.0 45 | 46 | # iOS targets 47 | TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64 48 | TRIPLE_OS-iOS=ios 49 | VERSION_MIN-iOS=13.0 50 | 51 | # tvOS targets 52 | TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64 53 | TRIPLE_OS-tvOS=tvos 54 | VERSION_MIN-tvOS=12.0 55 | 56 | # watchOS targets 57 | TARGETS-watchOS=watchsimulator.x86_64 watchsimulator.arm64 watchos.arm64_32 58 | TRIPLE_OS-watchOS=watchos 59 | VERSION_MIN-watchOS=4.0 60 | 61 | TARGETS-visionOS=xrsimulator.arm64 xros.arm64 62 | TRIPLE_OS-visionOS=xros 63 | VERSION_MIN-visionOS=2.0 64 | 65 | # The architecture of the machine doing the build 66 | HOST_ARCH=$(shell uname -m) 67 | HOST_PYTHON=$(shell which python$(PYTHON_VER)) 68 | 69 | # Force the path to be minimal. This ensures that anything in the user environment 70 | # (in particular, homebrew and user-provided Python installs) aren't inadvertently 71 | # linked into the support package. 72 | PATH=/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin 73 | 74 | # Build for all operating systems 75 | all: $(OS_LIST) 76 | 77 | .PHONY: \ 78 | all clean distclean update-patch vars config \ 79 | $(foreach os,$(OS_LIST),$(os) clean-$(os) dev-clean-$(os) vars-$(os)) \ 80 | $(foreach os,$(OS_LIST),$(foreach sdk,$$(sort $$(basename $$(TARGETS-$(os)))),$(sdk) vars-$(sdk))) 81 | $(foreach os,$(OS_LIST),$(foreach target,$$(TARGETS-$(os)),$(target) vars-$(target))) 82 | 83 | # Full clean - includes all downloaded products 84 | distclean: clean 85 | rm -rf downloads build dist install support 86 | 87 | update-patch: 88 | # Generate a diff from the clone of the python/cpython Github repository, 89 | # comparing between the current state of the 3.X branch against the v3.X.Y 90 | # tag associated with the release being built. This allows you to 91 | # maintain a branch that contains custom patches against the default Python. 92 | # The patch archived in this respository is based on github.com/freakboy3742/cpython 93 | # Requires patchutils (installable via `brew install patchutils`); this 94 | # also means we need to re-introduce homebrew to the path for the filterdiff 95 | # call 96 | 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 97 | cd $(PYTHON_REPO_DIR) && \ 98 | git diff -D v$(PYTHON_VERSION) $(PYTHON_VER)-patched \ 99 | | PATH="/usr/local/bin:/opt/homebrew/bin:$(PATH)" filterdiff \ 100 | -X $(PROJECT_DIR)/patch/Python/diff.exclude -p 1 --clean \ 101 | > $(PROJECT_DIR)/patch/Python/Python.patch 102 | 103 | ########################################################################### 104 | # Setup: Python 105 | ########################################################################### 106 | 107 | downloads/Python-$(PYTHON_VERSION).tar.gz: 108 | @echo ">>> Download Python sources" 109 | mkdir -p downloads 110 | curl $(CURL_FLAGS) -o $@ \ 111 | https://www.python.org/ftp/python/$(PYTHON_MICRO_VERSION)/Python-$(PYTHON_VERSION).tgz 112 | 113 | downloads/python-$(PYTHON_PKG_VERSION)-macos11.pkg: 114 | @echo ">>> Download macOS Python package" 115 | mkdir -p downloads 116 | curl $(CURL_FLAGS) -o $@ \ 117 | https://www.python.org/ftp/python/$(PYTHON_PKG_MICRO_VERSION)/python-$(PYTHON_PKG_VERSION)-macos11.pkg 118 | 119 | ########################################################################### 120 | # Build for specified target (from $(TARGETS-*)) 121 | ########################################################################### 122 | # 123 | # Parameters: 124 | # - $1 - target (e.g., iphonesimulator.x86_64, iphoneos.arm64) 125 | # - $2 - OS (e.g., iOS, tvOS) 126 | # 127 | ########################################################################### 128 | define build-target 129 | target=$1 130 | os=$2 131 | 132 | OS_LOWER-$(target)=$(shell echo $(os) | tr '[:upper:]' '[:lower:]') 133 | 134 | # $(target) can be broken up into is composed of $(SDK).$(ARCH) 135 | SDK-$(target)=$$(basename $(target)) 136 | ARCH-$(target)=$$(subst .,,$$(suffix $(target))) 137 | 138 | ifneq ($(os),macOS) 139 | ifeq ($$(findstring simulator,$$(SDK-$(target))),) 140 | TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(TRIPLE_OS-$(os))$$(VERSION_MIN-$(os)) 141 | IS_SIMULATOR-$(target)=False 142 | else 143 | TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(TRIPLE_OS-$(os))$$(VERSION_MIN-$(os))-simulator 144 | IS_SIMULATOR-$(target)=True 145 | endif 146 | endif 147 | 148 | SDK_ROOT-$(target)=$$(shell xcrun --sdk $$(SDK-$(target)) --show-sdk-path) 149 | 150 | ########################################################################### 151 | # Target: BZip2 152 | ########################################################################### 153 | 154 | BZIP2_INSTALL-$(target)=$(PROJECT_DIR)/install/$(os)/$(target)/bzip2-$(BZIP2_VERSION) 155 | BZIP2_LIB-$(target)=$$(BZIP2_INSTALL-$(target))/lib/libbz2.a 156 | 157 | downloads/bzip2-$(BZIP2_VERSION)-$(target).tar.gz: 158 | @echo ">>> Download BZip2 for $(target)" 159 | mkdir -p downloads 160 | curl $(CURL_FLAGS) -o $$@ \ 161 | https://github.com/beeware/cpython-apple-source-deps/releases/download/BZip2-$(BZIP2_VERSION)/bzip2-$(BZIP2_VERSION)-$(target).tar.gz 162 | 163 | $$(BZIP2_LIB-$(target)): downloads/bzip2-$(BZIP2_VERSION)-$(target).tar.gz 164 | @echo ">>> Install BZip2 for $(target)" 165 | mkdir -p $$(BZIP2_INSTALL-$(target)) 166 | cd $$(BZIP2_INSTALL-$(target)) && tar zxvf $(PROJECT_DIR)/downloads/bzip2-$(BZIP2_VERSION)-$(target).tar.gz --exclude="*.dylib" 167 | # Ensure the target is marked as clean. 168 | touch $$(BZIP2_LIB-$(target)) 169 | 170 | ########################################################################### 171 | # Target: XZ (LZMA) 172 | ########################################################################### 173 | 174 | XZ_INSTALL-$(target)=$(PROJECT_DIR)/install/$(os)/$(target)/xz-$(XZ_VERSION) 175 | XZ_LIB-$(target)=$$(XZ_INSTALL-$(target))/lib/liblzma.a 176 | 177 | downloads/xz-$(XZ_VERSION)-$(target).tar.gz: 178 | @echo ">>> Download XZ for $(target)" 179 | mkdir -p downloads 180 | curl $(CURL_FLAGS) -o $$@ \ 181 | https://github.com/beeware/cpython-apple-source-deps/releases/download/XZ-$(XZ_VERSION)/xz-$(XZ_VERSION)-$(target).tar.gz 182 | 183 | $$(XZ_LIB-$(target)): downloads/xz-$(XZ_VERSION)-$(target).tar.gz 184 | @echo ">>> Install XZ for $(target)" 185 | mkdir -p $$(XZ_INSTALL-$(target)) 186 | cd $$(XZ_INSTALL-$(target)) && tar zxvf $(PROJECT_DIR)/downloads/xz-$(XZ_VERSION)-$(target).tar.gz --exclude="*.dylib" 187 | # Ensure the target is marked as clean. 188 | touch $$(XZ_LIB-$(target)) 189 | 190 | ########################################################################### 191 | # Target: mpdecimal 192 | ########################################################################### 193 | 194 | MPDECIMAL_INSTALL-$(target)=$(PROJECT_DIR)/install/$(os)/$(target)/mpdecimal-$(MPDECIMAL_VERSION) 195 | MPDECIMAL_LIB-$(target)=$$(MPDECIMAL_INSTALL-$(target))/lib/libmpdec.a 196 | 197 | downloads/mpdecimal-$(MPDECIMAL_VERSION)-$(target).tar.gz: 198 | @echo ">>> Download mpdecimal for $(target)" 199 | mkdir -p downloads 200 | curl $(CURL_FLAGS) -o $$@ \ 201 | https://github.com/beeware/cpython-apple-source-deps/releases/download/mpdecimal-$(MPDECIMAL_VERSION)/mpdecimal-$(MPDECIMAL_VERSION)-$(target).tar.gz 202 | 203 | $$(MPDECIMAL_LIB-$(target)): downloads/mpdecimal-$(MPDECIMAL_VERSION)-$(target).tar.gz 204 | @echo ">>> Install mpdecimal for $(target)" 205 | mkdir -p $$(MPDECIMAL_INSTALL-$(target)) 206 | cd $$(MPDECIMAL_INSTALL-$(target)) && tar zxvf $(PROJECT_DIR)/downloads/mpdecimal-$(MPDECIMAL_VERSION)-$(target).tar.gz --exclude="*.dylib" 207 | # Ensure the target is marked as clean. 208 | touch $$(MPDECIMAL_LIB-$(target)) 209 | 210 | ########################################################################### 211 | # Target: OpenSSL 212 | ########################################################################### 213 | 214 | OPENSSL_INSTALL-$(target)=$(PROJECT_DIR)/install/$(os)/$(target)/openssl-$(OPENSSL_VERSION) 215 | OPENSSL_SSL_LIB-$(target)=$$(OPENSSL_INSTALL-$(target))/lib/libssl.a 216 | 217 | downloads/openssl-$(OPENSSL_VERSION)-$(target).tar.gz: 218 | @echo ">>> Download OpenSSL for $(target)" 219 | mkdir -p downloads 220 | curl $(CURL_FLAGS) -o $$@ \ 221 | https://github.com/beeware/cpython-apple-source-deps/releases/download/OpenSSL-$(OPENSSL_VERSION)/openssl-$(OPENSSL_VERSION)-$(target).tar.gz 222 | 223 | $$(OPENSSL_SSL_LIB-$(target)): downloads/openssl-$(OPENSSL_VERSION)-$(target).tar.gz 224 | @echo ">>> Install OpenSSL for $(target)" 225 | mkdir -p $$(OPENSSL_INSTALL-$(target)) 226 | cd $$(OPENSSL_INSTALL-$(target)) && tar zxvf $(PROJECT_DIR)/downloads/openssl-$(OPENSSL_VERSION)-$(target).tar.gz --exclude="*.dylib" 227 | # Ensure the target is marked as clean. 228 | touch $$(OPENSSL_SSL_LIB-$(target)) 229 | 230 | ########################################################################### 231 | # Target: libFFI 232 | ########################################################################### 233 | 234 | # macOS builds use the system libFFI, so there's no need to do 235 | # a per-target build on macOS. 236 | # The configure step is performed as part of the OS-level build. 237 | ifneq ($(os),macOS) 238 | 239 | LIBFFI_INSTALL-$(target)=$(PROJECT_DIR)/install/$(os)/$(target)/libffi-$(LIBFFI_VERSION) 240 | LIBFFI_LIB-$(target)=$$(LIBFFI_INSTALL-$(target))/lib/libffi.a 241 | 242 | downloads/libffi-$(LIBFFI_VERSION)-$(target).tar.gz: 243 | @echo ">>> Download libFFI for $(target)" 244 | mkdir -p downloads 245 | curl $(CURL_FLAGS) -o $$@ \ 246 | https://github.com/beeware/cpython-apple-source-deps/releases/download/libFFI-$(LIBFFI_VERSION)/libffi-$(LIBFFI_VERSION)-$(target).tar.gz 247 | 248 | $$(LIBFFI_LIB-$(target)): downloads/libffi-$(LIBFFI_VERSION)-$(target).tar.gz 249 | @echo ">>> Install libFFI for $(target)" 250 | mkdir -p $$(LIBFFI_INSTALL-$(target)) 251 | cd $$(LIBFFI_INSTALL-$(target)) && tar zxvf $(PROJECT_DIR)/downloads/libffi-$(LIBFFI_VERSION)-$(target).tar.gz --exclude="*.dylib" 252 | # Ensure the target is marked as clean. 253 | touch $$(LIBFFI_LIB-$(target)) 254 | 255 | endif 256 | 257 | ########################################################################### 258 | # Target: Python 259 | ########################################################################### 260 | 261 | # macOS builds are compiled as a single universal2 build. 262 | # The macOS Python build is configured in the `build-sdk` macro, rather than the 263 | # `build-target` macro. However, the site-customize scripts generated here, per target. 264 | ifneq ($(os),macOS) 265 | 266 | PYTHON_SRCDIR-$(target)=build/$(os)/$(target)/python-$(PYTHON_VERSION) 267 | PYTHON_INSTALL-$(target)=$(PROJECT_DIR)/install/$(os)/$(target)/python-$(PYTHON_VERSION) 268 | PYTHON_FRAMEWORK-$(target)=$$(PYTHON_INSTALL-$(target))/Python.framework 269 | PYTHON_LIB-$(target)=$$(PYTHON_FRAMEWORK-$(target))/Python 270 | PYTHON_BIN-$(target)=$$(PYTHON_INSTALL-$(target))/bin 271 | PYTHON_INCLUDE-$(target)=$$(PYTHON_FRAMEWORK-$(target))/Headers 272 | PYTHON_STDLIB-$(target)=$$(PYTHON_INSTALL-$(target))/lib/python$(PYTHON_VER) 273 | PYTHON_PLATFORM_CONFIG-$(target)=$$(PYTHON_INSTALL-$(target))/platform-config/$$(ARCH-$(target))-$$(SDK-$(target)) 274 | PYTHON_PLATFORM_SITECUSTOMIZE-$(target)=$$(PYTHON_PLATFORM_CONFIG-$(target))/sitecustomize.py 275 | 276 | 277 | $$(PYTHON_SRCDIR-$(target))/configure: \ 278 | downloads/Python-$(PYTHON_VERSION).tar.gz \ 279 | $$(BZIP2_LIB-$(target)) \ 280 | $$(LIBFFI_LIB-$(target)) \ 281 | $$(MPDECIMAL_LIB-$(target)) \ 282 | $$(OPENSSL_SSL_LIB-$(target)) \ 283 | $$(XZ_LIB-$(target)) 284 | @echo ">>> Unpack and configure Python for $(target)" 285 | mkdir -p $$(PYTHON_SRCDIR-$(target)) 286 | tar zxf downloads/Python-$(PYTHON_VERSION).tar.gz --strip-components 1 -C $$(PYTHON_SRCDIR-$(target)) 287 | # Apply target Python patches 288 | cd $$(PYTHON_SRCDIR-$(target)) && patch -p1 < $(PROJECT_DIR)/patch/Python/Python.patch 289 | # Make sure the binary scripts are executable 290 | chmod 755 $$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin/* 291 | # Touch the configure script to ensure that Make identifies it as up to date. 292 | touch $$(PYTHON_SRCDIR-$(target))/configure 293 | 294 | $$(PYTHON_SRCDIR-$(target))/Makefile: \ 295 | $$(PYTHON_SRCDIR-$(target))/configure 296 | # Configure target Python 297 | cd $$(PYTHON_SRCDIR-$(target)) && \ 298 | PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ 299 | ./configure \ 300 | LIBLZMA_CFLAGS="-I$$(XZ_INSTALL-$(target))/include" \ 301 | LIBLZMA_LIBS="-L$$(XZ_INSTALL-$(target))/lib -llzma" \ 302 | BZIP2_CFLAGS="-I$$(BZIP2_INSTALL-$(target))/include" \ 303 | BZIP2_LIBS="-L$$(BZIP2_INSTALL-$(target))/lib -lbz2" \ 304 | LIBMPDEC_CFLAGS="-I$$(MPDECIMAL_INSTALL-$(target))/include" \ 305 | LIBMPDEC_LIBS="-L$$(MPDECIMAL_INSTALL-$(target))/lib -lmpdec" \ 306 | LIBFFI_CFLAGS="-I$$(LIBFFI_INSTALL-$(target))/include" \ 307 | LIBFFI_LIBS="-L$$(LIBFFI_INSTALL-$(target))/lib -lffi" \ 308 | --host=$$(TARGET_TRIPLE-$(target)) \ 309 | --build=$(HOST_ARCH)-apple-darwin \ 310 | --with-build-python=$(HOST_PYTHON) \ 311 | --enable-ipv6 \ 312 | --with-openssl="$$(OPENSSL_INSTALL-$(target))" \ 313 | --enable-framework="$$(PYTHON_INSTALL-$(target))" \ 314 | --with-system-libmpdec \ 315 | 2>&1 | tee -a ../python-$(PYTHON_VERSION).config.log 316 | 317 | $$(PYTHON_SRCDIR-$(target))/python.exe: $$(PYTHON_SRCDIR-$(target))/Makefile 318 | @echo ">>> Build Python for $(target)" 319 | cd $$(PYTHON_SRCDIR-$(target)) && \ 320 | PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ 321 | make -j8 all \ 322 | 2>&1 | tee -a ../python-$(PYTHON_VERSION).build.log 323 | 324 | $$(PYTHON_LIB-$(target)): $$(PYTHON_SRCDIR-$(target))/python.exe 325 | @echo ">>> Install Python for $(target)" 326 | cd $$(PYTHON_SRCDIR-$(target)) && \ 327 | PATH="$(PROJECT_DIR)/$$(PYTHON_SRCDIR-$(target))/$(os)/Resources/bin:$(PATH)" \ 328 | make install \ 329 | 2>&1 | tee -a ../python-$(PYTHON_VERSION).install.log 330 | 331 | # Remove any .orig files produced by the compliance patching process 332 | find $$(PYTHON_INSTALL-$(target)) -name "*.orig" -exec rm {} \; 333 | 334 | 335 | $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)): 336 | @echo ">>> Create cross-plaform config for $(target)" 337 | mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(target)) 338 | # Create the cross-platform site definition 339 | echo "import _cross_$$(ARCH-$(target))_$$(SDK-$(target)); import _cross_venv;" \ 340 | > $$(PYTHON_PLATFORM_CONFIG-$(target))/_cross_venv.pth 341 | cp $(PROJECT_DIR)/patch/Python/make_cross_venv.py \ 342 | $$(PYTHON_PLATFORM_CONFIG-$(target))/make_cross_venv.py 343 | cp $(PROJECT_DIR)/patch/Python/_cross_venv.py \ 344 | $$(PYTHON_PLATFORM_CONFIG-$(target))/_cross_venv.py 345 | cp $$(PYTHON_STDLIB-$(target))/_sysconfig* \ 346 | $$(PYTHON_PLATFORM_CONFIG-$(target)) 347 | cat $(PROJECT_DIR)/patch/Python/_cross_target.py.tmpl \ 348 | | sed -e "s/{{os}}/$(os)/g" \ 349 | | sed -e "s/{{platform}}/$$(OS_LOWER-$(target))/g" \ 350 | | sed -e "s/{{arch}}/$$(ARCH-$(target))/g" \ 351 | | sed -e "s/{{sdk}}/$$(SDK-$(target))/g" \ 352 | | sed -e "s/{{version_min}}/$$(VERSION_MIN-$(os))/g" \ 353 | | sed -e "s/{{is_simulator}}/$$(IS_SIMULATOR-$(target))/g" \ 354 | > $$(PYTHON_PLATFORM_CONFIG-$(target))/_cross_$$(ARCH-$(target))_$$(SDK-$(target)).py 355 | cat $(PROJECT_DIR)/patch/Python/sitecustomize.py.tmpl \ 356 | | sed -e "s/{{arch}}/$$(ARCH-$(target))/g" \ 357 | | sed -e "s/{{sdk}}/$$(SDK-$(target))/g" \ 358 | > $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)) 359 | 360 | endif 361 | 362 | $(target): $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target)) $$(PYTHON_LIB-$(target)) 363 | 364 | ########################################################################### 365 | # Target: Debug 366 | ########################################################################### 367 | 368 | vars-$(target): 369 | @echo ">>> Environment variables for $(target)" 370 | @echo "SDK-$(target): $$(SDK-$(target))" 371 | @echo "ARCH-$(target): $$(ARCH-$(target))" 372 | @echo "TARGET_TRIPLE-$(target): $$(TARGET_TRIPLE-$(target))" 373 | @echo "SDK_ROOT-$(target): $$(SDK_ROOT-$(target))" 374 | @echo "BZIP2_INSTALL-$(target): $$(BZIP2_INSTALL-$(target))" 375 | @echo "BZIP2_LIB-$(target): $$(BZIP2_LIB-$(target))" 376 | @echo "LIBFFI_INSTALL-$(target): $$(LIBFFI_INSTALL-$(target))" 377 | @echo "LIBFFI_LIB-$(target): $$(LIBFFI_LIB-$(target))" 378 | @echo "MPDECIMAL_INSTALL-$(target): $$(MPDECIMAL_INSTALL-$(target))" 379 | @echo "MPDECIMAL_LIB-$(target): $$(MPDECIMAL_LIB-$(target))" 380 | @echo "OPENSSL_INSTALL-$(target): $$(OPENSSL_INSTALL-$(target))" 381 | @echo "OPENSSL_SSL_LIB-$(target): $$(OPENSSL_SSL_LIB-$(target))" 382 | @echo "XZ_INSTALL-$(target): $$(XZ_INSTALL-$(target))" 383 | @echo "XZ_LIB-$(target): $$(XZ_LIB-$(target))" 384 | @echo "PYTHON_SRCDIR-$(target): $$(PYTHON_SRCDIR-$(target))" 385 | @echo "PYTHON_INSTALL-$(target): $$(PYTHON_INSTALL-$(target))" 386 | @echo "PYTHON_FRAMEWORK-$(target): $$(PYTHON_FRAMEWORK-$(target))" 387 | @echo "PYTHON_LIB-$(target): $$(PYTHON_LIB-$(target))" 388 | @echo "PYTHON_BIN-$(target): $$(PYTHON_BIN-$(target))" 389 | @echo "PYTHON_INCLUDE-$(target): $$(PYTHON_INCLUDE-$(target))" 390 | @echo "PYTHON_STDLIB-$(target): $$(PYTHON_STDLIB-$(target))" 391 | @echo "PYTHON_PLATFORM_CONFIG-$(target): $$(PYTHON_PLATFORM_CONFIG-$(target))" 392 | @echo "PYTHON_PLATFORM_SITECUSTOMIZE-$(target): $$(PYTHON_PLATFORM_SITECUSTOMIZE-$(target))" 393 | @echo 394 | 395 | endef # build-target 396 | 397 | ########################################################################### 398 | # Build for specified sdk (extracted from the base names in $(TARGETS-*)) 399 | ########################################################################### 400 | # 401 | # Parameters: 402 | # - $1 sdk (e.g., iphoneos, iphonesimulator) 403 | # - $2 OS (e.g., iOS, tvOS) 404 | # 405 | ########################################################################### 406 | define build-sdk 407 | sdk=$1 408 | os=$2 409 | 410 | SDK_TARGETS-$(sdk)=$$(filter $(sdk).%,$$(TARGETS-$(os))) 411 | SDK_ARCHES-$(sdk)=$$(sort $$(subst .,,$$(suffix $$(SDK_TARGETS-$(sdk))))) 412 | 413 | ifeq ($$(findstring simulator,$(sdk)),) 414 | SDK_SLICE-$(sdk)=$$(TRIPLE_OS-$(os))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g") 415 | else 416 | SDK_SLICE-$(sdk)=$$(TRIPLE_OS-$(os))-$$(shell echo $$(SDK_ARCHES-$(sdk)) | sed "s/ /_/g")-simulator 417 | endif 418 | 419 | # Expand the build-target macro for target on this OS 420 | $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(eval $$(call build-target,$$(target),$(os)))) 421 | 422 | ########################################################################### 423 | # SDK: Python 424 | ########################################################################### 425 | 426 | 427 | ifeq ($(os),macOS) 428 | # macOS builds are extracted from the official installer package, then 429 | # reprocessed into an XCFramework. 430 | 431 | PYTHON_INSTALL-$(sdk)=$(PROJECT_DIR)/install/$(os)/$(sdk)/python-$(PYTHON_VERSION) 432 | PYTHON_FRAMEWORK-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/Python.framework 433 | PYTHON_INSTALL_VERSION-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Versions/$(PYTHON_VER) 434 | PYTHON_LIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/Python 435 | PYTHON_INCLUDE-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/include/python$(PYTHON_VER) 436 | PYTHON_MODULEMAP-$(sdk)=$$(PYTHON_INCLUDE-$(sdk))/module.modulemap 437 | PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL_VERSION-$(sdk))/lib/python$(PYTHON_VER) 438 | 439 | else 440 | # Non-macOS builds need to be merged on a per-SDK basis. The merge covers: 441 | # * Merging a fat libPython 442 | # * Installing an architecture-sensitive pyconfig.h 443 | # * Merging fat versions of the standard library lib-dynload folder 444 | # The non-macOS frameworks don't use the versioning structure. 445 | 446 | PYTHON_INSTALL-$(sdk)=$(PROJECT_DIR)/install/$(os)/$(sdk)/python-$(PYTHON_VERSION) 447 | PYTHON_MODULEMAP-$(sdk)=$$(PYTHON_INCLUDE-$(sdk))/module.modulemap 448 | PYTHON_FRAMEWORK-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/Python.framework 449 | PYTHON_LIB-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Python 450 | PYTHON_BIN-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/bin 451 | PYTHON_INCLUDE-$(sdk)=$$(PYTHON_FRAMEWORK-$(sdk))/Headers 452 | PYTHON_STDLIB-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/lib/python$(PYTHON_VER) 453 | PYTHON_PLATFORM_CONFIG-$(sdk)=$$(PYTHON_INSTALL-$(sdk))/platform-config 454 | 455 | $$(PYTHON_LIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(PYTHON_LIB-$$(target))) 456 | @echo ">>> Build Python fat library for the $(sdk) SDK" 457 | mkdir -p $$(dir $$(PYTHON_LIB-$(sdk))) 458 | lipo -create -output $$@ $$^ \ 459 | 2>&1 | tee -a install/$(os)/$(sdk)/python-$(PYTHON_VERSION).lipo.log 460 | # Disable dSYM production (for now) 461 | # dsymutil $$@ -o $$(PYTHON_INSTALL-$(sdk))/Python.dSYM 462 | 463 | $$(PYTHON_FRAMEWORK-$(sdk))/Info.plist: $$(PYTHON_LIB-$(sdk)) 464 | @echo ">>> Install Info.plist for the $(sdk) SDK" 465 | # Copy Info.plist as-is from the first target in the $(sdk) SDK 466 | cp -r $$(PYTHON_FRAMEWORK-$$(firstword $$(SDK_TARGETS-$(sdk))))/Info.plist $$(PYTHON_FRAMEWORK-$(sdk)) 467 | 468 | $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h: $$(PYTHON_LIB-$(sdk)) 469 | @echo ">>> Build Python fat headers for the $(sdk) SDK" 470 | # Copy binary helpers from the first target in the $(sdk) SDK 471 | cp -r $$(PYTHON_BIN-$$(firstword $$(SDK_TARGETS-$(sdk)))) $$(PYTHON_BIN-$(sdk)) 472 | 473 | # Create a non-executable stub binary python3 474 | echo "#!/bin/bash\necho Can\\'t run $(sdk) binary\nexit 1" > $$(PYTHON_BIN-$(sdk))/python$(PYTHON_VER) 475 | chmod 755 $$(PYTHON_BIN-$(sdk))/python$(PYTHON_VER) 476 | 477 | # Copy headers as-is from the first target in the $(sdk) SDK 478 | cp -r $$(PYTHON_INCLUDE-$$(firstword $$(SDK_TARGETS-$(sdk)))) $$(PYTHON_INCLUDE-$(sdk)) 479 | 480 | # Create the modulemap file 481 | cp -r patch/Python/module.modulemap.prefix $$(PYTHON_MODULEMAP-$(sdk)) 482 | echo "" >> $$(PYTHON_MODULEMAP-$(sdk)) 483 | cd $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/Include && \ 484 | find cpython -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-$(sdk)) && \ 485 | echo "" >> $$(PYTHON_MODULEMAP-$(sdk)) && \ 486 | find internal -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-$(sdk)) 487 | echo "\n}" >> $$(PYTHON_MODULEMAP-$(sdk)) 488 | 489 | # Link the PYTHONHOME version of the headers 490 | mkdir -p $$(PYTHON_INSTALL-$(sdk))/include 491 | ln -si ../Python.framework/Headers $$(PYTHON_INSTALL-$(sdk))/include/python$(PYTHON_VER) 492 | 493 | ifeq ($(os), visionOS) 494 | echo "Skipping arch-specific header copying for visionOS" 495 | else 496 | # Add the individual headers from each target in an arch-specific name 497 | $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_INCLUDE-$$(target))/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig-$$(ARCH-$$(target)).h; ) 498 | 499 | # Copy the cross-target header from the source folder of the first target in the $(sdk) SDK 500 | cp $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$(sdk))))/$(os)/Resources/pyconfig.h $$(PYTHON_INCLUDE-$(sdk))/pyconfig.h 501 | endif 502 | 503 | 504 | $$(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))) 505 | @echo ">>> Build Python stdlib for the $(sdk) SDK" 506 | mkdir -p $$(PYTHON_STDLIB-$(sdk))/lib-dynload 507 | # Copy stdlib from the first target associated with the $(sdk) SDK 508 | cp -r $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/ $$(PYTHON_STDLIB-$(sdk)) 509 | 510 | # Delete the single-SDK parts of the standard library 511 | rm -rf \ 512 | $$(PYTHON_STDLIB-$(sdk))/_sysconfigdata__*.py \ 513 | $$(PYTHON_STDLIB-$(sdk))/_sysconfig_vars__*.json \ 514 | $$(PYTHON_STDLIB-$(sdk))/config-* \ 515 | $$(PYTHON_STDLIB-$(sdk))/lib-dynload/* 516 | 517 | # Copy the individual _sysconfigdata modules into names that include the architecture 518 | $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfigdata_* $$(PYTHON_STDLIB-$(sdk))/; ) 519 | $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp $$(PYTHON_STDLIB-$$(target))/_sysconfig_vars_* $$(PYTHON_STDLIB-$(sdk))/; ) 520 | 521 | # Copy the platform site folders for each architecture 522 | mkdir -p $$(PYTHON_PLATFORM_CONFIG-$(sdk)) 523 | $$(foreach target,$$(SDK_TARGETS-$(sdk)),cp -r $$(PYTHON_PLATFORM_CONFIG-$$(target)) $$(PYTHON_PLATFORM_CONFIG-$(sdk)); ) 524 | 525 | # Merge the binary modules from each target in the $(sdk) SDK into a single binary 526 | $$(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))); ) 527 | 528 | # # Disable dSYM production (for now) 529 | # # Create dSYM files for each module 530 | # $$(foreach module,$$(wildcard $$(PYTHON_STDLIB-$$(firstword $$(SDK_TARGETS-$(sdk))))/lib-dynload/*),dsymutil $$(PYTHON_STDLIB-$(sdk))/lib-dynload/$$(notdir $$(module)); ) 531 | 532 | # Copy in known-required xcprivacy files. 533 | # Libraries linking OpenSSL must provide a privacy manifest. The one in this repository 534 | # has been sourced from https://github.com/openssl/openssl/blob/openssl-3.0/os-dep/Apple/PrivacyInfo.xcprivacy 535 | cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$(sdk))/lib-dynload/_hashlib.xcprivacy 536 | cp $(PROJECT_DIR)/patch/Python/OpenSSL.xcprivacy $$(PYTHON_STDLIB-$(sdk))/lib-dynload/_ssl.xcprivacy 537 | endif 538 | 539 | $(sdk): $$(PYTHON_STDLIB-$(sdk))/LICENSE.TXT 540 | 541 | ########################################################################### 542 | # SDK: Debug 543 | ########################################################################### 544 | 545 | vars-$(sdk): 546 | @echo ">>> Environment variables for $(sdk)" 547 | @echo "SDK_TARGETS-$(sdk): $$(SDK_TARGETS-$(sdk))" 548 | @echo "SDK_ARCHES-$(sdk): $$(SDK_ARCHES-$(sdk))" 549 | @echo "SDK_SLICE-$(sdk): $$(SDK_SLICE-$(sdk))" 550 | @echo "LDFLAGS-$(sdk): $$(LDFLAGS-$(sdk))" 551 | @echo "PYTHON_INSTALL-$(sdk): $$(PYTHON_INSTALL-$(sdk))" 552 | @echo "PYTHON_FRAMEWORK-$(sdk): $$(PYTHON_FRAMEWORK-$(sdk))" 553 | @echo "PYTHON_LIB-$(sdk): $$(PYTHON_LIB-$(sdk))" 554 | @echo "PYTHON_BIN-$(sdk): $$(PYTHON_BIN-$(sdk))" 555 | @echo "PYTHON_INCLUDE-$(sdk): $$(PYTHON_INCLUDE-$(sdk))" 556 | @echo "PYTHON_STDLIB-$(sdk): $$(PYTHON_STDLIB-$(sdk))" 557 | 558 | @echo 559 | 560 | endef # build-sdk 561 | 562 | ########################################################################### 563 | # Build for specified OS (from $(OS_LIST)) 564 | ########################################################################### 565 | # 566 | # Parameters: 567 | # - $1 - OS (e.g., iOS, tvOS) 568 | # 569 | ########################################################################### 570 | define build 571 | os=$1 572 | 573 | ########################################################################### 574 | # Build: Macro Expansions 575 | ########################################################################### 576 | 577 | SDKS-$(os)=$$(sort $$(basename $$(TARGETS-$(os)))) 578 | 579 | 580 | # Expand the build-sdk macro for all the sdks on this OS (e.g., iphoneos, iphonesimulator) 581 | $$(foreach sdk,$$(SDKS-$(os)),$$(eval $$(call build-sdk,$$(sdk),$(os)))) 582 | 583 | ########################################################################### 584 | # Build: Python 585 | ########################################################################### 586 | 587 | 588 | PYTHON_XCFRAMEWORK-$(os)=support/$(PYTHON_VER)/$(os)/Python.xcframework 589 | 590 | ifeq ($(os),macOS) 591 | 592 | PYTHON_FRAMEWORK-$(os)=$$(PYTHON_INSTALL-$(sdk))/Python.framework 593 | 594 | $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ 595 | downloads/python-$(PYTHON_PKG_VERSION)-macos11.pkg 596 | @echo ">>> Repackage macOS package as XCFramework" 597 | 598 | # Unpack .pkg file. It turns out .pkg files are readable by tar... although 599 | # their internal format is a bit of a mess. From tar's perspective, the .pkg 600 | # is a tarball that contains additional tarballs; the inner tarball has the 601 | # "payload" that is the framework. 602 | mkdir -p build/macOS/macosx/python-$(PYTHON_VERSION) 603 | tar zxf downloads/python-$(PYTHON_PKG_VERSION)-macos11.pkg -C build/macOS/macosx/python-$(PYTHON_VERSION) 604 | 605 | # Unpack payload inside .pkg file 606 | mkdir -p $$(PYTHON_FRAMEWORK-macosx) 607 | tar zxf build/macOS/macosx/python-$(PYTHON_VERSION)/Python_Framework.pkgPython_Framework.pkg/PayloadPython_Framework.pkgPython_Framework.pkg/PayloadPython_Framework.pkgPython_Framework.pkg/Payload -C $$(PYTHON_FRAMEWORK-macosx) -X patch/Python/release.macOS.exclude 608 | 609 | # Apply the App Store compliance patch 610 | patch --strip 2 --directory $$(PYTHON_INSTALL_VERSION-macosx)/lib/python$(PYTHON_VER) --input $(PROJECT_DIR)/patch/Python/app-store-compliance.patch 611 | 612 | # Remove any .orig files produced by the patching process 613 | find $$(PYTHON_INSTALL_VERSION-macosx) -name "*.orig" -exec rm {} \; 614 | 615 | # Rewrite the framework to make it standalone 616 | patch/make-relocatable.sh $$(PYTHON_INSTALL_VERSION-macosx) 2>&1 > /dev/null 617 | 618 | # Create the modulemap file 619 | cp -r patch/Python/module.modulemap.prefix $$(PYTHON_MODULEMAP-macosx) 620 | echo "" >> $$(PYTHON_MODULEMAP-macosx) 621 | cd $$(PYTHON_INCLUDE-macosx) && \ 622 | find cpython -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-macosx) && \ 623 | echo "" >> $$(PYTHON_MODULEMAP-macosx) && \ 624 | find internal -name "*.h" | sort | sed -e 's/^/ exclude header "/' | sed 's/$$$$/"/' >> $$(PYTHON_MODULEMAP-macosx) 625 | echo "\n}" >> $$(PYTHON_MODULEMAP-macosx) 626 | 627 | # Re-apply the signature on the binaries. 628 | codesign -s - --preserve-metadata=identifier,entitlements,flags,runtime -f $$(PYTHON_LIB-macosx) \ 629 | 2>&1 | tee $$(PYTHON_INSTALL-macosx)/python-$(os).codesign.log 630 | find $$(PYTHON_FRAMEWORK-macosx) -name "*.dylib" -type f -exec codesign -s - --preserve-metadata=identifier,entitlements,flags,runtime -f {} \; \ 631 | 2>&1 | tee -a $$(PYTHON_INSTALL-macosx)/python-$(os).codesign.log 632 | find $$(PYTHON_FRAMEWORK-macosx) -name "*.so" -type f -exec codesign -s - --preserve-metadata=identifier,entitlements,flags,runtime -f {} \; \ 633 | 2>&1 | tee -a $$(PYTHON_INSTALL-macosx)/python-$(os).codesign.log 634 | codesign -s - --preserve-metadata=identifier,entitlements,flags,runtime -f $$(PYTHON_FRAMEWORK-macosx) \ 635 | 2>&1 | tee -a $$(PYTHON_INSTALL-macosx)/python-$(os).codesign.log 636 | 637 | # Create XCFramework out of the extracted framework 638 | xcodebuild -create-xcframework -output $$(PYTHON_XCFRAMEWORK-$(os)) -framework $$(PYTHON_FRAMEWORK-macosx) \ 639 | 2>&1 | tee $$(PYTHON_INSTALL-macosx)/python-$(os).xcframework.log 640 | 641 | support/$(PYTHON_VER)/macOS/VERSIONS: 642 | @echo ">>> Create VERSIONS file for macOS" 643 | echo "Python version: $(PYTHON_VERSION) " > support/$(PYTHON_VER)/macOS/VERSIONS 644 | echo "Build: $(BUILD_NUMBER)" >> support/$(PYTHON_VER)/macOS/VERSIONS 645 | echo "Min macOS version: $$(VERSION_MIN-macOS)" >> support/$(PYTHON_VER)/macOS/VERSIONS 646 | 647 | dist/Python-$(PYTHON_VER)-macOS-support.$(BUILD_NUMBER).tar.gz: \ 648 | $$(PYTHON_XCFRAMEWORK-macOS)/Info.plist \ 649 | support/$(PYTHON_VER)/macOS/VERSIONS \ 650 | $$(foreach target,$$(TARGETS-macOS), $$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target))) 651 | 652 | @echo ">>> Create final distribution artefact for macOS" 653 | mkdir -p dist 654 | # Strip xattrs from the support files 655 | xattr -cr support/$(PYTHON_VER)/macOS 656 | # Build a distributable tarball 657 | tar zcvf $$@ -C support/$(PYTHON_VER)/macOS `ls -A support/$(PYTHON_VER)/macOS/` 658 | 659 | else 660 | 661 | $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist: \ 662 | $$(foreach sdk,$$(SDKS-$(os)),$$(PYTHON_STDLIB-$$(sdk))/LICENSE.TXT) 663 | @echo ">>> Create Python.XCFramework on $(os)" 664 | mkdir -p $$(dir $$(PYTHON_XCFRAMEWORK-$(os))) 665 | xcodebuild -create-xcframework \ 666 | -output $$(PYTHON_XCFRAMEWORK-$(os)) $$(foreach sdk,$$(SDKS-$(os)),-framework $$(PYTHON_FRAMEWORK-$$(sdk))) \ 667 | 2>&1 | tee -a support/$(PYTHON_VER)/python-$(os).xcframework.log 668 | 669 | @echo ">>> Install PYTHONHOME for $(os)" 670 | $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/include $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) 671 | $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/bin $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) 672 | $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/lib $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) 673 | $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/platform-config $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) 674 | # Disable dSYM production (for now) 675 | # $$(foreach sdk,$$(SDKS-$(os)),cp -r $$(PYTHON_INSTALL-$$(sdk))/Python.dSYM $$(PYTHON_XCFRAMEWORK-$(os))/$$(SDK_SLICE-$$(sdk)); ) 676 | 677 | ifeq ($(filter $(os),iOS visionOS),$(os)) 678 | @echo ">>> Clone testbed project for $(os)" 679 | $(HOST_PYTHON) $$(PYTHON_SRCDIR-$$(firstword $$(SDK_TARGETS-$$(firstword $$(SDKS-$(os))))))/$(os)/testbed clone --framework $$(PYTHON_XCFRAMEWORK-$(os)) support/$(PYTHON_VER)/$(os)/testbed 680 | endif 681 | 682 | @echo ">>> Create VERSIONS file for $(os)" 683 | echo "Python version: $(PYTHON_VERSION) " > support/$(PYTHON_VER)/$(os)/VERSIONS 684 | echo "Build: $(BUILD_NUMBER)" >> support/$(PYTHON_VER)/$(os)/VERSIONS 685 | echo "Min $(os) version: $$(VERSION_MIN-$(os))" >> support/$(PYTHON_VER)/$(os)/VERSIONS 686 | echo "---------------------" >> support/$(PYTHON_VER)/$(os)/VERSIONS 687 | echo "BZip2: $(BZIP2_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS 688 | echo "libFFI: $(LIBFFI_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS 689 | echo "mpdecimal: $(MPDECIMAL_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS 690 | echo "OpenSSL: $(OPENSSL_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS 691 | echo "XZ: $(XZ_VERSION)" >> support/$(PYTHON_VER)/$(os)/VERSIONS 692 | 693 | dist/Python-$(PYTHON_VER)-$(os)-support.$(BUILD_NUMBER).tar.gz: \ 694 | $$(PYTHON_XCFRAMEWORK-$(os))/Info.plist \ 695 | $$(foreach target,$$(TARGETS-$(os)), $$(PYTHON_PLATFORM_SITECUSTOMIZE-$$(target))) 696 | 697 | @echo ">>> Create final distribution artefact for $(os)" 698 | mkdir -p dist 699 | # Build a distributable tarball 700 | tar zcvf $$@ -X patch/Python/release.$(os).exclude -C support/$(PYTHON_VER)/$(os) `ls -A support/$(PYTHON_VER)/$(os)/` 701 | 702 | endif 703 | 704 | clean-$(os): 705 | @echo ">>> Clean Python build products on $(os)" 706 | rm -rf \ 707 | build/$(os)/*/python-$(PYTHON_VER)* \ 708 | build/$(os)/*/python-$(PYTHON_VER)*.*.log \ 709 | install/$(os)/*/python-$(PYTHON_VER)* \ 710 | install/$(os)/*/python-$(PYTHON_VER)*.*.log \ 711 | support/$(PYTHON_VER)/$(os) \ 712 | support/$(PYTHON_VER)/python-$(os).*.log \ 713 | dist/Python-$(PYTHON_VER)-$(os)-* 714 | 715 | dev-clean-$(os): 716 | @echo ">>> Partially clean Python build products on $(os) so that local code modifications can be made" 717 | rm -rf \ 718 | build/$(os)/*/Python-$(PYTHON_VERSION)/python.exe \ 719 | build/$(os)/*/python-$(PYTHON_VERSION).*.log \ 720 | install/$(os)/*/python-$(PYTHON_VERSION) \ 721 | install/$(os)/*/python-$(PYTHON_VERSION).*.log \ 722 | support/$(PYTHON_VER)/$(os) \ 723 | dist/Python-$(PYTHON_VER)-$(os)-* 724 | 725 | ########################################################################### 726 | # Build 727 | ########################################################################### 728 | 729 | $(os): dist/Python-$(PYTHON_VER)-$(os)-support.$(BUILD_NUMBER).tar.gz 730 | 731 | ########################################################################### 732 | # Build: Debug 733 | ########################################################################### 734 | 735 | vars-$(os): $$(foreach target,$$(TARGETS-$(os)),vars-$$(target)) $$(foreach sdk,$$(SDKS-$(os)),vars-$$(sdk)) 736 | @echo ">>> Environment variables for $(os)" 737 | @echo "SDKS-$(os): $$(SDKS-$(os))" 738 | @echo "LIBPYTHON_XCFRAMEWORK-$(os): $$(LIBPYTHON_XCFRAMEWORK-$(os))" 739 | @echo "PYTHON_XCFRAMEWORK-$(os): $$(PYTHON_XCFRAMEWORK-$(os))" 740 | @echo 741 | 742 | endef # build 743 | 744 | # Dump environment variables (for debugging purposes) 745 | vars: $(foreach os,$(OS_LIST),vars-$(os)) 746 | @echo ">>> Environment variables for $(os)" 747 | @echo "HOST_ARCH: $(HOST_ARCH)" 748 | @echo "HOST_PYTHON: $(HOST_PYTHON)" 749 | @echo 750 | 751 | config: 752 | @echo "PYTHON_VERSION=$(PYTHON_VERSION)" 753 | @echo "PYTHON_VER=$(PYTHON_VER)" 754 | @echo "BUILD_NUMBER=$(BUILD_NUMBER)" 755 | @echo "BZIP2_VERSION=$(BZIP2_VERSION)" 756 | @echo "LIBFFI_VERSION=$(LIBFFI_VERSION)" 757 | @echo "MPDECIMAL_VERSION=$(MPDECIMAL_VERSION)" 758 | @echo "OPENSSL_VERSION=$(OPENSSL_VERSION)" 759 | @echo "XZ_VERSION=$(XZ_VERSION)" 760 | 761 | # Expand cross-platform build and clean targets for each output product 762 | clean: $(foreach os,$(OS_LIST),clean-$(os)) 763 | dev-clean: $(foreach os,$(OS_LIST),dev-clean-$(os)) 764 | 765 | # Expand the build macro for every OS 766 | $(foreach os,$(OS_LIST),$(eval $(call build,$(os)))) 767 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python Apple Support 2 | ==================== 3 | 4 | This is a meta-package for building a version of Python that can be embedded 5 | into a macOS, iOS, tvOS, watchOS, or visionOS project. 6 | 7 | **This branch builds a packaged version of Python 3.14**. 8 | Other Python versions are available by cloning other branches of the main 9 | repository: 10 | 11 | * `Python 3.9 `__ 12 | * `Python 3.10 `__ 13 | * `Python 3.11 `__ 14 | * `Python 3.12 `__ 15 | * `Python 3.13 `__ 16 | 17 | It works by downloading, patching, and building a fat binary of Python and 18 | selected pre-requisites, and packaging them as frameworks that can be 19 | incorporated into an Xcode project. The binary modules in the Python standard 20 | library are distributed as binaries that can be dynamically loaded at runtime. 21 | 22 | The macOS package is a re-bundling of the official macOS binary, modified so that 23 | it is relocatable, with the IDLE, Tkinter and turtle packages removed, and the 24 | App Store compliance patch applied. 25 | 26 | The iOS, tvOS, watchOS, and visionOS packages compiled by this project use the 27 | official `PEP 730 `__ code that is part of 28 | Python 3.13 to provide iOS support; the relevant patches have been backported 29 | to 3.9-3.12. Additional patches have been applied to add tvOS, watchOS, and 30 | visionOS support. 31 | 32 | The binaries support x86_64 and arm64 for macOS; arm64 for iOS and appleTV 33 | devices; arm64_32 for watchOS devices; and arm64 for visionOS devices. It also 34 | supports device simulators on both x86_64 and M1 hardware, except for visionOS, 35 | for which x86_64 simulators are officially unsupported. This should enable the 36 | code to run on: 37 | 38 | * macOS 11 (Big Sur) or later, on: 39 | * MacBook (including MacBooks using Apple Silicon) 40 | * iMac (including iMacs using Apple Silicon) 41 | * Mac Mini (including Apple Silicon Mac minis) 42 | * Mac Studio (all models) 43 | * Mac Pro (all models) 44 | * iOS 13.0 or later, on: 45 | * iPhone (6s or later) 46 | * iPad (5th gen or later) 47 | * iPad Air (all models) 48 | * iPad Mini (2 or later) 49 | * iPad Pro (all models) 50 | * iPod Touch (7th gen or later) 51 | * tvOS 12.0 or later, on: 52 | * Apple TV (4th gen or later) 53 | * watchOS 4.0 or later, on: 54 | * Apple Watch (4th gen or later) 55 | * visionOS 2.0 or later, on: 56 | * Apple Vision Pro 57 | 58 | Quickstart 59 | ---------- 60 | 61 | The easist way to use these packages is by creating a project with `Briefcase 62 | `__. Briefcase will download pre-compiled 63 | versions of these support packages, and add them to an Xcode project (or 64 | pre-build stub application, in the case of macOS). 65 | 66 | Pre-built versions of the frameworks can be downloaded from the `Github releases page 67 | `__ and added to your project. 68 | 69 | Alternatively, to build the frameworks on your own, download/clone this 70 | repository, and then in the root directory, and run: 71 | 72 | * ``make`` (or ``make all``) to build everything. 73 | * ``make macOS`` to build everything for macOS. 74 | * ``make iOS`` to build everything for iOS. 75 | * ``make tvOS`` to build everything for tvOS. 76 | * ``make watchOS`` to build everything for watchOS. 77 | * ``make visionOS`` to build everything for visionOS. 78 | 79 | This should: 80 | 81 | 1. Download the original source packages 82 | 2. Patch them as required for compatibility with the selected OS 83 | 3. Build the packages as Xcode-compatible XCFrameworks. 84 | 85 | The resulting support packages will be packaged as ``.tar.gz`` files 86 | in the ``dist`` folder. 87 | 88 | Each support package contains: 89 | 90 | * ``VERSIONS``, a text file describing the specific versions of code used to build the 91 | support package; 92 | * ``Python.xcframework``, a multi-architecture build of the Python runtime library. 93 | 94 | On iOS/tvOS/watchOS/visionOS, the ``Python.xcframework`` contains a 95 | slice for each supported ABI (device and simulator). The folder containing the 96 | slice can also be used as a ``PYTHONHOME``, as it contains a ``bin``, ``include`` 97 | and ``lib`` directory. 98 | 99 | The ``bin`` folder does not contain Python executables (as they can't be 100 | invoked). However, it *does* contain shell aliases for the compilers that are 101 | needed to build packages. This is required because Xcode uses the ``xcrun`` 102 | alias to dynamically generate the name of binaries, but a lot of C tooling 103 | expects that ``CC`` will not contain spaces. 104 | 105 | Each slice of an iOS/tvOS/watchOS/visionOS XCframework also contains a 106 | ``platform-config`` folder with a subfolder for each supported architecture in 107 | that slice. These subfolders can be used to make a macOS Python environment 108 | behave as if it were on an iOS/tvOS/watchOS/visionOS device. This works in one 109 | of two ways: 110 | 111 | 1. **A sitecustomize.py script**. If the ``platform-config`` subfolder is on 112 | your ``PYTHONPATH`` when a Python interpreter is started, a site 113 | customization will be applied that patches methods in ``sys``, ``sysconfig`` 114 | and ``platform`` that are used to identify the system. 115 | 116 | 2. **A make_cross_venv.py script**. If you call ``make_cross_venv.py``, 117 | providing the location of a virtual environment, the script will add some 118 | files to the ``site-packages`` folder of that environment that will 119 | automatically apply the same set of patches as the ``sitecustomize.py`` 120 | script whenever the environment is activated, without any need to modify 121 | ``PYTHONPATH``. If you use ``build`` to create an isolated PEP 517 122 | environment to build a wheel, these patches will also be applied to the 123 | isolated build environment that is created. 124 | 125 | iOS and visionOS distributions also contain a copy of the iOS or visionOS 126 | ``testbed`` project - an Xcode project that can be used to run test suites of 127 | Python code. See the `CPython documentation on testing packages 128 | `__ for 129 | details on how to use this testbed. 130 | 131 | For a detailed instructions on using the support package in your own project, 132 | see the `usage guide <./USAGE.md>`__ 133 | 134 | Building binary wheels 135 | ---------------------- 136 | 137 | This project packages the Python standard library, but does not address building 138 | binary wheels. Binary wheels for macOS can be obtained from PyPI. `Mobile Forge 139 | `__ is a project that provides the 140 | tooling to build build binary wheels for iOS (and potentially for tvOS, watchOS, 141 | and visionOS, although that hasn't been tested). 142 | 143 | Historical support 144 | ------------------ 145 | 146 | The following versions were supported in the past, but are no longer 147 | maintained: 148 | 149 | * `Python 2.7 `__ (EOL January 2020) 150 | * `Python 3.4 `__ (EOL March 2019) 151 | * `Python 3.5 `__ (EOL February 2021) 152 | * `Python 3.6 `__ (EOL December 2021) 153 | * `Python 3.7 `__ (EOL September 2022) 154 | * `Python 3.8 `__ (EOL October 2024) 155 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # Usage Guide 2 | 3 | ## The easy way 4 | 5 | The easist way to use these packages is by creating a project with 6 | (Briefcase)[https://github.com/beeware/briefcase]. Briefcase will download 7 | pre-compiled versions of these support packages, and add them to an Xcode project 8 | (or pre-build stub application, in the case of macOS). 9 | 10 | ## The manual way 11 | 12 | **NOTE** Briefcase usage is the officially supported approach for using this 13 | support package. If you are experiencing diffculties, one approach for debugging 14 | is to generate a "Hello World" project with Briefcase, and compare the project that 15 | Briefcase has generated with your own project. 16 | 17 | The Python support package *can* be manually added to any Xcode project; 18 | however, you'll need to perform some steps manually (essentially reproducing 19 | what Briefcase is doing). The steps required are documented in the CPython usage 20 | guides: 21 | 22 | * [macOS](https://docs.python.org/3/using/mac.html) 23 | * [iOS](https://docs.python.org/3/using/ios.html#adding-python-to-an-ios-project) 24 | 25 | For tvOS, watchOS, and visionOS, you should be able to broadly follow the instructions 26 | in the iOS guide, changing some platform names in the first script. The testbed projects 27 | generated on iOS and visionOS may be used as rough references as well. 28 | 29 | ### Using Objective C 30 | 31 | Once you've added the Python XCframework to your project, you'll need to 32 | initialize the Python runtime in your Objective C code (This is step 10 of the 33 | iOS guide linked above). This initialization should generally be done as early 34 | as possible in the application's lifecycle, but definitely needs to be done 35 | before you invoke Python code. 36 | 37 | As a *bare minimum*, you can do the following: 38 | 39 | 1. Import the Python C API headers: 40 | ```objc 41 | #include 42 | ``` 43 | 44 | 2. Initialize the Python interpreter: 45 | ```objc 46 | NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; 47 | NSString *pythonHome = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; 48 | NSString *appPath = [NSString stringWithFormat:@"%@/app", resourcePath, nil]; 49 | 50 | setenv("PYTHONHOME", [pythonHome UTF8String], 1); 51 | setenv("PYTHONPATH", [appPath UTF8String], 1); 52 | 53 | Py_Initialize(); 54 | 55 | // we now have a Python interpreter ready to be used 56 | ``` 57 | 58 | Again - this is the *bare minimum* initialization. In practice, you will likely 59 | need to configure other aspects of the Python interpreter using the 60 | `PyPreConfig` and `PyConfig` mechanisms. Consult the [Python documentation on 61 | interpreter configuration](https://docs.python.org/3/c-api/init_config.html) for 62 | more details on the configuration options that are available. You may find the 63 | [bootstrap mainline code used by 64 | Briefcase](https://github.com/beeware/briefcase-iOS-Xcode-template/blob/main/%7B%7B%20cookiecutter.format%20%7D%7D/%7B%7B%20cookiecutter.class_name%20%7D%7D/main.m) 65 | a helpful point of comparison. 66 | 67 | ### Using Swift 68 | 69 | If you want to use Swift instead of Objective C, the bare minimum initialization 70 | code will look something like this: 71 | 72 | 1. Import the Python framework: 73 | ```swift 74 | import Python 75 | ``` 76 | 77 | 2. Initialize the Python interpreter: 78 | ```swift 79 | guard let pythonHome = Bundle.main.path(forResource: "python", ofType: nil) else { return } 80 | let appPath = Bundle.main.path(forResource: "app", ofType: nil) 81 | 82 | setenv("PYTHONHOME", pythonHome, 1) 83 | setenv("PYTHONPATH", appPath, 1) 84 | Py_Initialize() 85 | // we now have a Python interpreter ready to be used 86 | ``` 87 | 88 | Again, references to a specific Python version should reflect the version of 89 | Python you are using; and you will likely need to use `PyPreConfig` and 90 | `PreConfig` APIs. 91 | 92 | ## Accessing the Python runtime 93 | 94 | There are 2 ways to access the Python runtime in your project code. 95 | 96 | ### Embedded C API 97 | 98 | You can use the [Python Embedded C 99 | API](https://docs.python.org/3/extending/embedding.html) to invoke Python code 100 | and interact with Python objects. This is a raw C API that is accesible to both 101 | Objective C and Swift. 102 | 103 | ### PythonKit 104 | 105 | If you're using Swift, an alternate approach is to use 106 | [PythonKit](https://github.com/pvieito/PythonKit). PythonKit is a package that 107 | provides a Swift API to running Python code. 108 | 109 | To use PythonKit in your project, add the Python Apple Support package to your 110 | project and instantiate a Python interpreter as described above; then add 111 | PythonKit to your project using the Swift Package manager (see the [PythonKit 112 | documentation](https://github.com/pvieito/PythonKit) for details). 113 | 114 | Once you've done this, you can import PythonKit: 115 | ```swift 116 | import PythonKit 117 | ``` 118 | and use the PythonKit Swift API to interact with Python code: 119 | ```swift 120 | let sys = Python.import("sys") 121 | print("Python Version: \(sys.version_info.major).\(sys.version_info.minor)") 122 | print("Python Encoding: \(sys.getdefaultencoding().upper())") 123 | print("Python Path: \(sys.path)") 124 | ``` 125 | -------------------------------------------------------------------------------- /patch/Python/OpenSSL.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | NSPrivacyCollectedDataTypes 17 | 18 | NSPrivacyTrackingDomains 19 | 20 | NSPrivacyTracking 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /patch/Python/_cross_target.py.tmpl: -------------------------------------------------------------------------------- 1 | # A site package that turns a macOS virtual environment 2 | # into an {{arch}} {{sdk}} cross-platform virtual environment 3 | import platform 4 | import subprocess 5 | import sys 6 | import sysconfig 7 | 8 | ########################################################################### 9 | # sys module patches 10 | ########################################################################### 11 | sys.cross_compiling = True 12 | sys.platform = "{{platform}}" 13 | sys.implementation._multiarch = "{{arch}}-{{sdk}}" 14 | sys.base_prefix = sysconfig.get_config_var("prefix") 15 | sys.base_exec_prefix = sysconfig.get_config_var("prefix") 16 | 17 | ########################################################################### 18 | # subprocess module patches 19 | ########################################################################### 20 | subprocess._can_fork_exec = True 21 | 22 | 23 | ########################################################################### 24 | # platform module patches 25 | ########################################################################### 26 | 27 | def cross_system(): 28 | return "{{os}}" 29 | 30 | 31 | def cross_uname(): 32 | return platform.uname_result( 33 | system="{{os}}", 34 | node="build", 35 | release="{{version_min}}", 36 | version="", 37 | machine="{{arch}}", 38 | ) 39 | 40 | 41 | def cross_ios_ver(system="", release="", model="", is_simulator=False): 42 | if system == "": 43 | system = "{{os}}" 44 | if release == "": 45 | release = "{{version_min}}" 46 | if model == "": 47 | model = "{{sdk}}" 48 | 49 | return platform.IOSVersionInfo(system, release, model, {{is_simulator}}) 50 | 51 | 52 | platform.system = cross_system 53 | platform.uname = cross_uname 54 | platform.ios_ver = cross_ios_ver 55 | 56 | 57 | ########################################################################### 58 | # sysconfig module patches 59 | ########################################################################### 60 | 61 | def cross_get_platform(): 62 | return "{{platform}}-{{version_min}}-{{arch}}-{{sdk}}" 63 | 64 | 65 | def cross_get_sysconfigdata_name(): 66 | return "_sysconfigdata__{{platform}}_{{arch}}-{{sdk}}" 67 | 68 | 69 | sysconfig.get_platform = cross_get_platform 70 | sysconfig._get_sysconfigdata_name = cross_get_sysconfigdata_name 71 | 72 | # Ensure module-level values cached at time of import are updated. 73 | sysconfig._BASE_PREFIX = sys.base_prefix 74 | sysconfig._BASE_EXEC_PREFIX = sys.base_exec_prefix 75 | 76 | # Force sysconfig data to be loaded (and cached). 77 | sysconfig._init_config_vars() 78 | -------------------------------------------------------------------------------- /patch/Python/_cross_venv.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import sys 3 | import sysconfig 4 | from pathlib import Path 5 | 6 | SITE_PACKAGE_PATH = Path(__file__).parent 7 | 8 | ########################################################################### 9 | # importlib module patches 10 | ########################################################################### 11 | 12 | 13 | def patch_env_create(env): 14 | """ 15 | Patch the process of creating virtual environments to ensure that the cross 16 | environment modification files are also copied as part of environment 17 | creation. 18 | """ 19 | old_pip_env_create = env._PipBackend.create 20 | 21 | def pip_env_create(self, path, *args, **kwargs): 22 | result = old_pip_env_create(self, path, *args, **kwargs) 23 | # Copy any _cross_*.pth or _cross_*.py file, plus the cross-platform 24 | # sysconfigdata module and sysconfig_vars JSON to the new environment. 25 | data_name = sysconfig._get_sysconfigdata_name() 26 | json_name = data_name.replace("_sysconfigdata", "_sysconfig_vars") 27 | for filename in [ 28 | "_cross_venv.pth", 29 | "_cross_venv.py", 30 | f"_cross_{sys.implementation._multiarch.replace('-', '_')}.py", 31 | f"{data_name}.py", 32 | f"{json_name}.json", 33 | ]: 34 | src = SITE_PACKAGE_PATH / filename 35 | target = Path(path) / src.relative_to( 36 | SITE_PACKAGE_PATH.parent.parent.parent 37 | ) 38 | if not target.exists(): 39 | shutil.copy(src, target) 40 | return result 41 | 42 | env._PipBackend.create = pip_env_create 43 | 44 | 45 | # Import hook that patches the creation of virtual environments by `build` 46 | # 47 | # The approach used here is the same as the one used by virtualenv to patch 48 | # distutils (but without support for the older load_module API). 49 | # https://docs.python.org/3/library/importlib.html#setting-up-an-importer 50 | _BUILD_PATCH = ("build.env",) 51 | 52 | 53 | class _Finder: 54 | """A meta path finder that allows patching the imported build modules.""" 55 | 56 | fullname = None 57 | 58 | # lock[0] is threading.Lock(), but initialized lazily to avoid importing 59 | # threading very early at startup, because there are gevent-based 60 | # applications that need to be first to import threading by themselves. 61 | # See https://github.com/pypa/virtualenv/issues/1895 for details. 62 | lock = [] # noqa: RUF012 63 | 64 | def find_spec(self, fullname, path, target=None): 65 | if fullname in _BUILD_PATCH and self.fullname is None: 66 | # initialize lock[0] lazily 67 | if len(self.lock) == 0: 68 | import threading 69 | 70 | lock = threading.Lock() 71 | # there is possibility that two threads T1 and T2 are 72 | # simultaneously running into find_spec, observing .lock as 73 | # empty, and further going into hereby initialization. However 74 | # due to the GIL, list.append() operation is atomic and this 75 | # way only one of the threads will "win" to put the lock 76 | # - that every thread will use - into .lock[0]. 77 | # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe 78 | self.lock.append(lock) 79 | 80 | from functools import partial 81 | from importlib.util import find_spec 82 | 83 | with self.lock[0]: 84 | self.fullname = fullname 85 | try: 86 | spec = find_spec(fullname, path) 87 | if spec is not None: 88 | # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work 89 | old = spec.loader.exec_module 90 | func = self.exec_module 91 | if old is not func: 92 | spec.loader.exec_module = partial(func, old) 93 | return spec 94 | finally: 95 | self.fullname = None 96 | return None 97 | 98 | @staticmethod 99 | def exec_module(old, module): 100 | old(module) 101 | if module.__name__ in _BUILD_PATCH: 102 | patch_env_create(module) 103 | 104 | 105 | sys.meta_path.insert(0, _Finder()) 106 | -------------------------------------------------------------------------------- /patch/Python/app-store-compliance.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py 2 | index d6c83a75c1c..19ed4e01091 100644 3 | --- a/Lib/test/test_urlparse.py 4 | +++ b/Lib/test/test_urlparse.py 5 | @@ -237,11 +237,6 @@ def test_roundtrips(self): 6 | '','',''), 7 | ('git+ssh', 'git@github.com','/user/project.git', 8 | '', '')), 9 | - ('itms-services://?action=download-manifest&url=https://example.com/app', 10 | - ('itms-services', '', '', '', 11 | - 'action=download-manifest&url=https://example.com/app', ''), 12 | - ('itms-services', '', '', 13 | - 'action=download-manifest&url=https://example.com/app', '')), 14 | ('+scheme:path/to/file', 15 | ('', '', '+scheme:path/to/file', '', '', ''), 16 | ('', '', '+scheme:path/to/file', '', '')), 17 | diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py 18 | index 8f724f907d4..148caf742c9 100644 19 | --- a/Lib/urllib/parse.py 20 | +++ b/Lib/urllib/parse.py 21 | @@ -59,7 +59,7 @@ 22 | 'imap', 'wais', 'file', 'mms', 'https', 'shttp', 23 | 'snews', 'prospero', 'rtsp', 'rtsps', 'rtspu', 'rsync', 24 | 'svn', 'svn+ssh', 'sftp', 'nfs', 'git', 'git+ssh', 25 | - 'ws', 'wss', 'itms-services'] 26 | + 'ws', 'wss'] 27 | 28 | uses_params = ['', 'ftp', 'hdl', 'prospero', 'http', 'imap', 29 | 'https', 'shttp', 'rtsp', 'rtsps', 'rtspu', 'sip', 30 | -------------------------------------------------------------------------------- /patch/Python/diff.exclude: -------------------------------------------------------------------------------- 1 | .gitattributes 2 | .github/* 3 | .gitignore 4 | .mention-bot 5 | .travis.yml 6 | Misc/NEWS.d/* -------------------------------------------------------------------------------- /patch/Python/make_cross_venv.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pprint 3 | import shutil 4 | import sys 5 | from pathlib import Path 6 | from importlib import util as importlib_util 7 | 8 | 9 | def localized_vars(orig_vars, slice_path): 10 | """Update (where possible) any references to build-time variables with the 11 | best guess of the installed location. 12 | """ 13 | # The host's sysconfigdata will include references to build-time variables. 14 | # Update these to refer to the current known install location. 15 | orig_prefix = orig_vars["prefix"] 16 | localized_vars = {} 17 | for key, value in orig_vars.items(): 18 | final = value 19 | if isinstance(value, str): 20 | # Replace any reference to the build installation prefix 21 | final = final.replace(orig_prefix, str(slice_path)) 22 | # Replace any reference to the build-time Framework location 23 | final = final.replace("-F .", f"-F {slice_path}") 24 | localized_vars[key] = final 25 | 26 | return localized_vars 27 | 28 | 29 | def localize_sysconfigdata(platform_config_path, venv_site_packages): 30 | """Localize a sysconfigdata python module. 31 | 32 | :param platform_config_path: The platform config that contains the 33 | sysconfigdata module to localize. 34 | :param venv_site_packages: The site packages folder where the localized 35 | sysconfigdata module should be output. 36 | """ 37 | # Find the "_sysconfigdata_*.py" file in the platform config 38 | sysconfigdata_path = next(platform_config_path.glob("_sysconfigdata_*.py")) 39 | 40 | # Import the sysconfigdata module 41 | spec = importlib_util.spec_from_file_location( 42 | sysconfigdata_path.stem, 43 | sysconfigdata_path 44 | ) 45 | if spec is None: 46 | msg = f"Unable to load spec for {sysconfigdata_path}" 47 | raise ValueError(msg) 48 | if spec.loader is None: 49 | msg = f"Spec for {sysconfigdata_path} does not define a loader" 50 | raise ValueError(msg) 51 | sysconfigdata = importlib_util.module_from_spec(spec) 52 | spec.loader.exec_module(sysconfigdata) 53 | 54 | # Write the updated sysconfigdata module into the cross-platform site. 55 | slice_path = sysconfigdata_path.parent.parent.parent 56 | with (venv_site_packages / sysconfigdata_path.name).open("w") as f: 57 | f.write(f"# Generated from {sysconfigdata_path}\n") 58 | f.write("build_time_vars = ") 59 | pprint.pprint( 60 | localized_vars(sysconfigdata.build_time_vars, slice_path), 61 | stream=f, 62 | compact=True 63 | ) 64 | 65 | 66 | def localize_sysconfig_vars(platform_config_path, venv_site_packages): 67 | """Localize a sysconfig_vars.json file. 68 | 69 | :param platform_config_path: The platform config that contains the 70 | sysconfigdata module to localize. 71 | :param venv_site_packages: The site-packages folder where the localized 72 | sysconfig_vars.json file should be output. 73 | """ 74 | # Find the "_sysconfig_vars_*.json" file in the platform config 75 | sysconfig_vars_path = next(platform_config_path.glob("_sysconfig_vars_*.json")) 76 | 77 | with sysconfig_vars_path.open("rb") as f: 78 | build_time_vars = json.load(f) 79 | 80 | slice_path = sysconfig_vars_path.parent.parent.parent 81 | with (venv_site_packages / sysconfig_vars_path.name).open("w") as f: 82 | json.dump(localized_vars(build_time_vars, slice_path), f, indent=2) 83 | 84 | 85 | def make_cross_venv(venv_path: Path, platform_config_path: Path): 86 | """Convert a virtual environment into a cross-platform environment. 87 | 88 | :param venv_path: The path to the root of the venv. 89 | :param platform_config_path: The path containing the platform config. 90 | """ 91 | if not venv_path.exists(): 92 | raise ValueError(f"Virtual environment {venv_path} does not exist.") 93 | if not (venv_path / "bin/python3").exists(): 94 | raise ValueError(f"{venv_path} does not appear to be a virtual environment.") 95 | 96 | print( 97 | f"Converting {venv_path} into a {platform_config_path.name} environment... ", 98 | end="", 99 | ) 100 | 101 | LIB_PATH = f"lib/python{sys.version_info[0]}.{sys.version_info[1]}" 102 | 103 | # Update path references in the sysconfigdata to reflect local conditions. 104 | venv_site_packages = venv_path / LIB_PATH / "site-packages" 105 | localize_sysconfigdata(platform_config_path, venv_site_packages) 106 | localize_sysconfig_vars(platform_config_path, venv_site_packages) 107 | 108 | # Copy in the site-package environment modifications. 109 | cross_multiarch = f"_cross_{platform_config_path.name.replace('-', '_')}" 110 | shutil.copy( 111 | platform_config_path / f"{cross_multiarch}.py", 112 | venv_site_packages / f"{cross_multiarch}.py", 113 | ) 114 | shutil.copy( 115 | platform_config_path / "_cross_venv.py", 116 | venv_site_packages / "_cross_venv.py", 117 | ) 118 | # Write the .pth file that will enable the cross-env modifications 119 | (venv_site_packages / "_cross_venv.pth").write_text( 120 | f"import {cross_multiarch}; import _cross_venv\n" 121 | ) 122 | 123 | print("done.") 124 | 125 | 126 | if __name__ == "__main__": 127 | try: 128 | platform_config_path = Path(sys.argv[2]).resolve() 129 | except IndexError: 130 | platform_config_path = Path(__file__).parent 131 | 132 | try: 133 | venv_path = Path(sys.argv[1]).resolve() 134 | make_cross_venv(venv_path, platform_config_path) 135 | except IndexError: 136 | print(""" 137 | Convert a virtual environment in to a cross-platform environment. 138 | 139 | Usage: 140 | make_cross_venv () 141 | 142 | If an explicit platform config isn't provided, it is assumed the directory 143 | containing the make_cross_venv script *is* a platform config. 144 | """) 145 | -------------------------------------------------------------------------------- /patch/Python/module.modulemap.prefix: -------------------------------------------------------------------------------- 1 | module Python { 2 | umbrella header "Python.h" 3 | export * 4 | link "Python" 5 | 6 | exclude header "datetime.h" 7 | exclude header "dynamic_annotations.h" 8 | exclude header "errcode.h" 9 | exclude header "frameobject.h" 10 | exclude header "marshal.h" 11 | exclude header "opcode_ids.h" 12 | exclude header "opcode.h" 13 | exclude header "osdefs.h" 14 | exclude header "py_curses.h" 15 | exclude header "pyconfig-arm32_64.h" 16 | exclude header "pyconfig-arm64.h" 17 | exclude header "pyconfig-x86_64.h" 18 | exclude header "pydtrace.h" 19 | exclude header "pyexpat.h" 20 | exclude header "structmember.h" 21 | -------------------------------------------------------------------------------- /patch/Python/release.iOS.exclude: -------------------------------------------------------------------------------- 1 | # This is a list of support package path patterns that we exclude 2 | # from all Python-Apple-support tarballs. 3 | # It is used by `tar -X` during the Makefile build. 4 | # Remove pyc files. These take up space, but since most stdlib modules are 5 | # never imported by user code, they mostly have no value. 6 | */__pycache__ 7 | -------------------------------------------------------------------------------- /patch/Python/release.macOS.exclude: -------------------------------------------------------------------------------- 1 | # This is a list of Framework path patterns that we exclude 2 | # when building macOS Python-Apple-support tarballs from the official Framework 3 | # It is used by `tar -X` during the Makefile build. 4 | # 5 | ._Headers 6 | ._Python 7 | ._Resources 8 | Resources/._Python.app 9 | Resources/Python.app 10 | Versions/._Current 11 | Versions/*/.__CodeSignature 12 | Versions/*/._bin 13 | Versions/*/._etc 14 | Versions/*/._Frameworks 15 | Versions/*/._Headers 16 | Versions/*/._include 17 | Versions/*/._lib 18 | Versions/*/._Resources 19 | Versions/*/._share 20 | Versions/*/bin 21 | Versions/*/etc 22 | Versions/*/Frameworks 23 | Versions/*/lib/python*/idlelib 24 | Versions/*/lib/python*/lib-dynload/_tkinter.* 25 | Versions/*/lib/python*/tkinter 26 | Versions/*/lib/python*/turtle.py 27 | Versions/*/lib/python*/turtledemo 28 | Versions/*/share 29 | -------------------------------------------------------------------------------- /patch/Python/release.tvOS.exclude: -------------------------------------------------------------------------------- 1 | # This is a list of support package path patterns that we exclude 2 | # from all Python-Apple-support tarballs. 3 | # It is used by `tar -X` during the Makefile build. 4 | # Remove pyc files. These take up space, but since most stdlib modules are 5 | # never imported by user code, they mostly have no value. 6 | */__pycache__ 7 | -------------------------------------------------------------------------------- /patch/Python/release.visionOS.exclude: -------------------------------------------------------------------------------- 1 | # This is a list of support package path patterns that we exclude 2 | # from all Python-Apple-support tarballs. 3 | # It is used by `tar -X` during the Makefile build. 4 | # Remove pyc files. These take up space, but since most stdlib modules are 5 | # never imported by user code, they mostly have no value. 6 | */__pycache__ 7 | -------------------------------------------------------------------------------- /patch/Python/release.watchOS.exclude: -------------------------------------------------------------------------------- 1 | # This is a list of support package path patterns that we exclude 2 | # from all Python-Apple-support tarballs. 3 | # It is used by `tar -X` during the Makefile build. 4 | # Remove pyc files. These take up space, but since most stdlib modules are 5 | # never imported by user code, they mostly have no value. 6 | */__pycache__ 7 | -------------------------------------------------------------------------------- /patch/Python/sitecustomize.py.tmpl: -------------------------------------------------------------------------------- 1 | # A site customization that can be used to trick pip into installing packages 2 | # cross-platform. If the folder containing this file is on your PYTHONPATH when 3 | # you invoke python, the interpreter will behave as if it were running on 4 | # {{arch}} {{sdk}}. 5 | import sys 6 | import os 7 | 8 | # Apply the cross-platform patch 9 | import _cross_{{arch}}_{{sdk}} 10 | import _cross_venv 11 | 12 | 13 | # Call the next sitecustomize script if there is one 14 | # (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html). 15 | del sys.modules["sitecustomize"] 16 | this_dir = os.path.dirname(__file__) 17 | path_index = sys.path.index(this_dir) 18 | del sys.path[path_index] 19 | try: 20 | import sitecustomize # noqa: F401 21 | finally: 22 | sys.path.insert(path_index, this_dir) 23 | -------------------------------------------------------------------------------- /patch/make-relocatable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FRAMEWORK_BASEDIR=$1 4 | echo "Making $1 relocatable" 5 | PYTHON_VER=${FRAMEWORK_BASEDIR##*/} 6 | echo "Python version ${PYTHON_VER}" 7 | 8 | pushd ${FRAMEWORK_BASEDIR} 9 | 10 | echo "Rewrite ID of Python library" 11 | install_name_tool -id @rpath/Python.framework/Versions/${PYTHON_VER}/Python Python > /dev/null 12 | for dylib in `ls lib/*.*.dylib`; do 13 | # lib 14 | if [ "${dylib}" != "lib/libpython${PYTHON_VER}.dylib" ] ; then 15 | echo Rewrite ID of ${dylib} 16 | install_name_tool -id @rpath/Python.framework/Versions/${PYTHON_VER}/${dylib} ${FRAMEWORK_BASEDIR}/${dylib} 17 | fi 18 | done 19 | for module in `find . -name "*.dylib" -type f -o -name "*.so" -type f`; do 20 | if [ "$(otool -L ${module} | grep -c /Library/Frameworks/Python.framework)" != "0" ]; then 21 | for dylib in `ls lib/*.*.dylib`; do 22 | echo Rewrite references to ${dylib} in ${module} 23 | install_name_tool -change /Library/Frameworks/Python.framework/Versions/${PYTHON_VER}/${dylib} @rpath/Python.framework/Versions/${PYTHON_VER}/${dylib} ${module} 24 | done 25 | fi 26 | done 27 | popd 28 | --------------------------------------------------------------------------------