name: Release 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 pull-requests: write env: CARGO_TERM_COLOR: always jobs: # Opens (or updates) a "chore: release vX.Y.Z" PR with the version bump and # changelog derived from the conventional commits on master. Merging it is the # release trigger. release-pr: name: Release PR runs-on: ubuntu-latest concurrency: group: release-pr-${{ github.ref }} cancel-in-progress: false steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: release-plz/action@v0.5 with: command: release-pr env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # No-op until the release PR lands; then tags, publishes both crates to # crates.io in dependency order, and creates the GitHub release. release: name: Tag and publish runs-on: ubuntu-latest concurrency: group: release-${{ github.ref }} cancel-in-progress: false outputs: created: ${{ steps.release-plz.outputs.releases_created }} tag: ${{ steps.tag.outputs.tag }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: release-plz/action@v0.5 id: release-plz with: command: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # Read the tag out via jq rather than fromJSON: `releases` is an empty # 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' || inputs.tag != '' env: RELEASES: ${{ steps.release-plz.outputs.releases }} 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, 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' || inputs.tag != '' runs-on: ${{ matrix.os }} strategy: # One flaky cross-linker must not hold back the five targets that built. fail-fast: false matrix: include: - target: x86_64-unknown-linux-musl os: ubuntu-latest - target: aarch64-unknown-linux-musl os: ubuntu-24.04-arm - target: x86_64-apple-darwin os: macos-latest - target: aarch64-apple-darwin os: macos-latest - target: x86_64-pc-windows-msvc os: windows-latest - target: aarch64-pc-windows-msvc os: windows-latest steps: - uses: actions/checkout@v4 with: ref: ${{ needs.release.outputs.tag }} - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Install musl toolchain if: endsWith(matrix.target, '-musl') run: sudo apt-get update && sudo apt-get install -y musl-tools # 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 ref: refs/tags/${{ needs.release.outputs.tag }} token: ${{ secrets.GITHUB_TOKEN }}