|
1 | 1 | name: Release |
2 | 2 |
|
3 | | -# Builds release binaries for macOS + Linux on a v* tag and uploads them to the |
4 | | -# GitHub release. NOTE: untested without a real tag push — treat as a starting point. |
| 3 | +# Builds release binaries for macOS + Linux on a v* tag and uploads them to the GitHub release. |
| 4 | +# |
| 5 | +# The Release is created as a DRAFT and is promoted to published-and-latest only after the build |
| 6 | +# matrix has finished AND the expected number of assets is provably attached. This is the same |
| 7 | +# shape every first-party plugin repo uses, and it is here for the same reason: this workflow used |
| 8 | +# to have each matrix leg independently create-or-update a PUBLISHED release via |
| 9 | +# softprops/action-gh-release, so a leg that failed after a sibling had already uploaded left a |
| 10 | +# published release carrying fewer assets than it should, with `latest` pointing at it. A user |
| 11 | +# on the missing platform gets a 404 from a release that looks complete. Nothing in the old |
| 12 | +# workflow could detect that: every gate it had was about whether the build compiled. |
| 13 | +# |
| 14 | +# A draft is invisible to users and to `releases/latest`, so the failure mode is now "the release |
| 15 | +# does not appear" rather than "the release appears and is wrong". |
5 | 16 |
|
6 | 17 | on: |
7 | 18 | push: |
|
10 | 21 | permissions: |
11 | 22 | contents: write |
12 | 23 |
|
| 24 | +env: |
| 25 | + # Every target in the build matrix must land an asset before the release is promoted. Keep this |
| 26 | + # in step with the matrix below; the gate compares against it rather than against "more than |
| 27 | + # zero", so a silent drop from three platforms to one fails too. |
| 28 | + EXPECTED_ASSETS: "3" |
| 29 | + |
13 | 30 | jobs: |
| 31 | + # Created first so the parallel matrix legs have something to attach to (uploading from a matrix |
| 32 | + # with no pre-existing release races and fails with "release not found"). Draft from the start: |
| 33 | + # the promotion below is the only thing that ever publishes it. |
| 34 | + create-release: |
| 35 | + runs-on: ubuntu-latest |
| 36 | + steps: |
| 37 | + - uses: actions/checkout@v4 |
| 38 | + - name: Create draft Release |
| 39 | + env: |
| 40 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 41 | + run: | |
| 42 | + gh release create "${GITHUB_REF_NAME}" \ |
| 43 | + --repo "${GITHUB_REPOSITORY}" \ |
| 44 | + --title "${GITHUB_REF_NAME}" \ |
| 45 | + --draft \ |
| 46 | + --verify-tag --generate-notes \ |
| 47 | + || gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" |
| 48 | +
|
14 | 49 | build: |
| 50 | + needs: create-release |
15 | 51 | strategy: |
| 52 | + # Let every leg run and report. With fail-fast the first failure cancels its siblings, which |
| 53 | + # hides how many platforms are actually broken; the draft gate below is what keeps a partial |
| 54 | + # result from reaching users, so there is nothing to protect by stopping early. |
| 55 | + fail-fast: false |
16 | 56 | matrix: |
17 | 57 | include: |
18 | 58 | - target: x86_64-unknown-linux-gnu |
|
34 | 74 | tar -C target/${{ matrix.target }}/release -czf \ |
35 | 75 | busbar-admin-${{ github.ref_name }}-${{ matrix.target }}.tar.gz busbar-admin |
36 | 76 | - name: upload |
37 | | - uses: softprops/action-gh-release@v2 |
38 | | - with: |
39 | | - files: busbar-admin-${{ github.ref_name }}-${{ matrix.target }}.tar.gz |
| 77 | + env: |
| 78 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 79 | + run: | |
| 80 | + gh release upload "${GITHUB_REF_NAME}" \ |
| 81 | + "busbar-admin-${GITHUB_REF_NAME}-${{ matrix.target }}.tar.gz" \ |
| 82 | + --repo "${GITHUB_REPOSITORY}" --clobber |
| 83 | +
|
| 84 | + # The only step that ever publishes. `needs: build` means a total build failure never reaches it |
| 85 | + # at all and the release stays an invisible draft; a partial failure reaches it and is refused |
| 86 | + # here on the count. |
| 87 | + verify-assets: |
| 88 | + needs: build |
| 89 | + runs-on: ubuntu-latest |
| 90 | + steps: |
| 91 | + - name: Require the full asset set, then promote |
| 92 | + env: |
| 93 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 94 | + run: | |
| 95 | + set -euo pipefail |
| 96 | + count="$(gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" \ |
| 97 | + --json assets --jq '.assets | length')" |
| 98 | + echo "attached assets: ${count} (expected ${EXPECTED_ASSETS})" |
| 99 | + gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" \ |
| 100 | + --json assets --jq '.assets[].name' |
| 101 | + if [ "${count}" -ne "${EXPECTED_ASSETS}" ]; then |
| 102 | + echo "::error::PARTIAL RELEASE PREVENTED: ${GITHUB_REF_NAME} has ${count} assets, expected ${EXPECTED_ASSETS}. Leaving it as a DRAFT so no user can download an incomplete release." >&2 |
| 103 | + exit 1 |
| 104 | + fi |
| 105 | + gh release edit "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" --draft=false --latest |
0 commit comments