name: Release
on:
workflow_dispatch:
inputs:
bump:
description: Semver component to bump
type: choice
options: [patch, minor, major]
default: patch
permissions:
contents: write
concurrency: release
jobs:
tag:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- name: Compute next version
id: version
run: |
prev="$(git tag --list 'v*' --sort=-v:refname | head -1)"
prev="${prev:-v0.0.0}"
IFS=. read -r major minor patch <<< "${prev#v}"
case "${{ inputs.bump }}" in
major) major=$((major + 1)); minor=0; patch=0 ;;
minor) minor=$((minor + 1)); patch=0 ;;
patch) patch=$((patch + 1)) ;;
esac
echo "version=${major}.${minor}.${patch}" >> "$GITHUB_OUTPUT"
- name: Bump Cargo.toml, commit, and push tag
run: |
version="${{ steps.version.outputs.version }}"
sed -i "s/^version = \".*\"/version = \"${version}\"/" Cargo.toml
cargo update --workspace # sync Cargo.lock to the new version
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Cargo.toml may already carry the target version (e.g. the very
# first release); tag HEAD directly in that case.
if ! git diff --quiet; then
git commit -am "Release v${version}"
fi
git tag "v${version}"
git push origin main "v${version}"
build:
needs: tag
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
runner: macos-14
- target: x86_64-apple-darwin
runner: macos-14
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- target: x86_64-unknown-linux-musl
runner: ubuntu-latest
- target: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
- target: x86_64-pc-windows-msvc
runner: windows-latest
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
ref: v${{ needs.tag.outputs.version }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl toolchain
if: contains(matrix.target, 'musl')
run: sudo apt-get update && sudo apt-get install -y musl-tools
- run: cargo build --release --target ${{ matrix.target }}
- name: Package
shell: bash
run: |
name="linguo-v${{ needs.tag.outputs.version }}-${{ matrix.target }}"
mkdir "$name"
cp README.md LICENSE "$name/"
case "${{ matrix.target }}" in
*windows*)
cp "target/${{ matrix.target }}/release/linguo.exe" "$name/"
7z a "$name.zip" "$name" > /dev/null
sha256sum "$name.zip" > "$name.zip.sha256"
;;
*)
cp "target/${{ matrix.target }}/release/linguo" "$name/"
tar czf "$name.tar.gz" "$name"
shasum -a 256 "$name.tar.gz" > "$name.tar.gz.sha256"
;;
esac
- uses: taiki-e/install-action@v2
if: endsWith(matrix.target, '-linux-gnu')
with:
tool: cargo-deb,cargo-generate-rpm
- name: Package deb and rpm
if: endsWith(matrix.target, '-linux-gnu')
shell: bash
run: |
name="linguo-v${{ needs.tag.outputs.version }}-${{ matrix.target }}"
cargo deb --no-build --target ${{ matrix.target }} --output "$name.deb"
cargo generate-rpm --target ${{ matrix.target }} --output "$name.rpm"
sha256sum "$name.deb" > "$name.deb.sha256"
sha256sum "$name.rpm" > "$name.rpm.sha256"
- name: Package MSI
if: contains(matrix.target, 'windows')
shell: bash
run: |
cargo install cargo-wix --locked
name="linguo-v${{ needs.tag.outputs.version }}-${{ matrix.target }}"
cargo wix --no-build --target ${{ matrix.target }} --nocapture --output "$name.msi"
sha256sum "$name.msi" > "$name.msi.sha256"
- uses: actions/upload-artifact@v4
with:
name: linguo-${{ matrix.target }}
path: |
linguo-v*.tar.gz*
linguo-v*.zip*
linguo-v*.deb*
linguo-v*.rpm*
linguo-v*.msi*
publish-crates:
needs: [tag, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: v${{ needs.tag.outputs.version }}
- uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
if [ -z "$CARGO_REGISTRY_TOKEN" ]; then
echo "no CARGO_REGISTRY_TOKEN secret configured; skipping crates.io publish"
exit 0
fi
cargo publish
release:
needs: [tag, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: v${{ needs.tag.outputs.version }}
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Generate release notes from commit messages
run: |
version="v${{ needs.tag.outputs.version }}"
if prev="$(git describe --tags --abbrev=0 --match 'v*' "${version}^" 2>/dev/null)"; then
range="${prev}..${version}"
else
prev=""
range="$version"
fi
{
echo "## Changes"
echo
git log --no-merges --format='- %s (%h)' "$range" | grep -v '^- Release v' || true
if [ -n "$prev" ]; then
echo
echo "**Full changelog:** https://github.com/${{ github.repository }}/compare/${prev}...${version}"
fi
} > notes.md
cat notes.md
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
version="v${{ needs.tag.outputs.version }}"
gh release create "$version" dist/* --title "linguo $version" --notes-file notes.md
- name: Generate and attach Homebrew formula
env:
GH_TOKEN: ${{ github.token }}
run: |
version="${{ needs.tag.outputs.version }}"
./packaging/homebrew/generate.sh "$version" dist > linguo.rb
gh release upload "v${version}" linguo.rb
- name: Push formula to the Homebrew tap
env:
DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }}
run: |
if [ -z "$DEPLOY_KEY" ]; then
echo "no HOMEBREW_TAP_DEPLOY_KEY secret configured; skipping tap update"
exit 0
fi
version="${{ needs.tag.outputs.version }}"
mkdir -p ~/.ssh
printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/tap_deploy_key
chmod 600 ~/.ssh/tap_deploy_key
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
export GIT_SSH_COMMAND="ssh -i ~/.ssh/tap_deploy_key -o IdentitiesOnly=yes"
git clone git@github.com:BoxingOctopusCreative/homebrew-tap.git tap
mkdir -p tap/Formula
cp linguo.rb tap/Formula/linguo.rb
cd tap
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Formula/linguo.rb
git commit -m "linguo ${version}"
git push