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:
# A manual run builds and packages everything but publishes nothing, so the
# whole pipeline can be rehearsed without spending a tag on finding out it
# was wrong.
workflow_dispatch:
# Only `publish` writes anything; everything above it is read-only.
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Nothing else stops `git tag v0.2.0` on a tree whose manifest still says
# 0.1.0, and the mistake is only visible once the release is published.
- name: Tag must match the version in Cargo.toml
if: startsWith(github.ref, 'refs/tags/')
run: |
manifest=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[0].version')
tag=${GITHUB_REF_NAME#v}
if [ "$manifest" != "$tag" ]; then
echo "::error::tag $GITHUB_REF_NAME means version $tag, but Cargo.toml says $manifest"
exit 1
fi
echo "releasing rash $manifest"
# The full CI matrix — fmt, clippy and tests on stable, nightly and the MSRV,
# across Linux and macOS. Reused rather than restated so the two cannot drift.
test:
uses: ./.github/workflows/ci.yml
build:
needs:
strategy:
fail-fast: false
matrix:
include:
-
# Free on public repositories. On a private one this row needs a paid
# larger-runner plan, or cross-compilation in place of a native build.
-
# Both Apple targets come off the same arm64 image. Apple's toolchain
# cross-compiles between its own architectures, so an x86_64 binary
# built here is a real x86_64 Mach-O, not a translated one — and the
# alternative, macos-13, is the last x86_64 image and is on a
# deprecation path. Nothing here should be waiting on that.
-
-
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
# Deliberately uncached, unlike ci.yml: a published binary is worth the
# few extra minutes of building from scratch.
#
# The default feature set is empty, so --no-default-features changes
# nothing today. It stays as a latch: `test-harness` builds fake-ssh, the
# ssh stand-in the tests drive, and this keeps it out of a release tarball
# even if it is ever put back in the defaults.
- name: Build
run: >
cargo build --release --locked --no-default-features
--target ${{ matrix.target }}
- name: Package
run: |
name="rash-${GITHUB_REF_NAME}-${{ matrix.target }}"
mkdir "$name"
cp "target/${{ matrix.target }}/release/rash" "$name/"
# The manual matters: the README's install instructions place it, and
# `man ./rash.1` works straight out of the unpacked directory.
cp rash.1 LICENSE README.md "$name/"
tar -czf "$name.tar.gz" "$name"
ls -l "$name.tar.gz"
- uses: actions/upload-artifact@v4
with:
name: rash-${{ matrix.target }}
path: rash-*.tar.gz
if-no-files-found: error
publish:
# Skipped on a manual run: that is the rehearsal, and it stops here.
if: startsWith(github.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Checksums
run: cd dist && sha256sum *.tar.gz | tee SHA256SUMS
- name: Create the release
env:
GH_TOKEN: ${{ github.token }}
run: |
# A tag with a hyphen in it — v0.2.0-rc1 — is a pre-release.
case "$GITHUB_REF_NAME" in
*-*) pre=--prerelease ;;
*) pre= ;;
esac
gh release create "$GITHUB_REF_NAME" \
dist/*.tar.gz dist/SHA256SUMS \
--repo "$GITHUB_REPOSITORY" \
--title "rash $GITHUB_REF_NAME" \
--generate-notes \
--verify-tag \
$pre