name: Release
on:
push:
tags: ['v*']
permissions:
contents: write id-token: write
concurrency:
group: release-${{ github.repository }}
cancel-in-progress: false
jobs:
gate:
uses: paiml/infra/.github/workflows/clean-room-gate.yml@main
with:
repo: ${{ github.event.repository.name }}
pr_sha: ${{ github.sha }}
secrets: inherit
verify:
needs: gate
runs-on: [self-hosted, clean-room]
outputs:
crate_name: ${{ steps.parse.outputs.crate_name }}
version: ${{ steps.parse.outputs.version }}
has_binaries: ${{ steps.bincheck.outputs.has_binaries }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Parse tag and verify version
id: parse
run: |
TAG="${GITHUB_REF_NAME}"
# Parse tag format: v1.0.0 or v-cratename-1.0.0
if [[ "$TAG" =~ ^v-([a-z][a-z0-9_-]*)-([0-9]+\..+)$ ]]; then
CRATE_NAME="${BASH_REMATCH[1]}"
TAG_VER="${BASH_REMATCH[2]}"
echo "Workspace release: crate=$CRATE_NAME version=$TAG_VER"
elif [[ "$TAG" =~ ^v([0-9]+\..+)$ ]]; then
CRATE_NAME=""
TAG_VER="${BASH_REMATCH[1]}"
echo "Single-crate release: version=$TAG_VER"
else
echo "::error::Tag '$TAG' does not match expected format (v1.0.0 or v-crate-1.0.0)"
exit 1
fi
# Use cargo metadata for reliable version extraction
if [ -n "$CRATE_NAME" ]; then
CARGO_VER=$(cargo metadata --format-version 1 --no-deps \
| jq -r ".packages[] | select(.name == \"$CRATE_NAME\") | .version")
if [ -z "$CARGO_VER" ] || [ "$CARGO_VER" = "null" ]; then
echo "::error::Crate '$CRATE_NAME' not found in workspace"
exit 1
fi
else
CARGO_VER=$(cargo metadata --format-version 1 --no-deps \
| jq -r '.packages[0].version')
fi
if [ "$TAG_VER" != "$CARGO_VER" ]; then
echo "::error::Tag version $TAG_VER != Cargo.toml version $CARGO_VER"
exit 1
fi
echo "crate_name=$CRATE_NAME" >> "$GITHUB_OUTPUT"
echo "version=$TAG_VER" >> "$GITHUB_OUTPUT"
echo "Version verified: $TAG_VER"
- name: Detect binary targets
id: bincheck
run: |
HAS_BINS=$(cargo metadata --format-version 1 --no-deps \
| jq '[.packages[].targets[] | select(.kind[] == "bin")] | length')
echo "has_binaries=$( [ "$HAS_BINS" -gt 0 ] && echo true || echo false )" >> "$GITHUB_OUTPUT"
echo "Binary targets found: $HAS_BINS"
- name: Verify package tarball
run: |
CRATE="${{ steps.parse.outputs.crate_name }}"
if [ -n "$CRATE" ]; then
cargo package --verify -p "$CRATE"
else
cargo package --verify
fi
publish:
needs: verify
runs-on: [self-hosted, clean-room]
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Authenticate to crates.io (OIDC)
uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec
- name: Publish
run: |
CRATE="${{ needs.verify.outputs.crate_name }}"
if [ -n "$CRATE" ]; then
cargo publish -p "$CRATE"
else
cargo publish
fi
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--generate-notes
build-binaries:
needs: [verify, publish]
if: needs.verify.outputs.has_binaries == 'true'
runs-on: [self-hosted, clean-room]
strategy:
fail-fast: false
matrix:
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install target prerequisites
run: |
case "${{ matrix.target }}" in
*-musl)
sudo apt-get update -qq
sudo apt-get install -y -qq musl-tools >/dev/null
;;
esac
case "${{ matrix.target }}" in
aarch64-*)
# cross handles the toolchain via Docker; ensure it's installed
if ! command -v cross &>/dev/null; then
cargo install cross --locked
fi
;;
*-musl)
rustup target add "${{ matrix.target }}"
;;
esac
- name: Discover binary names
id: bins
run: |
BINS=$(cargo metadata --format-version 1 --no-deps \
| jq -r '[.packages[].targets[] | select(.kind[] == "bin") | .name] | join(" ")')
echo "names=$BINS" >> "$GITHUB_OUTPUT"
echo "Binaries to build: $BINS"
- name: Build release binaries
run: |
case "${{ matrix.target }}" in
aarch64-*)
cross build --release --target "${{ matrix.target }}"
;;
*)
cargo build --release --target "${{ matrix.target }}"
;;
esac
- name: Package binaries
run: |
VERSION="${{ needs.verify.outputs.version }}"
TARGET="${{ matrix.target }}"
STAGING="/tmp/release-staging"
mkdir -p "$STAGING"
for BIN in ${{ steps.bins.outputs.names }}; do
ARCHIVE="${BIN}-${VERSION}-${TARGET}.tar.gz"
tar -czf "${STAGING}/${ARCHIVE}" \
-C "target/${TARGET}/release" "$BIN"
echo "Packaged: $ARCHIVE"
done
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with:
name: binaries-${{ matrix.target }}
path: /tmp/release-staging/*.tar.gz
retention-days: 5
checksums:
needs: [verify, publish, build-binaries]
if: needs.verify.outputs.has_binaries == 'true'
runs-on: [self-hosted, clean-room]
steps:
- name: Download all binary artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with:
pattern: binaries-*
merge-multiple: true
path: /tmp/release-assets
- name: Generate SHA256SUMS
working-directory: /tmp/release-assets
run: |
sha256sum *.tar.gz > SHA256SUMS
echo "=== SHA256SUMS ==="
cat SHA256SUMS
- name: Upload to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: /tmp/release-assets
run: |
gh release upload "$GITHUB_REF_NAME" \
*.tar.gz SHA256SUMS \
--repo "${{ github.repository }}" \
--clobber