Files
wrkflw/.github/workflows/release.yml
bahdotsh ef407a4273 ci(release): add --locked to builds and guard changelog tag lookup
The release builds were running without --locked, which means cargo
is free to re-resolve dependencies however it pleases. For a release
binary, that's *not great* — you want reproducible builds from the
exact Cargo.lock that was committed, not whatever cargo feels like
doing today.

While at it, the changelog generation was silently falling through
to the "list all commits" path if the release tag wasn't found in
the tag list. Now it emits a ::warning annotation so you at least
know something went sideways instead of staring at a suspiciously
long changelog wondering where it all came from.
2026-04-02 16:29:38 +05:30

177 lines
5.6 KiB
YAML

name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Tag to release (e.g., v1.0.0)"
required: true
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
jobs:
changelog:
name: Generate Changelog
runs-on: ubuntu-latest
outputs:
changelog: ${{ steps.changelog.outputs.changelog }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate tag format
run: |
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Tag '${RELEASE_TAG}' does not match expected format vX.Y.Z"
exit 1
fi
- name: Validate tag exists
run: |
if ! git tag -l "${RELEASE_TAG}" | grep -q .; then
echo "::error::Tag ${RELEASE_TAG} does not exist as a git tag"
exit 1
fi
- name: Generate changelog
id: changelog
run: |
# Get the previous tag
all_tags=$(git tag --sort=-v:refname | grep "^v")
if ! echo "$all_tags" | grep -F -x -q "${RELEASE_TAG}"; then
echo "::warning::Tag ${RELEASE_TAG} not found in tag list; generating changelog from all commits"
fi
prev_tag=$(echo "$all_tags" | grep -F -x -A1 "${RELEASE_TAG}" | tail -1)
[ "$prev_tag" = "${RELEASE_TAG}" ] && prev_tag="" # No previous tag found
if [ -z "$prev_tag" ]; then
# First release: use all commits
changelog=$(git log --pretty=format:"- %s (%h)" --no-merges --max-count=100)
else
changelog=$(git log "${prev_tag}..${RELEASE_TAG}" --pretty=format:"- %s (%h)" --no-merges)
fi
# Escape for GitHub Actions output
{
echo "changelog<<CHANGELOG_EOF"
echo "$changelog"
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-apple-darwin
os: macos-latest
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Install cross-compilation tools
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Install musl tools
if: matrix.target == 'x86_64-unknown-linux-musl'
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
- name: Configure linker for aarch64-linux-gnu
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
mkdir -p .cargo
echo '[target.aarch64-unknown-linux-gnu]' > .cargo/config.toml
echo 'linker = "aarch64-linux-gnu-gcc"' >> .cargo/config.toml
- name: Build
run: cargo build --release --locked --target ${{ matrix.target }}
- name: Package (Unix)
if: runner.os != 'Windows'
run: tar czf wrkflw-${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/release wrkflw
- name: Package (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Compress-Archive -Path "target/${{ matrix.target }}/release/wrkflw.exe" -DestinationPath "wrkflw-${{ matrix.target }}.zip"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: wrkflw-${{ matrix.target }}
path: wrkflw-${{ matrix.target }}.*
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: [build, changelog]
# Only publish on tag push — manual dispatch uses a branch ref, intentionally excluded
# because publishing to crates.io is irreversible
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Cache cargo-workspaces
id: cache-cargo-ws
uses: actions/cache@v4
with:
path: ~/.cargo/bin/cargo-workspaces
key: cargo-workspaces-0.4.2
- name: Install cargo-workspaces
if: steps.cache-cargo-ws.outputs.cache-hit != 'true'
run: cargo install cargo-workspaces@0.4.2
- name: Publish all crates
run: cargo workspaces publish --from-git --yes
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [build, changelog]
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Create release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.RELEASE_TAG }}
name: wrkflw ${{ env.RELEASE_TAG }}
body: |
## What's Changed
${{ needs.changelog.outputs.changelog }}
files: artifacts/*
generate_release_notes: false