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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
name: Release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"
# Manual dry-run: builds all binaries but skips release creation + publish.
workflow_dispatch:
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
strategy:
# A release is all-or-nothing: if one platform fails, fix it and re-tag
# rather than shipping a partial asset set.
fail-fast: true
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- target: aarch64-apple-darwin
runner: macos-latest
- target: x86_64-apple-darwin
# Cross-compiled from the arm64 runner; Apple's toolchain builds
# both architectures natively, including rusqlite's bundled cc.
runner: macos-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build
run: cargo build --release --locked --target ${{ matrix.target }}
- name: Smoke-test the binary
# Only when the build architecture can execute on this runner.
if: matrix.target != 'x86_64-apple-darwin'
run: ./target/${{ matrix.target }}/release/githubdw --version
- name: Package
run: |
version="${GITHUB_REF_NAME#v}"
name="githubdw-${version}-${{ matrix.target }}"
mkdir -p "dist/${name}"
cp "target/${{ matrix.target }}/release/githubdw" "dist/${name}/"
cp LICENSE README.md CHANGELOG.md "dist/${name}/"
tar -czf "dist/${name}.tar.gz" -C dist "${name}"
(cd dist && shasum -a 256 "${name}.tar.gz" > "${name}.tar.gz.sha256")
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tarball-${{ matrix.target }}
path: |
dist/*.tar.gz
dist/*.tar.gz.sha256
if-no-files-found: error
release:
name: Release
# Dry-runs (workflow_dispatch) stop after the builds succeed.
if: startsWith(github.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Verify tag matches Cargo.toml version
run: |
crate_version=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2)
if [ "$crate_version" != "${{ steps.version.outputs.version }}" ]; then
echo "Tag ${{ steps.version.outputs.version }} != Cargo.toml $crate_version" >&2
exit 1
fi
- name: Extract changelog section
id: changelog
run: |
# Extract the section for this version from CHANGELOG.md
section=$(awk '/^## \['"${{ steps.version.outputs.version }}"'\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
{
echo "notes<<EOF"
echo "$section"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: dist
pattern: tarball-*
merge-multiple: true
- name: Create GitHub Release with binaries
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes "$RELEASE_NOTES" \
dist/*.tar.gz dist/*.tar.gz.sha256
env:
GH_TOKEN: ${{ github.token }}
RELEASE_NOTES: ${{ steps.changelog.outputs.notes }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}