diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8004983 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 diff --git a/.github/workflows/build_python_3.10.yml b/.github/workflows/build_python_3.10.yml deleted file mode 100644 index c09d98e..0000000 --- a/.github/workflows/build_python_3.10.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Build Python 3.10 - -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.10.11" - PYTHON_MAJOR_VERSION: "3.10" - -on: - workflow_dispatch: - pull_request: - -jobs: - build: - runs-on: macos-14 - - steps: - - name: Checkout python repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 - with: - fetch-depth: 0 - - - name: Install Apple Developer ID Application certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.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@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.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 "$TYPE" "$DEV_INSTALLER_ID" "$DEV_APPLICATION_ID" "$PYTHON_VERSION" "$PYTHON_MAJOR_VERSION" "${NOTARY_APP_PASSWORD}" - --xcode-path "/Applications/Xcode_15.2.app" - - - name: get environment variables - id: get_env_var - run: | - echo "PYTHON_BUILD_VERSION=$(/bin/cat ./build_info.txt)" >> $GITHUB_ENV - - - name: Generate changelog - id: changelog - uses: metcalfc/changelog-generator@afdcb9470aebdb2252c0c95a1c130723c9e21f3a # v4.1 - with: - myToken: ${{ secrets.GITHUB_TOKEN }} - reverse: 'true' - - - name: Create Release - id: create_release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 - 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.10.11 - **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. - - ## Security Notice - The python org [does not provide macOS packages for Python 3.10.12 and higher](https://www.python.org/downloads/release/python-31012/). This means that this project cannot use the most recent version of Python 3.10 with all security updates. Proceed with caution when using this release. - - > According to the release calendar specified in PEP 619, Python 3.10 is now in the "security fixes only" stage of its life cycle: the 3.10 branch only accepts security fixes and releases of those are made irregularly in source-only form until October 2026. Python 3.10 isn't receiving regular bug fixes anymore, and binary installers are no longer provided for it. Python 3.10.11 was the last full bugfix release of Python 3.10 with binary installers. - - ## Final Release - **This is the final release of the Python 3.10 framework.** Python 3.10 is in security-fixes-only status and python.org has not published a macOS installer past 3.10.11. Future framework updates will target Python 3.11 and newer. Plan your migration. - - ${{ 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: packages - path: outputs/ diff --git a/.github/workflows/build_python_3.11.yml b/.github/workflows/build_python_3.11.yml index 41263c7..422b61c 100644 --- a/.github/workflows/build_python_3.11.yml +++ b/.github/workflows/build_python_3.11.yml @@ -14,23 +14,23 @@ on: jobs: build: - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout python repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Install Apple Developer ID Application certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # 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@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # v7.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -38,7 +38,12 @@ jobs: p12-password: ${{ secrets.PKG_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Run build package script - run: ./build_python_framework_pkgs.zsh "$TYPE" "$DEV_INSTALLER_ID" "$DEV_APPLICATION_ID" "$PYTHON_VERSION" "$PYTHON_MAJOR_VERSION" "${NOTARY_APP_PASSWORD}" + 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 @@ -47,14 +52,14 @@ jobs: - name: Generate changelog id: changelog - uses: metcalfc/changelog-generator@afdcb9470aebdb2252c0c95a1c130723c9e21f3a # v4.1 + uses: metcalfc/changelog-generator@b794d7e4101afb6a8fe5c97eaedaf789d43b1288 # v4.7.0 with: myToken: ${{ secrets.GITHUB_TOKEN }} reverse: 'true' - name: Create Release id: create_release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: Python ${{env.PYTHON_BUILD_VERSION}} tag_name: v${{env.PYTHON_BUILD_VERSION}} @@ -85,7 +90,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: packages path: outputs/ diff --git a/.github/workflows/build_python_3.12.yml b/.github/workflows/build_python_3.12.yml index ddd8fc4..917d114 100644 --- a/.github/workflows/build_python_3.12.yml +++ b/.github/workflows/build_python_3.12.yml @@ -14,23 +14,23 @@ on: jobs: build: - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout python repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Install Apple Developer ID Application certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # 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@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # v7.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -38,7 +38,12 @@ jobs: p12-password: ${{ secrets.PKG_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Run build package script - run: ./build_python_framework_pkgs.zsh "$TYPE" "$DEV_INSTALLER_ID" "$DEV_APPLICATION_ID" "$PYTHON_VERSION" "$PYTHON_MAJOR_VERSION" "${NOTARY_APP_PASSWORD}" + 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 @@ -47,14 +52,14 @@ jobs: - name: Generate changelog id: changelog - uses: metcalfc/changelog-generator@afdcb9470aebdb2252c0c95a1c130723c9e21f3a # v4.1 + uses: metcalfc/changelog-generator@b794d7e4101afb6a8fe5c97eaedaf789d43b1288 # v4.7.0 with: myToken: ${{ secrets.GITHUB_TOKEN }} reverse: 'true' - name: Create Release id: create_release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: Python ${{env.PYTHON_BUILD_VERSION}} tag_name: v${{env.PYTHON_BUILD_VERSION}} @@ -85,7 +90,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: packages path: outputs/ diff --git a/.github/workflows/build_python_3.13.yml b/.github/workflows/build_python_3.13.yml index 721a49b..5da9c59 100644 --- a/.github/workflows/build_python_3.13.yml +++ b/.github/workflows/build_python_3.13.yml @@ -14,23 +14,23 @@ on: jobs: build: - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout python repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Install Apple Developer ID Application certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # 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@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # v7.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -38,7 +38,12 @@ jobs: p12-password: ${{ secrets.PKG_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Run build package script - run: ./build_python_framework_pkgs.zsh "$TYPE" "$DEV_INSTALLER_ID" "$DEV_APPLICATION_ID" "$PYTHON_VERSION" "$PYTHON_MAJOR_VERSION" "${NOTARY_APP_PASSWORD}" + 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 @@ -47,14 +52,14 @@ jobs: - name: Generate changelog id: changelog - uses: metcalfc/changelog-generator@afdcb9470aebdb2252c0c95a1c130723c9e21f3a # v4.1 + uses: metcalfc/changelog-generator@b794d7e4101afb6a8fe5c97eaedaf789d43b1288 # v4.7.0 with: myToken: ${{ secrets.GITHUB_TOKEN }} reverse: 'true' - name: Create Release id: create_release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: Python ${{env.PYTHON_BUILD_VERSION}} tag_name: v${{env.PYTHON_BUILD_VERSION}} @@ -87,7 +92,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: packages path: outputs/ diff --git a/.github/workflows/build_python_3.14.yml b/.github/workflows/build_python_3.14.yml index 2fecdf8..f0bb090 100644 --- a/.github/workflows/build_python_3.14.yml +++ b/.github/workflows/build_python_3.14.yml @@ -14,23 +14,23 @@ on: jobs: build: - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout python repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Install Apple Developer ID Application certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # 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@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@b2e261033a9e248f91a9b57201e8d1e12b15a24e # v7.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -38,7 +38,12 @@ jobs: p12-password: ${{ secrets.PKG_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Run build package script - run: ./build_python_framework_pkgs.zsh "$TYPE" "$DEV_INSTALLER_ID" "$DEV_APPLICATION_ID" "$PYTHON_VERSION" "$PYTHON_MAJOR_VERSION" "${NOTARY_APP_PASSWORD}" + 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 @@ -47,14 +52,14 @@ jobs: - name: Generate changelog id: changelog - uses: metcalfc/changelog-generator@afdcb9470aebdb2252c0c95a1c130723c9e21f3a # v4.1 + uses: metcalfc/changelog-generator@b794d7e4101afb6a8fe5c97eaedaf789d43b1288 # v4.7.0 with: myToken: ${{ secrets.GITHUB_TOKEN }} reverse: 'true' - name: Create Release id: create_release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: Python ${{env.PYTHON_BUILD_VERSION}} tag_name: v${{env.PYTHON_BUILD_VERSION}} @@ -64,15 +69,13 @@ jobs: body: | # Notes Python ${{env.PYTHON_VERSION}} Framework + Python 3.14.5 Framework ## Changes - Upgraded Python to 3.14.5 **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. - - ## Final Release - **This is the final release of the Python 3.14 framework for Intel macs.** macOS 27 will ship without Intel support so please plan your migration soon. ${{ steps.changelog.outputs.changelog }} @@ -87,7 +90,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: packages path: outputs/ diff --git a/.github/workflows/build_python_3.9.yml b/.github/workflows/build_python_3.9.yml deleted file mode 100644 index c7513e9..0000000 --- a/.github/workflows/build_python_3.9.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Build Python 3.9 - -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.9.13" - PYTHON_MAJOR_VERSION: "3.9" - -on: - workflow_dispatch: - pull_request: - -jobs: - build: - runs-on: macos-14 - - steps: - - name: Checkout python repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 - with: - fetch-depth: 0 - - - name: Install Apple Developer ID Application certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.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@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.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 "$TYPE" "$DEV_INSTALLER_ID" "$DEV_APPLICATION_ID" "$PYTHON_VERSION" "$PYTHON_MAJOR_VERSION" "${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 - id: changelog - uses: metcalfc/changelog-generator@afdcb9470aebdb2252c0c95a1c130723c9e21f3a # v4.1 - with: - myToken: ${{ secrets.GITHUB_TOKEN }} - reverse: 'true' - - - name: Create Release - id: create_release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 - 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.9.13 - **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. - - ## Security Notice - The python org [does not provide macOS packages for Python 3.9.14 and higher](https://www.python.org/downloads/release/python-3914/). This means that this project cannot use the most recent version of Python 3.9 with all security updates. Proceed with caution when using this release. - - > According to the release calendar specified in PEP 596, Python 3.9 is now in the "security fixes only" stage of its life cycle: the 3.9 branch only accepts security fixes and releases of those are made irregularly in source-only form until October 2025. Python 3.9 isn't receiving regular bug fixes anymore, and binary installers are no longer provided for it. Python 3.9.13 was the last full bugfix release of Python 3.9 with binary installers. - - ## Final Release - **This is the final release of the Python 3.9 framework.** Python 3.9 reached end-of-life in October 2025 and python.org has not published a macOS installer past 3.9.13. Future framework updates will target Python 3.11 and newer. Plan your migration. - - ${{ 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: packages - path: outputs/ diff --git a/README.md b/README.md index 4c3407a..fd9c582 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,22 @@ # python -A Python 3 framework that currently installs to `/Library/ManagedFrameworks/Python/Python3.framework`. +A Python 3 framework that installs to `/Library/ManagedFrameworks/Python/Python3.framework`. -Please see Apple's documentation on [file system basics](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) for more information on the thought process here. +Please see Apple's documentation on [file system basics](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) for context. -This is an intended replacement for when Apple removes `/usr/bin/python` (which is happening with the macOS 12.3 release Spring 2022) +This is an intended replacement for `/usr/bin/python`, which Apple removed in macOS 12.3 (Spring 2022). -## Build Issues -At this time, it is required to build this version of python on an Intel macOS device. +## 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. -## Why should I use this instead of a package from python.org? -- It comes with PyObjC and other modules useful for Mac admins pre-installed; making it more like the Apple Python it's intended to replace -- It installs to a location less likely to be overwritten, removed, or modified by developers or power users who are also working with Python +## 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 ## Using interactively -After installing any of the packages, a symbolic link can be used within terminal for interactive Python sessions. At the time of this writing `/usr/local/bin/managed_python3` points to `/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/bin/python3` +After installing the package, `/usr/local/bin/managed_python3` is a symlink to `/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python`. ## Using with scripts -It is currently recommended to point directly to symbolic link provided by the Python framework. - -At the time of this writing `/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/bin/python3` points to `/Library/ManagedFrameworks/Python/Python3.framework/Versions/3.8/bin/python3.8` - -An example script would look like the following: +Point your shebang directly at the symlink: ``` #!/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/bin/python3 @@ -28,65 +24,41 @@ An example script would look like the following: print('This is an example script.') ``` -### Other options to consider -#### zshenv global alias -If you are calling `python` within `zsh` scripts, adding a global alias to `/etc/zshenv` may be appropriate. +### zshenv global alias +For zsh scripts you can add a global alias to `/etc/zshenv`: `alias -g python3.framework='/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/bin/python3'` -For more information on this method, please see Armin Briegel's "Moving to Zsh" Part [II](https://scriptingosx.com/2019/06/moving-to-zsh-part-2-configuration-files/) and [IV](https://scriptingosx.com/2019/07/moving-to-zsh-part-4-aliases-and-functions/) +See Armin Briegel's "Moving to Zsh" Part [II](https://scriptingosx.com/2019/06/moving-to-zsh-part-2-configuration-files/) and [IV](https://scriptingosx.com/2019/07/moving-to-zsh-part-4-aliases-and-functions/). ## Notes -To decrease complexity, only a _single_ package may be installed at any given time on a machine. +Only a single package may be installed at any given time. The preinstall script removes any previous framework. ### Upgrades -While Python itself has its own update cadence and dot release schedule, it is likely that this package will have many updates as 3rd party libraries release their own updates, bug fixes and security enhancements. These packages should not break your workflow, but you should test your scripts prior to wide deployment to your devices. +Python itself has its own release cadence; this package will see additional updates as 3rd-party libraries release fixes and security updates. Always test your scripts before deploying broadly. ### Downgrades -Downgrades will not be supported by this repository. +Not supported. ### pip -While `pip` is bundled in this framework, it is **not recommended** to install any external libraries into your frameworks folder outside of what comes with the package. If you need to use or test external libraries not present in the package, it is recommended to use a [virtual environment](https://docs.python.org/3/library/venv.html) or a tool like [pyenv](https://github.com/pyenv/pyenv). - -Pull requests can and are encouraged to be issued to the `recommended` packages requirements file. - -# Flavors of Python -We currently offer three versions of Python. You can chose which version suits your needs, however only the `recommended` package is built/signed/notarized. - -## No Customization -This is a Python.framework that contains everything from the official Python package and nothing more. +`pip` is bundled but **not recommended** for installing external libraries into the framework. Use a [virtual environment](https://docs.python.org/3/library/venv.html) or a tool like [pyenv](https://github.com/pyenv/pyenv) instead. Pull requests to the `recommended` requirements file are welcome. -Many open source tools will not work with this, but it may be helpful for development purposes. +# Building locally +Build an unsigned framework on Apple Silicon with: -## Minimal -This is a Python.framework that includes `xattr` and `PyObjc` - the original intent of [Relocatable Python](https://github.com/gregneagle/relocatable-python). - -Tools that should work when using the "Minimal Flavor": -- [vfuse](https://github.com/chilcote/vfuse) -- [dockutil](https://github.com/kcrawford/dockutil) (Python 3 pull request [here](https://github.com/kcrawford/dockutil/pull/87)) -- [outset](https://github.com/chilcote/outset) - -## Recommended -This is a Python.framework that contains everything from "Minimal", and a few libraries that various well-known open source projects require. +``` +./build_python_framework_pkgs.zsh --python-version 3.13.13 +``` -Tools that should work when using the "Recommended Flavor": -- [Autopkg](https://github.com/autopkg/autopkg) -- [InstallApplications](https://github.com/macadmins/installapplications) -- [Munki](https://github.com/munki/munki) -- [munkipkg](https://github.com/munki/munki-pkg) -- [munki-facts](https://github.com/munki/munki-facts) (python 3 pull request [here](https://github.com/munki/munki-facts/pull/17)) -- [Nudge](https://github.com/macadmins/nudge) -- [UMAD](https://github.com/macadmins/umad) +Pass `--installer-id`, `--application-id`, and `--notary-password` to produce a signed and notarized `.pkg`. # Updating packages -This should be done in a clean virtual environment. After every Python package install, you can run `pip freeze | xargs pip uninstall -y` to cleanup the environment. +Do this in a clean virtual environment. After every Python package install, run `pip freeze | xargs pip uninstall -y` to reset the environment. # CI Job -To update the certificate, run `base64 -i /path/to/certificate.p12 -o base64string` and import that into the github secrets store and update the password secret as well. +To update the signing certificate, run `base64 -i /path/to/certificate.p12 -o base64string` and import it into the GitHub Actions secrets store along with the matching password. # Credits -These packages are created with two other open source tools: +Built on two open-source tools by [Greg Neagle](https://www.linkedin.com/in/gregneagle/): - [relocatable-python](https://github.com/gregneagle/relocatable-python) - [munki-pkg](https://github.com/munki/munki-pkg) - -Both are written by [Greg Neagle](https://www.linkedin.com/in/gregneagle/). Thank you for your continued dedication to the macOS platform. diff --git a/build_all_python_frameworks.zsh b/build_all_python_frameworks.zsh deleted file mode 100755 index 84948c0..0000000 --- a/build_all_python_frameworks.zsh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/zsh -# -# Build script for all Python 3 frameworks -# Adapted from https://github.com/munki/munki/blob/Munki3dev/code/tools/build_python_framework.sh -# IMPORTANT -# Run this with your current directory being the path where this script is located - -TOOLSDIR=$(dirname $0) -SIGNING_IDENTITY="Developer ID Installer: Mac Admins Open Source (T4SK8ZXCXG)" - -"$TOOLSDIR/build_python_framework_pkgs.zsh" minimal ${SIGNING_IDENTITY} -"$TOOLSDIR/build_python_framework_pkgs.zsh" no_customization ${SIGNING_IDENTITY} -"$TOOLSDIR/build_python_framework_pkgs.zsh" recommended ${SIGNING_IDENTITY} diff --git a/build_python_framework_pkgs.zsh b/build_python_framework_pkgs.zsh index b857ea0..87dcbe1 100755 --- a/build_python_framework_pkgs.zsh +++ b/build_python_framework_pkgs.zsh @@ -1,350 +1,282 @@ #!/bin/zsh # -# Build script for Python 3 frameworks -# Adaptd from https://github.com/munki/munki/blob/Munki3dev/code/tools/build_python_framework.sh -# IMPORTANT -# Run this with your current directory being the path where this script is located - -# Harcoded (commit) versions of relocatable-python & munki-pkg -RP_SHA="fb4dd9b024b249c71713f14d887f4bcea78aa8b0" # https://github.com/gregneagle/relocatable-python/commits/main/ -MP_SHA="96cffb4eac9207c1130404ec1fee8f4777fa38fd" # https://github.com/munki/munki-pkg/commits/main/ -MACOS_VERSION=11 # use 10.9 for non-universal -PYTHON_PRERELEASE_VERSION= -PYTHON_BASEURL="https://www.python.org/ftp/python/%s/python-%s${PYTHON_PRERELEASE_VERSION}-macos%s.pkg" -# Hardcoded paths +# Build the macadmins Python 3 framework. +# Produces an installable .pkg (when signing identities are supplied) and a +# portable framework zip targeting Apple Silicon. +# +# Adapted from https://github.com/munki/munki/blob/Munki3dev/code/tools/build_python_framework.sh + +set -eu + +# --- Pinned upstream commits --- +RP_SHA="8ee72fe3a5dbef733365370ebf44f25022b895ef" # gregneagle/relocatable-python +MP_SHA="bbd07730d1b93ed3828246575ef5676bba74b5d1" # munki/munki-pkg + +# --- Paths and constants --- +TYPE="recommended" FRAMEWORKDIR="/Library/ManagedFrameworks/Python" -PYTHON_BIN="$FRAMEWORKDIR/Python3.framework/Versions/Current/bin/python3" PYTHON_BIN_NEW="$FRAMEWORKDIR/Python3.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python" +PYTHON_BASEURL="https://www.python.org/ftp/python/%s/python-%s-macos%s.pkg" +TOOLSDIR="$(/usr/bin/dirname "$0")" +OUTPUTSDIR="$TOOLSDIR/outputs" RP_BINDIR="/tmp/relocatable-python" MP_BINDIR="/tmp/munki-pkg" -CONSOLEUSER=$(/usr/bin/stat -f "%Su" /dev/console) -PIPCACHEDIR="/Users/${CONSOLEUSER}/Library/Caches/pip" -XCODE_PATH="/Applications/Xcode_15.2.app" -XCODE_NOTARY_PATH="$XCODE_PATH/Contents/Developer/usr/bin/notarytool" -XCODE_STAPLER_PATH="$XCODE_PATH/Contents/Developer/usr/bin/stapler" -NEWSUBBUILD=$((80620 + $(/usr/bin/git rev-parse HEAD~0 | xargs -I{} /usr/bin/git rev-list --count {}))) - -# Sanity Checks -## Type Check -if [ -n "$1" ]; then - if [[ "$1" == 'minimal' ]]; then - TYPE=$1 - elif [[ "$1" == "no_customization" ]]; then - TYPE=$1 - elif [[ "$1" == 'recommended' ]]; then - TYPE=$1 - else - echo "Specified positional argument other than recommended. Using minimal workflow" - TYPE='minimal' - fi -else - echo "runner.zsh" - echo "" - echo " Configures Relocatable Python" - echo " Options:" - echo " minimal" - echo " Identical to the original relocatable python code" - echo "" - echo " no_customization" - echo " A python framework without any customizations" - echo "" - echo " recommended" - echo " A python framework with libraries for commonly used tools like autopkg and munki" - exit 1 -fi - -if [ -n "$4" ]; then - PYTHON_VERSION=$4 -else - PYTHON_VERSION=3.13.5 -fi - -if [ -n "$5" ]; then - PYTHON_MAJOR_VERSION=$5 -else - PYTHON_MAJOR_VERSION=3.13 -fi -# Set python bin version based on PYTHON_VERSION -PYTHON_BIN_VERSION="${PYTHON_VERSION%.*}" -AUTOMATED_PYTHON_BUILD="$PYTHON_VERSION.$NEWSUBBUILD" - -# Variables -TOOLSDIR=$(dirname $0) -OUTPUTSDIR="$TOOLSDIR/outputs" -CONSOLEUSER=$(/usr/bin/stat -f "%Su" /dev/console) RP_ZIP="/tmp/relocatable-python.zip" MP_ZIP="/tmp/munki-pkg.zip" +CONSOLEUSER="$(/usr/bin/stat -f "%Su" /dev/console)" +PIPCACHEDIR="/Users/${CONSOLEUSER}/Library/Caches/pip" + +# --- CLI arguments (set by parse_args) --- +PYTHON_VERSION="" +INSTALLER_ID="" +APPLICATION_ID="" +NOTARY_PASSWORD="" +XCODE_PATH="" + +usage() { + cat <&2; usage; exit 1 ;; + esac + done + + if [[ -z "$PYTHON_VERSION" ]]; then + echo "error: --python-version is required" >&2 + usage + exit 1 + fi + + PYTHON_MAJOR_VERSION="${PYTHON_VERSION%.*}" # 3.13.13 -> 3.13 + PYTHON_BIN_VERSION="$PYTHON_MAJOR_VERSION" +} + +is_ci() { + [[ -n "${CI:-}" || -n "${GITHUB_ACTIONS:-}" ]] +} + +derive_build_version() { + local rev_count + rev_count="$(/usr/bin/git -C "$TOOLSDIR" rev-list --count HEAD)" + NEWSUBBUILD=$((80620 + rev_count)) + AUTOMATED_PYTHON_BUILD="$PYTHON_VERSION.$NEWSUBBUILD" + echo "$AUTOMATED_PYTHON_BUILD" > "$TOOLSDIR/build_info.txt" + echo "Build version: $AUTOMATED_PYTHON_BUILD" +} + +prepare_ci_env() { + if ! is_ci; then + return + fi + echo "CI detected — clearing Homebrew and selecting Xcode." + /usr/local/bin/brew remove --force "$(/usr/local/bin/brew list)" || true + if [[ -n "$XCODE_PATH" && -d "$XCODE_PATH" ]]; then + /usr/bin/sudo /usr/bin/xcode-select -s "$XCODE_PATH" + fi +} + +prepare_build_dirs() { + /usr/bin/sudo /bin/mkdir -p "$FRAMEWORKDIR" + # mkdir -m only applies to newly created dirs; ensure existing dirs are writable. + /usr/bin/sudo /bin/chmod 777 "$FRAMEWORKDIR" + if [[ -d "$FRAMEWORKDIR/Python.framework" ]]; then + /usr/bin/sudo /bin/rm -rf "$FRAMEWORKDIR/Python.framework" + fi + if [[ -d "$PIPCACHEDIR" ]]; then + echo "Removing pip cache to reduce build errors" + /usr/bin/sudo /bin/rm -rf "$PIPCACHEDIR" + fi -# Create files to use for build process info -echo "$AUTOMATED_PYTHON_BUILD" > $TOOLSDIR/build_info.txt - -echo "Creating Python Framework - $TYPE" - -# Create framework path if not present with 777 so sudo is not needed -if [ ! -d "${FRAMEWORKDIR}" ]; then - /usr/bin/sudo /bin/mkdir -m 777 -p "${FRAMEWORKDIR}" -fi - -# remove existing Python.framework if present -if [ -d "${FRAMEWORKDIR}/Python.framework" ]; then - /usr/bin/sudo /bin/rm -rf "${FRAMEWORKDIR}/Python.framework" -fi - -# remove existing library Python.framework if present -if [ -d "${PIPCACHEDIR}" ]; then - echo "Removing pip cache to reduce framework build errors" - /usr/bin/sudo /bin/rm -rf "${PIPCACHEDIR}" -fi - -# kill homebrew packages on GitHub runner -/usr/local/bin/brew remove --force $(/usr/local/bin/brew list) - -# Ensure Xcode is set to run-time -sudo xcode-select -s "$XCODE_PATH" - -if [ -e $XCODE_BUILD_PATH ]; then - XCODE_BUILD="$XCODE_BUILD_PATH" -else - ls -la /Applications - echo "Could not find required Xcode build. Exiting..." - exit 1 -fi - -# Download specific version of relocatable-python -echo "Downloading relocatable-python tool from github..." -if [ -f "${RP_ZIP}" ]; then - /usr/bin/sudo /bin/rm -rf ${RP_ZIP} -fi -/usr/bin/curl https://github.com/gregneagle/relocatable-python/archive/${RP_SHA}.zip -L -o ${RP_ZIP} -if [ -d ${RP_BINDIR} ]; then - /usr/bin/sudo /bin/rm -rf ${RP_BINDIR} -fi -/usr/bin/unzip ${RP_ZIP} -d ${RP_BINDIR} -DL_RESULT="$?" -if [ "${DL_RESULT}" != "0" ]; then - echo "Error downloading relocatable-python tool: ${DL_RESULT}" 1>&2 - exit 1 -fi - -# remove existing Python package folders and recreate -if [ -d "$TOOLSDIR/$TYPE" ]; then /bin/rm -rf "$TOOLSDIR/$TYPE" /bin/mkdir -p "$TOOLSDIR/$TYPE/scripts" /bin/mkdir -p "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}" /bin/mkdir -p "$TOOLSDIR/$TYPE/payload/usr/local/bin" - /usr/bin/sudo /usr/sbin/chown -R ${CONSOLEUSER}:wheel "$TOOLSDIR/$TYPE" -else - /bin/mkdir -p "$TOOLSDIR/$TYPE/scripts" - /bin/mkdir -p "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}" - /bin/mkdir -p "$TOOLSDIR/$TYPE/payload/usr/local/bin" - /usr/bin/sudo /usr/sbin/chown -R ${CONSOLEUSER}:wheel "$TOOLSDIR/$TYPE" -fi - -# make a symbolic link to help with interactive use -if [[ "${PYTHON_MAJOR_VERSION}" == "3.9" ]]; then - /bin/ln -s "$PYTHON_BIN_NEW" "$TOOLSDIR/$TYPE/payload/usr/local/bin/managed_python3" -fi -if [[ "${PYTHON_MAJOR_VERSION}" == "3.10" ]]; then - /bin/ln -s "$PYTHON_BIN_NEW" "$TOOLSDIR/$TYPE/payload/usr/local/bin/managed_python3" -fi -if [[ "${PYTHON_MAJOR_VERSION}" == "3.11" ]]; then - /bin/ln -s "$PYTHON_BIN_NEW" "$TOOLSDIR/$TYPE/payload/usr/local/bin/managed_python3" -fi -if [[ "${PYTHON_MAJOR_VERSION}" == "3.12" ]]; then - /bin/ln -s "$PYTHON_BIN_NEW" "$TOOLSDIR/$TYPE/payload/usr/local/bin/managed_python3" -fi -if [[ "${PYTHON_MAJOR_VERSION}" == "3.13" ]]; then - /bin/ln -s "$PYTHON_BIN_NEW" "$TOOLSDIR/$TYPE/payload/usr/local/bin/managed_python3" -fi - -SB_RESULT="$?" -if [ "${SB_RESULT}" != "0" ]; then - echo "Failed create managed_python3 object" 1>&2 - exit 1 -fi - -# build the framework -# Force the C path depending on the version of Python to allow tools like cffi/xattr to build without wheels otherwise it errors -# Can't use Apple's headers for 3.10 and higher as they are (currently) 3.9 -# C_INCLUDE_PATH="/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/Current/Headers/" - -export C_INCLUDE_PATH="/Library/ManagedFrameworks/Python/Python.framework/Versions/Current/Headers/" - -C_INCLUDE_PATH="/Library/ManagedFrameworks/Python/Python.framework/Versions/Current/Headers/" RP_EXTRACT_BINDIR="${RP_BINDIR}/relocatable-python-${RP_SHA}" -"${RP_EXTRACT_BINDIR}/make_relocatable_python_framework.py" \ ---baseurl "${PYTHON_BASEURL}" \ ---python-version "${PYTHON_VERSION}" \ ---os-version "${MACOS_VERSION}" \ ---upgrade-pip \ ---no-unsign \ ---pip-requirements "${TOOLSDIR}/requirements_${TYPE}.txt" \ ---destination "${FRAMEWORKDIR}" - -RP_RESULT="$?" -if [ "${RP_RESULT}" != "0" ]; then - echo "Error running relocatable-python tool: ${RP_RESULT}" 1>&2 - exit 1 -fi - -# move the framework to the Python package folder -echo "Moving Python.framework to payload folder" -/bin/mv "${FRAMEWORKDIR}/Python.framework" "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework" - -RP_RESULT2="$?" -if [ "${RP_RESULT2}" != "0" ]; then - echo "Failed to move Python framework, likely due to a bug with relocatable python" 1>&2 - exit 1 -fi - -# confirm truly universal -TOTAL_DYLIB=$(/usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -name "*.dylib" | /usr/bin/wc -l | /usr/bin/xargs) -UNIVERSAL_DYLIB=$(/usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -name "*.dylib" | /usr/bin/xargs file | /usr/bin/grep "2 architectures" | /usr/bin/wc -l | /usr/bin/xargs) -if [ "${TOTAL_DYLIB}" != "${UNIVERSAL_DYLIB}" ] ; then - echo "Dynamic Libraries do not match, resulting in a non-universal Python framework." - echo "Total Dynamic Libraries found: ${TOTAL_DYLIB}" - echo "Universal Dynamic Libraries found: ${UNIVERSAL_DYLIB}" - exit 1 -fi - -echo "Dynamic Libraries are confirmed as universal" - -TOTAL_SO=$(/usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -name "*.so" | /usr/bin/wc -l | /usr/bin/xargs) -UNIVERSAL_SO=$(/usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -name "*.so" | /usr/bin/xargs file | /usr/bin/grep "2 architectures" | /usr/bin/wc -l | /usr/bin/xargs) -if [ "${TOTAL_SO}" != "${UNIVERSAL_SO}" ] ; then - echo "Shared objects do not match, resulting in a non-universal Python framework." - echo "Total shared objects found: ${TOTAL_SO}" - echo "Universal shared objects found: ${UNIVERSAL_SO}" - UNIVERSAL_SO_ARRAY=("${(@f)$(/usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -name "*.so" | /usr/bin/xargs file | /usr/bin/grep "2 architectures" | awk '{print $1;}' | sed 's/:*$//g')}") - TOTAL_SO_ARRAY=("${(@f)$(/usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -name "*.so" )}") - echo ${TOTAL_SO_ARRAY[@]} ${UNIVERSAL_SO_ARRAY[@]} | tr ' ' '\n' | sort | uniq -u - exit 1 -fi - -echo "Shared objects are confirmed as universal" - -# re-sign the framework so it will run on Apple Silicon -# Notes: -# - Use --options=runtime to force-enable hardened runtime (required for -# notarization on macOS 13+). Don't rely on --preserve-metadata=runtime -# since install_name_tool just invalidated the existing signature, so -# there is nothing reliable to preserve. -# - Do NOT sign Versions/Current/Python; it's a symlink to Versions/X.Y/Python -# which we just signed. Re-signing through the symlink double-signs the -# same target and corrupts the signature on newer Python frameworks. -NESTED_FRAMEWORKS_DIR="$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/Frameworks" -if [ -n "$3" ]; then - echo "Adding developer id code signing so the framework will run on Apple Silicon..." - /usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/bin" -type f -perm -u=x -exec /usr/bin/codesign --sign "$3" --timestamp --options=runtime --preserve-metadata=identifier,entitlements,flags -f {} \; - /usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -type f -perm -u=x -exec /usr/bin/codesign --sign "$3" --timestamp --options=runtime --preserve-metadata=identifier,entitlements,flags -f {} \; - /usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -type f -name "*dylib" -exec /usr/bin/codesign --sign "$3" --timestamp --options=runtime --preserve-metadata=identifier,entitlements,flags -f {} \; - # Nested Tcl/Tk frameworks (bundled inside Python 3.13+). install_name_tool - # invalidates their python.org signatures during the relocatable rewrite. - # Sign each nested framework as a bundle (NOT the inner binary alone) so - # codesign regenerates the framework's _CodeSignature/CodeResources file - # to match the re-signed binary. --deep walks the framework's Versions/ - # tree and signs the binary at the same time. Without this, signing only - # the inner binary leaves the bundle's CodeResources pointing at the old - # binary hash → "nested code is modified or invalid". - if [ -d "$NESTED_FRAMEWORKS_DIR" ]; then - for nested_fw in "$NESTED_FRAMEWORKS_DIR"/*.framework; do - [ -d "$nested_fw" ] || continue - /usr/bin/codesign --sign "$3" --timestamp --options=runtime --force --deep "$nested_fw" - done - fi - /usr/bin/codesign --sign "$3" --timestamp --options=runtime --deep --force --preserve-metadata=identifier,entitlements,flags "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/Resources/Python.app" - /usr/bin/codesign --sign "$3" --timestamp --options=runtime --force --preserve-metadata=identifier,entitlements,flags "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/Python" -else - echo "Adding ad-hoc code signing so the framework will run on Apple Silicon..." - /usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/bin" -type f -perm -u=x -exec /usr/bin/codesign -s - --options=runtime --preserve-metadata=identifier,entitlements,flags -f {} \; - /usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -type f -perm -u=x -exec /usr/bin/codesign -s - --options=runtime --preserve-metadata=identifier,entitlements,flags -f {} \; - /usr/bin/find "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/lib" -type f -name "*dylib" -exec /usr/bin/codesign -s - --options=runtime --preserve-metadata=identifier,entitlements,flags -f {} \; - if [ -d "$NESTED_FRAMEWORKS_DIR" ]; then - for nested_fw in "$NESTED_FRAMEWORKS_DIR"/*.framework; do - [ -d "$nested_fw" ] || continue - /usr/bin/codesign -s - --options=runtime --force --deep "$nested_fw" - done - fi - /usr/bin/codesign -s - --options=runtime --deep --force --preserve-metadata=identifier,entitlements,flags "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/Resources/Python.app" - /usr/bin/codesign -s - --options=runtime --force --preserve-metadata=identifier,entitlements,flags "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/Python" -fi - -# Print out some information about the signatures -/usr/sbin/spctl -a -vvvv "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework/Versions/${PYTHON_BIN_VERSION}/Python" - -# take ownership of the payload folder -echo "Taking ownership of the Payload directory" -/usr/bin/sudo /usr/sbin/chown -R ${CONSOLEUSER}:wheel "$TOOLSDIR/$TYPE" - -# Download specific version of munki-pkg -echo "Downloading munki-pkg tool from github..." -if [ -f "${MP_ZIP}" ]; then - /usr/bin/sudo /bin/rm -rf ${MP_ZIP} -fi -/usr/bin/curl https://github.com/munki/munki-pkg/archive/${MP_SHA}.zip -L -o ${MP_ZIP} -if [ -d ${MP_BINDIR} ]; then - /usr/bin/sudo /bin/rm -rf ${MP_BINDIR} -fi -/usr/bin/unzip ${MP_ZIP} -d ${MP_BINDIR} -DL_RESULT="$?" -if [ "${DL_RESULT}" != "0" ]; then - echo "Error downloading munki-pkg tool: ${DL_RESULT}" 1>&2 - exit 1 -fi - -# Create outputs folder -/bin/mkdir -p "$TOOLSDIR/outputs" - -# -/bin/cp "${TOOLSDIR}/preinstall-cleanup" "$TOOLSDIR/$TYPE/scripts/preinstall" - -if [ -n "$2" ]; then - # Create the json file for munki-pkg (signed) - /bin/cat << SIGNED_JSONFILE > "$TOOLSDIR/$TYPE/build-info.json" - { - "ownership": "recommended", - "suppress_bundle_relocation": true, - "identifier": "io.macadmins.python.$TYPE", - "postinstall_action": "none", - "distribution_style": true, - "version": "$AUTOMATED_PYTHON_BUILD", - "name": "python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg", - "install_location": "/", - "preserve_xattr": true, - "signing_info": { - "identity": "$2", - "timestamp": true - } + /usr/bin/sudo /usr/sbin/chown -R "${CONSOLEUSER}":wheel "$TOOLSDIR/$TYPE" + + /bin/ln -s "$PYTHON_BIN_NEW" "$TOOLSDIR/$TYPE/payload/usr/local/bin/managed_python3" +} + +download_tool() { + local name="$1" sha="$2" url="$3" zip_path="$4" dest="$5" + echo "Downloading $name @ $sha" + /bin/rm -rf "$zip_path" "$dest" + /usr/bin/curl -fL "$url" -o "$zip_path" + /usr/bin/unzip -q "$zip_path" -d "$dest" +} + +build_framework() { + export C_INCLUDE_PATH="/Library/ManagedFrameworks/Python/Python.framework/Versions/Current/Headers/" + local rp_extract="${RP_BINDIR}/relocatable-python-${RP_SHA}" + "${rp_extract}/make_relocatable_python_framework.py" \ + --baseurl "${PYTHON_BASEURL}" \ + --python-version "${PYTHON_VERSION}" \ + --os-version 11 \ + --upgrade-pip \ + --pip-requirements "${TOOLSDIR}/requirements_${TYPE}.txt" \ + --destination "${FRAMEWORKDIR}" + + /bin/mv "${FRAMEWORKDIR}/Python.framework" \ + "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework" +} + +codesign_framework() { + local identity="${APPLICATION_ID:--}" # `-` means ad-hoc + local framework_root="$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework" + local versioned="$framework_root/Versions/${PYTHON_BIN_VERSION}" + local nested_frameworks_dir="$versioned/Frameworks" + + if [[ "$identity" == "-" ]]; then + echo "Ad-hoc signing framework" + else + echo "Signing framework with identity: $identity" + fi + + # Codesign notes: + # - Use --options=runtime to force-enable hardened runtime (required for + # notarization on macOS 13+). Do NOT include `runtime` in + # --preserve-metadata: install_name_tool just invalidated the prior + # signature, so there is nothing reliable to inherit. + # - Do NOT sign Versions/Current/Python — it's a symlink to + # Versions/X.Y/Python which we already signed. Double-signing through + # the symlink corrupts the signature on newer Python frameworks. + # - Sign nested .framework bundles (Tcl, Tk in Python 3.13+) with --deep, + # not via per-binary find. The bundle's _CodeSignature/CodeResources + # file must be regenerated to match the re-signed inner binary; + # otherwise notarytool reports "nested code is modified or invalid". + + local -a cs_args + if [[ "$identity" == "-" ]]; then + cs_args=(--options=runtime --preserve-metadata=identifier,entitlements,flags -f) + else + cs_args=(--timestamp --options=runtime --preserve-metadata=identifier,entitlements,flags -f) + fi + + /usr/bin/find "$versioned/bin" -type f -perm -u=x -exec \ + /usr/bin/codesign -s "$identity" "${cs_args[@]}" {} \; + /usr/bin/find "$versioned/lib" -type f -perm -u=x -exec \ + /usr/bin/codesign -s "$identity" "${cs_args[@]}" {} \; + /usr/bin/find "$versioned/lib" -type f -name "*dylib" -exec \ + /usr/bin/codesign -s "$identity" "${cs_args[@]}" {} \; + + if [[ -d "$nested_frameworks_dir" ]]; then + local nested_fw + for nested_fw in "$nested_frameworks_dir"/*.framework; do + [[ -d "$nested_fw" ]] || continue + if [[ "$identity" == "-" ]]; then + /usr/bin/codesign -s "$identity" --options=runtime --force --deep "$nested_fw" + else + /usr/bin/codesign -s "$identity" --timestamp --options=runtime --force --deep "$nested_fw" + fi + done + fi + + /usr/bin/codesign -s "$identity" --deep "${cs_args[@]}" "$versioned/Resources/Python.app" + /usr/bin/codesign -s "$identity" "${cs_args[@]}" "$versioned/Python" + + /usr/sbin/spctl -a -vvvv "$versioned/Python" || true +} + +build_pkg() { + /bin/mkdir -p "$OUTPUTSDIR" + /bin/cp "${TOOLSDIR}/preinstall-cleanup" "$TOOLSDIR/$TYPE/scripts/preinstall" + + if [[ -z "$INSTALLER_ID" ]]; then + echo "No installer identity provided; skipping signed pkg" + return + fi + + /bin/cat < "$TOOLSDIR/$TYPE/build-info.json" +{ + "ownership": "recommended", + "suppress_bundle_relocation": true, + "identifier": "io.macadmins.python.$TYPE", + "postinstall_action": "none", + "distribution_style": true, + "version": "$AUTOMATED_PYTHON_BUILD", + "name": "python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg", + "install_location": "/", + "preserve_xattr": true, + "signing_info": { + "identity": "$INSTALLER_ID", + "timestamp": true } -SIGNED_JSONFILE - # Create the signed pkg - "${MP_BINDIR}/munki-pkg-${MP_SHA}/munkipkg" "$TOOLSDIR/$TYPE" - PKG_RESULT="$?" - if [ "${PKG_RESULT}" != "0" ]; then - echo "Could not sign package: ${PKG_RESULT}" 1>&2 - exit 1 - else - if [ -n "$6" ]; then - # Notarize and staple the package - $XCODE_NOTARY_PATH store-credentials --apple-id "opensource@macadmins.io" --team-id "T4SK8ZXCXG" --password "$NOTARY_APP_PASSWORD" macadminpython - # If these fail, it will bail on the entire process - $XCODE_NOTARY_PATH submit "$TOOLSDIR/$TYPE/build/python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg" --keychain-profile "macadminpython" --wait - $XCODE_STAPLER_PATH staple "$TOOLSDIR/$TYPE/build/python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg" +} +JSON + + "${MP_BINDIR}/munki-pkg-${MP_SHA}/munkipkg" "$TOOLSDIR/$TYPE" + local pkg_built="$TOOLSDIR/$TYPE/build/python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg" + /bin/mv "$pkg_built" "$OUTPUTSDIR" +} + +notarize_and_staple() { + if [[ -z "$NOTARY_PASSWORD" || -z "$INSTALLER_ID" ]]; then + echo "Skipping notarization (no notary password or installer id)" + return fi - # Move the signed + notarized pkg - /bin/mv "$TOOLSDIR/$TYPE/build/python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg" "$OUTPUTSDIR" - fi -else - echo "no signing identity passed, skipping signed package creation" -fi - -# Zip and move the framework -ZIPFILE="Python3.framework_$TYPE-$AUTOMATED_PYTHON_BUILD.zip" -/usr/bin/ditto -c -k --sequesterRsrc "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/" ${ZIPFILE} -/bin/mv ${ZIPFILE} "$OUTPUTSDIR" - -# Ensure outputs directory is owned by the current user -/usr/bin/sudo /usr/sbin/chown -R ${CONSOLEUSER}:wheel "$OUTPUTSDIR" - -# Cleanup the temporary files -/usr/bin/sudo /bin/rm -rf "$TOOLSDIR/$TYPE" -/usr/bin/sudo /bin/rm -rf "${FRAMEWORKDIR}" + local xcode_dev xcode_notary xcode_stapler pkg + xcode_dev="$(/usr/bin/xcode-select -p)" + xcode_notary="$xcode_dev/usr/bin/notarytool" + xcode_stapler="$xcode_dev/usr/bin/stapler" + pkg="$OUTPUTSDIR/python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg" + + "$xcode_notary" store-credentials \ + --apple-id "opensource@macadmins.io" \ + --team-id "T4SK8ZXCXG" \ + --password "$NOTARY_PASSWORD" \ + macadminpython + "$xcode_notary" submit "$pkg" --keychain-profile macadminpython --wait + "$xcode_stapler" staple "$pkg" +} + +zip_framework() { + local zipfile="Python3.framework_$TYPE-$AUTOMATED_PYTHON_BUILD.zip" + /usr/bin/ditto -c -k --sequesterRsrc \ + "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/" "$zipfile" + /bin/mv "$zipfile" "$OUTPUTSDIR" + /usr/bin/sudo /usr/sbin/chown -R "${CONSOLEUSER}":wheel "$OUTPUTSDIR" +} + +cleanup() { + /usr/bin/sudo /bin/rm -rf "$TOOLSDIR/$TYPE" + /usr/bin/sudo /bin/rm -rf "$FRAMEWORKDIR" +} + +# --- Main --- +parse_args "$@" +echo "Building Python framework — $PYTHON_VERSION" +prepare_ci_env +derive_build_version +prepare_build_dirs +download_tool relocatable-python "$RP_SHA" \ + "https://github.com/gregneagle/relocatable-python/archive/${RP_SHA}.zip" \ + "$RP_ZIP" "$RP_BINDIR" +download_tool munki-pkg "$MP_SHA" \ + "https://github.com/munki/munki-pkg/archive/${MP_SHA}.zip" \ + "$MP_ZIP" "$MP_BINDIR" +build_framework +codesign_framework +build_pkg +notarize_and_staple +zip_framework +cleanup +echo "Done." diff --git a/docs/superpowers/plans/2026-05-11-apple-silicon-modernization-tests.md b/docs/superpowers/plans/2026-05-11-apple-silicon-modernization-tests.md new file mode 100644 index 0000000..2033800 --- /dev/null +++ b/docs/superpowers/plans/2026-05-11-apple-silicon-modernization-tests.md @@ -0,0 +1,55 @@ +```bash +sudo rm -rf /Library/ManagedFrameworks/Python +./build_python_framework_pkgs.zsh --python-version 3.9.13 +sudo ditto -x -k outputs/Python3.framework_recommended-3.9.13.*.zip /Library/ManagedFrameworks/Python/ +managed_python3 --version && managed_python3 -c "import platform; print(platform.machine())" && managed_python3 -c "import objc, xattr, requests, yaml; print('ok')" + +Python 3.9.13 +arm64 +ok + +sudo rm -rf /Library/ManagedFrameworks/Python +./build_python_framework_pkgs.zsh --python-version 3.10.11 +sudo ditto -x -k outputs/Python3.framework_recommended-3.10.11.*.zip /Library/ManagedFrameworks/Python/ +managed_python3 --version && managed_python3 -c "import platform; print(platform.machine())" && managed_python3 -c "import objc, xattr, requests, yaml; print('ok')" + +Python 3.10.11 +arm64 +ok + +sudo rm -rf /Library/ManagedFrameworks/Python +./build_python_framework_pkgs.zsh --python-version 3.11.9 +sudo ditto -x -k outputs/Python3.framework_recommended-3.11.9.*.zip /Library/ManagedFrameworks/Python/ +managed_python3 --version && managed_python3 -c "import platform; print(platform.machine())" && managed_python3 -c "import objc, xattr, requests, yaml; print('ok')" + +Python 3.11.9 +arm64 +ok + +sudo rm -rf /Library/ManagedFrameworks/Python +./build_python_framework_pkgs.zsh --python-version 3.12.10 +sudo ditto -x -k outputs/Python3.framework_recommended-3.12.10.*.zip /Library/ManagedFrameworks/Python/ +managed_python3 --version && managed_python3 -c "import platform; print(platform.machine())" && managed_python3 -c "import objc, xattr, requests, yaml; print('ok')" + +Python 3.12.10 +arm64 +ok + +sudo rm -rf /Library/ManagedFrameworks/Python +./build_python_framework_pkgs.zsh --python-version 3.13.13 +sudo ditto -x -k outputs/Python3.framework_recommended-3.13.13.*.zip /Library/ManagedFrameworks/Python/ +managed_python3 --version && managed_python3 -c "import platform; print(platform.machine())" && managed_python3 -c "import objc, xattr, requests, yaml; print('ok')" + +Python 3.13.13 +arm64 +ok + +sudo rm -rf /Library/ManagedFrameworks/Python +./build_python_framework_pkgs.zsh --python-version 3.14.5 +sudo ditto -x -k outputs/Python3.framework_recommended-3.14.5.*.zip /Library/ManagedFrameworks/Python/ +managed_python3 --version && managed_python3 -c "import platform; print(platform.machine())" && managed_python3 -c "import objc, xattr, requests, yaml; print('ok')" + +Python 3.14.5 +arm64 +ok +``` \ No newline at end of file diff --git a/docs/superpowers/plans/2026-05-11-apple-silicon-modernization.md b/docs/superpowers/plans/2026-05-11-apple-silicon-modernization.md new file mode 100644 index 0000000..bdb8fb4 --- /dev/null +++ b/docs/superpowers/plans/2026-05-11-apple-silicon-modernization.md @@ -0,0 +1,878 @@ +# Apple Silicon Modernization Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Refactor `build_python_framework_pkgs.zsh` to run natively on Apple Silicon without universal2 enforcement, trim to a single build flavor, bump upstream SHAs and Python interpreter versions, add Python 3.14, and cut a final release for the EOL 3.9 and 3.10 branches. + +**Architecture:** The build script is reorganized into named functions (`parse_args`, `prepare_build_dirs`, `download_tool`, `build_framework`, `codesign_framework`, `build_pkg`, `notarize_and_staple`, `zip_framework`, `cleanup`) with `set -eu` short-circuiting on failure. Long-flag arguments replace positional ones, with the major Python version derived from the full version. CI-only steps (Homebrew nuke, `xcode-select`) are gated behind `$CI` / `$GITHUB_ACTIONS`. The universal2 dylib/so audit is deleted; arm64 wheels from PyPI are used directly. + +**Tech Stack:** zsh, `relocatable-python`, `munki-pkg`, `codesign`, `notarytool`, GitHub Actions, Python 3.9 – 3.14. + +--- + +## File Structure + +**Modified files:** +- `build_python_framework_pkgs.zsh` — full rewrite into functions; drops universal check, ad-hoc/signed codesign duplication, per-version symlink branches, and CI-environment hardcodes. Bumps `RP_SHA` and `MP_SHA`. +- `requirements_recommended.txt` — drop `--no-binary` directives for `black`, `cffi`, `charset-normalizer`, `PyYAML`, `tomli`, `xattr`. Bump package pins where newer versions have arm64 wheels across all supported branches. +- `README.md` — drop "Flavors of Python", "Minimal", "No Customization", and "build on Intel macOS" sections; update examples for Apple Silicon. +- `.github/workflows/build_python_3.11.yml` — bump `PYTHON_VERSION` to `3.11.9`. +- `.github/workflows/build_python_3.12.yml` — bump `PYTHON_VERSION` to `3.12.10`. +- `.github/workflows/build_python_3.13.yml` — bump `PYTHON_VERSION` to `3.13.13`. +- `.github/workflows/build_python_3.9.yml` — augment release notes to mark final release; leave Python version at `3.9.13`. +- `.github/workflows/build_python_3.10.yml` — augment release notes to mark final release; leave Python version at `3.10.11`. + +**Created files:** +- `.github/workflows/build_python_3.14.yml` — new workflow for Python 3.14.5 (cloned and adapted from 3.13). +- `.github/dependabot.yml` — Dependabot config for GitHub Actions only (no pip). + +**Deleted files:** +- `requirements_minimal.txt` +- `requirements_no_customization.txt` +- `requirement_files/requirements_minimal.txt` +- `requirement_files/requirements_opinionated.txt` +- `build_all_python_frameworks.zsh` + +--- + +## Task 1: Rewrite `build_python_framework_pkgs.zsh` + +**Files:** +- Modify: `build_python_framework_pkgs.zsh` (full rewrite) + +- [ ] **Step 1: Replace the entire file contents** + +Replace the file with this content verbatim: + +```zsh +#!/bin/zsh +# +# Build the macadmins Python 3 framework. +# Produces an installable .pkg (when signing identities are supplied) and a +# portable framework zip targeting Apple Silicon. +# +# Adapted from https://github.com/munki/munki/blob/Munki3dev/code/tools/build_python_framework.sh + +set -eu + +# --- Pinned upstream commits --- +RP_SHA="8ee72fe3a5dbef733365370ebf44f25022b895ef" # gregneagle/relocatable-python +MP_SHA="bbd07730d1b93ed3828246575ef5676bba74b5d1" # munki/munki-pkg + +# --- Paths and constants --- +TYPE="recommended" +FRAMEWORKDIR="/Library/ManagedFrameworks/Python" +PYTHON_BIN_NEW="$FRAMEWORKDIR/Python3.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python" +PYTHON_BASEURL="https://www.python.org/ftp/python/%s/python-%s-macos11.pkg" +TOOLSDIR="$(/usr/bin/dirname "$0")" +OUTPUTSDIR="$TOOLSDIR/outputs" +RP_BINDIR="/tmp/relocatable-python" +MP_BINDIR="/tmp/munki-pkg" +RP_ZIP="/tmp/relocatable-python.zip" +MP_ZIP="/tmp/munki-pkg.zip" +CONSOLEUSER="$(/usr/bin/stat -f "%Su" /dev/console)" +PIPCACHEDIR="/Users/${CONSOLEUSER}/Library/Caches/pip" + +# --- CLI arguments (set by parse_args) --- +PYTHON_VERSION="" +INSTALLER_ID="" +APPLICATION_ID="" +NOTARY_PASSWORD="" +XCODE_PATH="" + +usage() { + cat <&2; usage; exit 1 ;; + esac + done + + if [[ -z "$PYTHON_VERSION" ]]; then + echo "error: --python-version is required" >&2 + usage + exit 1 + fi + + PYTHON_MAJOR_VERSION="${PYTHON_VERSION%.*}" # 3.13.13 -> 3.13 + PYTHON_BIN_VERSION="$PYTHON_MAJOR_VERSION" +} + +is_ci() { + [[ -n "${CI:-}" || -n "${GITHUB_ACTIONS:-}" ]] +} + +derive_build_version() { + local rev_count + rev_count="$(/usr/bin/git -C "$TOOLSDIR" rev-list --count HEAD)" + NEWSUBBUILD=$((80620 + rev_count)) + AUTOMATED_PYTHON_BUILD="$PYTHON_VERSION.$NEWSUBBUILD" + echo "$AUTOMATED_PYTHON_BUILD" > "$TOOLSDIR/build_info.txt" + echo "Build version: $AUTOMATED_PYTHON_BUILD" +} + +prepare_ci_env() { + if ! is_ci; then + return + fi + echo "CI detected — clearing Homebrew and selecting Xcode." + /usr/local/bin/brew remove --force "$(/usr/local/bin/brew list)" || true + if [[ -n "$XCODE_PATH" && -d "$XCODE_PATH" ]]; then + /usr/bin/sudo /usr/bin/xcode-select -s "$XCODE_PATH" + fi +} + +prepare_build_dirs() { + /usr/bin/sudo /bin/mkdir -m 777 -p "$FRAMEWORKDIR" + if [[ -d "$FRAMEWORKDIR/Python.framework" ]]; then + /usr/bin/sudo /bin/rm -rf "$FRAMEWORKDIR/Python.framework" + fi + if [[ -d "$PIPCACHEDIR" ]]; then + echo "Removing pip cache to reduce build errors" + /usr/bin/sudo /bin/rm -rf "$PIPCACHEDIR" + fi + + /bin/rm -rf "$TOOLSDIR/$TYPE" + /bin/mkdir -p "$TOOLSDIR/$TYPE/scripts" + /bin/mkdir -p "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}" + /bin/mkdir -p "$TOOLSDIR/$TYPE/payload/usr/local/bin" + /usr/bin/sudo /usr/sbin/chown -R "${CONSOLEUSER}":wheel "$TOOLSDIR/$TYPE" + + /bin/ln -s "$PYTHON_BIN_NEW" "$TOOLSDIR/$TYPE/payload/usr/local/bin/managed_python3" +} + +download_tool() { + local name="$1" sha="$2" url="$3" zip_path="$4" dest="$5" + echo "Downloading $name @ $sha" + /bin/rm -rf "$zip_path" "$dest" + /usr/bin/curl -fL "$url" -o "$zip_path" + /usr/bin/unzip -q "$zip_path" -d "$dest" +} + +build_framework() { + export C_INCLUDE_PATH="/Library/ManagedFrameworks/Python/Python.framework/Versions/Current/Headers/" + local rp_extract="${RP_BINDIR}/relocatable-python-${RP_SHA}" + "${rp_extract}/make_relocatable_python_framework.py" \ + --baseurl "${PYTHON_BASEURL}" \ + --python-version "${PYTHON_VERSION}" \ + --os-version 11 \ + --upgrade-pip \ + --no-unsign \ + --pip-requirements "${TOOLSDIR}/requirements_${TYPE}.txt" \ + --destination "${FRAMEWORKDIR}" + + /bin/mv "${FRAMEWORKDIR}/Python.framework" \ + "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework" +} + +codesign_framework() { + local identity="${APPLICATION_ID:--}" # `-` means ad-hoc + local framework_root="$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/Python3.framework" + local versioned="$framework_root/Versions/${PYTHON_BIN_VERSION}" + + if [[ "$identity" == "-" ]]; then + echo "Ad-hoc signing framework" + else + echo "Signing framework with identity: $identity" + fi + + local -a cs_args + if [[ "$identity" == "-" ]]; then + cs_args=(--preserve-metadata=identifier,entitlements,flags,runtime -f) + else + cs_args=(--timestamp --preserve-metadata=identifier,entitlements,flags,runtime -f) + fi + + /usr/bin/find "$versioned/bin" -type f -perm -u=x -exec \ + /usr/bin/codesign -s "$identity" "${cs_args[@]}" {} \; + /usr/bin/find "$versioned/lib" -type f -perm -u=x -exec \ + /usr/bin/codesign -s "$identity" "${cs_args[@]}" {} \; + /usr/bin/find "$versioned/lib" -type f -name "*dylib" -exec \ + /usr/bin/codesign -s "$identity" "${cs_args[@]}" {} \; + /usr/bin/codesign -s "$identity" --deep "${cs_args[@]}" "$versioned/Resources/Python.app" + /usr/bin/codesign -s "$identity" "${cs_args[@]}" "$versioned/Python" + /usr/bin/codesign -s "$identity" "${cs_args[@]}" "$framework_root/Versions/Current/Python" + + /usr/sbin/spctl -a -vvvv "$versioned/Python" || true +} + +build_pkg() { + /bin/mkdir -p "$OUTPUTSDIR" + /bin/cp "${TOOLSDIR}/preinstall-cleanup" "$TOOLSDIR/$TYPE/scripts/preinstall" + + if [[ -z "$INSTALLER_ID" ]]; then + echo "No installer identity provided; skipping signed pkg" + return + fi + + /bin/cat < "$TOOLSDIR/$TYPE/build-info.json" +{ + "ownership": "recommended", + "suppress_bundle_relocation": true, + "identifier": "io.macadmins.python.$TYPE", + "postinstall_action": "none", + "distribution_style": true, + "version": "$AUTOMATED_PYTHON_BUILD", + "name": "python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg", + "install_location": "/", + "preserve_xattr": true, + "signing_info": { + "identity": "$INSTALLER_ID", + "timestamp": true + } +} +JSON + + "${MP_BINDIR}/munki-pkg-${MP_SHA}/munkipkg" "$TOOLSDIR/$TYPE" +} + +notarize_and_staple() { + if [[ -z "$NOTARY_PASSWORD" || -z "$INSTALLER_ID" ]]; then + echo "Skipping notarization (no notary password or installer id)" + return + fi + local xcode_dev xcode_notary xcode_stapler pkg + xcode_dev="$(/usr/bin/xcode-select -p)" + xcode_notary="$xcode_dev/usr/bin/notarytool" + xcode_stapler="$xcode_dev/usr/bin/stapler" + pkg="$TOOLSDIR/$TYPE/build/python_${TYPE}_signed-$AUTOMATED_PYTHON_BUILD.pkg" + + "$xcode_notary" store-credentials \ + --apple-id "opensource@macadmins.io" \ + --team-id "T4SK8ZXCXG" \ + --password "$NOTARY_PASSWORD" \ + macadminpython + "$xcode_notary" submit "$pkg" --keychain-profile macadminpython --wait + "$xcode_stapler" staple "$pkg" + /bin/mv "$pkg" "$OUTPUTSDIR" +} + +zip_framework() { + local zipfile="Python3.framework_$TYPE-$AUTOMATED_PYTHON_BUILD.zip" + /usr/bin/ditto -c -k --sequesterRsrc \ + "$TOOLSDIR/$TYPE/payload${FRAMEWORKDIR}/" "$zipfile" + /bin/mv "$zipfile" "$OUTPUTSDIR" + /usr/bin/sudo /usr/sbin/chown -R "${CONSOLEUSER}":wheel "$OUTPUTSDIR" +} + +cleanup() { + /usr/bin/sudo /bin/rm -rf "$TOOLSDIR/$TYPE" + /usr/bin/sudo /bin/rm -rf "$FRAMEWORKDIR" +} + +# --- Main --- +parse_args "$@" +echo "Building Python framework — $PYTHON_VERSION" +prepare_ci_env +derive_build_version +prepare_build_dirs +download_tool relocatable-python "$RP_SHA" \ + "https://github.com/gregneagle/relocatable-python/archive/${RP_SHA}.zip" \ + "$RP_ZIP" "$RP_BINDIR" +download_tool munki-pkg "$MP_SHA" \ + "https://github.com/munki/munki-pkg/archive/${MP_SHA}.zip" \ + "$MP_ZIP" "$MP_BINDIR" +build_framework +codesign_framework +build_pkg +notarize_and_staple +zip_framework +cleanup +echo "Done." +``` + +- [ ] **Step 2: Lint with shellcheck (best-effort)** + +Run: `shellcheck -s bash build_python_framework_pkgs.zsh || true` +Expected: any issues are warnings about zsh-only constructs (e.g., `[[ ]]`); resolve real bugs only. shellcheck has limited zsh support — informational only. + +- [ ] **Step 3: Verify executable bit** + +Run: `ls -l build_python_framework_pkgs.zsh` +Expected: shows `-rwxr-xr-x` (mode 755). If not, run `chmod +x build_python_framework_pkgs.zsh`. + +- [ ] **Step 4: Commit** + +```bash +git add build_python_framework_pkgs.zsh +git commit -m "Refactor build script for Apple Silicon + +- Drop universal2 enforcement; arm64 wheels used directly +- Single 'recommended' flavor; remove minimal/no_customization branches +- Long-flag arguments; derive major version from full version +- Functions: parse_args, prepare_build_dirs, download_tool, + build_framework, codesign_framework, build_pkg, + notarize_and_staple, zip_framework, cleanup +- Collapse signed/ad-hoc codesign duplication; fixes latent path bug +- Bump relocatable-python and munki-pkg SHAs +- Gate CI-only steps (brew remove, xcode-select) on \$CI" +``` + +--- + +## Task 2: Drop `--no-binary` markers from `requirements_recommended.txt` + +**Files:** +- Modify: `requirements_recommended.txt` + +- [ ] **Step 1: Remove all `--no-binary` lines** + +Replace the file with: + +``` +asn1crypto==1.5.1 +aspy.yaml==1.3.0 +attrs==25.3.0 +black==25.1.0 +certifi==2025.6.15 +cffi==1.17.1 +cfgv==3.4.0 +charset-normalizer==3.4.2 +click==8.2.1 +distlib==0.3.9 +docklib==2.0.0 +entrypoints==0.4 +filelock==3.18.0 +flake8==7.3.0 +flake8-bugbear==24.12.12 +identify==2.6.12 +idna==3.10 +isort==6.0.1 +mccabe==0.7.0 +mypy-extensions==1.1.0 +nodeenv==1.9.1 +packaging==25.0 +pathspec==0.12.1 +platformdirs==4.3.8 +pre-commit==4.2.0 +pycodestyle==2.14.0 +pycparser==2.22 +pyflakes==3.4.0 +pyobjc==11.1 +PyYAML==6.0.2 +requests==2.32.4 +six==1.17.0 +tokenize-rt==6.2.0 +tomli==2.2.1 +urllib3==2.5.0 +virtualenv==20.31.2 +xattr==1.1.4 +``` + +- [ ] **Step 2: Verify no `--no-binary` remains** + +Run: `grep -n -- '--no-binary' requirements_recommended.txt` +Expected: no output (exit code 1 — no matches). + +- [ ] **Step 3: Commit** + +```bash +git add requirements_recommended.txt +git commit -m "Drop --no-binary directives (use prebuilt arm64 wheels)" +``` + +--- + +## Task 3: Delete obsolete build flavors + +**Files:** +- Delete: `requirements_minimal.txt` +- Delete: `requirements_no_customization.txt` +- Delete: `requirement_files/requirements_minimal.txt` +- Delete: `requirement_files/requirements_opinionated.txt` +- Delete: `build_all_python_frameworks.zsh` + +- [ ] **Step 1: Delete files** + +Run: + +```bash +git rm requirements_minimal.txt requirements_no_customization.txt \ + requirement_files/requirements_minimal.txt \ + requirement_files/requirements_opinionated.txt \ + build_all_python_frameworks.zsh +``` + +Expected: 5 files removed (`rm 'requirements_minimal.txt'`, etc.). + +- [ ] **Step 2: Verify no remaining references** + +Run: `grep -rn 'minimal\|no_customization\|build_all_python_frameworks' --include='*.zsh' --include='*.yml' --include='*.md' .` +Expected: only matches in the design spec (`docs/superpowers/specs/…`) and the deletion-context release notes. If any active script or workflow still references them, fix that reference now. + +- [ ] **Step 3: Commit** + +```bash +git commit -m "Remove minimal and no_customization build flavors" +``` + +--- + +## Task 4: Update README.md + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Replace the file contents** + +Replace `README.md` with: + +```markdown +# python +A Python 3 framework that installs to `/Library/ManagedFrameworks/Python/Python3.framework`. + +Please see Apple's documentation on [file system basics](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) for context. + +This is an intended replacement for `/usr/bin/python`, which Apple removed in macOS 12.3 (Spring 2022). + +## 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. + +## 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 + +## Using interactively +After installing the package, `/usr/local/bin/managed_python3` is a symlink to `/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python`. + +## Using with scripts +Point your shebang directly at the symlink: + +``` +#!/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/bin/python3 + +print('This is an example script.') +``` + +### zshenv global alias +For zsh scripts you can add a global alias to `/etc/zshenv`: + +`alias -g python3.framework='/Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/bin/python3'` + +See Armin Briegel's "Moving to Zsh" Part [II](https://scriptingosx.com/2019/06/moving-to-zsh-part-2-configuration-files/) and [IV](https://scriptingosx.com/2019/07/moving-to-zsh-part-4-aliases-and-functions/). + +## Notes +Only a single package may be installed at any given time. The preinstall script removes any previous framework. + +### Upgrades +Python itself has its own release cadence; this package will see additional updates as 3rd-party libraries release fixes and security updates. Always test your scripts before deploying broadly. + +### Downgrades +Not supported. + +### pip +`pip` is bundled but **not recommended** for installing external libraries into the framework. Use a [virtual environment](https://docs.python.org/3/library/venv.html) or a tool like [pyenv](https://github.com/pyenv/pyenv) instead. Pull requests to the `recommended` requirements file are welcome. + +# Building locally +Build an unsigned framework on Apple Silicon with: + +``` +./build_python_framework_pkgs.zsh --python-version 3.13.13 +``` + +Pass `--installer-id`, `--application-id`, and `--notary-password` to produce a signed and notarized `.pkg`. + +# Updating packages +Do this in a clean virtual environment. After every Python package install, run `pip freeze | xargs pip uninstall -y` to reset the environment. + +# CI Job +To update the signing certificate, run `base64 -i /path/to/certificate.p12 -o base64string` and import it into the GitHub Actions secrets store along with the matching password. + +# Credits +Built on two open-source tools by [Greg Neagle](https://www.linkedin.com/in/gregneagle/): +- [relocatable-python](https://github.com/gregneagle/relocatable-python) +- [munki-pkg](https://github.com/munki/munki-pkg) +``` + +- [ ] **Step 2: Verify no flavor references remain** + +Run: `grep -E 'Minimal|No Customization|Flavors of Python|Intel macOS device' README.md` +Expected: no output. + +- [ ] **Step 3: Commit** + +```bash +git add README.md +git commit -m "Update README for Apple Silicon, single-flavor build" +``` + +--- + +## Task 5: Local validation — unsigned 3.13.13 build + +**Files:** none (validation only) + +- [ ] **Step 1: Run unsigned build** + +Run: `./build_python_framework_pkgs.zsh --python-version 3.13.13` +Expected: +- Final lines include `Build version: 3.13.13.` and `Done.` +- `outputs/Python3.framework_recommended-3.13.13..zip` exists. +- No `outputs/*.pkg` (no installer identity passed). +- No error about `2 architectures` (the validation block is gone). + +- [ ] **Step 2: Install the framework and smoke-test** + +Run: + +```bash +sudo rm -rf /Library/ManagedFrameworks/Python/Python3.framework +sudo mkdir -p /Library/ManagedFrameworks/Python +sudo ditto -x -k outputs/Python3.framework_recommended-3.13.13.*.zip /Library/ManagedFrameworks/Python/ +sudo ln -sf /Library/ManagedFrameworks/Python/Python3.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python /usr/local/bin/managed_python3 +managed_python3 --version +managed_python3 -c "import platform; print(platform.machine())" +managed_python3 -c "import objc, xattr, requests, yaml; print('ok')" +``` + +Expected output: +- `Python 3.13.13` +- `arm64` +- `ok` + +If `import objc` fails with `Symbol not found` or an architecture mismatch, halt and investigate — the framework is not arm64 compatible. + +- [ ] **Step 3: Tear down the test install** + +Run: `sudo rm -rf /Library/ManagedFrameworks/Python/Python3.framework /usr/local/bin/managed_python3` +Expected: no output. + +- [ ] **Step 4: Record the validation in the spec / plan** + +No commit yet — validation is a checkpoint. If steps 1 and 2 passed, mark this task done and move on. + +--- + +## Task 6: Local validation — unsigned 3.14.5 build + +**Files:** none (validation only) + +- [ ] **Step 1: Run unsigned build** + +Run: `./build_python_framework_pkgs.zsh --python-version 3.14.5` +Expected: `Done.` and `outputs/Python3.framework_recommended-3.14.5..zip` exists. + +- [ ] **Step 2: Smoke-test** + +Run the same install + smoke-test commands from Task 5 Step 2, substituting `3.14.5` for `3.13.13` in the zip filename. + +Expected: +- `Python 3.14.5` +- `arm64` +- `ok` + +If any pip package fails to install during step 1 (no arm64 wheel for 3.14), record which package failed and proceed to Task 10 (pin sweep) to address. + +- [ ] **Step 3: Tear down the test install** + +Run: `sudo rm -rf /Library/ManagedFrameworks/Python/Python3.framework /usr/local/bin/managed_python3` + +--- + +## Task 7: Bump Python patch versions in 3.11 / 3.12 / 3.13 workflows + +**Files:** +- Modify: `.github/workflows/build_python_3.11.yml` +- Modify: `.github/workflows/build_python_3.12.yml` +- Modify: `.github/workflows/build_python_3.13.yml` + +- [ ] **Step 1: Bump 3.11** + +In `.github/workflows/build_python_3.11.yml`, change `PYTHON_VERSION: "3.11.7"` to `PYTHON_VERSION: "3.11.9"`. Also update the release-notes line `- Upgraded Python to 3.11.7` to `- Upgraded Python to 3.11.9`. + +- [ ] **Step 2: Bump 3.12** + +In `.github/workflows/build_python_3.12.yml`, change `PYTHON_VERSION: "3.12.1"` to `PYTHON_VERSION: "3.12.10"`. Also update the release-notes line `- Upgraded Python to 3.12.1` to `- Upgraded Python to 3.12.10`. + +- [ ] **Step 3: Bump 3.13** + +In `.github/workflows/build_python_3.13.yml`, change `PYTHON_VERSION: "3.13.5"` to `PYTHON_VERSION: "3.13.13"`. Also update the release-notes line `- Upgraded Python to 3.13.5` to `- Upgraded Python to 3.13.13`. + +- [ ] **Step 4: Verify the script argument call still works** + +Search each workflow for the `Run build package script` step. Today it reads: + +```yaml +run: ./build_python_framework_pkgs.zsh "$TYPE" "$DEV_INSTALLER_ID" "$DEV_APPLICATION_ID" "$PYTHON_VERSION" "$PYTHON_MAJOR_VERSION" "${NOTARY_APP_PASSWORD}" +``` + +After Task 1 the script no longer accepts positional arguments, so this **will break in CI**. Phase 3 will rewrite the workflows, but to keep CI green for the in-between window, update the call in each of the three modified workflows to: + +```yaml +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" \ + --xcode-path "/Applications/Xcode_15.2.app" +``` + +The `TYPE` env var is unused by the new script; leave it in the env block for now (Phase 3 cleanup will remove it). + +- [ ] **Step 5: Commit** + +```bash +git add .github/workflows/build_python_3.11.yml \ + .github/workflows/build_python_3.12.yml \ + .github/workflows/build_python_3.13.yml +git commit -m "Bump 3.11/3.12/3.13 to latest patches; update script invocation" +``` + +--- + +## Task 8: Add Python 3.14 workflow + +**Files:** +- Create: `.github/workflows/build_python_3.14.yml` + +- [ ] **Step 1: Create the workflow file** + +Create `.github/workflows/build_python_3.14.yml` by copying `.github/workflows/build_python_3.13.yml` and changing: + +- `name: Build Python 3.13` → `name: Build Python 3.14` +- `PYTHON_VERSION: "3.13.13"` → `PYTHON_VERSION: "3.14.5"` +- `PYTHON_MAJOR_VERSION: "3.13"` → `PYTHON_MAJOR_VERSION: "3.14"` +- `Python 3.13.13 Framework` → `Python 3.14.5 Framework` +- `- Upgraded Python to 3.13.13` → `- Upgraded Python to 3.14.5` + +All other contents (action versions, build script invocation from Task 7) remain identical. + +- [ ] **Step 2: Verify YAML parses** + +Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/build_python_3.14.yml'))"` +Expected: no output (no errors). + +- [ ] **Step 3: Commit** + +```bash +git add .github/workflows/build_python_3.14.yml +git commit -m "Add Python 3.14.5 build workflow" +``` + +--- + +## Task 9: Mark 3.9 and 3.10 workflows as final releases + +**Files:** +- Modify: `.github/workflows/build_python_3.9.yml` +- Modify: `.github/workflows/build_python_3.10.yml` + +- [ ] **Step 1: Update script invocation in 3.9 workflow** + +In `.github/workflows/build_python_3.9.yml`, replace the `Run build package script` step's `run:` line with the same long-flag invocation as Task 7 Step 4 (so the final release works against the new script). + +- [ ] **Step 2: Add final-release notice to 3.9 release body** + +In `.github/workflows/build_python_3.9.yml`, find the `Create Release` step's `body:` block. Insert a new section immediately after the existing `## Security Notice` paragraph: + +```yaml + ## Final Release + **This is the final release of the Python 3.9 framework.** Python 3.9 reached end-of-life on October 2025 and python.org has not published a macOS installer past 3.9.13. Future framework updates will target Python 3.11 and newer. Plan your migration. +``` + +- [ ] **Step 3: Update script invocation in 3.10 workflow** + +In `.github/workflows/build_python_3.10.yml`, apply the same `run:` replacement as Step 1. + +- [ ] **Step 4: Add final-release notice to 3.10 release body** + +In `.github/workflows/build_python_3.10.yml`, insert after the existing `## Security Notice` paragraph: + +```yaml + ## Final Release + **This is the final release of the Python 3.10 framework.** Python 3.10 is in security-fixes-only status and python.org has not published a macOS installer past 3.10.11. Future framework updates will target Python 3.11 and newer. Plan your migration. +``` + +- [ ] **Step 5: Commit** + +```bash +git add .github/workflows/build_python_3.9.yml .github/workflows/build_python_3.10.yml +git commit -m "Mark 3.9 and 3.10 as final releases; switch to new script flags" +``` + +--- + +## Task 10: Python package pin sweep + +**Files:** +- Modify: `requirements_recommended.txt` (only if a package needs a bump or per-branch hold) + +- [ ] **Step 1: List currently pinned packages with their versions** + +Run: `grep -E '^[a-zA-Z]' requirements_recommended.txt` +Expected: 37 lines of `name==version`. + +- [ ] **Step 2: For each pinned package, check PyPI for a newer release** + +Run for each package (using `pyobjc` as the example; substitute each name): + +```bash +pip index versions pyobjc --python-version 3.14 2>&1 | head -5 +``` + +This requires `pip` 23.3+; if not available, use `pip install --dry-run pyobjc==99.99 2>&1 | grep "from versions"` instead. + +Expected: a sorted list of available versions. Note the newest. + +- [ ] **Step 3: Verify arm64 wheel availability for each candidate bump** + +For each package where a newer version exists, check that arm64 macOS wheels are published for **every** supported Python branch (3.9, 3.10, 3.11, 3.12, 3.13, 3.14). Visit `https://pypi.org/project//#files` in a browser or run: + +```bash +curl -s "https://pypi.org/pypi///json" \ + | python3 -c "import json,sys; data=json.load(sys.stdin); files=data['urls']; [print(f['filename']) for f in files if 'macosx' in f['filename'] and 'arm64' in f['filename']]" +``` + +A package qualifies for an unconditional bump only if arm64 macOS wheels exist for **all** supported Python versions. If 3.9 / 3.10 lack a wheel for the new version, hold those at the older pin via Pip's per-version syntax — example: `pyobjc==11.1; python_version >= "3.11"` plus `pyobjc==10.5.1; python_version < "3.11"`. + +- [ ] **Step 4: Update `requirements_recommended.txt` with the bumps** + +For each package that has a newer version with full arm64 wheel coverage, update the pin. For packages with partial coverage, use the marker syntax from Step 3. Leave packages without newer versions unchanged. + +Document each change with a single-line trailing comment if it is a holdback (`pyobjc==10.5.1; python_version < "3.11" # last version with 3.9/3.10 arm64 wheels`). + +- [ ] **Step 5: Re-run local validation for 3.9, 3.10, 3.11, 3.12, 3.13, 3.14** + +For each version, run: + +```bash +./build_python_framework_pkgs.zsh --python-version +``` + +Then install and smoke-test as in Task 5 Step 2. Each must produce a working framework. If any version fails on a freshly bumped package, revert that pin or hold it for the affected version. + +The patch versions to validate: `3.9.13`, `3.10.11`, `3.11.9`, `3.12.10`, `3.13.13`, `3.14.5`. + +- [ ] **Step 6: Commit** + +```bash +git add requirements_recommended.txt +git commit -m "Bump Python package pins; add per-branch holdbacks where needed" +``` + +--- + +## Task 11: Add Dependabot config + +**Files:** +- Create: `.github/dependabot.yml` + +- [ ] **Step 1: Create the config** + +Create `.github/dependabot.yml` with: + +```yaml +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 +``` + +- [ ] **Step 2: Verify YAML parses** + +Run: `python3 -c "import yaml; yaml.safe_load(open('.github/dependabot.yml'))"` +Expected: no output. + +- [ ] **Step 3: Commit** + +```bash +git add .github/dependabot.yml +git commit -m "Enable Dependabot for GitHub Actions" +``` + +--- + +## Task 12: Cut releases for all supported branches + +**Files:** none (manual CI dispatch) + +This is the final operational step. Each release is kicked off manually via `workflow_dispatch` so we can stage them and verify outputs. + +- [ ] **Step 1: Trigger 3.14 release first** + +Run: `gh workflow run build_python_3.14.yml --ref ` + +Expected: a workflow run starts. Watch via `gh run watch` or the GitHub UI. + +- [ ] **Step 2: Verify 3.14 release artifact** + +When the run finishes: + +```bash +gh release view v3.14.5. +``` + +Expected: the release exists, has a signed `.pkg` asset, and the release body matches the new template. + +- [ ] **Step 3: Trigger remaining releases in order** + +Repeat Steps 1–2 for: `build_python_3.13.yml`, `build_python_3.12.yml`, `build_python_3.11.yml`, `build_python_3.10.yml`, `build_python_3.9.yml`. + +The 3.9 and 3.10 releases must include the **Final Release** notice in their body (added in Task 9). + +- [ ] **Step 4: No commit** + +Nothing to commit — these are CI-side actions only. Phase 3 work (archiving the 3.9 / 3.10 workflows, consolidating the rest) starts after this plan is fully complete. + +--- + +## Self-Review Notes + +- Phase 1 spec coverage: Tasks 1–6 cover the script refactor, requirements, deletions, README, and local validation. +- Phase 2 spec coverage: Tasks 7–12 cover patch bumps, 3.14 addition, final-release notes, pin sweep, Dependabot, and release execution. +- Phase 3 explicitly excluded: workflow consolidation, runner migration, action bumps, release-trigger change — Task 7 Step 4 includes a deliberate stopgap (long-flag invocation inside the still-duplicated workflows) so CI keeps working in the in-between state. +- No placeholders: every code block is concrete; every `Expected:` describes verifiable output. +- Type / name consistency: long-flag names (`--python-version`, `--installer-id`, `--application-id`, `--notary-password`, `--xcode-path`) are identical across Tasks 1, 7, and 9. + +--- + +## Post-Implementation Notes (2026-05-11) + +Tasks 1–11 are complete on branch `claude`. Task 12 is pending the branch push + merge. + +### Deltas from the original plan text above + +- **Task 1 — Script content has three additional fixes** layered onto the version this plan documents: + - `PYTHON_BASEURL` keeps three `%s` slots (not the two-slot literal `macos11` version). Commit `e611fb1`. + - `prepare_build_dirs()` explicitly `chmod 777`s `FRAMEWORKDIR` after `mkdir`. Commit `3057706`. + - The `--no-unsign` flag was removed from the `make_relocatable_python_framework.py` invocation; it was disabling relocatable-python's ad-hoc re-sign step that satisfies Apple Silicon Gatekeeper. Commit `69af8f1`. + - `build_pkg()` now `mv`s the produced `.pkg` to `outputs/` directly so signed-but-unnotarized builds survive `cleanup()`. Commit `29b3a55`. + + See the design spec's "Validation Findings" section for the full reasoning. + +- **Task 7 Step 4 — Workflow `run:` block** now matches the spec exactly; the in-between-window stopgap (long-flag invocation inside the still-duplicated workflows) is in place across all six workflow files. Commits `c10dc22`, `590f478`, `1764c84`. + +- **Task 9 — Release notes** also include a corrected "Recommended flavor" description, since the original `… everything from minimal …` line referenced a flavor that no longer exists. The fix went into the same commit as the final-release notice. + +- **Task 10 — Pin sweep was pulled forward** and merged with the initial 3.13.13 / 3.14.5 validation (commit `1216394`). The cross-version follow-up (commit `f9c0853`) added one holdback: `pyobjc==11.1; python_version < "3.10"`, since `pyobjc 12.1` declares `requires_python >= 3.10` and `pyobjc-core 12.1` lacks a cp39 wheel. Every other native-code package (`cffi`, `charset-normalizer`, `PyYAML`, `tomli`, `xattr`) ships universal2 wheels for cp39 through cp314 at latest. + +- **Task 12 — Still pending.** Branch is local-only; releases will be dispatched after push + merge. + +### Open follow-ups + +- `RP_SHA` is held at `8ee72fe` (latest available). When upstream addresses [gregneagle/relocatable-python#32](https://github.com/gregneagle/relocatable-python/issues/32), revisit whether further bumps are possible. +- Phase 3 (CI/CD overhaul) is its own design exercise. diff --git a/docs/superpowers/specs/2026-05-11-apple-silicon-modernization-design.md b/docs/superpowers/specs/2026-05-11-apple-silicon-modernization-design.md new file mode 100644 index 0000000..19e2224 --- /dev/null +++ b/docs/superpowers/specs/2026-05-11-apple-silicon-modernization-design.md @@ -0,0 +1,241 @@ +# Apple Silicon Modernization — Design + +**Date:** 2026-05-11 +**Author:** Erik Gomez +**Status:** Draft + +## Summary + +Modernize the macadmins Python framework build pipeline by: + +1. Refactoring `build_python_framework_pkgs.zsh` to drop universal2 enforcement, run natively on Apple Silicon, and trim to a single build type (`recommended`). +2. Bumping all upstream dependencies — `relocatable-python`, `munki-pkg`, Python interpreter patches, and Python package pins — and adding Python 3.14 as a supported branch. + +CI/CD overhaul (Phase 3) is **deferred to a follow-up spec**; this work targets local-only changes plus the requirement and version updates that ride on top of them. + +## Background + +`build_python_framework_pkgs.zsh` produces a relocatable `Python3.framework` installed under `/Library/ManagedFrameworks/Python/`, packaged via `munki-pkg`, signed, notarized, and shipped as a `.pkg`. It is consumed by Munki 6, Autopkg, InstallApplications, Nudge, and similar fleet tools. + +Today the script: + +- Requires an Intel build host. Lines 200–223 enumerate every `.dylib` and `.so` under the built framework and fail the build unless each contains two architectures. This forces `--no-binary` on `cffi`, `charset-normalizer`, `PyYAML`, `tomli`, `xattr`, and `black` in `requirements_recommended.txt` so wheels are compiled fat instead of installed pre-built. +- Supports three build types (`minimal`, `no_customization`, `recommended`) but CI only builds `recommended`. +- Pins `relocatable-python` to `fb4dd9b…` and `munki-pkg` to `96cffb4e…`, both stale. +- Hardcodes `Xcode_15.2.app` and `macos-13` (Intel) GitHub runners. +- Carries a latent path bug in the ad-hoc codesign branch (`build_python_framework_pkgs.zsh:241` — missing `/` between `${FRAMEWORKDIR}` and `Python3.framework`). + +The team is shipping macOS deployments to Apple Silicon hardware; the universal2 contract is no longer worth the build-host constraints, the `--no-binary` slowdowns, or the maintenance surface. + +## Goals + +- Build cleanly from an Apple Silicon Mac with no Intel host required. +- Use pre-built arm64 wheels from PyPI. +- Single supported build flavor (`recommended`); delete the other two. +- Current upstream SHAs and Python patch versions. +- Add Python 3.14 to the supported set. +- One last release of Python 3.9 and 3.10, then retire them. +- Script is readable, factored into clear functions, and easy to invoke locally without simulating a CI environment. + +## Non-Goals + +- CI/CD changes (workflow consolidation, action bumps, runner migration, release trigger changes). Deferred to Phase 3. +- Compiling CPython from source for EOL branches. 3.9 / 3.10 final releases continue to use python.org's last published `.pkg` (`3.9.13` / `3.10.11`). +- Changing the install location, package identifier, or signing identity. +- Producing an explicitly arm64-thinned framework via `lipo`. The python.org base remains universal2; arm64-only wheels make the *added* content single-arch. We do not strip x86_64 slices from upstream binaries. + +## Phase 1 — Script Refactor + +### Script signature + +Current: + +``` +build_python_framework_pkgs.zsh +``` + +New (positional args dropped in favor of long flags for clarity; major version derived from full version): + +``` +build_python_framework_pkgs.zsh \ + --python-version 3.13.13 \ + [--installer-id "Developer ID Installer: ..."] \ + [--application-id "Developer ID Application: ..."] \ + [--notary-password "$NOTARY_APP_PASSWORD"] \ + [--xcode-path /Applications/Xcode_16.x.app] +``` + +When `--installer-id` / `--application-id` are omitted, the script falls back to ad-hoc signing and does not produce a signed `.pkg`. When `--notary-password` is omitted, notarization is skipped. This makes local invocation `./build_python_framework_pkgs.zsh --python-version 3.13.13` and nothing more. + +### Behavioral changes + +1. **Drop the universal2 validation block.** Remove the `find … "2 architectures"` checks for `.dylib` and `.so` files. +2. **Drop `--no-binary` markers** in `requirements_recommended.txt` for `black`, `cffi`, `charset-normalizer`, `PyYAML`, `tomli`, `xattr`. +3. **Guard CI-only steps.** The `brew remove --force …` and the `sudo xcode-select -s "$XCODE_PATH"` calls run only when `$CI` or `$GITHUB_ACTIONS` is set. Locally they're no-ops. +4. **Parametrize Xcode path.** `--xcode-path` (or `XCODE_PATH` env var) replaces the `Xcode_15.2.app` hardcode. Default to `xcode-select -p`'s active developer dir when not specified. +5. **Collapse per-version symlink block.** The five identical `if [[ "${PYTHON_MAJOR_VERSION}" == "3.x" ]]` blocks at lines 144–158 become a single unconditional `ln -s`. +6. **Single build type.** Delete the `minimal` / `no_customization` branches. `TYPE` becomes an internal constant (`recommended`) used for path naming only. +7. **Fix the codesign path bug** at line 241. +8. **Bump pinned SHAs:** + - `RP_SHA` → `8ee72fe3a5dbef733365370ebf44f25022b895ef` + - `MP_SHA` → `bbd07730d1b93ed3828246575ef5676bba74b5d1` + +### Refactor for readability + +Pull discrete steps into named functions so the top-level script reads as a sequence of intents rather than a wall of inline shell: + +- `parse_args` — long-flag parsing, validation, defaults. +- `prepare_build_dirs` — framework dir setup, payload skeleton. +- `download_tool ` — generic curl-and-unzip used for both `relocatable-python` and `munki-pkg`. +- `build_framework` — runs `make_relocatable_python_framework.py`. +- `codesign_framework` — single implementation that takes the identity (or `-` for ad-hoc) as an argument, replacing the duplicate signed-vs-ad-hoc branches. +- `build_pkg` — emits `build-info.json`, runs `munkipkg`. +- `notarize_and_staple` — runs only when notary credentials are present. +- `zip_framework` — emits the standalone framework zip. +- `cleanup` — removes temp dirs. + +Use `set -eu` (zsh equivalent) at the top of the script so a step failure short-circuits the run instead of relying on per-step exit-code checks. + +### Files removed + +- `requirements_minimal.txt` +- `requirements_no_customization.txt` (empty) +- `requirement_files/requirements_minimal.txt` +- `requirement_files/requirements_opinionated.txt` (no longer referenced) +- `build_all_python_frameworks.zsh` (only purpose was to invoke all three build types) + +### Files updated + +- `build_python_framework_pkgs.zsh` — full refactor per above. +- `requirements_recommended.txt` — drop `--no-binary` markers. +- `README.md` — drop the "Flavors of Python", "No Customization", "Minimal" sections; drop the "build on an Intel macOS device" note; update interactive-use snippet for Apple Silicon. +- `requirement_files/requirements_recommended.txt` — remains as the human-curated source for which package families are included (no behavior change in Phase 1). + +### Local validation steps + +After the refactor: + +1. `./build_python_framework_pkgs.zsh --python-version 3.13.13` on the Apple Silicon Mac. Expect: an unsigned framework zip in `outputs/`, no signed `.pkg`. +2. Manually install the framework to `/Library/ManagedFrameworks/Python/Python3.framework` and smoke-test: + - `managed_python3 --version` reports `3.13.13`. + - `managed_python3 -c "import objc; import xattr; import requests; print('ok')"` succeeds. + - `managed_python3 -c "import platform; print(platform.machine())"` prints `arm64`. +3. Repeat for `--python-version 3.14.5`. + +## Phase 2 — Dependency and Version Bumps + +### Python interpreter versions + +| Branch | Old | New | Notes | +|---|---|---|---| +| 3.9 | 3.9.13 | 3.9.13 | Final release. No upstream `.pkg` past this. | +| 3.10 | 3.10.11 | 3.10.11 | Final release. No upstream `.pkg` past this. | +| 3.11 | 3.11.7 | 3.11.9 | | +| 3.12 | 3.12.1 | 3.12.10 | | +| 3.13 | 3.13.5 | 3.13.13 | | +| 3.14 | — | 3.14.5 | New supported branch. | + +### Python package pin sweep + +For each pinned package in `requirements_recommended.txt`: + +1. Check if a newer release exists on PyPI. +2. Verify an `arm64` macOS wheel exists for **every** supported Python branch (3.9–3.14). If a version lacks a wheel for 3.9 / 3.10 (likely for newer pyobjc, cffi, etc.), pin a per-branch override or hold the package at the last version that supports all branches. Document the holdback inline. +3. The packages most likely to need attention: `pyobjc` (currently 11.1; check arm64 wheel coverage for 3.14), `cffi`, `xattr`, `cryptography` if pulled transitively. + +### 3.9 / 3.10 final-release release notes + +Each gets a one-time release, kicked off manually via `workflow_dispatch` (no CI restructuring needed for this in Phase 2), with: + +- Updated package pins (from the sweep above). +- Release-notes call-out: "**This is the final release of the Python framework. Future updates will target 3.11 and newer. Plan your migration.**" + +The workflow files themselves (`build_python_3.9.yml`, `build_python_3.10.yml`) stay in place during Phase 2 so the final builds can run. They are moved to `.github/workflows/archived/` as part of Phase 3. + +### 3.14 enablement + +`.github/workflows/build_python_3.14.yml` is added as a near-copy of `build_python_3.13.yml`, parametrized for 3.14.5. The file lives alongside the existing per-version workflows; consolidation happens in Phase 3. + +### Dependency-update tooling + +- **Dependabot for GitHub Actions:** enable in `.github/dependabot.yml`. Low noise; catches stale `apple-actions/import-codesign-certs`, `actions/checkout`, etc. without us thinking about it. +- **Pip pins:** keep manual. We want intentional bumps with smoke testing, not auto-merged churn. + +## Deferred to Phase 3 + +Captured here for context — not in scope for this spec: + +- Consolidate the five per-version workflows into one reusable workflow + thin callers. +- Migrate runner from `macos-13` (Intel) → `macos-14` (Apple Silicon). +- Bump action versions: `actions/checkout@v3 → v5`, `softprops/action-gh-release@v0.1.15 → v2`, `actions/upload-artifact@v4.6.2 → latest v4`, `apple-actions/import-codesign-certs → latest`, `metcalfc/changelog-generator → latest` (or replace with a `gh api` shell step). +- Switch release trigger from `pull_request` (currently cuts a prerelease per PR) to `push` on a release tag plus `workflow_dispatch`. PR runs build artifacts but don't publish releases. +- Archive `build_python_3.9.yml` and `build_python_3.10.yml`. + +## Risks and Open Questions + +- **arm64 wheel coverage for 3.9 / 3.10.** Older Pythons may not have arm64 wheels for the newest pin of every package. Mitigation: per-branch pin overrides, or hold the package at a known-good version. The pin sweep in Phase 2 will surface this. +- **First arm64 build may expose latent assumptions** in `relocatable-python`. The pinned-SHA bump pulls in a year+ of upstream changes; we may need to file/patch downstream issues. Mitigation: local validation gate before merging. +- **3.9 / 3.10 final builds depend on the new script working with old Python branches.** The bumped `relocatable-python` should still accept the older `--python-version` values, but it's a small contract worth verifying as part of local validation. + +## Acceptance Criteria + +Phase 1 is done when: + +- `./build_python_framework_pkgs.zsh --python-version 3.13.13` succeeds end-to-end on an Apple Silicon Mac with no `sudo` other than the existing `mkdir`/`chown` steps and produces an installable framework zip. +- The resulting `managed_python3` runs `import objc; import xattr; import requests` on Apple Silicon. +- `git grep -- '--no-binary'` returns no matches in `requirements_recommended.txt`. +- `minimal` / `no_customization` are gone from the repo and README. + +Phase 2 is done when: + +- All non-EOL workflows pin the patch versions in the table above. +- `build_python_3.14.yml` exists and runs green end-to-end. +- 3.9.13 and 3.10.11 each have one final release published with "final release" notes. +- `.github/dependabot.yml` is enabled for GitHub Actions. + +## Validation Findings (post-implementation, 2026-05-11) + +The Phase 1 + Phase 2 implementation surfaced four bugs and one design refinement that the earlier sections don't reflect. All are fixed in code; this section is the authoritative record of why each change was made. + +### 1. `PYTHON_BASEURL` needs three `%s` slots, not two + +The URL template must be `https://www.python.org/ftp/python/%s/python-%s-macos%s.pkg`. `relocatable-python`'s `locallibs/get.py` passes (version, version, os-version) into `base_url % (...)`. An earlier refactor that hardcoded `macos11` left only two slots and raised `TypeError: not all arguments converted during string formatting` at framework-download time. Fixed in `e611fb1`. + +### 2. `mkdir -m 777 -p` does not chmod existing directories + +`prepare_build_dirs()` must `mkdir -p` and then `chmod 777` explicitly. The `-m 777` flag only applies to newly created directories; an existing `/Library/ManagedFrameworks/Python/` (from a prior install) keeps its stricter perms, causing `relocatable-python` to fail with `Permission denied` when it ditto-extracts the python.org pkg into the framework. Fixed in `3057706`. + +### 3. `--no-unsign` is incompatible with Apple Silicon Gatekeeper + +The original Phase 1 design kept `--no-unsign` in the `make_relocatable_python_framework.py` invocation to "preserve the python.org signature". In practice, `install_name_tool` invalidates that signature whether the flag is set or not; the flag's only behavioral effect is to *disable* `relocatable-python`'s own `fix_broken_signatures` step (`locallibs/fix.py`), which ad-hoc re-signs the modified binaries before `ensurepip` runs. + +Without that re-sign, Apple Silicon's Gatekeeper SIGKILLs the Python binary when `ensurepip` tries to execute it. This is the upstream issue [gregneagle/relocatable-python#32](https://github.com/gregneagle/relocatable-python/issues/32). Solution: drop `--no-unsign`. The intermediate ad-hoc signature is overwritten by our `codesign_framework()` step at the end, so the final artifact's signature comes from our Developer ID. Fixed in `69af8f1`. + +This problem only manifests on Apple Silicon hosts; prior Intel CI runs masked it because Intel Gatekeeper is more lenient about invalid signatures. Switching the build host architecture is part of what this whole modernization exists to enable. + +### 4. Signed `.pkg` must move to `outputs/` from `build_pkg()`, not `notarize_and_staple()` + +The pre-refactor script moved the produced `.pkg` to `outputs/` unconditionally after `munkipkg` succeeded. The Phase 1 refactor consolidated that `mv` into `notarize_and_staple()`, which early-returns when no notary password is provided — so a signed-but-unnotarized build had its `.pkg` deleted by `cleanup()` before it ever landed in `outputs/`. The move now happens at the end of `build_pkg()`; `notarize_and_staple()` operates on the `.pkg` in its final location. Fixed in `29b3a55`. + +### 5. pyobjc holdback for Python 3.9 + +The Phase 2 pin sweep aspirationally bumped every package to PyPI latest. That works for 3.10 → 3.14 universally, but `pyobjc 12.1` declares `requires_python >= 3.10` and `pyobjc-core 12.1` lacks a cp39 wheel — the Python 3.9 final-release build would fail at pip-install time. Resolution in `requirements_recommended.txt`: + +``` +pyobjc==12.1; python_version >= "3.10" +pyobjc==11.1; python_version < "3.10" +``` + +A second sweep during cross-version validation surfaced 12 more packages whose latest versions declare `requires_python >= 3.10`: `black`, `cfgv`, `click`, `filelock`, `flake8-bugbear`, `identify`, `isort`, `platformdirs`, `pre-commit`, `pycparser`, `requests`, `urllib3`. Each got the same dual-pin treatment, pinning the latest 3.9-compatible release for `python_version < "3.10"`. 3.10 / 3.11 / 3.12 / 3.13 / 3.14 use the modern pins unchanged. Fixed in `f9c0853` (pyobjc) and `f83f774` (the other 12). + +### Acceptance criteria — actual results + +- ✅ `./build_python_framework_pkgs.zsh --python-version ` succeeds end-to-end on Apple Silicon for all six supported Python versions (3.9.13, 3.10.11, 3.11.9, 3.12.10, 3.13.13, 3.14.5). Each produces an installable framework zip; `managed_python3 --version` reports the correct version, `platform.machine()` returns `arm64`, and `import objc, xattr, requests, yaml` succeeds in every framework. Test record: `docs/superpowers/plans/2026-05-11-apple-silicon-modernization-tests.md`. +- ✅ Five workflow files updated to long-flag invocation; `build_python_3.14.yml` added. +- ✅ Dependabot enabled for GitHub Actions. +- ⏳ Final releases for 3.9 / 3.10 — pending the manual `workflow_dispatch` after merge. + +### Items rolled over to follow-up work + +- **Bump `RP_SHA` past `8ee72fe`** once upstream addresses the Apple Silicon ensurepip flow (tracked: gregneagle/relocatable-python#32). +- **Phase 3 (CI/CD overhaul)** — workflow consolidation, runner migration to `macos-14`, action version bumps, release trigger change, archival of `build_python_3.9.yml` and `build_python_3.10.yml`. Out of scope for this spec. diff --git a/requirement_files/Sphinx.txt b/requirement_files/Sphinx.txt deleted file mode 100644 index 75c1961..0000000 --- a/requirement_files/Sphinx.txt +++ /dev/null @@ -1,22 +0,0 @@ -alabaster==0.7.12 -Babel==2.11.0 -certifi==2023.7.22 -charset-normalizer==2.1.1 -docutils==0.19 -idna==3.4 -imagesize==1.4.1 -Jinja2==3.1.2 -MarkupSafe==2.1.1 -packaging==23.0 -Pygments==2.14.0 -pytz==2022.7 -requests==2.31.0 -snowballstemmer==2.2.0 -Sphinx==6.1.2 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.0 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 -sphinxcontrib.applehelp==1.0.3 -urllib3==1.26.18 diff --git a/requirement_files/arrow.txt b/requirement_files/arrow.txt deleted file mode 100644 index 7ddfed8..0000000 --- a/requirement_files/arrow.txt +++ /dev/null @@ -1,3 +0,0 @@ -arrow==1.2.3 -python-dateutil==2.8.2 -six==1.16.0 diff --git a/requirement_files/asn1crypto.txt b/requirement_files/asn1crypto.txt deleted file mode 100644 index bafaba2..0000000 --- a/requirement_files/asn1crypto.txt +++ /dev/null @@ -1 +0,0 @@ -asn1crypto==1.5.1 diff --git a/requirement_files/aspy.yaml.txt b/requirement_files/aspy.yaml.txt deleted file mode 100644 index 99f1cf0..0000000 --- a/requirement_files/aspy.yaml.txt +++ /dev/null @@ -1,2 +0,0 @@ -aspy.yaml==1.3.0 -PyYAML==6.0 diff --git a/requirement_files/atomicwrites.txt b/requirement_files/atomicwrites.txt deleted file mode 100644 index 51e6fcd..0000000 --- a/requirement_files/atomicwrites.txt +++ /dev/null @@ -1 +0,0 @@ -atomicwrites==1.4.1 diff --git a/requirement_files/black.txt b/requirement_files/black.txt deleted file mode 100644 index 3c44e2c..0000000 --- a/requirement_files/black.txt +++ /dev/null @@ -1,6 +0,0 @@ -black==22.12.0 -click==8.1.3 -mypy-extensions==0.4.3 -pathspec==0.10.3 -platformdirs==2.6.2 -tomli==2.0.1 diff --git a/requirement_files/boto.txt b/requirement_files/boto.txt deleted file mode 100644 index a842ade..0000000 --- a/requirement_files/boto.txt +++ /dev/null @@ -1 +0,0 @@ -boto==2.49.0 diff --git a/requirement_files/docklib.txt b/requirement_files/docklib.txt deleted file mode 100644 index d7e5bf6..0000000 --- a/requirement_files/docklib.txt +++ /dev/null @@ -1 +0,0 @@ -docklib==1.3.0 diff --git a/requirement_files/entrypoints.txt b/requirement_files/entrypoints.txt deleted file mode 100644 index f5fc04a..0000000 --- a/requirement_files/entrypoints.txt +++ /dev/null @@ -1 +0,0 @@ -entrypoints==0.4 diff --git a/requirement_files/flake8-bugbear.txt b/requirement_files/flake8-bugbear.txt deleted file mode 100644 index 67e48d6..0000000 --- a/requirement_files/flake8-bugbear.txt +++ /dev/null @@ -1,6 +0,0 @@ -attrs==22.2.0 -flake8==6.0.0 -flake8-bugbear==22.12.6 -mccabe==0.7.0 -pycodestyle==2.10.0 -pyflakes==3.0.1 diff --git a/requirement_files/funcsigs.txt b/requirement_files/funcsigs.txt deleted file mode 100644 index 0a9a367..0000000 --- a/requirement_files/funcsigs.txt +++ /dev/null @@ -1 +0,0 @@ -funcsigs==1.0.2 diff --git a/requirement_files/isort.txt b/requirement_files/isort.txt deleted file mode 100644 index 3dfd857..0000000 --- a/requirement_files/isort.txt +++ /dev/null @@ -1 +0,0 @@ -isort==5.11.4 diff --git a/requirement_files/packaging.txt b/requirement_files/packaging.txt deleted file mode 100644 index 7eba1b4..0000000 --- a/requirement_files/packaging.txt +++ /dev/null @@ -1 +0,0 @@ -packaging==23.0 diff --git a/requirement_files/pre-commit.txt b/requirement_files/pre-commit.txt deleted file mode 100644 index 3f67961..0000000 --- a/requirement_files/pre-commit.txt +++ /dev/null @@ -1,9 +0,0 @@ -cfgv==3.3.1 -distlib==0.3.6 -filelock==3.9.0 -identify==2.5.12 -nodeenv==1.7.0 -platformdirs==2.6.2 -pre-commit==2.21.0 -PyYAML==6.0 -virtualenv==20.17.1 diff --git a/requirement_files/pyobjc.txt b/requirement_files/pyobjc.txt deleted file mode 100644 index 9ca0459..0000000 --- a/requirement_files/pyobjc.txt +++ /dev/null @@ -1,147 +0,0 @@ -pyobjc==9.0.1 -pyobjc-core==9.0.1 -pyobjc-framework-Accessibility==9.0.1 -pyobjc-framework-Accounts==9.0.1 -pyobjc-framework-AddressBook==9.0.1 -pyobjc-framework-AdServices==9.0.1 -pyobjc-framework-AdSupport==9.0.1 -pyobjc-framework-AppleScriptKit==9.0.1 -pyobjc-framework-AppleScriptObjC==9.0.1 -pyobjc-framework-ApplicationServices==9.0.1 -pyobjc-framework-AppTrackingTransparency==9.0.1 -pyobjc-framework-AudioVideoBridging==9.0.1 -pyobjc-framework-AuthenticationServices==9.0.1 -pyobjc-framework-AutomaticAssessmentConfiguration==9.0.1 -pyobjc-framework-Automator==9.0.1 -pyobjc-framework-AVFoundation==9.0.1 -pyobjc-framework-AVKit==9.0.1 -pyobjc-framework-AVRouting==9.0.1 -pyobjc-framework-BackgroundAssets==9.0.1 -pyobjc-framework-BusinessChat==9.0.1 -pyobjc-framework-CalendarStore==9.0.1 -pyobjc-framework-CallKit==9.0.1 -pyobjc-framework-CFNetwork==9.0.1 -pyobjc-framework-ClassKit==9.0.1 -pyobjc-framework-CloudKit==9.0.1 -pyobjc-framework-Cocoa==9.0.1 -pyobjc-framework-Collaboration==9.0.1 -pyobjc-framework-ColorSync==9.0.1 -pyobjc-framework-Contacts==9.0.1 -pyobjc-framework-ContactsUI==9.0.1 -pyobjc-framework-CoreAudio==9.0.1 -pyobjc-framework-CoreAudioKit==9.0.1 -pyobjc-framework-CoreBluetooth==9.0.1 -pyobjc-framework-CoreData==9.0.1 -pyobjc-framework-CoreHaptics==9.0.1 -pyobjc-framework-CoreLocation==9.0.1 -pyobjc-framework-CoreMedia==9.0.1 -pyobjc-framework-CoreMediaIO==9.0.1 -pyobjc-framework-CoreMIDI==9.0.1 -pyobjc-framework-CoreML==9.0.1 -pyobjc-framework-CoreMotion==9.0.1 -pyobjc-framework-CoreServices==9.0.1 -pyobjc-framework-CoreSpotlight==9.0.1 -pyobjc-framework-CoreText==9.0.1 -pyobjc-framework-CoreWLAN==9.0.1 -pyobjc-framework-CryptoTokenKit==9.0.1 -pyobjc-framework-DataDetection==9.0.1 -pyobjc-framework-DeviceCheck==9.0.1 -pyobjc-framework-DictionaryServices==9.0.1 -pyobjc-framework-DiscRecording==9.0.1 -pyobjc-framework-DiscRecordingUI==9.0.1 -pyobjc-framework-DiskArbitration==9.0.1 -pyobjc-framework-DVDPlayback==9.0.1 -pyobjc-framework-EventKit==9.0.1 -pyobjc-framework-ExceptionHandling==9.0.1 -pyobjc-framework-ExecutionPolicy==9.0.1 -pyobjc-framework-ExtensionKit==9.0.1 -pyobjc-framework-ExternalAccessory==9.0.1 -pyobjc-framework-FileProvider==9.0.1 -pyobjc-framework-FileProviderUI==9.0.1 -pyobjc-framework-FinderSync==9.0.1 -pyobjc-framework-FSEvents==9.0.1 -pyobjc-framework-GameCenter==9.0.1 -pyobjc-framework-GameController==9.0.1 -pyobjc-framework-GameKit==9.0.1 -pyobjc-framework-GameplayKit==9.0.1 -pyobjc-framework-HealthKit==9.0.1 -pyobjc-framework-ImageCaptureCore==9.0.1 -pyobjc-framework-IMServicePlugIn==9.0.1 -pyobjc-framework-InputMethodKit==9.0.1 -pyobjc-framework-InstallerPlugins==9.0.1 -pyobjc-framework-InstantMessage==9.0.1 -pyobjc-framework-Intents==9.0.1 -pyobjc-framework-IntentsUI==9.0.1 -pyobjc-framework-IOSurface==9.0.1 -pyobjc-framework-iTunesLibrary==9.0.1 -pyobjc-framework-KernelManagement==9.0.1 -pyobjc-framework-LatentSemanticMapping==9.0.1 -pyobjc-framework-LaunchServices==9.0.1 -pyobjc-framework-libdispatch==9.0.1 -pyobjc-framework-LinkPresentation==9.0.1 -pyobjc-framework-LocalAuthentication==9.0.1 -pyobjc-framework-LocalAuthenticationEmbeddedUI==9.0.1 -pyobjc-framework-MailKit==9.0.1 -pyobjc-framework-MapKit==9.0.1 -pyobjc-framework-MediaAccessibility==9.0.1 -pyobjc-framework-MediaLibrary==9.0.1 -pyobjc-framework-MediaPlayer==9.0.1 -pyobjc-framework-MediaToolbox==9.0.1 -pyobjc-framework-Metal==9.0.1 -pyobjc-framework-MetalFX==9.0.1 -pyobjc-framework-MetalKit==9.0.1 -pyobjc-framework-MetalPerformanceShaders==9.0.1 -pyobjc-framework-MetalPerformanceShadersGraph==9.0.1 -pyobjc-framework-MetricKit==9.0.1 -pyobjc-framework-MLCompute==9.0.1 -pyobjc-framework-ModelIO==9.0.1 -pyobjc-framework-MultipeerConnectivity==9.0.1 -pyobjc-framework-NaturalLanguage==9.0.1 -pyobjc-framework-NetFS==9.0.1 -pyobjc-framework-Network==9.0.1 -pyobjc-framework-NetworkExtension==9.0.1 -pyobjc-framework-NotificationCenter==9.0.1 -pyobjc-framework-OpenDirectory==9.0.1 -pyobjc-framework-OSAKit==9.0.1 -pyobjc-framework-OSLog==9.0.1 -pyobjc-framework-PassKit==9.0.1 -pyobjc-framework-PencilKit==9.0.1 -pyobjc-framework-Photos==9.0.1 -pyobjc-framework-PhotosUI==9.0.1 -pyobjc-framework-PreferencePanes==9.0.1 -pyobjc-framework-PushKit==9.0.1 -pyobjc-framework-Quartz==9.0.1 -pyobjc-framework-QuickLookThumbnailing==9.0.1 -pyobjc-framework-ReplayKit==9.0.1 -pyobjc-framework-SafariServices==9.0.1 -pyobjc-framework-SafetyKit==9.0.1 -pyobjc-framework-SceneKit==9.0.1 -pyobjc-framework-ScreenCaptureKit==9.0.1 -pyobjc-framework-ScreenSaver==9.0.1 -pyobjc-framework-ScreenTime==9.0.1 -pyobjc-framework-ScriptingBridge==9.0.1 -pyobjc-framework-SearchKit==9.0.1 -pyobjc-framework-Security==9.0.1 -pyobjc-framework-SecurityFoundation==9.0.1 -pyobjc-framework-SecurityInterface==9.0.1 -pyobjc-framework-ServiceManagement==9.0.1 -pyobjc-framework-SharedWithYou==9.0.1 -pyobjc-framework-SharedWithYouCore==9.0.1 -pyobjc-framework-ShazamKit==9.0.1 -pyobjc-framework-Social==9.0.1 -pyobjc-framework-SoundAnalysis==9.0.1 -pyobjc-framework-Speech==9.0.1 -pyobjc-framework-SpriteKit==9.0.1 -pyobjc-framework-StoreKit==9.0.1 -pyobjc-framework-SyncServices==9.0.1 -pyobjc-framework-SystemConfiguration==9.0.1 -pyobjc-framework-SystemExtensions==9.0.1 -pyobjc-framework-ThreadNetwork==9.0.1 -pyobjc-framework-UniformTypeIdentifiers==9.0.1 -pyobjc-framework-UserNotifications==9.0.1 -pyobjc-framework-UserNotificationsUI==9.0.1 -pyobjc-framework-VideoSubscriberAccount==9.0.1 -pyobjc-framework-VideoToolbox==9.0.1 -pyobjc-framework-Virtualization==9.0.1 -pyobjc-framework-Vision==9.0.1 -pyobjc-framework-WebKit==9.0.1 diff --git a/requirement_files/pytest-docker.txt b/requirement_files/pytest-docker.txt deleted file mode 100644 index 13b09bb..0000000 --- a/requirement_files/pytest-docker.txt +++ /dev/null @@ -1,8 +0,0 @@ -attrs==22.2.0 -exceptiongroup==1.1.0 -iniconfig==2.0.0 -packaging==23.0 -pluggy==1.0.0 -pytest==7.2.0 -pytest-docker==1.0.1 -tomli==2.0.1 diff --git a/requirement_files/pytest.txt b/requirement_files/pytest.txt deleted file mode 100644 index d429f7d..0000000 --- a/requirement_files/pytest.txt +++ /dev/null @@ -1,7 +0,0 @@ -attrs==22.2.0 -exceptiongroup==1.1.0 -iniconfig==2.0.0 -packaging==23.0 -pluggy==1.0.0 -pytest==7.2.0 -tomli==2.0.1 diff --git a/requirement_files/python-dotenv.txt b/requirement_files/python-dotenv.txt deleted file mode 100644 index 9995d1a..0000000 --- a/requirement_files/python-dotenv.txt +++ /dev/null @@ -1 +0,0 @@ -python-dotenv==0.21.0 diff --git a/requirement_files/requests.txt b/requirement_files/requests.txt deleted file mode 100644 index be15481..0000000 --- a/requirement_files/requests.txt +++ /dev/null @@ -1,5 +0,0 @@ -certifi==2023.7.22 -charset-normalizer==2.1.1 -idna==3.4 -requests==2.31.0 -urllib3==1.26.18 diff --git a/requirement_files/requirements_minimal.txt b/requirement_files/requirements_minimal.txt deleted file mode 100644 index 22907fe..0000000 --- a/requirement_files/requirements_minimal.txt +++ /dev/null @@ -1,2 +0,0 @@ --r pyobjc.txt --r xattr.txt diff --git a/requirement_files/requirements_opinionated.txt b/requirement_files/requirements_opinionated.txt deleted file mode 100755 index 57dcf44..0000000 --- a/requirement_files/requirements_opinionated.txt +++ /dev/null @@ -1,23 +0,0 @@ --r arrow.txt --r asn1crypto.txt --r aspy.yaml.txt --r atomicwrites.txt --r black.txt --r boto.txt --r docklib.txt --r entrypoints.txt --r flake8-bugbear.txt --r funcsigs.txt --r isort.txt --r packaging.txt --r pre-commit.txt --r pyobjc.txt --r pytest.txt --r pytest-docker.txt --r python-dotenv.txt --r requests.txt --r rsa.txt --r slacker.txt --r Sphinx.txt --r tokenize-rt.txt --r xattr.txt diff --git a/requirement_files/requirements_recommended.txt b/requirement_files/requirements_recommended.txt deleted file mode 100644 index 08f734c..0000000 --- a/requirement_files/requirements_recommended.txt +++ /dev/null @@ -1,13 +0,0 @@ --r asn1crypto.txt --r aspy.yaml.txt --r black.txt --r docklib.txt --r entrypoints.txt --r flake8-bugbear.txt --r isort.txt --r packaging.txt --r pre-commit.txt --r pyobjc.txt --r requests.txt --r tokenize-rt.txt --r xattr.txt diff --git a/requirement_files/rsa.txt b/requirement_files/rsa.txt deleted file mode 100644 index b0907e3..0000000 --- a/requirement_files/rsa.txt +++ /dev/null @@ -1,2 +0,0 @@ -pyasn1==0.4.8 -rsa==4.9 diff --git a/requirement_files/slacker.txt b/requirement_files/slacker.txt deleted file mode 100644 index adf6857..0000000 --- a/requirement_files/slacker.txt +++ /dev/null @@ -1,6 +0,0 @@ -certifi==2023.7.22 -charset-normalizer==2.1.1 -idna==3.4 -requests==2.31.0 -slacker==0.14.0 -urllib3==1.26.18 diff --git a/requirement_files/tokenize-rt.txt b/requirement_files/tokenize-rt.txt deleted file mode 100644 index fed774b..0000000 --- a/requirement_files/tokenize-rt.txt +++ /dev/null @@ -1 +0,0 @@ -tokenize-rt==5.0.0 diff --git a/requirement_files/xattr.txt b/requirement_files/xattr.txt deleted file mode 100644 index b1e3106..0000000 --- a/requirement_files/xattr.txt +++ /dev/null @@ -1,3 +0,0 @@ -cffi==1.15.1 -pycparser==2.21 -xattr==0.10.1 diff --git a/requirements_minimal.txt b/requirements_minimal.txt deleted file mode 100644 index b6277d1..0000000 --- a/requirements_minimal.txt +++ /dev/null @@ -1,7 +0,0 @@ -cffi==1.16.0 ---no-binary cffi -pycparser==2.21 -pyobjc==10.1 -six==1.16.0 -xattr==1.0.0 ---no-binary xattr diff --git a/requirements_no_customization.txt b/requirements_no_customization.txt deleted file mode 100644 index e69de29..0000000 diff --git a/requirements_recommended.txt b/requirements_recommended.txt index 22a9eb6..13558b2 100644 --- a/requirements_recommended.txt +++ b/requirements_recommended.txt @@ -3,18 +3,22 @@ aspy.yaml==1.3.0 attrs==26.1.0 black==26.3.1; python_version >= "3.10" black==25.11.0; python_version < "3.10" ---no-binary black ---no-binary pytokens certifi==2026.4.22 cffi==2.0.0 ---no-binary cffi cfgv==3.5.0; python_version >= "3.10" cfgv==3.4.0; python_version < "3.10" charset-normalizer==3.4.7 ---no-binary charset-normalizer click==8.3.3; python_version >= "3.10" click==8.1.8; python_version < "3.10" distlib==0.4.0 +attrs==25.3.0 +black==25.1.0 +certifi==2025.6.15 +cffi==1.17.1 +cfgv==3.4.0 +charset-normalizer==3.4.2 +click==8.2.1 +distlib==0.3.9 docklib==2.0.0 entrypoints==0.4 filelock==3.29.0; python_version >= "3.10" @@ -29,7 +33,6 @@ isort==8.0.1; python_version >= "3.10" isort==6.1.0; python_version < "3.10" mccabe==0.7.0 mypy-extensions==1.1.0 ---no-binary mypy-extensions nodeenv==1.10.0 packaging==26.2 pathspec==1.1.1 @@ -44,15 +47,12 @@ pyflakes==3.4.0 pyobjc==12.1; python_version >= "3.10" pyobjc==11.1; python_version < "3.10" PyYAML==6.0.3 ---no-binary PyYAML requests==2.34.0; python_version >= "3.10" requests==2.32.5; python_version < "3.10" six==1.17.0 tokenize-rt==6.2.0 tomli==2.4.1 ---no-binary tomli urllib3==2.7.0; python_version >= "3.10" urllib3==2.6.3; python_version < "3.10" virtualenv==21.3.2 xattr==1.3.0 ---no-binary xattr