name: Release
on:
  push:
    branches:
      - "**"
    tags-ignore:
      - "**"
    paths:
      - .github/workflows/release.yml
      - Library/Homebrew/utils/macos_user.sh
      - 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
      issues: read # for checking release blocker labels
      pull-requests: read # for checking release blocker labels
    steps:
      - name: Check for release blockers
        if: github.event_name == 'workflow_dispatch'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG: ${{ inputs.tag }}
        # Keep in sync with the release blocker check in
        # Library/Homebrew/dev-cmd/release.rb.
        run: |
          labels=("release blocker")
          if [[ "${TAG}" =~ ^[0-9]+\.[0-9]+\.0$ ]]
          then
            labels+=("major/minor release blocker")
          fi

          blockers="$(for label in "${labels[@]}"
          do
            gh api --method GET repos/Homebrew/brew/issues \
              --raw-field state=open --raw-field labels="${label}" \
              --jq '.[].html_url'
          done | sort -u)"

          if [[ -n "${blockers}" ]]
          then
            echo "Open issues or pull requests are blocking this release:"
            echo "${blockers}"
            exit 1
          fi

      - 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@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
        with:
          core: false
          cask: false

      - name: Install Pandoc
        uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
        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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        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: Prepare Homebrew API cache for package
        run: |
          package_api_cache="${RUNNER_TEMP}/homebrew-api-cache"
          rm -rvf ~/Library/Caches/Homebrew/api "${package_api_cache}"

          HOMEBREW_DEVELOPER=1 HOMEBREW_UPDATE_SKIP_BREW=1 brew update --auto-update --verbose
          cp -vR ~/Library/Caches/Homebrew/api brew/cache_api

      - name: Check installer Git safety
        run: |
          # Keep any future privileged repository Git calls aligned with the hardening above.
          if grep -nE 'git .* (checkout|reset|clean)\b|git .* tag --list\b' \
            brew/package/scripts/postinstall
          then
            echo "Privileged Git repository operations must use \`git_no_hooks\`."
            exit 1
          fi

      - name: Open macOS keychain
        run: security list-keychain -d user -s "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}"

      - name: Prepare Homebrew installer scripts
        run: |
          installer_scripts="${RUNNER_TEMP}/homebrew-installer-scripts"
          rm -rvf "${installer_scripts}"
          mkdir -vp "${installer_scripts}"
          cp -vp brew/package/scripts/preinstall \
            brew/package/scripts/postinstall \
            brew/Library/Homebrew/utils/macos_user.sh \
            "${installer_scripts}/"
          echo "HOMEBREW_INSTALLER_SCRIPTS=${installer_scripts}" >> "${GITHUB_ENV}"

      - 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 "${HOMEBREW_INSTALLER_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@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        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
