name: Release
on:
push:
branches: [main]
paths:
- ".github/workflows/**"
- "Cargo.toml"
- "Cargo.lock"
- "src/**"
- "tests/**"
- "scripts/**"
- "extensions/**"
- "docs/**"
- "install.sh"
- "lint.sh"
- "test.sh"
- "rho"
- "rho-gondolin-run"
workflow_dispatch:
inputs:
bump:
description: Version bump to apply
required: true
default: patch
type: choice
options:
- patch
- minor
- major
force_version:
description: Exact version to publish, overriding bump
required: false
type: string
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
RHO_BINS: rho
jobs:
quality:
name: Quality (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu
runner: namespace-profile-linux-medium
- os: macos
runner: namespace-profile-mac-medium
- os: windows
runner: namespace-profile-windows-medium
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Format
if: runner.os == 'Linux'
run: cargo fmt --all -- --check
- name: Lint
run: cargo clippy --all-targets --all-features --locked -- -D warnings
- name: Test
run: cargo test --all-targets --all-features --locked
- name: Build all targets
run: cargo build --all-targets --all-features --locked
e2e:
name: E2E tests
needs: quality
runs-on: namespace-profile-linux-medium
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: ./test.sh
bump:
name: Bump version
needs: e2e
runs-on: namespace-profile-linux-medium
outputs:
version: ${{ steps.bump.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- name: Bump Cargo version
id: bump
env:
BUMP: ${{ inputs.bump || 'patch' }}
FORCE_VERSION: ${{ inputs.force_version }}
run: |
set -euo pipefail
python3 - <<'PY'
import os
import re
from pathlib import Path
cargo = Path("Cargo.toml")
text = cargo.read_text()
match = re.search(r'(?m)^version = "([0-9]+)\.([0-9]+)\.([0-9]+)"$', text)
if not match:
raise SystemExit("could not find semver package version in Cargo.toml")
force = os.environ.get("FORCE_VERSION", "").strip()
if force:
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", force):
raise SystemExit("force_version must use MAJOR.MINOR.PATCH")
new_version = force
else:
major, minor, patch = map(int, match.groups())
bump = os.environ.get("BUMP", "patch")
if bump == "major":
major += 1
minor = 0
patch = 0
elif bump == "minor":
minor += 1
patch = 0
elif bump == "patch":
patch += 1
else:
raise SystemExit(f"unsupported bump: {bump}")
new_version = f"{major}.{minor}.{patch}"
text = text[:match.start()] + f'version = "{new_version}"' + text[match.end():]
cargo.write_text(text)
Path(os.environ["GITHUB_OUTPUT"]).write_text(f"version={new_version}\n")
PY
cargo update --workspace
- name: Commit version bump
env:
VERSION: ${{ steps.bump.outputs.version }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Cargo.toml Cargo.lock
git commit -m "chore: bump version to ${VERSION} [skip ci]"
git push origin HEAD:main
binary:
name: Binary (${{ matrix.target }})
needs: bump
strategy:
fail-fast: false
matrix:
include:
- runner: namespace-profile-linux-medium
target: x86_64-unknown-linux-gnu
archive: tar.gz
- runner: namespace-profile-mac-medium
target: x86_64-apple-darwin
archive: tar.gz
- runner: namespace-profile-mac-medium
target: aarch64-apple-darwin
archive: tar.gz
- runner: namespace-profile-windows-medium
target: x86_64-pc-windows-msvc
archive: zip
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
ref: main
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Build release binary
run: cargo build --release --locked --target "${{ matrix.target }}" --bin rho
- name: Package Unix binary
if: matrix.archive == 'tar.gz'
shell: bash
run: |
set -euo pipefail
package="rho-${{ matrix.target }}"
mkdir -p "dist/${package}"
for bin in $RHO_BINS; do
cp "target/${{ matrix.target }}/release/${bin}" "dist/${package}/${bin}"
done
cp install.sh "dist/${package}/install.sh"
tar -C dist -czf "${package}.tar.gz" "${package}"
- name: Package Windows binary
if: matrix.archive == 'zip'
shell: pwsh
run: |
$package = "rho-${{ matrix.target }}"
New-Item -ItemType Directory -Path "dist/$package" | Out-Null
foreach ($bin in $env:RHO_BINS.Split(" ")) {
Copy-Item "target/${{ matrix.target }}/release/$bin.exe" "dist/$package/$bin.exe"
}
Copy-Item "install.sh" "dist/$package/install.sh"
Compress-Archive -Path "dist/$package" -DestinationPath "$package.zip"
- uses: actions/upload-artifact@v4
with:
name: rho-${{ matrix.target }}
path: rho-${{ matrix.target }}.*
if-no-files-found: error
publish:
name: Publish crate
needs: [bump, binary]
runs-on: namespace-profile-linux-medium
steps:
- uses: actions/checkout@v4
with:
ref: main
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Verify package
run: cargo publish --dry-run --locked
- name: Publish to crates.io
run: cargo publish --locked --token "${CARGO_REGISTRY_TOKEN}"
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
github-release:
name: GitHub Release
needs: [bump, publish]
runs-on: namespace-profile-linux-medium
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Tag and release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.bump.outputs.version }}
run: |
set -euo pipefail
git tag "v${VERSION}"
git push origin "v${VERSION}"
gh release create "v${VERSION}" artifacts/* \
--title "rho-cli v${VERSION}" \
--notes "Published rho-cli ${VERSION} to crates.io and attached the cross-platform rho binary."