1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
name: Release
# Guarded: every step through `cargo publish --dry-run` always runs, so a tag with
# a version mismatch or a broken package fails loudly. The one irreversible step —
# `cargo publish` itself — additionally requires the `CARGO_REGISTRY_TOKEN`
# repository secret to be configured, so pushing this workflow file grants no
# publish capability by itself. A maintainer prepares a release by running
# `towncrier build --version X.Y.Z`, committing the folded `CHANGELOG.md`, bumping
# `Cargo.toml`, and tagging — this workflow verifies that was done, it does not do
# it for you.
on:
push:
tags:
permissions:
contents: read
jobs:
verify:
name: verify
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.value }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: version
run: |
tag="${GITHUB_REF_NAME#v}"
cargo_version="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)"
if [ "$tag" != "$cargo_version" ]; then
echo "::error::tag v$tag does not match Cargo.toml version $cargo_version"
exit 1
fi
if ! grep -qF "## [$tag]" CHANGELOG.md; then
echo "::error::CHANGELOG.md has no '## [$tag]' entry — run 'towncrier build --version $tag' and commit it before tagging"
exit 1
fi
echo "value=$tag" >> "$GITHUB_OUTPUT"
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: stable
components: clippy, rustfmt
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- run: cargo fmt --all --check
- run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- run: cargo test --workspace --release --all-features --locked
- env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps --all-features --locked
- uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7
with:
tool: cargo-deny
- run: cargo deny check
- run: cargo package --locked
- run: cargo publish --dry-run --locked
publish:
name: publish
needs: verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
# The one irreversible step in this whole workflow, so it carries its own gate
# rather than trusting the job to have been skipped correctly: no repository
# secret, no publish, regardless of what triggered this run.
- name: cargo publish
if: ${{ secrets.CARGO_REGISTRY_TOKEN != '' }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --locked
github-release:
name: github release
needs:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: notes
run: |
version="${{ needs.verify.outputs.version }}"
awk -v v="## [$version]" '
$0 == v { found=1; next }
found && /^## \[/ { exit }
found { print }
' CHANGELOG.md > /tmp/release-notes.md
- uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
body_path: /tmp/release-notes.md
generate_release_notes: false