name: release
on:
push:
tags:
- "[0-9]*.[0-9]*.[0-9]*"
workflow_dispatch:
inputs:
version:
description: "Release version (x.y.z tag name)"
required: true
type: string
permissions:
contents: write
jobs:
create-release:
name: create-release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.version || github.ref_name }}
- name: Get the release version from the tag
id: release_version
shell: bash
run: |
requested="${{ inputs.version }}"
if [ -n "$requested" ]; then
version="$requested"
else
version="${{ github.ref_name }}"
fi
echo "VERSION=$version" >> $GITHUB_ENV
echo "version=$version" >> $GITHUB_OUTPUT
- name: Show the version
run: |
echo "version is: $VERSION"
- name: Check that tag version and Cargo.toml version are the same
shell: bash
run: |
if ! grep -q "version = \"$VERSION\"" Cargo.toml; then
echo "version does not match Cargo.toml" >&2
exit 1
fi
- name: Create GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release create $VERSION --draft --verify-tag --title $VERSION
outputs:
version: ${{ steps.release_version.outputs.version }}
generate-cli-assets:
name: generate-cli-assets
needs: ["create-release"]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.version }}
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install help2man
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y help2man
- name: Build precursor (host)
shell: bash
run: cargo build --release
- name: Generate completions and man page
shell: bash
run: |
set -euo pipefail
ci/generate_cli_assets.sh dist/cli-assets ./target/release/precursor
- name: Upload CLI assets
uses: actions/upload-artifact@v4
with:
name: precursor-cli-assets
path: dist/cli-assets
if-no-files-found: error
build-release:
name: build-release
needs: ["create-release", "generate-cli-assets"]
runs-on: ${{ matrix.os }}
env:
CARGO: cargo
TARGET_FLAGS:
TARGET_DIR: ./target
CROSS_VERSION: v0.2.5
RUST_BACKTRACE: 1
PCRE2_SYS_STATIC: 1
strategy:
fail-fast: false
matrix:
include:
- build: linux
os: ubuntu-latest
rust: nightly
target: x86_64-unknown-linux-musl
strip: x86_64-linux-musl-strip
- build: stable-x86
os: ubuntu-latest
rust: stable
target: i686-unknown-linux-gnu
strip: x86_64-linux-gnu-strip
qemu: i386
- build: stable-aarch64
os: ubuntu-latest
rust: stable
target: aarch64-unknown-linux-gnu
strip: aarch64-linux-gnu-strip
qemu: qemu-aarch64
- build: stable-powerpc64
os: ubuntu-latest
rust: stable
target: powerpc64-unknown-linux-gnu
strip: powerpc64-linux-gnu-strip
qemu: qemu-ppc64
- build: stable-s390x
os: ubuntu-latest
rust: stable
target: s390x-unknown-linux-gnu
strip: s390x-linux-gnu-strip
qemu: qemu-s390x
- build: macos
os: macos-latest
rust: nightly
target: x86_64-apple-darwin
- build: macos
os: macos-latest
rust: nightly
target: aarch64-apple-darwin
- build: win-msvc
os: windows-latest
rust: nightly
target: x86_64-pc-windows-msvc
- build: win32-msvc
os: windows-latest
rust: nightly
target: i686-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.version }}
- name: Install packages (Ubuntu)
if: matrix.os == 'ubuntu-latest'
shell: bash
run: |
ci/ubuntu-install-packages
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- name: Ensure target is installed
shell: bash
run: rustup target add ${{ matrix.target }}
- name: Use Cross
if: matrix.os == 'ubuntu-latest' && matrix.target != ''
shell: bash
run: |
# In the past, new releases of 'cross' have broken CI. So for now, we
# pin it. We also use their pre-compiled binary releases because cross
# has over 100 dependencies and takes a bit to compile.
dir="$RUNNER_TEMP/cross-download"
mkdir "$dir"
echo "$dir" >> $GITHUB_PATH
cd "$dir"
curl -LO "https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz"
tar xf cross-x86_64-unknown-linux-musl.tar.gz
echo "CARGO=cross" >> $GITHUB_ENV
- name: Set target variables
shell: bash
run: |
echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
- name: Show command used for Cargo
shell: bash
run: |
echo "cargo command is: ${{ env.CARGO }}"
echo "target flag is: ${{ env.TARGET_FLAGS }}"
echo "target dir is: ${{ env.TARGET_DIR }}"
- name: Build release binary
shell: bash
run: |
${{ env.CARGO }} build --verbose --release ${{ env.TARGET_FLAGS }}
if [ "${{ matrix.os }}" = "windows-latest" ]; then
bin="target/${{ matrix.target }}/release/precursor.exe"
else
bin="target/${{ matrix.target }}/release/precursor"
fi
echo "BIN=$bin" >> $GITHUB_ENV
- name: Strip release binary (macos)
if: matrix.os == 'macos-latest'
shell: bash
run: strip "$BIN"
- name: Strip release binary (cross)
if: env.CARGO == 'cross'
shell: bash
run: |
docker run --rm -v \
"$PWD/target:/target:Z" \
"rustembedded/cross:${{ matrix.target }}" \
"${{ matrix.strip }}" \
"/target/${{ matrix.target }}/release/precursor"
- name: Determine archive name
shell: bash
run: |
version="${{ needs.create-release.outputs.version }}"
echo "ARCHIVE=precursor-$version-${{ matrix.target }}" >> $GITHUB_ENV
- name: Creating directory for archive
shell: bash
run: |
mkdir -p "$ARCHIVE"/{complete,doc}
cp "$BIN" "$ARCHIVE"/
cp {README.md,COPYING,UNLICENSE,LICENSE-MIT} "$ARCHIVE"/
cp CHANGELOG.md "$ARCHIVE"/doc/
- name: Download CLI assets
uses: actions/download-artifact@v4
with:
name: precursor-cli-assets
path: ${{ env.ARCHIVE }}
- name: Build archive (Windows)
shell: bash
if: matrix.os == 'windows-latest'
run: |
7z a "$ARCHIVE.zip" "$ARCHIVE"
certutil -hashfile "$ARCHIVE.zip" SHA256 > "$ARCHIVE.zip.sha256"
echo "ASSET=$ARCHIVE.zip" >> $GITHUB_ENV
echo "ASSET_SUM=$ARCHIVE.zip.sha256" >> $GITHUB_ENV
- name: Build archive (Unix)
shell: bash
if: matrix.os != 'windows-latest'
run: |
tar czf "$ARCHIVE.tar.gz" "$ARCHIVE"
shasum -a 256 "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256"
echo "ASSET=$ARCHIVE.tar.gz" >> $GITHUB_ENV
echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> $GITHUB_ENV
- name: Upload release archive
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
version="${{ needs.create-release.outputs.version }}"
gh release upload --clobber "$version" ${{ env.ASSET }} ${{ env.ASSET_SUM }}
build-release-deb:
name: build-release-deb
needs: ["create-release"]
runs-on: ubuntu-latest
env:
TARGET: x86_64-unknown-linux-gnu
RUST_BACKTRACE: 1
PCRE2_SYS_STATIC: 1
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.version }}
- name: Install packages (Ubuntu)
shell: bash
run: |
ci/ubuntu-install-packages
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
target: ${{ env.TARGET }}
- name: Install cargo-deb
shell: bash
run: |
cargo install cargo-deb
- name: Build release binary
shell: bash
run: |
cargo deb --target ${{ env.TARGET }}
version="${{ needs.create-release.outputs.version }}"
echo "DEB_DIR=target/${{ env.TARGET }}/debian" >> $GITHUB_ENV
echo "DEB_NAME=precursor_$version-1_amd64.deb" >> $GITHUB_ENV
- name: Create sha256 sum of deb file
shell: bash
run: |
cd "$DEB_DIR"
sum="$DEB_NAME.sha256"
shasum -a 256 "$DEB_NAME" > "$sum"
echo "SUM=$sum" >> $GITHUB_ENV
- name: Upload release archive
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
cd "$DEB_DIR"
version="${{ needs.create-release.outputs.version }}"
gh release upload --clobber "$version" "$DEB_NAME" "$SUM"
publish-crates:
name: publish-crates
needs: ["create-release", "build-release", "build-release-deb"]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.version }}
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Check whether version is already on crates.io
id: crates_check
shell: bash
run: |
set -euo pipefail
version="${{ needs.create-release.outputs.version }}"
status="$(curl -sS -o /dev/null -w "%{http_code}" \
-H "User-Agent: precursor-release-ci" \
-H "Accept: application/json" \
"https://crates.io/api/v1/crates/precursor/$version")"
if [ "$status" = "200" ]; then
echo "already_published=true" >> "$GITHUB_OUTPUT"
echo "precursor $version is already published on crates.io; skipping cargo publish."
elif [ "$status" = "404" ]; then
echo "already_published=false" >> "$GITHUB_OUTPUT"
echo "precursor $version is not yet published on crates.io; proceeding."
else
echo "Unexpected crates.io status code: $status" >&2
exit 1
fi
- name: Publish to crates.io
if: steps.crates_check.outputs.already_published != 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
shell: bash
run: |
set -euo pipefail
cargo publish --locked
- name: Verify crates.io publication
shell: bash
run: |
set -euo pipefail
version="${{ needs.create-release.outputs.version }}"
for attempt in $(seq 1 18); do
status="$(curl -sS -o /dev/null -w "%{http_code}" \
-H "User-Agent: precursor-release-ci" \
-H "Accept: application/json" \
"https://crates.io/api/v1/crates/precursor/$version")"
if [ "$status" = "200" ]; then
echo "Confirmed precursor $version on crates.io."
exit 0
fi
sleep 10
done
echo "Timed out waiting for precursor $version to appear on crates.io." >&2
exit 1