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."