Refresh Cargo.lock for 0.9.0; auto-tag releases from Release branches
Some checks are pending
CI / linux (push) Waiting to run
CI / windows-cross (push) Waiting to run
CI / release (push) Blocked by required conditions

This commit is contained in:
= 2026-08-04 03:40:19 -04:00
parent b8eedf2137
commit 40a6ed8993
2 changed files with 86 additions and 13 deletions

View file

@ -7,7 +7,13 @@ name: CI
on: on:
push: push:
branches: [master] # Pushing a branch whose name starts with Release cuts a release: the version
# comes from Cargo.toml and the release job creates the tag itself. Pushing a
# v* tag by hand still works and takes the same path. The lowercase pattern is
# there because branch names are case-sensitive and a silent no-op would be a
# miserable thing to debug, as is the fact that * does not match / in these
# filters - Release/0.9.1 needs the ** form to be seen at all.
branches: [master, 'Release*', 'Release/**', 'release*', 'release/**']
tags: ['v*'] tags: ['v*']
pull_request: pull_request:
branches: [master] branches: [master]
@ -50,6 +56,22 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Check the tag matches the workspace version
# Asset names come from [workspace.package] version, not from the tag, so
# tagging v0.9.0 against version 0.8.8 would publish a release called
# v0.9.0 full of 0.8.8 files. Fails in seconds, before anything is built.
if: startsWith(github.ref, 'refs/tags/v')
run: |
version=$(sed -n '/^\[workspace\.package\]/,/^\[/{ s/^version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p }' Cargo.toml)
tag="${GITHUB_REF#refs/tags/}"
if [ "v$version" != "$tag" ]; then
echo "ERROR: tag $tag does not match the workspace version $version." >&2
echo "Bump [workspace.package] version in Cargo.toml, refresh Cargo.lock," >&2
echo "commit, delete the tag and re-tag." >&2
exit 1
fi
echo "OK: $tag matches the workspace version"
- name: Install build dependencies - name: Install build dependencies
run: | run: |
apt-get update -qq apt-get update -qq
@ -242,13 +264,31 @@ jobs:
retention-days: 14 retention-days: 14
# -------------------------------------------------------------- release ---- # -------------------------------------------------------------- release ----
#
# Two ways in, one path out:
# push a Release* branch -> the tag is derived from the workspace version
# push a v* tag by hand -> that tag is used as-is
#
# Either way this runs only after linux and windows-cross are green, so a
# failing test cannot produce a release.
#
# The tag is created by the same API call that creates the release. Creating it
# in an earlier step would not work: a tag pushed with CI's own token does not
# re-trigger workflows, so a design that tagged first and waited for the tag
# event would stall with no release and no error.
release: release:
needs: [linux, windows-cross] needs: [linux, windows-cross]
if: startsWith(github.ref, 'refs/tags/v') if: >-
startsWith(github.ref, 'refs/tags/v')
|| startsWith(github.ref, 'refs/heads/Release')
|| startsWith(github.ref, 'refs/heads/release')
runs-on: forgejo-runner runs-on: forgejo-runner
container: container:
image: catthehacker/ubuntu:act-24.04 image: catthehacker/ubuntu:act-24.04
steps: steps:
# Only needed to read the workspace version on a Release* branch push.
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
path: artifacts path: artifacts
@ -261,20 +301,47 @@ jobs:
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
api="${GITHUB_API_URL:-$GITHUB_SERVER_URL/api/v1}" api="${GITHUB_API_URL:-$GITHUB_SERVER_URL/api/v1}"
tag="${GITHUB_REF#refs/tags/}"
auth="Authorization: token $RELEASE_TOKEN" auth="Authorization: token $RELEASE_TOKEN"
repo="$GITHUB_REPOSITORY"
# One source of truth for the version: the same [workspace.package]
# field build-deb.sh reads and the artifact names already carry. The
# branch name only signals intent - nothing is parsed out of it.
case "$GITHUB_REF" in
refs/tags/*)
tag="${GITHUB_REF#refs/tags/}" ;;
*)
version=$(sed -n '/^\[workspace\.package\]/,/^\[/{ s/^version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p }' Cargo.toml)
[ -n "$version" ] || { echo "could not read the version from Cargo.toml" >&2; exit 1; }
tag="v$version" ;;
esac
echo "release tag: $tag commit: $GITHUB_SHA"
# A published version is immutable. If the tag already exists at some
# other commit, stop rather than ship two different builds under one
# version - the usual cause is forgetting to bump Cargo.toml.
at=$(curl -sS -H "$auth" "$api/repos/$repo/tags/$tag" | jq -r '.commit.sha // empty')
if [ -n "$at" ] && [ "$at" != "$GITHUB_SHA" ]; then
echo "ERROR: tag $tag already exists at $at, not $GITHUB_SHA." >&2
echo "Bump [workspace.package] version in Cargo.toml, refresh Cargo.lock" >&2
echo "with 'cargo update -w', commit and push again." >&2
exit 1
fi
mkdir -p dist mkdir -p dist
find artifacts -type f -exec mv -t dist -- {} + find artifacts -type f -exec mv -t dist -- {} +
ls -l dist/ ls -l dist/
# Reuse the release when it already exists, so re-running a tag build # Creating a release whose tag_name does not exist yet makes Forgejo
# replaces assets instead of failing. # create the tag at target_commitish, so this one call both tags and
id=$(curl -sS -H "$auth" "$api/repos/$GITHUB_REPOSITORY/releases/tags/$tag" | jq -r '.id // empty') # publishes. Reuse an existing release so a re-run replaces assets
# instead of failing.
id=$(curl -sS -H "$auth" "$api/repos/$repo/releases/tags/$tag" | jq -r '.id // empty')
if [ -z "$id" ]; then if [ -z "$id" ]; then
id=$(curl -fsS -X POST "$api/repos/$GITHUB_REPOSITORY/releases" \ id=$(curl -fsS -X POST "$api/repos/$repo/releases" \
-H "$auth" -H 'Content-Type: application/json' \ -H "$auth" -H 'Content-Type: application/json' \
-d "$(jq -n --arg t "$tag" '{tag_name: $t, name: $t, draft: false, prerelease: false}')" \ -d "$(jq -n --arg t "$tag" --arg c "$GITHUB_SHA" \
'{tag_name: $t, target_commitish: $c, name: $t, draft: false, prerelease: false}')" \
| jq -r '.id // empty') | jq -r '.id // empty')
fi fi
[ -n "$id" ] || { echo "could not create or find a release for $tag" >&2; exit 1; } [ -n "$id" ] || { echo "could not create or find a release for $tag" >&2; exit 1; }
@ -283,11 +350,11 @@ jobs:
name=$(basename "$f") name=$(basename "$f")
echo "uploading $name" echo "uploading $name"
# Replace an asset of the same name left by an earlier run. # Replace an asset of the same name left by an earlier run.
old=$(curl -sS -H "$auth" "$api/repos/$GITHUB_REPOSITORY/releases/$id/assets" \ old=$(curl -sS -H "$auth" "$api/repos/$repo/releases/$id/assets" \
| jq -r --arg n "$name" '.[] | select(.name == $n) | .id') | jq -r --arg n "$name" '.[] | select(.name == $n) | .id')
[ -z "$old" ] || curl -fsS -X DELETE -H "$auth" \ [ -z "$old" ] || curl -fsS -X DELETE -H "$auth" \
"$api/repos/$GITHUB_REPOSITORY/releases/$id/assets/$old" "$api/repos/$repo/releases/$id/assets/$old"
curl -fsS -X POST "$api/repos/$GITHUB_REPOSITORY/releases/$id/assets?name=$name" \ curl -fsS -X POST "$api/repos/$repo/releases/$id/assets?name=$name" \
-H "$auth" -F "attachment=@$f" -o /dev/null -H "$auth" -F "attachment=@$f" -o /dev/null
done done
echo "published $tag" echo "published $tag"

View file

@ -387,8 +387,14 @@ pagination: the table is virtualized, so a single scroll list capped at
- `QSB_SNIPPET_PERF=1 cargo test --release -p quicksearch-core --test - `QSB_SNIPPET_PERF=1 cargo test --release -p quicksearch-core --test
snippet_perf -- --nocapture`: snippet pipeline benchmark. snippet_perf -- --nocapture`: snippet pipeline benchmark.
- `.forgejo/workflows/ci.yml`: builds both platforms on every push to `master` - `.forgejo/workflows/ci.yml`: builds both platforms on every push to `master`
and every pull request, and attaches the `.deb`, a Linux tarball and a and every pull request. To cut a release, bump `[workspace.package] version`
Windows zip to a release on a `v*` tag. The Linux job runs in an Ubuntu in `Cargo.toml`, run `cargo update -w` so the lockfile agrees (CI builds with
`--locked`), and push the commit on a branch named `Release...`. Once both
build jobs are green, CI tags that commit `v<version>` and publishes a release
with the `.deb`, a Linux tarball and a Windows zip attached; pushing a `v*`
tag by hand does the same thing. The version is never taken from the branch
name, and a tag that already exists at a different commit aborts the release
rather than shipping two builds under one version. The Linux job runs in an Ubuntu
22.04 container on purpose — `packaging/build-deb.sh` reads the package's 22.04 container on purpose — `packaging/build-deb.sh` reads the package's
`libc6` floor from the binary it just built, so the builder's glibc becomes `libc6` floor from the binary it just built, so the builder's glibc becomes
the package's minimum, and 22.04 pins it at 2.35. The Windows job the package's minimum, and 22.04 pins it at 2.35. The Windows job