From 9e7be33b76f571c3362f821e0e2b12b9fd94ec72 Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Thu, 22 Oct 2026 17:49:39 -0700 Subject: [PATCH] Add Python 3.15 builds and compatible native dependencies --- .github/workflows/build_python_3.15.yml | 97 +++++++++++++++++++++++++ README.md | 10 ++- requirements_recommended.txt | 4 +- verify_framework.py | 17 +++++ 4 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/build_python_3.15.yml diff --git a/.github/workflows/build_python_3.15.yml b/.github/workflows/build_python_3.15.yml new file mode 100644 index 0000000..21c754d --- /dev/null +++ b/.github/workflows/build_python_3.15.yml @@ -0,0 +1,97 @@ +name: Build Python 3.15 + +env: + TYPE: "recommended" + DEV_INSTALLER_ID: "Developer ID Installer: Mac Admins Open Source (T4SK8ZXCXG)" + DEV_APPLICATION_ID: "Developer ID Application: Mac Admins Open Source (T4SK8ZXCXG)" + NOTARY_APP_PASSWORD: ${{ secrets.NOTARY_APP_PASSWORD_MAOS }} + PYTHON_VERSION: "3.15.1" + PYTHON_MAJOR_VERSION: "3.15" + +on: + workflow_dispatch: + pull_request: + +jobs: + build: + runs-on: macos-26 + + steps: + - name: Checkout python repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Install Apple Developer ID Application certificates + uses: apple-actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 # v7.0.0 + with: + keychain-password: ${{ github.run_id }} + p12-file-base64: ${{ secrets.APP_CERTIFICATES_P12_MAOS }} + p12-password: ${{ secrets.APP_CERTIFICATES_P12_PASSWORD_MAOS }} + + - name: Install Apple Developer ID Installer certificates + uses: apple-actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 # v7.0.0 + with: + create-keychain: false # do not create a new keychain for this value + keychain-password: ${{ github.run_id }} + p12-file-base64: ${{ secrets.PKG_CERTIFICATES_P12_MAOS }} + p12-password: ${{ secrets.PKG_CERTIFICATES_P12_PASSWORD_MAOS }} + + - name: Run build package script + run: | + ./build_python_framework_pkgs.zsh \ + --python-version "$PYTHON_VERSION" \ + --installer-id "$DEV_INSTALLER_ID" \ + --application-id "$DEV_APPLICATION_ID" \ + --notary-password "$NOTARY_APP_PASSWORD" + + - name: get environment variables + id: get_env_var + run: | + echo "PYTHON_BUILD_VERSION=$(/bin/cat ./build_info.txt)" >> $GITHUB_ENV + + - name: Generate changelog + if: github.event_name == 'workflow_dispatch' + id: changelog + uses: metcalfc/changelog-generator@0440d0932f9a0dd1cc9ecd8412830761351323bd # v4.7.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + reverse: 'true' + + - name: Create Release + if: github.event_name == 'workflow_dispatch' + id: create_release + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + with: + name: Python ${{env.PYTHON_BUILD_VERSION}} + tag_name: v${{env.PYTHON_BUILD_VERSION}} + draft: false + prerelease: true + token: ${{ secrets.GITHUB_TOKEN }} + body: | + # Notes + Python ${{env.PYTHON_VERSION}} Framework + + ## Changes + - Upgraded Python to 3.15.1 + **Note: Some of these updates may have breaking changes. Always test your code before deploying to production!** + + Please see the `requirements_recommended.txt` for the current libraries being used. + + ${{ steps.changelog.outputs.changelog }} + + # Flavors of Python + At this time, the automated build process will **only** create the Recommended package + + ## Recommended + This is a Python.framework with a recommended set of libraries for tools like Autopkg, Munki, and InstallApplications. + + ## Signing/Notarization + The signed package is fully notarized, including the Python.framework file + files: ${{github.workspace}}/outputs/*.pkg + + - name: Upload packages + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: packages + path: outputs/ diff --git a/README.md b/README.md index baeed5c..5af02b0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ This is an intended replacement for `/usr/bin/python`, which Apple removed in ma ## Apple Silicon Only Builds and packages target Apple Silicon (arm64). Universal2 outputs are no longer produced. Build hosts and target machines must be Apple Silicon Macs. +CI builds Python 3.11.9, 3.12.10, 3.13.17, 3.14.9, and 3.15.1 with the shared +recommended dependencies, including PyObjC 12.2.2 and CFFI 2.1.1 for Python +3.15 compatibility. + ## Why use this instead of a package from python.org? - Ships with PyObjC and other modules useful for Mac admins, similar in spirit to the Apple Python it replaces - Installs to a location less likely to be overwritten, removed, or modified by other Python installations @@ -47,7 +51,7 @@ Not supported. Build an unsigned framework on Apple Silicon with: ``` -./build_python_framework_pkgs.zsh --python-version 3.13.17 +./build_python_framework_pkgs.zsh --python-version 3.15.1 ``` Pass `--installer-id`, `--application-id`, and `--notary-password` to produce a signed and notarized `.pkg`. @@ -56,8 +60,8 @@ The build bootstraps pip 26.2.2 from a SHA-256-verified wheel before accessing PyPI, updates the pip bundled with `ensurepip`, and installs certifi 2026.10.19 and virtualenv 21.14.13. These updates address the [eMudhra/emSign certificate security incident](https://discuss.python.org/t/109576) -for the framework and newly created Python 3.11–3.14 virtual environments. -Python 3.13 and 3.14 builds use the updated 3.13.17 and 3.14.9 installers; +for the framework and newly created Python 3.11–3.15 virtual environments. +Python 3.13, 3.14, and 3.15 builds use the 3.13.17, 3.14.9, and 3.15.1 installers; 3.11 and 3.12 retain their last upstream macOS installers with patched `ensurepip`. Before packaging, the build verifies package dependencies, CA stores, offline diff --git a/requirements_recommended.txt b/requirements_recommended.txt index 30d3c57..17d132a 100644 --- a/requirements_recommended.txt +++ b/requirements_recommended.txt @@ -3,7 +3,7 @@ aspy.yaml==1.3.0 attrs==26.1.0 black==26.3.1 certifi==2026.10.19 -cffi==2.0.0 +cffi==2.1.1 cfgv==3.5.0 charset-normalizer==3.4.7 click==8.3.3 @@ -28,7 +28,7 @@ pycodestyle==2.14.0 pycparser==3.0 pyflakes==3.4.0 python-discovery==1.6.4 -pyobjc==12.1 +pyobjc==12.2.2 PyYAML==6.0.3 requests==2.34.0 six==1.17.0 diff --git a/verify_framework.py b/verify_framework.py index 664512e..1a5d936 100644 --- a/verify_framework.py +++ b/verify_framework.py @@ -11,10 +11,12 @@ from pathlib import Path import certifi +import cffi import objc import requests import xattr import yaml +from Foundation import NSObject from packaging.version import Version @@ -50,6 +52,20 @@ def check_context(context): def main(): print(f"Verifying framework: {sys.version}", flush=True) + # Exercise native extensions as well as their package metadata. This also + # covers source builds when wheels are not yet available for a new Python. + if cffi.FFI().sizeof("int") != 4: + raise RuntimeError("CFFI native extension check failed") + if NSObject.alloc().init() is None: + raise RuntimeError("PyObjC Foundation bridge check failed") + if yaml.safe_load("enabled: true") != {"enabled": True}: + raise RuntimeError("PyYAML check failed") + with tempfile.NamedTemporaryFile(prefix="macadmins-xattr-verify-") as temporary: + attributes = xattr.xattr(temporary.name) + attributes["io.macadmins.python.verify"] = b"working" + if attributes["io.macadmins.python.verify"] != b"working": + raise RuntimeError("xattr native extension check failed") + print("Verified CFFI, PyObjC, PyYAML, and xattr", flush=True) for package, minimum in (("certifi", "2026.10.19"), ("pip", "26.2.2")): version = importlib.metadata.version(package) if Version(version) < Version(minimum): @@ -65,6 +81,7 @@ def main(): # Exercise relocated console scripts as well as python -m entry points. run(str(Path(sys.prefix) / "bin/pip"), "--version") run(str(Path(sys.prefix) / "bin/virtualenv"), "--version") + run(str(Path(sys.prefix) / "bin/black"), "--version") with tempfile.TemporaryDirectory(prefix="macadmins-python-verify-") as temporary: root = Path(temporary)