Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/scripts/set-workspace-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Rewrites the workspace version and refreshes Cargo.lock, then asserts the
# result. Called twice by the publish workflow: once on the tagged checkout
# before publishing, once on main to record the released version.
#
# Usage: set-workspace-version.sh <version>
set -euo pipefail

VERSION="${1:?usage: set-workspace-version.sh <version>}"

# Version is centralized in [workspace.package]; both crates inherit it via
# `version.workspace = true`. Written as `sed > tmp && mv` rather than `sed -i`,
# whose syntax differs between GNU and BSD, so the command exercised locally on
# macOS is the one that runs on the Ubuntu runner.
sed -E '/^\[workspace\.package\]/,/^\[workspace\.dependencies\]/ s/^version = "[^"]*"/version = "'"$VERSION"'"/' \
Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml

# The internal pin must move in lockstep: sqlx-gen depends on sqlx-gen-macros by
# version and both crates are always released together, so leaving it behind
# would publish a sqlx-gen requiring a macros version that never shipped.
sed -E 's|^(sqlx-gen-macros = \{ path = "crates/sqlx_gen_macros", version = )"[^"]*"|\1"'"$VERSION"'"|' \
Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml

cargo update --workspace

# Neither sed fails loudly if its pattern stops matching, so assert on the
# resolved metadata: without this, a drifted manifest layout would silently
# republish the previous version.
failed=0
for pkg in sqlx-gen sqlx-gen-macros; do
got=$(cargo metadata --no-deps --format-version 1 \
| jq -r --arg p "$pkg" '.packages[] | select(.name==$p) | .version')
echo "$pkg: $got"
if [ "$got" != "$VERSION" ]; then
echo "::error::$pkg is at $got, expected $VERSION"
failed=1
fi
done

# Both package versions above are inherited from [workspace.package], so they
# would still read correct if only the internal pin stopped being rewritten.
# That drift is invisible to the loop and must be asserted separately.
req=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name=="sqlx-gen") | .dependencies[]
| select(.name=="sqlx-gen-macros") | .req')
echo "sqlx-gen -> sqlx-gen-macros: $req"
if [ "$req" != "^$VERSION" ]; then
echo "::error::sqlx-gen requires sqlx-gen-macros $req, expected ^$VERSION"
failed=1
fi

exit $failed
62 changes: 47 additions & 15 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,62 @@ on:
jobs:
publish:
runs-on: ubuntu-latest
# Needed by the final step, which records the released version on main.
permissions:
contents: write
steps:
# fetch-depth: 0 so main is available locally for the commit-back step —
# a tag push otherwise checks out a detached, shallow history.
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: dtolnay/rust-toolchain@stable

- name: Verify tag matches workspace version
run: |
# Version is centralized in the root [workspace.package] block;
# individual crates inherit via `version.workspace = true`.
CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name=="sqlx-gen") | .version')
TAG_VERSION=${GITHUB_REF#refs/tags/}
echo "Cargo workspace version: $CARGO_VERSION"
echo "Tag version: $TAG_VERSION"
if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then
echo "::error::Tag $TAG_VERSION does not match workspace version $CARGO_VERSION"
exit 1
fi
# The tag is the single source of truth for a release: the manifest is
# rewritten from it rather than bumped by hand beforehand, so a release
# can no longer fail on a tag/manifest mismatch.
- name: Apply tag version to workspace
run: .github/scripts/set-workspace-version.sh "${GITHUB_REF#refs/tags/}"

# --allow-dirty: the version above is applied in the runner and is not
# committed until the release has actually succeeded.
- name: Publish sqlx-gen-macros
run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --package sqlx-gen-macros
run: cargo publish --allow-dirty --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --package sqlx-gen-macros

- name: Wait for crates.io index update
run: sleep 30

- name: Publish sqlx-gen
run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --package sqlx-gen
run: cargo publish --allow-dirty --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --package sqlx-gen

# Deliberately last: a commit on main must never announce a release that
# failed to publish. Pushes made with GITHUB_TOKEN do not trigger further
# workflow runs, so this cannot loop back into a publish.
- name: Record the released version on main
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/}

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

# The tag checkout is detached and may sit behind main, so re-apply the
# version on top of main's current state instead of reusing the dirty
# tree left by the publish steps (-f discards it).
git fetch origin main
git checkout -f -B main origin/main
.github/scripts/set-workspace-version.sh "$TAG_VERSION"

# Re-running a release, or tagging a commit whose version already
# matches, must not fail on an empty commit.
if git diff --quiet -- Cargo.toml Cargo.lock; then
echo "main already records $TAG_VERSION, nothing to commit"
exit 0
fi

git add Cargo.toml Cargo.lock
git commit -m "deploy: release $TAG_VERSION"

# If this push loses a race with a concurrent push to main, the crates
# are already published: re-run this step, or bump main by hand.
git push origin main
Loading