Skip to content
Merged
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
29 changes: 10 additions & 19 deletions .github/ISSUE_TEMPLATE/minor-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ assignees: ""
### Checklist

Release numbering must follow [Semantic Versioning]. These steps assume the current `master`
branch **development** version is _MAJOR.MINOR.0_.
branch **development** version is _MAJOR.MINOR.0_. Release-managed crates are `payjoin`,
`payjoin-cli`, and `payjoin-mailroom`; tags use the `<crate>-<version>` scheme.

#### On the day of the feature freeze

Expand All @@ -32,9 +33,11 @@ Change the `master` branch to the next MINOR+1 version:
- [ ] Switch to the `master` branch.
- [ ] Create a new PR branch called `bump-CRATE-MAJOR-MINOR+1`, eg. `bump-CRATE-0-22`.
- [ ] Bump the `bump-CRATE-MAJOR-MINOR+1` branch to the next development MINOR+1 version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0` for all crates in the workspace.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0` for the crate being released,
and update every workspace member's version requirement on it to match.
- Run `contrib/update-lock-files.sh` to apply upgrades to the Cargo lock files.
- Update the `CHANGELOG.md` file.
- Update the crate's `CHANGELOG.md` file, adding a `## MAJOR.MINOR+1.0` section that
summarizes the PRs merged since the last release tag.
- The commit message should be "Bump CRATE version to MAJOR.MINOR+1.0".
- [ ] Create PR for the `bump-CRATE-MAJOR-MINOR+1` branch to `master`.
- Title PR "Bump CRATE version to MAJOR.MINOR+1.0".
Expand All @@ -48,25 +51,13 @@ If any issues need to be fixed before the _MAJOR.MINOR+1.0_ version is released:

#### On the day of the release

Tag and publish new release:

- [ ] Check that the crate is publishable with `cargo publish --dry-run` from that crate's directory.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still want this step because dry run covers things that aren't covered in a plain build, or is this gone because --dry-run is already part of the action as I see, and the assumption just that you'd re-tag? Seems like sanity before tag & retag is cheap insurance to leave here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that the check-bump.sh runs automatically on any version bump PR and includes a --dry-run. This PR must be merged before the tag is made.

- [ ] Add a tag to the `HEAD` commit in the `master` branch.
- The tag name should be `CRATE-MAJOR.MINOR+1.0`
- [ ] Create a signed annotated tag on the `HEAD` commit in the `master` branch.
- The tag name should be `CRATE-MAJOR.MINOR+1.0`, eg. `payjoin-1.0.0`.
- The first line of the tag message should be "Release CRATE-MAJOR.MINOR+1.0".
- In the body of the tag message put a copy of the **Summary** and **Changelog** for the release.
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
- [ ] Wait for the CI to finish one last time.
- [ ] Build the docs locally to ensure they are building correctly.
- Sign with a key committed under `contrib/release/keys/`, using the explicit `--sign` flag.
- [ ] Push the new tag to the `payjoin/rust-payjoin` repo.
- [ ] Publish the crate in question to crates.io.
- [ ] Create the release on GitHub.
- Go to "tags", click on the dots on the right and select "Create Release".
- Set the title to `Release CRATE-MAJOR.MINOR+1.0`.
- In the release notes body put the **Summary** and **Changelog**.
- Use the "+ Auto-generate release notes" button to add details from included PRs.
- Until we reach a `1.0.0` release check the "Pre-release" box.
- [ ] Make sure the new release shows up on [crates.io] and that the docs are built correctly on [docs.rs].
- [ ] Approve the `crates-release` environment when it requests a reviewer.
- [ ] Announce the release, using the **Summary**, on Discord, Twitter, Nostr, and stacker.news.
- [ ] Celebrate 🎉

Expand Down
207 changes: 207 additions & 0 deletions .github/workflows/crates-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
name: Release

# A pull request that bumps a release crate's version is checked for
# consistency and publishability. Pushing a `<crate>-<version>` tag verifies
# the tag, waits for CI at that commit, then publishes to crates.io through
# the `crates-release` environment (required reviewer) with keyless OIDC,
# cuts the GitHub release, and confirms the upload.

on:
pull_request:
paths:
- payjoin/Cargo.toml
- payjoin-cli/Cargo.toml
- payjoin-mailroom/Cargo.toml
push:
tags:
- payjoin-[0-9]*
- payjoin-cli-[0-9]*
- payjoin-mailroom-[0-9]*

permissions:
contents: read

jobs:
check-bump:
name: Check release version bump
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install nix
uses: DeterminateSystems/determinate-nix-action@main
- name: Use nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Check the bump is consistent and publishable
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: nix --quiet develop .#release -c ./contrib/release/check-bump.sh "$BASE_SHA"

verify-tag:
name: Verify release tag
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
crate: ${{ steps.meta.outputs.crate }}
version: ${{ steps.meta.outputs.version }}
prerelease: ${{ steps.meta.outputs.prerelease }}
steps:
- name: Checkout repo
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch master
run: git fetch --no-tags origin +refs/heads/master:refs/remotes/origin/master
- name: Install nix
uses: DeterminateSystems/determinate-nix-action@main
- name: Use nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Compute tag metadata
id: meta
env:
TAG: ${{ github.ref_name }}
run: |
source contrib/release/crates.sh
version="$(version_from_tag "$TAG")"
{
echo "crate=$(crate_from_tag "$TAG")"
echo "version=$version"
is_prerelease "$version" && echo "prerelease=true" || echo "prerelease=false"
} >>"$GITHUB_OUTPUT"
- name: Verify tag
env:
TAG: ${{ github.ref_name }}
run: nix --quiet develop .#release -c ./contrib/release/verify-tag.sh "$TAG"

wait-for-ci:
name: Wait for CI
if: github.event_name == 'push'
needs: verify-tag
runs-on: ubuntu-latest
permissions:
checks: read
contents: read
steps:
# Wait only for the correctness checks (rust.yml, format.yml) at the
# tagged commit. Scoping by regexp avoids waiting on this workflow's
# own downstream publish jobs, which would deadlock.
- name: Wait for test, lint, and format checks
uses: lewagon/wait-on-check-action@v1.9.0
with:
ref: ${{ github.sha }}
check-regexp: ^(Test|Lint|Format)
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 30

publish:
name: Publish to crates.io
if: github.event_name == 'push'
needs: [verify-tag, wait-for-ci]
runs-on: ubuntu-latest
environment: crates-release
permissions:
id-token: write
attestations: write
contents: read
env:
RUSTUP_TOOLCHAIN: stable
CRATE: ${{ needs.verify-tag.outputs.crate }}
VERSION: ${{ needs.verify-tag.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
- name: Use cache
uses: Swatinem/rust-cache@v2
- name: Prepare lockfile
run: cp Cargo-recent.lock Cargo.lock
- name: Package the crate
run: cargo package --locked -p "$CRATE"
- name: Attest build provenance
uses: actions/attest-build-provenance@v4
with:
subject-path: target/package/${{ needs.verify-tag.outputs.crate }}-${{ needs.verify-tag.outputs.version }}.crate
- name: Authenticate to crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish --locked -p "$CRATE"
- name: Upload packaged crate
uses: actions/upload-artifact@v4
with:
name: crate
path: target/package/${{ needs.verify-tag.outputs.crate }}-${{ needs.verify-tag.outputs.version }}.crate

github-release:
name: Create GitHub release
if: github.event_name == 'push'
needs: [verify-tag, publish]
runs-on: ubuntu-latest
permissions:
contents: write
env:
CRATE: ${{ needs.verify-tag.outputs.crate }}
VERSION: ${{ needs.verify-tag.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: Install nix
uses: DeterminateSystems/determinate-nix-action@main
- name: Use nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Download packaged crate
uses: actions/download-artifact@v4
with:
name: crate
path: dist
- name: Generate SHA256SUMS
run: (cd dist && sha256sum ./*.crate >SHA256SUMS)
- name: Extract changelog section
id: changelog
run: |
echo "Extracting the ## $VERSION section of $CRATE/CHANGELOG.md"
{
echo "body<<CHANGELOG_EOF"
nix --quiet develop .#release -c ./contrib/release/extract-changelog.sh "$CRATE" "$VERSION"
echo "CHANGELOG_EOF"
} >>"$GITHUB_OUTPUT"
- name: Create release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body: ${{ steps.changelog.outputs.body }}
generate_release_notes: true
prerelease: ${{ needs.verify-tag.outputs.prerelease }}
files: |
dist/*.crate
dist/SHA256SUMS

verify-published:
name: Verify publication
if: github.event_name == 'push'
needs: [verify-tag, publish]
runs-on: ubuntu-latest
env:
CRATE: ${{ needs.verify-tag.outputs.crate }}
VERSION: ${{ needs.verify-tag.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: Install nix
uses: DeterminateSystems/determinate-nix-action@main
- name: Use nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Download packaged crate
uses: actions/download-artifact@v4
with:
name: crate
path: dist
- name: Verify crates.io and docs.rs
run: nix --quiet develop .#release -c ./contrib/release/verify-published.sh "$CRATE" "$VERSION" "dist/$CRATE-$VERSION.crate"
2 changes: 1 addition & 1 deletion .github/workflows/release-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- payjoin-mailroom/**
push:
tags:
- payjoin-mailroom-**
- payjoin-mailroom-[0-9]*
workflow_dispatch:

jobs:
Expand Down
58 changes: 58 additions & 0 deletions contrib/release/check-bump.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
#
# Pull request check. For each release crate whose version changed relative
# to the base commit, confirm the bump is consistent (check-invariants) and
# the crate still publishes (cargo publish --dry-run). No-ops when no release
# version changed. A sibling release crate not yet on crates.io is skipped,
# not failed, since a PR may bump two crates at once.
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=contrib/release/crates.sh
source "$DIR/crates.sh"
cd "$REPO_ROOT"
# shellcheck source=contrib/lockfile.sh
source contrib/lockfile.sh

[ "$#" -eq 1 ] || {
echo "usage: check-bump.sh <base-sha>" >&2
exit 1
}
base="$1"

# A crate's [package] version at a git ref (the only line-anchored `version`).
version_at() {
git show "$1:$2/Cargo.toml" 2>/dev/null | sed -n 's/^version = "\(.*\)"/\1/p' | head -1
}

RELEASE_METADATA="$(cargo_metadata)"
echo "Comparing release crate versions against $base"
changed=()
for crate in $RELEASE_CRATES; do
[ "$(version_at "$base" "$crate")" = "$(manifest_version "$crate")" ] || changed+=("$crate")
done

if [ "${#changed[@]}" -eq 0 ]; then
echo "No release crate version changed"
exit 0
fi

echo "Version changed for: ${changed[*]}"
"$DIR/check-invariants.sh" "${changed[@]}"

use_lockfile Cargo-recent.lock
for crate in "${changed[@]}"; do
unpublished=""
while IFS=$'\t' read -r dep req; do
[ -n "$dep" ] || continue
v="${req#^}"
v="${v#=}"
crate_published "$dep" "$v" || unpublished="$unpublished $dep $v"
done < <(sibling_deps "$crate")
if [ -n "$unpublished" ]; then
echo "Skipping $crate dry-run; sibling not on crates.io yet:$unpublished"
continue
fi
echo "Dry-run publishing $crate"
cargo publish --dry-run --locked -q -p "$crate"
echo "$crate packages and publishes cleanly"
done
47 changes: 47 additions & 0 deletions contrib/release/check-invariants.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
#
# Check that each release crate is internally consistent: every dependent's
# version requirement on it matches its manifest version, both tracked lock
# files record that version, and its CHANGELOG.md has a section for it.
# Offline, a few seconds. Checks all release crates, or the ones named.
set -euo pipefail
# shellcheck source=contrib/release/crates.sh
source "$(dirname "${BASH_SOURCE[0]}")/crates.sh"

crates="${*:-$RELEASE_CRATES}"
RELEASE_METADATA="$(cargo_metadata)"
status=0
problem() {
echo "$*" >&2
status=1
}

# Print a crate's version as recorded in a Cargo lock file.
lockfile_version() {
grep -A1 "^name = \"$2\"\$" "$1" | sed -n 's/^version = "\(.*\)"/\1/p'
}

echo "Checking version requirements on: $crates"
# Version requirements that do not match the depended-on crate's version.
mismatches="$(cargo_metadata | jq -r --argjson t "$(printf '%s' "$crates" | jq -R 'split(" ")')" '
(.packages | map({key: .name, value: .version}) | from_entries) as $ver
| .packages[] as $p
| $p.dependencies[]
| select(.path == null and (.name | IN($t[])))
| select((.req | ltrimstr("^") | ltrimstr("=")) != $ver[.name])
| "\($p.name) requires \(.name) \(.req), expected \($ver[.name])"
')"
[ -z "$mismatches" ] || problem "$mismatches"

for crate in $crates; do
version="$(manifest_version "$crate")"
echo "Checking $crate $version: lock files and CHANGELOG.md"
for lock in Cargo-minimal.lock Cargo-recent.lock; do
recorded="$(lockfile_version "$REPO_ROOT/$lock" "$crate")"
[ "$recorded" = "$version" ] || problem "$lock records $crate ${recorded:-<missing>}, expected $version"
done
grep -qxF "## $version" "$REPO_ROOT/$crate/CHANGELOG.md" || problem "$crate/CHANGELOG.md has no ## $version section"
done

[ "$status" -eq 0 ] && echo "Release invariants hold for: $crates"
exit "$status"
Loading
Loading