From f6dc84a36de98f9116f74fdbacdc87e446afa471 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Sun, 30 Aug 2026 19:23:42 +0900 Subject: [PATCH 1/2] Overhaul release workflow for publish and multi-arch binaries - Validate tags, publish efmt to crates.io, then create the GitHub release - Ship Linux musl (x86_64/arm64) and macOS aarch64 assets --- .github/workflows/release.yml | 172 ++++++++++++++++++++++++---------- 1 file changed, 121 insertions(+), 51 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c1e815..4ee1c1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,79 +1,149 @@ -name: Release +name: Create GitHub Release and Publish to crates.io on: push: - tags: - - '*' + tags: ['v*'] jobs: - github-release-draft: - name: 'Create GitHub Release Draft' + validate: + name: Validate release runs-on: ubuntu-latest - outputs: - version: ${{ steps.get_version.outputs.VERSION }} - upload-url: ${{ steps.create-release.outputs.upload_url }} steps: - - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Get the version - id: get_version - run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - run: rustup update stable + - run: rustup default stable + - run: rustup component add rustfmt clippy + # Guard against a `v*` tag whose name does not match Cargo.toml. + # Without this, a tag like `v0.1.2` pushed against a crate + # still on `version = "0.1.1"` would silently publish 0.1.1 to + # crates.io while the GitHub Release advertised v0.1.2. + # `cargo metadata + jq` is preferred over `cargo pkgid | sed` so + # a future change to cargo's pkgid output format cannot silently + # break the check. `jq` is preinstalled on ubuntu-latest. + - name: Tag matches Cargo.toml version + run: | + set -euo pipefail + tag_version="${GITHUB_REF_NAME#v}" + cargo_version="$(cargo metadata --format-version=1 --no-deps \ + | jq -r '.packages[] | select(.name == "efmt") | .version')" + if [ "$tag_version" != "$cargo_version" ]; then + echo "tag version ($tag_version) does not match Cargo.toml version ($cargo_version)" + exit 1 + fi + # The `validate` job re-runs the CI suite so a broken commit + # that somehow received a `v*` tag never publishes, even if the + # push-time CI on that commit failed or was skipped. + - run: cargo test --all + - run: cargo test --doc + - run: cargo fmt --all -- --check + - run: cargo clippy --all-targets -- -D warnings + - env: + RUSTDOCFLAGS: -D warnings + run: cargo doc --no-deps + # Packaging smoke test for the `efmt` binary crate only. + # `efmt_core` / `efmt_derive` are published manually beforehand. + - run: cargo publish -p efmt --dry-run - - name: Create Release Draft - id: create-release + # publish first: crates.io publish is non-reversible (unpublish is + # restricted to 72 hours and only removes the version from the index, + # not from downstream Cargo.lock files), so make it the guarding step. + # If it fails, no orphan github-release with a version that does not + # exist on crates.io is left behind. A github-release failure after + # publish succeeds is recoverable by re-running `gh release create` + # against the existing tag. + publish: + needs: validate + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 + id: auth + - run: cargo publish -p efmt env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create ${{ steps.get_version.outputs.VERSION }} \ - --generate-notes \ - --draft \ - --title "v${{ steps.get_version.outputs.VERSION }}" - echo "upload_url=https://uploads.github.com/repos/${{ github.repository }}/releases/latest/assets" >> $GITHUB_OUTPUT + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} - linux-binary: - name: 'Upload Binary for Linux' + github-release: + needs: publish runs-on: ubuntu-latest - needs: github-release-draft + permissions: + contents: write steps: - - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - id: create-release + run: gh release create ${{ github.ref_name }} --title "${{ github.ref_name }}" --generate-notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Install dependent packages - run: sudo apt install -y musl-tools + # The release must already exist before any asset upload, so every binary + # job depends on `github-release`. The jobs run in parallel and attach + # statically linked or SDK-pinned binaries to the same tag. + # Asset names strip the leading `v` from the tag, e.g. `efmt-0.21.0.xxx`. + linux-x86_64-binary: + name: Upload Binary for Linux x86_64 + needs: github-release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # musl-tools provides the musl linker for a fully static binary. + - run: sudo apt install -y musl-tools - run: rustup update stable - run: rustup target add x86_64-unknown-linux-musl - run: cargo build --release --target=x86_64-unknown-linux-musl + - name: Upload Release Asset + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mv target/x86_64-unknown-linux-musl/release/efmt \ + efmt-${GITHUB_REF_NAME#v}.x86_64-unknown-linux-musl + gh release upload ${{ github.ref_name }} \ + efmt-${GITHUB_REF_NAME#v}.x86_64-unknown-linux-musl + linux-arm64-binary: + name: Upload Binary for Linux arm64 + needs: github-release + # arm64 native runner, so aarch64-unknown-linux-musl needs no cross + # toolchain: apt musl-tools on this image is the aarch64 one. + runs-on: ubuntu-24.04-arm + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - run: sudo apt install -y musl-tools + - run: rustup update stable + - run: rustup target add aarch64-unknown-linux-musl + - run: cargo build --release --target=aarch64-unknown-linux-musl - name: Upload Release Asset env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mv target/x86_64-unknown-linux-musl/release/efmt efmt-${{ needs.github-release-draft.outputs.version }}.x86_64-unknown-linux-musl - gh release upload ${{ needs.github-release-draft.outputs.version }} \ - efmt-${{ needs.github-release-draft.outputs.version }}.x86_64-unknown-linux-musl + mv target/aarch64-unknown-linux-musl/release/efmt \ + efmt-${GITHUB_REF_NAME#v}.aarch64-unknown-linux-musl + gh release upload ${{ github.ref_name }} \ + efmt-${GITHUB_REF_NAME#v}.aarch64-unknown-linux-musl macos-binary: - name: 'Upload Binary for MacOS' - runs-on: macos-latest - needs: github-release-draft - strategy: - matrix: - target: ["x86_64-apple-darwin", "aarch64-apple-darwin"] + name: Upload Binary for macOS + needs: github-release + # macos-15 (not macos-latest): the older SDK keeps the minimum macOS + # version of the produced binaries low and the image stable. + runs-on: macos-15 + permissions: + contents: write steps: - - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - run: rustup update stable - - run: rustup target add ${{ matrix.target }} - - run: cargo build --release --target=${{ matrix.target }} - + - run: rustup target add aarch64-apple-darwin + - run: cargo build --release --target=aarch64-apple-darwin - name: Upload Release Asset env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mv target/${{ matrix.target }}/release/efmt efmt-${{ needs.github-release-draft.outputs.version }}.${{ matrix.target }} - gh release upload ${{ needs.github-release-draft.outputs.version }} \ - efmt-${{ needs.github-release-draft.outputs.version }}.${{ matrix.target }} - + mv target/aarch64-apple-darwin/release/efmt \ + efmt-${GITHUB_REF_NAME#v}.aarch64-apple-darwin + gh release upload ${{ github.ref_name }} \ + efmt-${GITHUB_REF_NAME#v}.aarch64-apple-darwin From 6e0ddd3263d470dd68000764c8b674b4829e2446 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Sun, 30 Aug 2026 19:26:17 +0900 Subject: [PATCH 2/2] Document binary installs with gh release download --- README.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ea83d0d..d134426 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,31 @@ Installation ### Pre-built binaries -Pre-built binaries for Linux and MacOS are available in [the releases page](https://github.com/sile/efmt/releases). +Pre-built binaries for Linux and macOS are available from the +[releases page](https://github.com/sile/efmt/releases): + +- `x86_64-unknown-linux-musl` (Linux x86_64, fully static) +- `aarch64-unknown-linux-musl` (Linux arm64, fully static) +- `aarch64-apple-darwin` (macOS Apple Silicon) + +For example, download the Linux x86_64 binary from the latest release with +the GitHub CLI: + +```console +$ gh release download --repo sile/efmt \ + --pattern "efmt-*.x86_64-unknown-linux-musl" +$ chmod +x efmt-*.x86_64-unknown-linux-musl +$ ./efmt-*.x86_64-unknown-linux-musl --version +``` + +The asset name is `efmt-.`, where `` is one of the +triples above and `` is the release version without the leading `v`. +To download a specific version, pass its tag name (for example `v0.21.0`) +instead of the latest release: ```console -// An example to download the binary for Linux. -$ VERSION=0.21.0 -$ curl -L https://github.com/sile/efmt/releases/download/${VERSION}/efmt-${VERSION}.x86_64-unknown-linux-musl -o efmt -$ chmod +x efmt -$ ./efmt +$ gh release download v0.21.0 --repo sile/efmt \ + --pattern "efmt-0.21.0.x86_64-unknown-linux-musl" ``` ### With [Cargo](https://doc.rust-lang.org/cargo/)