name: Release
on:
push:
tags: ['v*']
permissions: read-all
env:
CARGO_TERM_COLOR: always
jobs:
preflight:
name: Verify tag matches Cargo.toml
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 - name: Compare tag with Cargo.toml version
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
cargo_version="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n 1)"
tag_version="${TAG#v}"
echo "tag (no v): $tag_version"
echo "Cargo.toml: $cargo_version"
if [ "$cargo_version" != "$tag_version" ]; then
echo "::error::tag/Cargo.toml mismatch -- bump Cargo.toml to match the tag (RELEASING.md step 1)"
exit 1
fi
echo "version alignment: OK"
manpages:
name: Generate manpages
needs: preflight
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 with:
key: manpages
- name: Generate manpages
run: cargo run -p xtask --no-default-features --release --locked -- man --out man/man1
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: bzr-manpages
path: man/man1/*.1
if-no-files-found: error
build:
name: Build ${{ matrix.target }}
needs: manpages
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write attestations: write strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
use_cross: false
archive: tar.gz
pkg_deb: true
pkg_rpm: true
vendored_keyring: true
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
use_cross: true
archive: tar.gz
pkg_deb: true
pkg_rpm: true
vendored_keyring: true
- target: powerpc64le-unknown-linux-gnu
os: ubuntu-latest
use_cross: false
use_qemu: true
archive: tar.gz
pkg_deb: true
pkg_rpm: true
- target: s390x-unknown-linux-gnu
os: ubuntu-latest
use_cross: true
archive: tar.gz
pkg_deb: false
pkg_rpm: true
- target: aarch64-apple-darwin
os: macos-14
use_cross: false
archive: tar.gz
- target: x86_64-pc-windows-msvc
os: windows-latest
use_cross: false
archive: zip
- target: aarch64-pc-windows-msvc
os: windows-latest
use_cross: false
archive: zip
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Install libdbus-1-dev (native Linux)
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 with:
key: ${{ matrix.target }}
- name: Set up QEMU
if: matrix.use_qemu
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3
- name: Install cross
if: matrix.use_cross
run: cargo install cross --locked
- name: Static CRT link (Windows)
if: contains(matrix.target, 'windows-msvc')
shell: bash
run: echo "RUSTFLAGS=-C target-feature=+crt-static" >> "$GITHUB_ENV"
- name: Build
env:
PKG_CONFIG_ALLOW_CROSS: "1"
run: |
# Self-contained libdbus for the targets that feed Homebrew bottles
# (x86_64/aarch64 Linux); other targets keep dynamic linking and the
# .deb/.rpm declare libdbus as a package dependency. ppc64le (qemu)
# carries no bottle, so it is not vendored.
FEATURES=()
if [ "${{ matrix.vendored_keyring }}" = "true" ]; then
FEATURES=(--features vendored-keyring)
fi
if [ "${{ matrix.use_qemu }}" = "true" ]; then
# Use the latest stable Rust image so the container's rustc tracks
# `dtolnay/rust-toolchain@stable` used by the rest of the matrix.
# A pinned older image (e.g. rust:1.85-bookworm) silently breaks
# whenever Cargo.toml's `rust-version` advances.
docker run --rm --platform linux/ppc64le \
-v "${{ github.workspace }}:/workspace" -w /workspace \
rust:bookworm \
bash -c "apt-get update && apt-get install -y libdbus-1-dev pkg-config && cargo build --release --locked --target ${{ matrix.target }}"
# The container ran as root and left target/ owned by root.
# Subsequent host-side steps (cargo deb / cargo generate-rpm /
# tarball staging / lintian) run as the runner user and would
# otherwise hit "Permission denied" on target/.
sudo chown -R "$(id -u):$(id -g)" target
elif [ "${{ matrix.use_cross }}" = "true" ]; then
cross build --release --locked --target ${{ matrix.target }} "${FEATURES[@]}"
else
cargo build --release --locked --target ${{ matrix.target }} "${FEATURES[@]}"
fi
shell: bash
- name: Download manpages
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: bzr-manpages
path: man/man1
- name: Package (unix)
if: matrix.archive == 'tar.gz'
run: |
STAGING="bzr-${{ github.ref_name }}-${{ matrix.target }}"
mkdir "$STAGING"
cp "target/${{ matrix.target }}/release/bzr" "$STAGING/"
cp LICENSE README.md "$STAGING/"
mkdir -p "$STAGING/man/man1"
cp man/man1/*.1 "$STAGING/man/man1/"
tar czf "$STAGING.tar.gz" "$STAGING"
shell: bash
- name: Package (windows)
if: matrix.archive == 'zip'
run: |
$STAGING = "bzr-${{ github.ref_name }}-${{ matrix.target }}"
New-Item -ItemType Directory -Path $STAGING
Copy-Item "target\${{ matrix.target }}\release\bzr.exe" "$STAGING\"
Copy-Item LICENSE, README.md "$STAGING\"
New-Item -ItemType Directory -Path "$STAGING\man\man1"
Copy-Item "man\man1\*.1" "$STAGING\man\man1\"
Compress-Archive -Path $STAGING -DestinationPath "$STAGING.zip"
shell: pwsh
- name: Install cargo-deb
if: matrix.pkg_deb
uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154 with:
tool: cargo-deb
- name: Install cargo-generate-rpm
if: matrix.pkg_rpm
uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154 with:
tool: cargo-generate-rpm
- name: Build .deb
if: matrix.pkg_deb
run: cargo deb --no-build --no-strip --target ${{ matrix.target }}
shell: bash
- name: Build .rpm
if: matrix.pkg_rpm
shell: bash
run: |
# RPM's Version field disallows '-' (it separates the package's
# Version from its Release in NVR notation), so 0.2.0-rc6 is a
# hard error from cargo-generate-rpm. Translate the prerelease
# suffix to '~', which RPM accepts and which sorts correctly:
# 0.2.0~rc6 < 0.2.0 < 0.2.0~rc7. cargo-deb (run earlier in this
# job) accepts the hyphen form and is unaffected; this rewrite
# only persists for the rest of the cross-compile job in CI.
sed -i 's/^version = "\([^-"]*\)-\(.*\)"/version = "\1~\2"/' Cargo.toml
cargo generate-rpm --target ${{ matrix.target }}
- name: Stage packages alongside tarball
if: matrix.pkg_deb || matrix.pkg_rpm
run: |
set -euo pipefail
if [ "${{ matrix.pkg_deb }}" = "true" ]; then
cp target/${{ matrix.target }}/debian/*.deb .
fi
if [ "${{ matrix.pkg_rpm }}" = "true" ]; then
cp target/${{ matrix.target }}/generate-rpm/*.rpm .
fi
shell: bash
- name: Lint .deb (warn-only)
if: matrix.pkg_deb
continue-on-error: true
run: |
sudo apt-get update && sudo apt-get install -y lintian
lintian --no-tag-display-limit ./*.deb || true
shell: bash
- name: Lint .rpm (warn-only)
if: matrix.pkg_rpm
continue-on-error: true
run: |
sudo apt-get update && sudo apt-get install -y rpmlint
rpmlint ./*.rpm || true
shell: bash
- name: Install-test .deb (x86_64 only)
if: matrix.target == 'x86_64-unknown-linux-gnu' && matrix.pkg_deb
run: |
docker run --rm -v "$PWD:/pkg" -w /pkg debian:stable bash -c '
apt-get update && apt-get install -y ./*.deb && bzr --version'
shell: bash
- name: Install-test .rpm (x86_64 only)
if: matrix.target == 'x86_64-unknown-linux-gnu' && matrix.pkg_rpm
run: |
docker run --rm -v "$PWD:/pkg" -w /pkg fedora:latest bash -c '
dnf install -y ./*.rpm && bzr --version'
shell: bash
- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 with:
subject-path: |
bzr-${{ github.ref_name }}-${{ matrix.target }}.${{ matrix.archive }}
*.deb
*.rpm
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: bzr-${{ matrix.target }}
path: |
bzr-${{ github.ref_name }}-${{ matrix.target }}.*
*.deb
*.rpm
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
path: artifacts
pattern: bzr-*-*
merge-multiple: true
- name: Stage installer scripts (with version baked in)
working-directory: artifacts
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
python3 - <<'PYEOF'
import os, pathlib
tag = os.environ["TAG"]
sh_marker = 'BZR_VERSION="${BZR_VERSION:-}"'
sh_replace = 'BZR_VERSION="${BZR_VERSION:-' + tag + '}"'
sh_in = pathlib.Path("../install.sh").read_text()
sh_out = sh_in.replace(sh_marker, sh_replace, 1)
assert sh_out != sh_in, f"install.sh marker line not found: {sh_marker!r}"
pathlib.Path("install.sh").write_text(sh_out)
ps_marker = "$BzrVersion = $env:BZR_VERSION"
ps_replace = (
"$BzrVersion = if ($env:BZR_VERSION) "
"{ $env:BZR_VERSION } else { '" + tag + "' }"
)
ps_in = pathlib.Path("../install.ps1").read_text()
ps_out = ps_in.replace(ps_marker, ps_replace, 1)
assert ps_out != ps_in, f"install.ps1 marker line not found: {ps_marker!r}"
pathlib.Path("install.ps1").write_text(ps_out)
PYEOF
chmod +x install.sh
- name: Generate SHA256SUMS
working-directory: artifacts
run: |
set -euo pipefail
# Hash every release artifact (tarballs, zips, .deb, .rpm).
# Sorted, LF-only output so reproducible across re-runs of the
# same set of inputs.
find . -maxdepth 1 -type f ! -name 'SHA256SUMS' -printf '%f\n' \
| sort \
| xargs -d '\n' sha256sum \
> SHA256SUMS
echo "Generated SHA256SUMS:"
cat SHA256SUMS
- name: Extract release notes from CHANGELOG.md
id: notes
shell: bash
run: |
set -euo pipefail
TAG="${{ github.ref_name }}"
VERSION="${TAG#v}"
NOTES_FILE="$(mktemp)"
awk -v v="$VERSION" '
$0 ~ "^## \\[" v "\\]" { capture = 1; next }
capture && /^## \[/ { exit }
capture { print }
' CHANGELOG.md > "$NOTES_FILE"
if ! [ -s "$NOTES_FILE" ]; then
echo "::error::No CHANGELOG.md entry found for [$VERSION]"
exit 1
fi
echo "notes_file=$NOTES_FILE" >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
PRERELEASE=""
if [[ "$GITHUB_REF_NAME" == *-* ]]; then
PRERELEASE="--prerelease"
fi
gh release create "$GITHUB_REF_NAME" \
--title "bzr $GITHUB_REF_NAME" \
--notes-file "${{ steps.notes.outputs.notes_file }}" \
$PRERELEASE \
artifacts/*
installer-smoke:
name: Installer smoke ${{ matrix.os }}
needs: release
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Smoke install.sh
if: matrix.os == 'ubuntu-latest'
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
export BZR_INSTALL_DIR="$HOME/.local/bin"
curl -fsSL "https://github.com/randomparity/bzr/releases/download/${TAG}/install.sh" | sh
installed_version="$("$BZR_INSTALL_DIR/bzr" --version 2>&1)"
expected="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n 1)"
echo "installed: $installed_version"
echo "expected: $expected"
case "$installed_version" in
*"$expected"*) echo "binary version: OK" ;;
*) echo "binary mismatch: expected=$expected output=$installed_version" >&2; exit 1 ;;
esac
shell: bash
- name: Smoke install.ps1
if: matrix.os == 'windows-latest'
env:
TAG: ${{ github.ref_name }}
shell: pwsh
run: |
$env:BZR_INSTALL_DIR = Join-Path $env:LOCALAPPDATA 'Programs\bzr'
irm "https://github.com/randomparity/bzr/releases/download/$env:TAG/install.ps1" | iex
$installed = & (Join-Path $env:BZR_INSTALL_DIR 'bzr.exe') --version
$expected = (Select-String -Path Cargo.toml -Pattern '^version = "(.*)"' |
Select-Object -First 1).Matches[0].Groups[1].Value
Write-Host "installed: $installed"
Write-Host "expected: $expected"
if ($installed -notlike "*$expected*") {
Write-Error "binary mismatch: expected=$expected output=$installed"
exit 1
}
Write-Host "binary version: OK"
homebrew:
name: Bump randomparity/homebrew-tap
needs: release
if: ${{ !contains(github.ref_name, '-') }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout bzr (for template)
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Checkout tap
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
repository: randomparity/homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: tap
- name: Render formula
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
VERSION="${TAG#v}"
BASE="https://github.com/randomparity/bzr/releases/download/${TAG}"
fetch_sha() {
curl --fail --silent --show-error --location "$1" | sha256sum | awk '{print $1}'
}
MAC_ARM_SHA=$(fetch_sha "${BASE}/bzr-${TAG}-aarch64-apple-darwin.tar.gz")
LINUX_ARM_SHA=$(fetch_sha "${BASE}/bzr-${TAG}-aarch64-unknown-linux-gnu.tar.gz")
LINUX_INTEL_SHA=$(fetch_sha "${BASE}/bzr-${TAG}-x86_64-unknown-linux-gnu.tar.gz")
SRC_SHA=$(fetch_sha "https://github.com/randomparity/bzr/archive/refs/tags/${TAG}.tar.gz")
mkdir -p tap/Formula
sed \
-e "s|{{VERSION}}|${VERSION}|g" \
-e "s|{{MAC_ARM_SHA}}|${MAC_ARM_SHA}|g" \
-e "s|{{LINUX_ARM_SHA}}|${LINUX_ARM_SHA}|g" \
-e "s|{{LINUX_INTEL_SHA}}|${LINUX_INTEL_SHA}|g" \
-e "s|{{SRC_SHA}}|${SRC_SHA}|g" \
homebrew/bzr.rb.template > tap/Formula/bzr.rb
- name: Commit and push
working-directory: tap
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Formula/bzr.rb
if git diff --staged --quiet; then
echo "Formula already up-to-date for ${TAG}; nothing to commit."
exit 0
fi
git commit -m "bzr ${TAG#v}"
git push
bottles:
name: Build bottle (${{ matrix.label }})
needs: homebrew
if: ${{ !contains(github.ref_name, '-') }}
runs-on: ${{ matrix.os }}
permissions:
contents: write strategy:
fail-fast: false
matrix:
include:
- os: macos-14
label: arm64 macOS
- os: ubuntu-latest
label: x86_64 Linux
- os: ubuntu-24.04-arm
label: arm64 Linux
steps:
- name: Set up Homebrew on PATH (Linux)
if: runner.os == 'Linux'
run: |
set -euo pipefail
# GitHub's ubuntu images ship Homebrew at /home/linuxbrew; install
# it if a future image (or the arm runner) does not.
if [ ! -x /home/linuxbrew/.linuxbrew/bin/brew ]; then
NONINTERACTIVE=1 /bin/bash -c \
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
echo "/home/linuxbrew/.linuxbrew/bin" >> "$GITHUB_PATH"
# The Linux binary dynamically links libdbus (keyring default
# feature); `brew test` runs `bzr --version`, so the runtime lib
# must be present or the loader fails before main().
sudo apt-get update && sudo apt-get install -y libdbus-1-3
- name: Build, bottle, and upload
env:
TAG: ${{ github.ref_name }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
VERSION="${TAG#v}"
BASE="https://github.com/randomparity/bzr/releases/download/${TAG}"
brew tap randomparity/tap
brew install --build-bottle randomparity/tap/bzr
workdir="$(mktemp -d)"
cd "$workdir"
# --root-url bakes the GitHub-release download base into the JSON so
# the later `--merge` writes a `bottle do` block that resolves to
# these assets.
brew bottle --json --no-rebuild --root-url "$BASE" randomparity/tap/bzr
# `brew bottle` writes a DOUBLE-dash local file
# (bzr--<ver>.<tag>.bottle.tar.gz); Homebrew downloads the
# SINGLE-dash form from root_url. Rename before upload so the asset
# name matches what `brew install` will request.
dd_file="$(ls bzr--"${VERSION}".*.bottle.tar.gz)"
sd_file="${dd_file/--/-}"
mv "$dd_file" "$sd_file"
gh release upload "$TAG" "$sd_file" --repo randomparity/bzr --clobber
mkdir -p "$GITHUB_WORKSPACE/bottle-json"
cp ./*.json "$GITHUB_WORKSPACE/bottle-json/"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: bottle-json-${{ matrix.os }}
path: bottle-json/*.json
if-no-files-found: error
bottles-merge:
name: Add bottle block to formula
needs: bottles
if: ${{ !contains(github.ref_name, '-') }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Set up Homebrew on PATH
run: echo "/home/linuxbrew/.linuxbrew/bin" >> "$GITHUB_PATH"
- name: Download bottle JSONs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: bottle-json-*
path: bottle-json
merge-multiple: true
- name: Merge bottle block and push to tap
env:
TAG: ${{ github.ref_name }}
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
set -euo pipefail
# Tap (read-only https clone) so `--merge` can resolve and edit the
# formula at $(brew --repository randomparity/tap)/Formula/bzr.rb.
brew tap randomparity/tap
brew bottle --merge --write --no-commit bottle-json/*.json
tap_dir="$(brew --repository randomparity/tap)"
cd "$tap_dir"
# A bottle block exists => Homebrew pours instead of building, and a
# broken formula here would hard-fail every install with no source
# fallback. Gate the push on the formula at least parsing as Ruby.
ruby -c Formula/bzr.rb
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git remote set-url origin \
"https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/randomparity/homebrew-tap.git"
git add Formula/bzr.rb
if git diff --staged --quiet; then
echo "Formula already carries the bottle block for ${TAG}; nothing to commit."
exit 0
fi
git commit -m "bzr ${TAG#v}: add bottles"
git push origin HEAD:main
post-release-bump:
name: Open post-release version-bump PR
needs: [release, installer-smoke]
if: ${{ !contains(github.ref_name, '-') }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
ref: main
fetch-depth: 0
- uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e
- name: Compute next dev version
id: ver
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
VERSION="${TAG#v}"
# Default bump policy: patch. Maintainer can override the
# version on the auto-opened PR before merging.
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
NEXT_PATCH=$((PATCH + 1))
NEXT="${MAJOR}.${MINOR}.${NEXT_PATCH}-dev"
BRANCH="chore/bump-to-v${MAJOR}.${MINOR}.${NEXT_PATCH}-dev"
echo "next=$NEXT" >> "$GITHUB_OUTPUT"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
echo "Computed next dev version: $NEXT (branch: $BRANCH)"
- name: Abort if branch already exists on remote
env:
BRANCH: ${{ steps.ver.outputs.branch }}
run: |
set -euo pipefail
if git ls-remote --exit-code --heads origin "$BRANCH" > /dev/null; then
echo "::error::Branch $BRANCH already exists on origin; resolve manually." >&2
exit 1
fi
- name: Update Cargo.toml
env:
NEXT: ${{ steps.ver.outputs.next }}
run: |
set -euo pipefail
# Replace the version line inside [package] only. Dependency
# lines have `version = "..."` mid-line, so anchoring on `^`
# is necessary; section tracking ensures we ignore version
# entries in other tables (e.g. [workspace.package] if added).
awk -v v="$NEXT" '
/^\[/ { in_pkg = ($0 == "[package]") }
in_pkg && /^version = "[^"]*"$/ {
sub(/"[^"]*"/, "\"" v "\"")
}
{ print }
' Cargo.toml > Cargo.toml.new
mv Cargo.toml.new Cargo.toml
grep -n "^version = " Cargo.toml | head -n 1
- name: Refresh Cargo.lock
run: cargo update -p bzr
- name: Prepend [Unreleased] to CHANGELOG.md
run: |
set -euo pipefail
if grep -q '^## \[Unreleased\]' CHANGELOG.md; then
echo "[Unreleased] section already present; skipping prepend."
exit 0
fi
awk '
BEGIN { inserted = 0 }
!inserted && /^## \[/ {
print "## [Unreleased]"
print ""
inserted = 1
}
{ print }
' CHANGELOG.md > CHANGELOG.md.new
mv CHANGELOG.md.new CHANGELOG.md
- name: Commit and push branch
env:
BRANCH: ${{ steps.ver.outputs.branch }}
NEXT: ${{ steps.ver.outputs.next }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add Cargo.toml Cargo.lock CHANGELOG.md
if git diff --staged --quiet; then
echo "::error::No changes staged after bump steps; investigate." >&2
exit 1
fi
git commit -m "chore: bump version to $NEXT after $TAG release"
git push -u origin "$BRANCH"
- name: Render PR body
env:
BRANCH: ${{ steps.ver.outputs.branch }}
NEXT: ${{ steps.ver.outputs.next }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
cat > /tmp/pr-body.md <<EOF
Automated post-release version bump opened by \`release.yml\` after \`$TAG\` shipped successfully (\`release\` + \`installer-smoke\` green).
The default bump policy is \`patch\` (\`$TAG\` -> \`$NEXT\`) -- the smallest forward step. If the next planned release is a minor or major bump, edit \`Cargo.toml\`, run \`cargo update -p bzr\` to refresh \`Cargo.lock\`, and amend the version line in this commit before merging.
Per the dev-version convention in [\`RELEASING.md\`](../blob/main/RELEASING.md), \`Cargo.toml\` on \`main\` between releases carries a \`-dev\` SemVer pre-release suffix so dev builds are visually distinct from the most recent release. \`build.rs\` additionally embeds the git short SHA, so \`bzr --version\` prints e.g. \`bzr $NEXT (abcdef12)\`.
### CI on this PR
Pull requests opened by \`GITHUB_TOKEN\` do not trigger downstream \`pull_request\` workflows. To run CI before merging, push an empty commit:
\`\`\`bash
gh pr checkout $BRANCH
git commit --allow-empty -m "trigger ci"
git push
\`\`\`
Or run the test suite locally (\`cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test\`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
- name: Open PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: ${{ steps.ver.outputs.branch }}
NEXT: ${{ steps.ver.outputs.next }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
gh pr create \
--base main \
--head "$BRANCH" \
--title "chore: bump version to $NEXT (post-$TAG)" \
--body-file /tmp/pr-body.md