diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 90799c0..5f74fd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,11 @@ on: push: branches: [master] workflow_dispatch: + inputs: + tag: + description: 'Existing tag to rebuild binaries for (e.g. v6.0.0). Leave empty for a normal release run.' + required: false + type: string permissions: contents: write @@ -68,16 +73,25 @@ jobs: # string on the runs where nothing was released, and fromJSON chokes on it. - name: Resolve tag id: tag - if: steps.release-plz.outputs.releases_created == 'true' + if: steps.release-plz.outputs.releases_created == 'true' || inputs.tag != '' env: RELEASES: ${{ steps.release-plz.outputs.releases }} - run: echo "tag=$(jq -r '.[0].tag' <<< "$RELEASES")" >> "$GITHUB_OUTPUT" + INPUT_TAG: ${{ inputs.tag }} + run: | + tag="${INPUT_TAG:-$(jq -r '.[0].tag' <<< "$RELEASES")}" + # This value is handed to actions/checkout as `ref:` and INPUT_TAG is + # attacker-shaped (anyone who can dispatch the workflow types it), so + # refuse anything that is not a literal version tag. + [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || { echo "::error::unexpected tag: $tag"; exit 1; } + echo "tag=$tag" >> "$GITHUB_OUTPUT" - # Attaches downloadable binaries to the release GitHub just created. + # Attaches downloadable binaries to the release GitHub just created, or to an + # existing tag when dispatched manually with `tag` (recovering a target that + # failed to upload needs a re-run of *this* file, not the old one). binaries: name: ${{ matrix.target }} needs: release - if: needs.release.outputs.created == 'true' + if: needs.release.outputs.created == 'true' || inputs.tag != '' runs-on: ${{ matrix.os }} strategy: # One flaky cross-linker must not hold back the five targets that built. @@ -111,10 +125,17 @@ jobs: # Builds, strips, archives (.tar.gz / .zip on Windows), writes a .sha256 # and uploads the lot to the release. + # + # build-tool is pinned to cargo because the action otherwise reaches for + # `cross` on any *-musl target. Every entry in the matrix builds on a + # runner of its own architecture — only the libc differs — so cross adds + # nothing, and on the native arm64 runner it hard-fails trying to install + # an x86_64 toolchain. - uses: taiki-e/upload-rust-binary-action@v1 with: bin: soulseek-rs target: ${{ matrix.target }} + build-tool: cargo archive: $bin-$tag-$target include: README.md,LICENSE checksum: sha256 diff --git a/RELEASE.md b/RELEASE.md index 9f10d9e..2511caa 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -41,11 +41,14 @@ Both crates always share one version, one tag, and one changelog — see `releas - A merge with no releasable commits (only `docs:`/`chore:`/`test:`) opens no release PR. That is intended. -- The release PR does not run CI: GitHub does not trigger workflows for PRs opened with - `GITHUB_TOKEN`. It only touches `Cargo.toml`, `Cargo.lock`, and `CHANGELOG.md`; CI already - ran on the commits it is releasing. -- If one target's build fails, the other five still upload (`fail-fast: false`). Re-run just - that matrix job from the Actions UI. +- The release PR does not run CI on its own, because GitHub does not trigger workflows for + PRs opened with `GITHUB_TOKEN`. Pushing any commit to that branch yourself does start it — + handy if you want the version bump validated before merging. +- If one target's build fails, the other five still upload (`fail-fast: false`). To retry, + run the **Release** workflow manually and set `tag` to the existing tag (e.g. `v6.0.0`); + it rebuilds and re-uploads every target for that tag. Re-running the failed job from the + Actions UI replays the *old* workflow file, so it is no use when the workflow is the thing + that needs fixing. - To skip a release for a commit that would otherwise trigger one, use a `chore:` type. ## Installing a release