Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/workflows/build_python_3.15.yml
Original file line number Diff line number Diff line change
@@ -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/
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions requirements_recommended.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
17 changes: 17 additions & 0 deletions verify_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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):
Expand All @@ -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)
Expand Down
Loading