From 0fb38cedba48167ecf073d076e707a636eba2ec6 Mon Sep 17 00:00:00 2001 From: Jack Marsh Date: Thu, 8 Oct 2026 16:09:36 +0100 Subject: [PATCH] ci: rebuild only what changed, and release on merge Every PR and master push rebuilt all 22 versions on Linux and macOS, and the v* tag rebuilt them all again before publishing: three full builds per release, about an hour of macOS runners each. Each (version, platform) build is now cached under the hash of everything that decides its binaries (the version's binaries/ dir, build_defs, plugins, .plzconfig, pleasew, the smoke test and this workflow). A hit restores the tarballs and re-runs only the smoke test; a miss builds, smoke-tests, and only then saves. GitHub scopes caches by branch, so a PR can never put a binary into master's cache. Releasing no longer needs a hand-pushed tag: a push to master whose RELEASE_VERSION has no release yet publishes the tarballs that run built, tagged at the merge commit itself. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yaml | 87 ++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8b0fb1f..e079974 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,7 +1,15 @@ # Builds every PostgreSQL version under binaries/ for every platform we ship, -# smoke-tests each tarball, and on a version tag publishes them all as one -# release. A release is only created when every build and test passed, so a -# release never carries one platform without the other. +# smoke-tests each tarball, and publishes them all as one release when a merge +# to master names a RELEASE_VERSION that has no release yet. A release is only +# created when every build and test passed, so it never carries one platform +# without the other. +# +# Only what changed is rebuilt. Each (version, platform) build is cached under +# the hash of everything that decides its binaries; a hit restores the tarballs +# and only re-runs the smoke test. GitHub scopes caches by branch: a PR sees +# master's caches but master never sees a PR's, so a version a PR changes is +# built once on the PR (to verify it) and once on master (to publish it), and +# a PR can never put a binary into master's cache. # # Platforms: # linux_x86_64 ubuntu-24.04 pinned: the binaries link the runner's libicu @@ -9,22 +17,20 @@ # 24.04 or newer; moving the runner moves that # darwin_arm64 macos-14 Apple Silicon # -# To release: bump RELEASE_VERSION in build_defs/postgres.build_defs, merge, -# then push the matching tag (git tag v0.0.N && git push origin v0.0.N). +# To release: bump RELEASE_VERSION in build_defs/postgres.build_defs and merge. name: Release binaries on: pull_request: push: branches: [master] - tags: ["v*"] workflow_dispatch: permissions: contents: read # A new push to a PR supersedes its previous run (macOS runners are scarce); -# master and tag runs always finish. +# master runs always finish. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} @@ -51,6 +57,16 @@ jobs: steps: - uses: actions/checkout@v4 + # Everything that decides this version's binaries on this platform. + - name: Restore this version's build + id: cache + uses: actions/cache/restore@v4 + with: + path: dist + key: psql-${{ matrix.runner }}-${{ matrix.version }}-${{ hashFiles(format('binaries/{0}/**', matrix.version), 'build_defs/**', 'plugins/**', '.plzconfig', 'pleasew', 'release/smoke_test.sh', '.github/workflows/release.yaml') }} + + # Also needed on a cache hit: the smoke test runs the binaries, and the + # macOS ones load Homebrew's icu4c and readline. - name: Install build dependencies (Linux) if: runner.os == 'Linux' run: | @@ -67,6 +83,7 @@ jobs: test -d /opt/homebrew/opt/readline/lib/pkgconfig - name: Build + if: steps.cache.outputs.cache-hit != 'true' shell: bash run: | # Retries only network failures (40+ jobs downloading at once from @@ -80,6 +97,8 @@ jobs: status=${PIPESTATUS[0]} set -e if [[ "$status" -eq 0 ]]; then + mkdir -p dist + cp $(./pleasew query output //binaries/${{ matrix.version }}/... | grep 'psql-.*\.tar\.gz$') dist/ exit 0 fi if [[ "$attempt" -eq 3 ]] || ! grep -qE "$TRANSIENT" "$RUNNER_TEMP/build.log"; then @@ -90,25 +109,39 @@ jobs: done - name: Smoke test + shell: bash run: | set -euo pipefail - for tarball in $(./pleasew query output //binaries/${{ matrix.version }}/... | grep 'psql-.*\.tar\.gz$'); do + tarballs=(dist/psql-*.tar.gz) + if [[ "${#tarballs[@]}" -ne 2 || ! -e "${tarballs[0]}" ]]; then + echo "expected a base and an AI tarball in dist/, found: ${tarballs[*]}" + exit 1 + fi + for tarball in "${tarballs[@]}"; do case "$(basename "$tarball")" in - psql-ai-*) variant=ai ;; - *) variant=base ;; + (psql-ai-*) variant=ai ;; + (*) variant=base ;; esac release/smoke_test.sh "$tarball" "$variant" - cp "$tarball" "$RUNNER_TEMP/" done + # Saved only after the smoke test passed, so the cache never holds a + # build that failed it. + - name: Save this version's build + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: dist + key: ${{ steps.cache.outputs.cache-primary-key }} + - uses: actions/upload-artifact@v4 with: name: psql-${{ matrix.version }}-${{ matrix.runner }} - path: ${{ runner.temp }}/psql-*.tar.gz + path: dist/psql-*.tar.gz if-no-files-found: error release: - if: startsWith(github.ref, 'refs/tags/v') + if: github.event_name == 'push' && github.ref == 'refs/heads/master' needs: build runs-on: ubuntu-24.04 permissions: @@ -116,23 +149,39 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Check the tag matches RELEASE_VERSION + - name: Is RELEASE_VERSION unreleased? + id: version + env: + GH_TOKEN: ${{ github.token }} run: | RELEASE_VERSION=$(sed -n 's/^RELEASE_VERSION = "\(.*\)"$/\1/p' build_defs/postgres.build_defs) - if [[ "$RELEASE_VERSION" != "$GITHUB_REF_NAME" ]]; then - echo "Tag $GITHUB_REF_NAME does not match RELEASE_VERSION $RELEASE_VERSION in build_defs/postgres.build_defs" + if [[ -z "$RELEASE_VERSION" ]]; then + echo "no RELEASE_VERSION in build_defs/postgres.build_defs" exit 1 fi + echo "version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT" + if gh release view "$RELEASE_VERSION" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then + echo "$RELEASE_VERSION is already released; nothing to publish." + echo "publish=false" >> "$GITHUB_OUTPUT" + else + echo "publish=true" >> "$GITHUB_OUTPUT" + fi - uses: actions/download-artifact@v4 + if: steps.version.outputs.publish == 'true' with: path: dist merge-multiple: true - name: Publish + if: steps.version.outputs.publish == 'true' env: GH_TOKEN: ${{ github.token }} + RELEASE_VERSION: ${{ steps.version.outputs.version }} run: | - gh release create "$GITHUB_REF_NAME" dist/psql-*.tar.gz \ - --title "PostgreSQL Release $GITHUB_REF_NAME" \ - --notes "PostgreSQL binaries for linux_x86_64 and darwin_arm64, built and smoke-tested by the Release binaries workflow." + # Tagged at this merge commit: the release is exactly what was built + # and smoke-tested from it. + gh release create "$RELEASE_VERSION" dist/psql-*.tar.gz \ + --target "$GITHUB_SHA" \ + --title "PostgreSQL Release $RELEASE_VERSION" \ + --notes "PostgreSQL binaries for linux_x86_64 and darwin_arm64, built and smoke-tested by the Release binaries workflow from $GITHUB_SHA."