name: CI
on:
push:
branches:
- main
tags:
- v*
pull_request:
permissions:
contents: read
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
persist-credentials: false
- name: Install Rust
run: rustup toolchain install stable --profile minimal --component clippy,rustfmt
- name: Check formatting
run: cargo fmt --check
- name: Lint
run: cargo clippy --all-targets -- -D warnings
- name: Test
run: cargo test --locked
plan:
name: Plan release
needs:
- test
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
runs-on: ubuntu-22.04
permissions:
contents: write
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ github.ref_name }}
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
persist-credentials: false
ref: ${{ github.ref_name }}
- name: Install dist
shell: bash
run: |
set -euo pipefail
curl --proto '=https' --tlsv1.2 -LsSf \
"https://github.com/axodotdev/cargo-dist/releases/download/v0.32.0/cargo-dist-installer.sh" |
sh
- name: Cache dist
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
- id: plan
name: Plan release
run: |
set -euo pipefail
dist host --steps=create --tag "$TAG" --output-format=json > plan-dist-manifest.json
cat plan-dist-manifest.json
{
printf 'manifest='
jq -c . plan-dist-manifest.json
} >> "$GITHUB_OUTPUT"
- name: Upload dist manifest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
build-local-artifacts:
name: Build local artifacts (${{ join(matrix.targets, ', ') }})
needs:
- plan
if: ${{ startsWith(github.ref, 'refs/tags/v') && fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container && matrix.container.image || null }}
permissions:
contents: read
env:
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
DIST_ARGS: ${{ matrix.dist_args }}
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.plan.outputs.tag }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
persist-credentials: false
ref: ${{ needs.plan.outputs.tag }}
submodules: recursive
- name: Install Rust in container
if: ${{ matrix.container }}
run: |
set -euo pipefail
if ! command -v cargo > /dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
fi
- name: Install dist
shell: bash
run: |
set -euo pipefail
curl --proto '=https' --tlsv1.2 -LsSf \
"https://github.com/axodotdev/cargo-dist/releases/download/v0.32.0/cargo-dist-installer.sh" |
sh
- name: Fetch local artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- name: Build artifacts
shell: bash
run: |
set -euo pipefail
read -r -a dist_args <<< "$DIST_ARGS"
dist build --tag "$TAG" --print=linkage --output-format=json "${dist_args[@]}" > dist-manifest.json
- id: cargo-dist
name: Collect artifacts
shell: bash
run: |
set -euo pipefail
{
echo "paths<<EOF"
dist print-upload-files-from-manifest --manifest dist-manifest.json
echo "EOF"
} >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
build-global-artifacts:
name: Build global artifacts
needs:
- plan
- build-local-artifacts
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
runs-on: ubuntu-22.04
permissions:
contents: read
env:
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.plan.outputs.tag }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
persist-credentials: false
ref: ${{ needs.plan.outputs.tag }}
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- name: Make dist executable
run: chmod +x ~/.cargo/bin/dist
- name: Fetch local artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
name: Build artifacts
shell: bash
run: |
set -euo pipefail
dist build --tag "$TAG" --output-format=json "--artifacts=global" > dist-manifest.json
{
echo "paths<<EOF"
jq --raw-output ".upload_files[]" dist-manifest.json
echo "EOF"
} >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
publish-crate:
name: Publish crate
needs:
- plan
- build-local-artifacts
- build-global-artifacts
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
runs-on: ubuntu-latest
environment: release
permissions:
contents: read
id-token: write
env:
TAG: ${{ needs.plan.outputs.tag }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
persist-credentials: false
ref: ${{ needs.plan.outputs.tag }}
- name: Install Rust
run: rustup toolchain install stable --profile minimal
- name: Verify tag version
run: |
set -euo pipefail
version="${TAG#v}"
manifest_version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
if [ "$version" = "$manifest_version" ]; then
exit 0
fi
echo "tag ${TAG} does not match Cargo.toml version ${manifest_version}" >&2
exit 1
- name: Authenticate with crates.io
id: crates-io-auth
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}
run: cargo publish --locked
host:
name: Host release
needs:
- plan
- build-local-artifacts
- build-global-artifacts
- publish-crate
if: ${{ always() && startsWith(github.ref, 'refs/tags/v') && needs.plan.result == 'success' && needs.publish-crate.result == 'success' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
runs-on: ubuntu-22.04
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
RELEASE_COMMIT: ${{ github.sha }}
TAG: ${{ needs.plan.outputs.tag }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
persist-credentials: false
ref: ${{ needs.plan.outputs.tag }}
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- name: Make dist executable
run: chmod +x ~/.cargo/bin/dist
- name: Fetch artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: host
name: Host artifacts
shell: bash
run: |
set -euo pipefail
dist host --tag "$TAG" --steps=upload --steps=release --output-format=json > dist-manifest.json
cat dist-manifest.json
{
printf 'manifest='
jq -c . dist-manifest.json
} >> "$GITHUB_OUTPUT"
- name: Upload dist manifest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: artifacts-dist-manifest
path: dist-manifest.json
- name: Download GitHub artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup manifests
run: rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
env:
ANNOUNCEMENT_BODY: ${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}
ANNOUNCEMENT_TITLE: ${{ fromJson(steps.host.outputs.manifest).announcement_title }}
PRERELEASE_FLAG: ${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}
run: |
set -euo pipefail
prerelease_args=()
if [ -n "$PRERELEASE_FLAG" ]; then
prerelease_args=("$PRERELEASE_FLAG")
fi
echo "$ANNOUNCEMENT_BODY" > "$RUNNER_TEMP/notes.txt"
gh release create "$TAG" \
--target "$RELEASE_COMMIT" \
"${prerelease_args[@]}" \
--title "$ANNOUNCEMENT_TITLE" \
--notes-file "$RUNNER_TEMP/notes.txt" \
artifacts/*