diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 547624d..bd224e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,11 @@ name: CI on: - push: - branches: ["**"] pull_request: branches: ["**"] + # release-plz opens its pull request under GITHUB_TOKEN, which starts no + # workflow, so the release workflow dispatches CI on the release branch. + workflow_dispatch: env: CARGO_TERM_COLOR: always @@ -182,3 +183,21 @@ jobs: - uses: taiki-e/install-action@cargo-machete - run: cargo machete + + # The one required check on `main`: it passes only when every job above did. + ci-result: + name: CI result + if: always() + needs: [lint, test, wasm, minimal-versions, coverage, feature-checks, audit, unused-deps] + runs-on: ubuntu-latest + steps: + - name: Check job results + env: + RESULTS: ${{ join(needs.*.result, ', ') }} + FAILED: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + run: | + echo "job results: $RESULTS" + if [ "$FAILED" = "true" ]; then + echo "::error::one or more jobs failed or were cancelled" + exit 1 + fi diff --git a/.github/workflows/pr-base-gate.yml b/.github/workflows/pr-base-gate.yml deleted file mode 100644 index 84aae34..0000000 --- a/.github/workflows/pr-base-gate.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: PR Base Gate - -on: - pull_request: - branches: [main] - types: [opened, reopened, synchronize, edited] - -permissions: - checks: read - -jobs: - verify-source: - name: PR source gate - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - name: main only accepts merges from dev - env: - HEAD_REF: ${{ github.event.pull_request.head.ref }} - HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} - BASE_REPO: ${{ github.repository }} - run: | - if [ "${HEAD_REPO}" != "${BASE_REPO}" ]; then - echo "::error::Pull requests into 'main' must come from this repository, not a fork." - exit 1 - fi - if [ "${HEAD_REF}" != "dev" ]; then - echo "::error::Pull requests into 'main' are only allowed from 'dev'. Retarget this pull request at 'dev' instead." - exit 1 - fi - echo "'${HEAD_REF}' -> 'main': source branch allowed." - - # This check doubles as the single required check on main: instead of - # pinning CI job names in the ruleset (which break on every rename), it - # waits for every other check run on the PR's head and merge commits and - # goes green only when all of them did. - - name: wait for every other check on this PR to go green - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} - run: | - set -euo pipefail - deadline=$(( $(date +%s) + 3300 )) - while :; do - { - gh api "repos/${GITHUB_REPOSITORY}/commits/${HEAD_SHA}/check-runs?per_page=100" \ - --jq '.check_runs[] | [.name, (.conclusion // "pending")] | @tsv' - if [ -n "${MERGE_SHA:-}" ] && [ "${MERGE_SHA}" != "null" ]; then - gh api "repos/${GITHUB_REPOSITORY}/commits/${MERGE_SHA}/check-runs?per_page=100" \ - --jq '.check_runs[] | [.name, (.conclusion // "pending")] | @tsv' - fi - } 2>/dev/null | grep -v '^PR source gate\b' > checks.tsv || true - - if grep -E '\t(failure|cancelled|timed_out|action_required|startup_failure|stale)$' checks.tsv; then - echo "::error::a check on this pull request did not pass (see list above)" - exit 1 - fi - pending=$(grep -c $'\tpending$' checks.tsv || true) - if [ "${pending}" -eq 0 ]; then - echo "all checks on this pull request have passed" - exit 0 - fi - if [ "$(date +%s)" -gt "${deadline}" ]; then - echo "::error::timed out after 55 minutes waiting for ${pending} check(s) to conclude" - grep $'\tpending$' checks.tsv - exit 1 - fi - echo "waiting on ${pending} check(s)..." - sleep 30 - done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b93fd73..b2abd84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,19 +1,16 @@ name: Release -# `release` publishes what a push to `main` carries; the required checks on -# `main` already proved the merged commit. `release-pr` keeps the version bump -# and changelog pull request open against `dev` on every push to `dev`. The -# bump belongs on `dev` because a pull request opened with the default -# `GITHUB_TOKEN` starts no workflow runs, so one targeting `main` could never -# satisfy the required check there. +# Every push to `main` runs both jobs: `release` publishes what the push +# carries (the required `CI result` check on `main` already proved the merged +# commit), and `release-pr` opens or refreshes the version bump and changelog +# pull request against `main`. Merging that pull request is the release. on: push: - branches: [main, dev] + branches: [main] jobs: release: name: Publish release - if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write @@ -46,37 +43,35 @@ jobs: release-pr: name: Prepare next release - if: github.ref == 'refs/heads/dev' runs-on: ubuntu-latest permissions: contents: write pull-requests: write + # The dispatch below starts CI on the release branch. + actions: write concurrency: - group: release-plz-dev + group: release-plz-pr cancel-in-progress: false steps: - - name: Checkout the pushed dev commit + - name: Checkout the pushed main commit uses: actions/checkout@v6 with: - ref: dev fetch-depth: 0 persist-credentials: false - - name: Verify pushed commit - env: - EXPECTED_SHA: ${{ github.sha }} - run: | - actual_sha="$(git rev-parse HEAD)" - if [[ "$actual_sha" != "$EXPECTED_SHA" ]]; then - echo "::error::dev moved after this push: expected $EXPECTED_SHA, checked out $actual_sha" - exit 1 - fi - *rust-toolchain - # The pull request lands the bump on `dev`; the release itself is the - # `dev` -> `main` pull request that carries it, which runs the full - # check set. - name: Create or update release PR + id: release-plz uses: release-plz/action@v0.5 with: command: release-pr env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # A pull request opened or pushed under GITHUB_TOKEN starts no + # workflow, so the required `CI result` would never report on it; + # dispatching CI on its branch is the exception GITHUB_TOKEN allows. + - name: Run CI on the release branch + if: steps.release-plz.outputs.prs_created == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ steps.release-plz.outputs.pr }} + run: gh workflow run ci.yml -R "$GITHUB_REPOSITORY" --ref "$(jq -er .head_branch <<<"$PR")"