Skip to content

Merge develop into main for v0.1.19 release #9

Merge develop into main for v0.1.19 release

Merge develop into main for v0.1.19 release #9

Workflow file for this run

name: Release
on:
push:
tags: ["v*"]
permissions:
contents: read
jobs:
test:
# Gate the entire release on `cargo test --workspace` passing on every
# native platform we ship a binary for. Without this, a tag push could
# produce release artifacts from a commit whose tests never ran (the
# bump commit's CI workflow runs in parallel with this one, not before
# it). Cross targets — aarch64 Linux / Android — can't run tests on
# GitHub-hosted runners; we trust their native counterparts to surface
# logic bugs.
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable pinned 2026-04
- name: Test
run: cargo test --workspace --locked
build:
name: Build (${{ matrix.target }})
needs: test
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-pc-windows-msvc
os: windows-latest
archive: zip
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
archive: tar.gz
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
archive: tar.gz
- target: x86_64-apple-darwin
os: macos-latest
archive: tar.gz
- target: aarch64-apple-darwin
os: macos-latest
archive: tar.gz
- target: aarch64-linux-android
os: ubuntu-latest
archive: tar.gz
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable pinned 2026-04
with:
targets: ${{ matrix.target }}
- name: Install cross-compilation tools (Linux ARM)
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Install cross-compilation tools (Android)
if: matrix.target == 'aarch64-linux-android'
run: |
# Use the NDK bundled with the runner
NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}"
if [ -z "$NDK_HOME" ]; then
NDK_HOME="$(ls -d "$ANDROID_HOME"/ndk/*/ 2>/dev/null | sort -V | tail -1)"
fi
echo "NDK_HOME=$NDK_HOME" >> "$GITHUB_ENV"
TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64"
echo "CC_aarch64_linux_android=${TOOLCHAIN}/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
echo "AR_aarch64_linux_android=${TOOLCHAIN}/bin/llvm-ar" >> "$GITHUB_ENV"
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${TOOLCHAIN}/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
- name: Build
run: cargo build --release --target ${{ matrix.target }} -p runex
env:
RUNEX_GIT_COMMIT: ${{ github.sha }}
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
- name: Package (tar.gz)
if: matrix.archive == 'tar.gz'
run: |
cd target/${{ matrix.target }}/release
tar czf ../../../runex-${{ matrix.target }}.tar.gz runex
- name: Package (zip)
if: matrix.archive == 'zip'
shell: pwsh
run: |
Compress-Archive -Path target/${{ matrix.target }}/release/runex.exe -DestinationPath runex-${{ matrix.target }}.zip
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: runex-${{ matrix.target }}
path: runex-${{ matrix.target }}.*
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
# Required for actions/attest-build-provenance to mint a Sigstore
# signing certificate via the GitHub OIDC provider.
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
merge-multiple: true
- name: Attest build provenance
# Generates a signed SLSA provenance statement for every release
# artifact and stores it on the GitHub attestations API. Lets a
# downstream installer (or paranoid user) verify the binary was
# built by *this* workflow on *this* commit, not slipped in by a
# compromised maintainer account. Run before `gh release create`
# so a failure here aborts the release entirely rather than
# publishing unattested binaries.
uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4
with:
subject-path: artifacts/*
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$TAG" \
--generate-notes \
artifacts/*
publish-crates:
# Publish runex to crates.io via OIDC Trusted Publishing. No
# long-lived CARGO_REGISTRY_TOKEN is stored anywhere — every run
# exchanges a short-lived GitHub OIDC token for an equally short-
# lived crates.io token via `rust-lang/crates-io-auth-action`.
#
# Phase C absorbed the previous `runex-core` crate into `runex`,
# so this job now publishes a single crate. The Trusted Publisher
# registration on crates.io for `runex-core` is left in place
# (harmless; future republish would no-op since the crate now
# builds nothing) — only the `runex` publish actually fires.
# `runex-core 0.1.13` (the last version before absorption) stays
# on crates.io un-yanked for any cargo lockfile that still pins
# it; no new versions will appear.
#
# Set `[skip publish]` in the bump commit message to bypass this
# job — useful for re-tagging after a release-process glitch where
# the binaries are out of date but the crate already published.
name: Publish to crates.io (OIDC)
needs: [test, build, release]
if: ${{ !contains(github.event.head_commit.message, '[skip publish]') }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable pinned 2026-04
- name: Exchange OIDC token for crates.io token
id: auth
# v1.0.4 (2026-03-23) is the first release on Node 24, silencing
# the "Node.js 20 actions are deprecated" warning. Re-evaluate
# when v1.1 / v2 ships.
uses: rust-lang/crates-io-auth-action@v1.0.4
- name: Dry-run publish (sanity check)
# Catches packaging issues (missing files, dirty workdir,
# manifest rejection) before we burn the OIDC token on a real
# publish. Uses the same token the live publish step will
# use, so any auth-side problem also surfaces here. Cheap
# insurance — adds ~30 s to the job and saves a manual yank
# when packaging breaks.
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
RUNEX_GIT_COMMIT: ${{ github.sha }}
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
status=$(curl -fsS -o /dev/null -w '%{http_code}' \
"https://crates.io/api/v1/crates/runex/$version" || echo "000")
if [ "$status" = "200" ]; then
echo "runex $version already on crates.io — skipping dry-run"
else
cargo publish -p runex --dry-run
fi
- name: Publish runex
# Skip when this version is already on crates.io. Reruns of a
# partially-failed publish-crates job would otherwise abort
# here with "crate already exists on crates.io index" — that
# bit us on 0.1.13 (back when there were two crates) and the
# guard pattern is worth keeping for the same reason.
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
RUNEX_GIT_COMMIT: ${{ github.sha }}
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
status=$(curl -fsS -o /dev/null -w '%{http_code}' \
"https://crates.io/api/v1/crates/runex/$version" || echo "000")
if [ "$status" = "200" ]; then
echo "runex $version already on crates.io — skipping publish"
else
cargo publish -p runex
fi