Skip to content

Publish PR #178455 #106628

Publish PR #178455

Publish PR #178455 #106628

name: Publish and commit bottles
run-name: "Publish PR #${{ inputs.pull_request }}"
concurrency:
group: ${{ github.workflow }}-${{ github.event.inputs.pull_request }}
cancel-in-progress: false
on:
workflow_dispatch:
inputs:
pull_request:
description: Pull request number
required: true
large_runner:
description: "Run the upload job on a large runner? (default: false)"
type: boolean
required: false
default: false
autosquash:
description: "Squash pull request commits according to Homebrew style? (default: false)"
type: boolean
required: false
default: false
warn_on_upload_failure:
description: "Pass `--warn-on-upload-failure` to `brew pr-pull`? (default: false)"
type: boolean
required: false
default: false
message:
description: "Message to include when autosquashing revision bumps, deletions, and rebuilds (requires autosquash)"
required: false
env:
PR: ${{inputs.pull_request}}
INPUT_MESSAGE: ${{ inputs.message }}
GNUPGHOME: /tmp/gnupghome
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_FROM_API: 1
GH_REPO: ${{github.repository}}
GH_NO_UPDATE_NOTIFIER: 1
GH_PROMPT_DISABLED: 1
RUN_URL: ${{github.event.repository.html_url}}/actions/runs/${{github.run_id}}
NON_PUSHABLE_MESSAGE: >-
:no_entry: It looks like @BrewTestBot cannot push to your PR branch. For future pull requests, please
[allow maintainers to edit your PR](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) to simplify the merge process.
ORG_FORK_MESSAGE: >-
:no_entry: It looks like @BrewTestBot cannot push to your PR branch. Please open
future pull requests from a non-organization fork to simplify the merge process.
jobs:
check:
runs-on: ubuntu-latest
outputs:
bottles: ${{steps.pr-branch-check.outputs.bottles}}
head_sha: ${{steps.pr-branch-check.outputs.head_sha}}
branch: ${{steps.pr-branch-check.outputs.branch}}
remote_branch: ${{steps.pr-branch-check.outputs.remote_branch}}
remote: ${{steps.pr-branch-check.outputs.remote}}
replace: ${{steps.pr-branch-check.outputs.replace}}
requires_merge: ${{steps.pr-branch-check.outputs.requires_merge}}
permissions:
contents: read
actions: write # for `gh workflow run`
pull-requests: write # for `gh pr edit|comment|review`
repository-projects: write # for `gh pr edit`
steps:
- name: Check PR approval
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
if jq --exit-status 'all(.[].state; .!= "APPROVED")'
then
echo "::error ::PR #$PR is not approved!"
exit 1
fi < <(
gh api \
--header 'Accept: application/vnd.github+json' \
--header 'X-GitHub-Api-Version: 2022-11-28' \
--paginate \
"repos/{owner}/{repo}/pulls/$PR/reviews"
)
- name: Check PR branch for mergeability
id: pr-branch-check
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
pr_data="$(
gh api \
--header 'Accept: application/vnd.github+json' \
--header 'X-GitHub-Api-Version: 2022-11-28' \
"repos/$GH_REPO/pulls/$PR"
)"
pushable="$(jq .maintainer_can_modify <<< "$pr_data")"
branch="$(jq --raw-output .head.ref <<< "$pr_data")"
remote="$(jq --raw-output .head.repo.clone_url <<< "$pr_data")"
head_repo="$(jq --raw-output .head.repo.full_name <<< "$pr_data")"
head_repo_owner="$(jq --raw-output .head.repo.owner.login <<< "$pr_data")"
head_sha="$(jq --raw-output .head.sha <<< "$pr_data")"
fork_type="$(jq --raw-output .head.repo.owner.type <<< "$pr_data")"
state="$(jq --raw-output .state <<< "$pr_data")"
node_id="$(jq --raw-output .node_id <<< "$pr_data")"
merged="$(jq --raw-output .merged <<< "$pr_data")"
automerge_enabled="$(jq --raw-output '.auto_merge != null' <<< "$pr_data")"
if [[ -z "$pushable" ]] ||
[[ -z "$branch" ]] ||
[[ -z "$remote" ]] ||
[[ -z "$head_repo" ]] ||
[[ -z "$head_repo_owner" ]] ||
[[ -z "$head_sha" ]] ||
[[ -z "$fork_type" ]] ||
[[ -z "$state" ]] ||
[[ -z "$merged" ]] ||
[[ -z "$node_id" ]] ||
[[ -z "$automerge_enabled" ]]
then
echo "::error ::Failed to get PR data!"
exit 1
fi
if [[ "$state" = "closed" ]]
then
echo "::error ::PR #$PR is closed!"
exit 1
fi
bottles=true
while IFS='' read -r label
do
if [[ "$label" = "CI-syntax-only" ]] ||
[[ "$label" = "CI-no-bottles" ]] ||
[[ "$label" = "CI-published-bottle-commits" ]]
then
echo '::notice ::No bottles to publish according to PR labels.'
bottles=false
break
fi
done < <(jq --raw-output '.labels[].name' <<< "$pr_data")
requires_merge=true
if [[ "$merged" = "true" || "$automerge_enabled" = "true" ]]
then
echo '::notice ::Pull request is either already merged or queued to merge.'
requires_merge=false
fi
if [[ "$branch" = "master" ]]
then
branch="$head_repo_owner/master"
remote_branch="master"
else
remote_branch="$branch"
fi
{
echo "bottles=$bottles"
echo "head_sha=$head_sha"
echo "branch=$branch"
echo "remote_branch=$remote_branch"
echo "remote=$remote"
echo "node_id=$node_id"
echo "requires_merge=$requires_merge"
echo "replace=${{ inputs.autosquash }}"
} >> "$GITHUB_OUTPUT"
if "$pushable" && [[ "$fork_type" != "Organization" ]] ||
[[ "$head_repo" = "$GH_REPO" ]] ||
[[ "$bottles" = "false" ]]
then
exit 0
elif "$pushable"
then
MESSAGE="$ORG_FORK_MESSAGE"
else
MESSAGE="$NON_PUSHABLE_MESSAGE"
fi
echo "replace=true" >> "$GITHUB_OUTPUT"
gh pr comment "$PR" --body "$MESSAGE"
gh pr edit --add-label 'no push access' "$PR"
- name: Dispatch replacement pull request
if: >
fromJson(steps.pr-branch-check.outputs.replace) &&
fromJson(steps.pr-branch-check.outputs.bottles) &&
fromJson(steps.pr-branch-check.outputs.requires_merge)
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
gh workflow run create-replacement-pr.yml \
--ref "$GITHUB_REF_NAME" \
--field pull_request="$PR" \
--field autosquash=${{ inputs.autosquash }} \
--field upload=${{ !inputs.autosquash }} \
--field warn_on_upload_failure=false \
--field message="$INPUT_MESSAGE"
- name: Post comment on failure
if: ${{!success()}}
uses: Homebrew/actions/post-comment@master
with:
token: ${{secrets.GITHUB_TOKEN}}
issue: ${{inputs.pull_request}}
body: ":warning: @${{github.actor}} pre-merge checks [failed](${{env.RUN_URL}})."
bot_body: ":warning: Pre-merge checks [failed](${{env.RUN_URL}})."
bot: github-actions[bot]
- name: Enqueue PR for merge
if: >
fromJson(steps.pr-branch-check.outputs.requires_merge) &&
!fromJson(steps.pr-branch-check.outputs.bottles) &&
!inputs.autosquash
env:
GH_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
ID: ${{ steps.pr-branch-check.outputs.node_id }}
EXPECTED_SHA: ${{ steps.pr-branch-check.outputs.head_sha }}
MUTATION: |-
mutation ($input: EnqueuePullRequestInput!) {
enqueuePullRequest(input: $input) {
clientMutationId
}
}
run: |
# TODO Try using `gh pr merge` when the following is resolved:
# https://github.com/cli/cli/issues/7213
gh api graphql \
--field "input[pullRequestId]=$ID" \
--field "input[expectedHeadOid]=$EXPECTED_SHA" \
--raw-field query="$MUTATION"
upload:
needs: check
if: >
fromJson(needs.check.outputs.requires_merge) &&
fromJson(needs.check.outputs.bottles) &&
!fromJson(needs.check.outputs.replace)
runs-on: ${{inputs.large_runner && 'homebrew-large-bottle-upload' || 'ubuntu-22.04'}}
container:
image: ghcr.io/homebrew/ubuntu22.04:master
volumes:
- /mnt:/mnt
permissions:
attestations: write # for `generate build provenance`
id-token: write # for `generate build provenance`
actions: read # for `brew pr-pull`
pull-requests: write # for `gh pr edit|review`
repository-projects: write # for `gh pr edit`
defaults:
run:
shell: bash
steps:
- name: Post comment once started
uses: Homebrew/actions/post-comment@master
with:
token: ${{secrets.GITHUB_TOKEN}}
issue: ${{inputs.pull_request}}
body: ":shipit: @${{github.actor}} has [requested bottles to be published to this PR](${{env.RUN_URL}})."
bot_body: ":robot: An automated task has [requested bottles to be published to this PR](${{env.RUN_URL}})."
bot: github-actions[bot]
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
with:
core: true
cask: false
test-bot: false
- name: Configure Git user
id: git-user-config
uses: Homebrew/actions/git-user-config@master
with:
username: ${{ (github.actor != 'github-actions[bot]' && github.actor) || 'BrewTestBot' }}
- name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@master
with:
signing_key: ${{ secrets.BREWTESTBOT_GPG_SIGNING_SUBKEY }}
- name: Checkout PR branch
working-directory: ${{steps.set-up-homebrew.outputs.repository-path}}
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: gh pr checkout "$PR"
- name: Pull PR bottles
id: pr-pull
working-directory: ${{steps.set-up-homebrew.outputs.repository-path}}
env:
BREWTESTBOT_NAME_EMAIL: "BrewTestBot <[email protected]>"
HOMEBREW_GPG_PASSPHRASE: ${{ inputs.autosquash && secrets.BREWTESTBOT_GPG_SIGNING_SUBKEY_PASSPHRASE }}
HOMEBREW_GITHUB_API_TOKEN: ${{secrets.HOMEBREW_CORE_PUBLIC_REPO_EMAIL_TOKEN}}
EXPECTED_SHA: ${{needs.check.outputs.head_sha}}
LARGE_RUNNER: ${{inputs.large_runner}}
run: |
local_git_head="$(git rev-parse HEAD)"
remote_git_head="$(git ls-remote origin "pull/$PR/head" | cut -f1)"
if [ "$local_git_head" != "$EXPECTED_SHA" ] ||
[ "$remote_git_head" != "$EXPECTED_SHA" ]
then
echo "::error ::Unexpected change in target branch."
echo "::error ::Expected SHA1 $EXPECTED_SHA"
echo "::error ::Checked out SHA1 $local_git_head"
echo "::error ::PR branch SHA1 $remote_git_head"
exit 1
fi
if [ -z "${LARGE_RUNNER}" ] || [ "${LARGE_RUNNER}" == "false" ]
then
sudo install -o "$(id -u)" -d "$(id -g)" /mnt/homebrew
export HOMEBREW_CACHE=/mnt/homebrew/cache
export HOMEBREW_TEMP=/mnt/homebrew/temp
fi
# Don't quote arguments that might be empty; this causes errors.
brew pr-pull \
--no-upload \
--debug \
--clean \
--no-cherry-pick \
--workflows=tests.yml \
--committer="$BREWTESTBOT_NAME_EMAIL" \
--root-url="https://ghcr.io/v2/homebrew/core" \
--retain-bottle-dir \
${{inputs.warn_on_upload_failure && '--warn-on-upload-failure' || ''}} \
${{inputs.message && '--message="$INPUT_MESSAGE"' || ''}} \
"$PR"
- name: Generate build provenance
uses: actions/attest-build-provenance@v1
with:
subject-path: '${{steps.pr-pull.outputs.bottle_path}}/*.tar.gz'
- name: Upload bottles to GitHub Packages
id: pr-upload
working-directory: ${{steps.pr-pull.outputs.bottle_path}}
env:
BREWTESTBOT_NAME_EMAIL: "BrewTestBot <[email protected]>"
HOMEBREW_GPG_PASSPHRASE: ${{ secrets.BREWTESTBOT_GPG_SIGNING_SUBKEY_PASSPHRASE }}
HOMEBREW_GITHUB_PACKAGES_USER: brewtestbot
HOMEBREW_GITHUB_PACKAGES_TOKEN: ${{secrets.HOMEBREW_CORE_GITHUB_PACKAGES_TOKEN}}
REPO_PATH: ${{steps.set-up-homebrew.outputs.repository-path}}
run: |
# Don't quote arguments that might be empty; this causes errors when `brew`
# interprets them as empty arguments when we want `brew` to ignore them instead.
brew pr-upload \
--debug \
--committer="$BREWTESTBOT_NAME_EMAIL" \
--root-url="https://ghcr.io/v2/homebrew/core" \
${{inputs.warn_on_upload_failure && '--warn-on-upload-failure' || ''}}
echo "head_sha=$(git -C "$REPO_PATH" rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Push commits
uses: Homebrew/actions/git-try-push@master
with:
token: ${{secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN}}
directory: ${{steps.set-up-homebrew.outputs.repository-path}}
remote: ${{needs.check.outputs.remote}}
branch: ${{needs.check.outputs.branch}}
remote_branch: ${{needs.check.outputs.remote_branch}}
env:
GIT_COMMITTER_NAME: BrewTestBot
GIT_COMMITTER_EMAIL: [email protected]
HOMEBREW_GPG_PASSPHRASE: ${{ secrets.BREWTESTBOT_GPG_SIGNING_SUBKEY_PASSPHRASE }}
- name: Add CI-published-bottle-commits label
run: gh pr edit --add-label CI-published-bottle-commits "$PR"
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
working-directory: ${{steps.set-up-homebrew.outputs.repository-path}}
- name: Post comment on failure
if: failure()
uses: Homebrew/actions/post-comment@master
with:
token: ${{secrets.GITHUB_TOKEN}}
issue: ${{inputs.pull_request}}
body: ":warning: @${{github.actor}} bottle publish [failed](${{env.RUN_URL}})."
bot_body: ":warning: Bottle publish [failed](${{env.RUN_URL}})."
bot: github-actions[bot]
- name: Wait until pull request branch is in sync with local repository
id: wait-until-in-sync
working-directory: ${{steps.set-up-homebrew.outputs.repository-path}}
env:
EXPECTED_SHA: ${{steps.pr-upload.outputs.head_sha}}
run: |
echo "::notice ::Local repository HEAD: $EXPECTED_SHA"
attempt=0
max_attempts=10
timeout=1
# Wait (with exponential backoff) until the PR branch is in sync
while [[ "$attempt" -lt "$max_attempts" ]]
do
remote_head="$(git ls-remote origin "pull/$PR/head" | cut -f1)"
echo "::notice ::Pull request HEAD: $remote_head"
if [[ "$EXPECTED_SHA" = "$remote_head" ]]
then
success=1
break
fi
echo "::notice ::Remote repository not in sync. Checking again in ${timeout}s..."
sleep "$timeout"
attempt=$(( attempt + 1 ))
timeout=$(( timeout * 2 ))
done
# One last check...
if [[ -z "$success" ]] && [[ "$EXPECTED_SHA" != "$(git ls-remote origin "pull/$PR/head" | cut -f1)" ]]
then
echo "::error ::No attempts remaining. Giving up."
exit 1
fi
- run: gh pr review --approve "$PR"
id: approve
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Enable automerge
id: automerge
env:
GH_TOKEN: ${{secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN}}
EXPECTED_SHA: ${{steps.pr-upload.outputs.head_sha}}
working-directory: ${{steps.set-up-homebrew.outputs.repository-path}}
run: |
local_git_head="$(git rev-parse HEAD)"
remote_git_head="$(git ls-remote origin "pull/$PR/head" | cut -f1)"
if [[ "$local_git_head" != "$EXPECTED_SHA" ]] ||
[[ "$remote_git_head" != "$EXPECTED_SHA" ]]
then
echo "::error ::Unexpected change in target branch."
echo "::error ::Expected SHA1 $EXPECTED_SHA"
echo "::error ::Checked out SHA1 $local_git_head"
echo "::error ::PR branch SHA1 $remote_git_head"
exit 1
fi
gh pr merge "$PR" \
--auto \
--merge \
--delete-branch \
--match-head-commit "$EXPECTED_SHA"
- name: Post comment on failure
if: >
failure() &&
(steps.approve.conclusion == 'failure' ||
steps.wait-until-in-sync.conclusion == 'failure' ||
steps.automerge.conclusion == 'failure')
uses: Homebrew/actions/post-comment@master
with:
token: ${{secrets.GITHUB_TOKEN}}
issue: ${{inputs.pull_request}}
body: ":warning: @${{github.actor}} [Failed to enable automerge](${{env.RUN_URL}})."
bot_body: ":warning: [Failed to enable automerge](${{env.RUN_URL}})."
bot: github-actions[bot]