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
185 changes: 185 additions & 0 deletions .github/workflows/crates-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: Release

# A pull request that bumps a released 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 the crate to
# crates.io through the `release` environment (required reviewer) with
# keyless OIDC, cuts the GitHub release, and confirms the upload.

on:
pull_request:
paths:
- ohttp/Cargo.toml
push:
tags:
- 'bitcoin-ohttp-[0-9]*'

permissions:
contents: read

jobs:
check-bump:
name: Check ${{ matrix.crate }} version bump
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
crate: [bitcoin-ohttp, bhttp]
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
- name: Use cache
uses: Swatinem/rust-cache@v2
- name: Install cargo-semver-checks
uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks
- name: Check the bump is consistent and publishable
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: ./contrib/release/check-bump.sh "${{ matrix.crate }}" "$BASE_SHA"

verify-tag:
name: Verify release tag
if: github.event_name == 'push'
runs-on: ubuntu-24.04
outputs:
crate: ${{ steps.meta.outputs.crate }}
version: ${{ steps.meta.outputs.version }}
prerelease: ${{ steps.meta.outputs.prerelease }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch main
run: git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
- name: Compute tag metadata
id: meta
env:
TAG: ${{ github.ref_name }}
run: |
source contrib/release/crates.sh
crate="$(crate_from_tag "$TAG")"
version="$(version_from_tag "$TAG")"
{
echo "crate=$crate"
echo "version=$version"
is_prerelease "$version" && echo "prerelease=true" || echo "prerelease=false"
} >>"$GITHUB_OUTPUT"
- name: Verify tag
env:
TAG: ${{ github.ref_name }}
run: ./contrib/release/verify-tag.sh "$TAG"

wait-for-ci:
name: Wait for CI
if: github.event_name == 'push'
needs: verify-tag
runs-on: ubuntu-24.04
permissions:
checks: read
contents: read
steps:
# Wait only for the correctness checks (check.yml) at the tagged
# commit. Scoping by regexp avoids waiting on this workflow's own
# downstream publish jobs, which would deadlock.
- name: Wait for CI checks
uses: lewagon/wait-on-check-action@v1.9.0
with:
ref: ${{ github.sha }}
check-regexp: ^Continuous
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-24.04
environment: 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@v4
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
- name: Use cache
uses: Swatinem/rust-cache@v2
- name: Package the crate
run: cargo package -p "$CRATE"
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: target/package/${{ env.CRATE }}-${{ env.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 -p "$CRATE"
- name: Upload packaged crate
uses: actions/upload-artifact@v4
with:
name: crate
path: target/package/${{ env.CRATE }}-${{ env.VERSION }}.crate

github-release:
name: Create GitHub release
if: github.event_name == 'push'
needs: [verify-tag, publish]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Download packaged crate
uses: actions/download-artifact@v4
with:
name: crate
path: dist
- name: Generate SHA256SUMS
run: (cd dist && sha256sum ./*.crate >SHA256SUMS)
- name: Create release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
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-24.04
env:
CRATE: ${{ needs.verify-tag.outputs.crate }}
VERSION: ${{ needs.verify-tag.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Download packaged crate
uses: actions/download-artifact@v4
with:
name: crate
path: dist
- name: Verify crates.io and docs.rs
run: ./contrib/release/verify-published.sh "$CRATE" "$VERSION" "dist/$CRATE-$VERSION.crate"
56 changes: 56 additions & 0 deletions contrib/release/check-bump.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
#
# Pull request check. When the crate version changed relative to the base
# commit, confirm the crate still publishes (cargo publish --dry-run) and
# the bump is large enough for the API changes since the base version
# (cargo semver-checks). No-ops when the version did not change.
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=contrib/release/crates.sh
source "$DIR/crates.sh"
cd "$REPO_ROOT"

[ "$#" -eq 2 ] || {
echo "usage: check-bump.sh <crate> <base-sha>" >&2
exit 1
}
crate="$1"
base="$2"
is_released_crate "$crate" || {
echo "check-bump.sh: $crate is not a released crate ($RELEASED_CRATES)" >&2
exit 1
}

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

baseline="$(version_at "$base")"
version="$(manifest_version "$crate")"
echo "Comparing $crate version against $base"
if [ "$baseline" = "$version" ]; then
echo "Version did not change ($version)"
exit 0
fi

echo "Version changed: $baseline -> $version"

echo "Dry-run publishing $crate"
cargo publish --dry-run -q -p "$crate"
echo "$crate packages and publishes cleanly"

# Semver only binds between stable releases: any comparison involving a
# pre-release is classified as a major bump, which permits everything, so
# running the tool there proves nothing.
if is_prerelease "$version" || is_prerelease "$baseline"; then
echo "Skipping semver check for pre-release ($baseline -> $version)"
exit 0
fi
if ! crate_published "$crate" "$baseline"; then
echo "Skipping semver check; baseline $baseline not on crates.io"
exit 0
fi
echo "Checking $crate $version API against $baseline"
cargo semver-checks --baseline-version "$baseline" -p "$crate"
64 changes: 64 additions & 0 deletions contrib/release/crates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
#
# Shared helpers for the release scripts. The workspace releases two crates:
# bitcoin-ohttp (member dir ohttp/) and bhttp (bhttp/), each released with
# its own <crate>-<version> tag.

# The crates these scripts release, in publish order.
RELEASED_CRATES="bitcoin-ohttp bhttp"

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"

# Succeed if $1 is a released crate.
is_released_crate() {
case " $RELEASED_CRATES " in
*" $1 "*) ;;
*) return 1 ;;
esac
}

# Print the workspace-relative manifest path of a released crate.
crate_manifest() {
case "$1" in
bitcoin-ohttp) printf 'ohttp/Cargo.toml' ;;
bhttp) printf 'bhttp/Cargo.toml' ;;
*) return 1 ;;
esac
}

# Print the crate's manifest version.
manifest_version() {
cargo metadata --no-deps --format-version 1 \
--manifest-path "$REPO_ROOT/$(crate_manifest "$1")" |
jq -r --arg c "$1" '.packages[] | select(.name == $c) | .version'
}

# Print the crate a <crate>-<version> release tag belongs to, or fail.
crate_from_tag() {
for crate in $RELEASED_CRATES; do
case "$1" in
"$crate"-[0-9]*) printf '%s' "$crate"; return 0 ;;
esac
done
return 1
}

# Print the version in a <crate>-<version> release tag, or fail.
version_from_tag() {
crate="$(crate_from_tag "$1")" || return 1
printf '%s' "${1#"${crate}"-}"
}

# Succeed if the version has a semver pre-release suffix.
is_prerelease() {
case "$1" in
*-*) return 0 ;;
*) return 1 ;;
esac
}

# Succeed if <version> of the crate exists on crates.io.
crate_published() {
curl -sfL -o /dev/null -H "User-Agent: ohttp release tooling" \
"https://crates.io/api/v1/crates/$1/$2"
}
61 changes: 61 additions & 0 deletions contrib/release/keys/benalleng.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

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
UMMRQCMlDeFCKn6Zic/y9omML7NB
=xZhb
-----END PGP PUBLIC KEY BLOCK-----
13 changes: 13 additions & 0 deletions contrib/release/keys/dangould.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEZZcCPRYJKwYBBAHaRw8BAQdA0XA/rCxWBl0fmT6KZMhW5KuND3xF3Jnt1+Kp
d/U4jma0GERhbiBHb3VsZCA8ZEBuZ291bGQuZGV2PoiTBBMWCgA7FiEEaafonlu1
mRILLuAkOHEzVgkiD1kFAmWXAj0CGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcC
F4AACgkQOHEzVgkiD1mp3wD/fc2bsdC6BuTiLfdcOSkgUX3IlO97GL998sP5cjJT
DkIA/3RXFhz1RxR7ow4SHc4/acG1UB5PMixVD6UQ6W703MMIuDgEZZcCPRIKKwYB
BAGXVQEFAQEHQD1ECC7I0/zHh7JqFwu8TKJSjo3VhdSavTy5TgmGu9lrAwEIB4h4
BBgWCgAgFiEEaafonlu1mRILLuAkOHEzVgkiD1kFAmWXAj0CGwwACgkQOHEzVgki
D1kLfAD9EqNGoCVj05oXU5B2pT2sCQ9jKOy12DKvzQ3Qln1C2QQBAM0gTG6lfSIF
kTHp2utk9+Qlowc+gfiDtBmxocg+ihgP
=Pwkf
-----END PGP PUBLIC KEY BLOCK-----
13 changes: 13 additions & 0 deletions contrib/release/keys/spacebear.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEZPisAhYJKwYBBAHaRw8BAQdAG+NC2tjYBYU912vi6XP/OK7Jss3VP0FRzYjl
YXHhocW0HXNwYWNlYmVhciA8Z2l0QHNwYWNlYmVhci5kZXY+iJMEExYKADsWIQRy
SsuPzcTOAjP6wu/IDD31KdZ/FgUCZoRsqgIbAwULCQgHAgIiAgYVCgkICwIEFgID
AQIeBwIXgAAKCRDIDD31KdZ/FskkAP9v3xm+deKTOLyx/NeFS1wGoUGZkQ1s6f5a
DO6Nk+yWVAEA/DO6qOB3qbWrpjKRrxbt2uhLY4V54BsbaoHLvHpvJgK4OARk+KwC
EgorBgEEAZdVAQUBAQdAfNAOveqsjpUOZTA+4gx79bktSTfx8qaUiq9pE5TfXxED
AQgHiHgEGBYKACAWIQRySsuPzcTOAjP6wu/IDD31KdZ/FgUCZPisAgIbDAAKCRDI
DD31KdZ/Fn3VAP9AyiR+iokV5+AAPVUA1eEgfaX0cFNaKTuSX4uxKTDimwD/TLcn
NNJS5ZtPELqjXDIS94U+Le2pX124FeyW/VFGbQk=
=vJCs
-----END PGP PUBLIC KEY BLOCK-----
Loading
Loading