name: Release on: push: branches: - "**" tags-ignore: - "**" paths: - .github/workflows/release.yml - package/**/* workflow_dispatch: inputs: tag: description: "Git tag for the release" required: true type: string env: PKG_APPLE_DEVELOPER_TEAM_ID: ${{ secrets.PKG_APPLE_DEVELOPER_TEAM_ID }} HOMEBREW_NO_ANALYTICS_THIS_RUN: 1 HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT: 1 permissions: {} defaults: run: shell: bash -xeuo pipefail {0} jobs: build: if: github.repository_owner == 'Homebrew' && github.actor != 'dependabot[bot]' runs-on: macos-26 outputs: installer_path: "Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg" env: TEMPORARY_CERTIFICATE_FILE: "homebrew_developer_id_installer_certificate.p12" TEMPORARY_KEYCHAIN_FILE: "homebrew_installer_signing.keychain-db" # Set to the oldest supported version of macOS HOMEBREW_MACOS_OLDEST_SUPPORTED: "14.0" permissions: contents: write # for creating tags and releases attestations: write # for actions/attest id-token: write # for actions/attest steps: - name: Remove existing API cache (to force update) run: rm -rvf ~/Library/Caches/Homebrew/api - name: Set up Homebrew id: set-up-homebrew uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false - name: Install Pandoc uses: Homebrew/actions/cache-homebrew-prefix@main with: install: pandoc workflow-key: release uninstall: true - name: Create and unlock temporary macOS keychain run: | TEMPORARY_KEYCHAIN_PASSWORD="$(openssl rand -base64 20)" TEMPORARY_KEYCHAIN_PATH="${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" security create-keychain -p "${TEMPORARY_KEYCHAIN_PASSWORD}" "${TEMPORARY_KEYCHAIN_PATH}" security set-keychain-settings -l -u -t 21600 "${TEMPORARY_KEYCHAIN_PATH}" security unlock-keychain -p "${TEMPORARY_KEYCHAIN_PASSWORD}" "${TEMPORARY_KEYCHAIN_PATH}" - name: Create temporary certificate file env: PKG_APPLE_SIGNING_CERTIFICATE_BASE64: ${{ secrets.PKG_APPLE_SIGNING_CERTIFICATE_BASE64 }} run: echo -n "${PKG_APPLE_SIGNING_CERTIFICATE_BASE64}" | base64 --decode --output="${RUNNER_TEMP}/${TEMPORARY_CERTIFICATE_FILE}" - name: Import certificate file into macOS keychain env: PKG_APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.PKG_APPLE_SIGNING_CERTIFICATE_PASSWORD }} run: security import "${RUNNER_TEMP}/${TEMPORARY_CERTIFICATE_FILE}" -k "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" -P "${PKG_APPLE_SIGNING_CERTIFICATE_PASSWORD}" -t cert -f pkcs12 -A - name: Clean up temporary certificate file if: ${{ always() }} run: rm -f "${RUNNER_TEMP}/${TEMPORARY_CERTIFICATE_FILE}" - name: Checkout another Homebrew to brew subdirectory uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: brew fetch-depth: 0 persist-credentials: false - name: Create tag if needed, check and get Homebrew version from Git id: homebrew-version env: TAG: ${{ inputs.tag }} run: | if [ -n "${TAG}" ]; then git -C brew tag "${TAG}" fi homebrew_version=$(git -C brew describe --tags --always) if [ -n "${TAG}" ] && [ "${homebrew_version}" != "${TAG}" ]; then echo "Homebrew version ${homebrew_version} does not match tag ${TAG}" exit 1 fi echo "version=${homebrew_version}" >> "${GITHUB_OUTPUT}" - name: Copy Homebrew API cache to brew subdirectory run: cp -vR ~/Library/Caches/Homebrew/api brew/cache_api - name: Open macOS keychain run: security list-keychain -d user -s "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" - name: Build Homebrew installer component package env: HOMEBREW_VERSION: ${{ steps.homebrew-version.outputs.version }} # Note: `Library/Homebrew/test/support/fixtures/` contains unsigned # binaries so it needs to be excluded from notarization. run: pkgbuild --root brew --scripts brew/package/scripts --identifier sh.brew.homebrew --version "${HOMEBREW_VERSION}" --install-location /opt/homebrew --filter .DS_Store --filter "(.*)/Library/Homebrew/test/support/fixtures/" --min-os-version "${HOMEBREW_MACOS_OLDEST_SUPPORTED}" --sign "${PKG_APPLE_DEVELOPER_TEAM_ID}" Homebrew.pkg - name: Convert Homebrew license file to RTF run: (printf "### " && cat brew/LICENSE.txt) | pandoc --from markdown --standalone --output brew/package/resources/LICENSE.rtf - name: Build Homebrew installer product package env: HOMEBREW_VERSION: ${{ steps.homebrew-version.outputs.version }} run: productbuild --resources brew/package/resources --distribution brew/package/Distribution.xml --package-path Homebrew.pkg --sign "${PKG_APPLE_DEVELOPER_TEAM_ID}" "Homebrew-${HOMEBREW_VERSION}.pkg" - name: Clean up temporary macOS keychain if: ${{ always() }} run: | if [[ -f "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" ]] then security delete-keychain "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" fi - name: Generate build provenance uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg - name: Upload installer to GitHub Actions uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg test: needs: build name: "test (${{matrix.name}})" runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: # Intel - runner: macos-15-intel name: macos-15-x86_64 # Apple Silicon - runner: macos-15 name: macos-15-arm64 - runner: macos-26 name: macos-26-arm64 steps: - name: Download installer from GitHub Actions uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: "${{ needs.build.outputs.installer_path }}" - name: Unset global Git safe directory setting run: git config --global --unset-all safe.directory - name: Remove existing Homebrew installations run: | sudo rm -rf brew /{usr/local,opt/homebrew}/{Cellar,Caskroom,Homebrew/Library/Taps} brew cleanup --prune-prefix sudo rm -rf /usr/local/{bin/brew,Homebrew} /opt/homebrew /home/linuxbrew - name: Zero existing installer logs run: echo | sudo tee /var/log/install.log - name: Install Homebrew from installer package env: INSTALLER_PATH: ${{ needs.build.outputs.installer_path }} run: sudo installer -verbose -pkg "${INSTALLER_PATH}" -target / - name: Output installer logs if: ${{ always() }} run: sudo cat /var/log/install.log - name: Check Homebrew version matches tag env: TAG: ${{ inputs.tag }} run: | homebrew_version=$(brew --version) if [ -n "${TAG}" ] && [ "${homebrew_version}" != "Homebrew ${TAG}" ]; then echo "Homebrew version ${homebrew_version} does not match tag ${TAG}" exit 1 fi - run: brew config - run: brew doctor - name: Zero existing installer logs (again) run: echo | sudo tee /var/log/install.log - name: Reinstall Homebrew from installer package env: INSTALLER_PATH: ${{ needs.build.outputs.installer_path }} run: sudo installer -verbose -pkg "${INSTALLER_PATH}" -target / - name: Output installer logs (again) if: ${{ always() }} run: sudo cat /var/log/install.log - run: brew config - run: brew doctor upload: needs: [build, test] runs-on: macos-26 permissions: # To write assets to GitHub release contents: write steps: - name: Checkout Homebrew/brew if: github.event_name == 'workflow_dispatch' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - name: Download installer from GitHub Actions uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: "${{ needs.build.outputs.installer_path }}" - name: Notarize Homebrew installer package env: PKG_APPLE_ID_EMAIL: ${{ secrets.PKG_APPLE_ID_EMAIL }} PKG_APPLE_ID_APP_SPECIFIC_PASSWORD: ${{ secrets.PKG_APPLE_ID_APP_SPECIFIC_PASSWORD }} INSTALLER_PATH: ${{ needs.build.outputs.installer_path }} run: xcrun notarytool submit "${INSTALLER_PATH}" --team-id "${PKG_APPLE_DEVELOPER_TEAM_ID}" --apple-id "${PKG_APPLE_ID_EMAIL}" --password "${PKG_APPLE_ID_APP_SPECIFIC_PASSWORD}" --wait - name: Create draft release if: github.event_name == 'workflow_dispatch' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ inputs.tag }} run: | gh release create "${TAG}" \ --repo Homebrew/brew \ --title "${TAG}" \ --draft \ --generate-notes \ --fail-on-no-commits - name: Rename installer to unversioned path env: INSTALLER_PATH: ${{ needs.build.outputs.installer_path }} run: mv "${INSTALLER_PATH}" Homebrew.pkg - name: Upload installer to GitHub release if: github.event_name == 'workflow_dispatch' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} INSTALLER_PATH: ${{ needs.build.outputs.installer_path }} run: | VERSION="${INSTALLER_PATH#Homebrew-}" VERSION="${VERSION%.pkg}" gh release upload --repo Homebrew/brew "${VERSION}" Homebrew.pkg