Skip to content

Commit 134fb26

Browse files
committed
build: create release with cargo-dist
Takes idea from #823 and builds on it. Relevant issues: - axodotdev/cargo-dist#75 - axodotdev/cargo-dist#74 - axodotdev/cargo-dist#81 Still missing: - [ ] cargo-auditable setting - [ ] check if config directory gets included Signed-off-by: simonsan <[email protected]>
1 parent 8b9bd0f commit 134fb26

File tree

5 files changed

+630
-0
lines changed

5 files changed

+630
-0
lines changed

.cargo/config.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@
22
rustdocflags = ["--document-private-items"]
33
# rustflags = "-C target-cpu=native -D warnings"
44
# incremental = true
5+
6+
[target.armv7-unknown-linux-gnueabihf]
7+
linker = "arm-linux-gnueabihf-gcc"
8+
9+
[target.aarch64-unknown-linux-gnu]
10+
linker = "aarch64-linux-gnu-gcc"

.github/workflows/release.yml

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/
2+
#
3+
# Copyright 2022-2024, axodotdev
4+
# SPDX-License-Identifier: MIT or Apache-2.0
5+
#
6+
# CI that:
7+
#
8+
# * checks for a Git Tag that looks like a release
9+
# * builds artifacts with dist (archives, installers, hashes)
10+
# * uploads those artifacts to temporary workflow zip
11+
# * on success, uploads the artifacts to a GitHub Release
12+
#
13+
# Note that the GitHub Release will be created with a generated
14+
# title/body based on your changelogs.
15+
16+
name: Release
17+
permissions:
18+
"attestations": "write"
19+
"contents": "write"
20+
"id-token": "write"
21+
22+
# This task will run whenever you push a git tag that looks like a version
23+
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
24+
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
25+
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
26+
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
27+
#
28+
# If PACKAGE_NAME is specified, then the announcement will be for that
29+
# package (erroring out if it doesn't have the given version or isn't dist-able).
30+
#
31+
# If PACKAGE_NAME isn't specified, then the announcement will be for all
32+
# (dist-able) packages in the workspace with that version (this mode is
33+
# intended for workspaces with only one dist-able package, or with all dist-able
34+
# packages versioned/released in lockstep).
35+
#
36+
# If you push multiple tags at once, separate instances of this workflow will
37+
# spin up, creating an independent announcement for each one. However, GitHub
38+
# will hard limit this to 3 tags per commit, as it will assume more tags is a
39+
# mistake.
40+
#
41+
# If there's a prerelease-style suffix to the version, then the release(s)
42+
# will be marked as a prerelease.
43+
on:
44+
pull_request:
45+
push:
46+
tags:
47+
- '**[0-9]+.[0-9]+.[0-9]+*'
48+
49+
jobs:
50+
# Run 'dist plan' (or host) to determine what tasks we need to do
51+
plan:
52+
runs-on: "ubuntu-20.04"
53+
outputs:
54+
val: ${{ steps.plan.outputs.manifest }}
55+
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
56+
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
57+
publishing: ${{ !github.event.pull_request }}
58+
env:
59+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60+
steps:
61+
- uses: actions/checkout@v4
62+
with:
63+
submodules: recursive
64+
- name: Install dist
65+
# we specify bash to get pipefail; it guards against the `curl` command
66+
# failing. otherwise `sh` won't catch that `curl` returned non-0
67+
shell: bash
68+
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.25.1/cargo-dist-installer.sh | sh"
69+
- name: Cache dist
70+
uses: actions/upload-artifact@v4
71+
with:
72+
name: cargo-dist-cache
73+
path: ~/.cargo/bin/dist
74+
# sure would be cool if github gave us proper conditionals...
75+
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
76+
# functionality based on whether this is a pull_request, and whether it's from a fork.
77+
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
78+
# but also really annoying to build CI around when it needs secrets to work right.)
79+
- id: plan
80+
run: |
81+
dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
82+
echo "dist ran successfully"
83+
cat plan-dist-manifest.json
84+
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
85+
- name: "Upload dist-manifest.json"
86+
uses: actions/upload-artifact@v4
87+
with:
88+
name: artifacts-plan-dist-manifest
89+
path: plan-dist-manifest.json
90+
91+
# Build and packages all the platform-specific things
92+
build-local-artifacts:
93+
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
94+
# Let the initial task tell us to not run (currently very blunt)
95+
needs:
96+
- plan
97+
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
98+
strategy:
99+
fail-fast: false
100+
# Target platforms/runners are computed by dist in create-release.
101+
# Each member of the matrix has the following arguments:
102+
#
103+
# - runner: the github runner
104+
# - dist-args: cli flags to pass to dist
105+
# - install-dist: expression to run to install dist on the runner
106+
#
107+
# Typically there will be:
108+
# - 1 "global" task that builds universal installers
109+
# - N "local" tasks that build each platform's binaries and platform-specific installers
110+
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
111+
runs-on: ${{ matrix.runner }}
112+
env:
113+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114+
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
115+
steps:
116+
- name: enable windows longpaths
117+
run: |
118+
git config --global core.longpaths true
119+
- uses: actions/checkout@v4
120+
with:
121+
submodules: recursive
122+
- uses: swatinem/rust-cache@v2
123+
with:
124+
key: ${{ join(matrix.targets, '-') }}
125+
cache-provider: ${{ matrix.cache_provider }}
126+
- name: Install dist
127+
run: ${{ matrix.install_dist }}
128+
# Get the dist-manifest
129+
- name: Fetch local artifacts
130+
uses: actions/download-artifact@v4
131+
with:
132+
pattern: artifacts-*
133+
path: target/distrib/
134+
merge-multiple: true
135+
- name: Install dependencies
136+
run: |
137+
${{ matrix.packages_install }}
138+
- name: Build artifacts
139+
run: |
140+
# Actually do builds and make zips and whatnot
141+
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
142+
echo "dist ran successfully"
143+
- name: Attest
144+
uses: actions/attest-build-provenance@v1
145+
with:
146+
subject-path: "target/distrib/*${{ join(matrix.targets, ', ') }}*"
147+
- id: cargo-dist
148+
name: Post-build
149+
# We force bash here just because github makes it really hard to get values up
150+
# to "real" actions without writing to env-vars, and writing to env-vars has
151+
# inconsistent syntax between shell and powershell.
152+
shell: bash
153+
run: |
154+
# Parse out what we just built and upload it to scratch storage
155+
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
156+
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
157+
echo "EOF" >> "$GITHUB_OUTPUT"
158+
159+
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
160+
- name: "Upload artifacts"
161+
uses: actions/upload-artifact@v4
162+
with:
163+
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
164+
path: |
165+
${{ steps.cargo-dist.outputs.paths }}
166+
${{ env.BUILD_MANIFEST_NAME }}
167+
168+
# Build and package all the platform-agnostic(ish) things
169+
build-global-artifacts:
170+
needs:
171+
- plan
172+
- build-local-artifacts
173+
runs-on: "ubuntu-20.04"
174+
env:
175+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
176+
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
177+
steps:
178+
- uses: actions/checkout@v4
179+
with:
180+
submodules: recursive
181+
- name: Install cached dist
182+
uses: actions/download-artifact@v4
183+
with:
184+
name: cargo-dist-cache
185+
path: ~/.cargo/bin/
186+
- run: chmod +x ~/.cargo/bin/dist
187+
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
188+
- name: Fetch local artifacts
189+
uses: actions/download-artifact@v4
190+
with:
191+
pattern: artifacts-*
192+
path: target/distrib/
193+
merge-multiple: true
194+
- id: cargo-dist
195+
shell: bash
196+
run: |
197+
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
198+
echo "dist ran successfully"
199+
200+
# Parse out what we just built and upload it to scratch storage
201+
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
202+
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
203+
echo "EOF" >> "$GITHUB_OUTPUT"
204+
205+
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
206+
- name: "Upload artifacts"
207+
uses: actions/upload-artifact@v4
208+
with:
209+
name: artifacts-build-global
210+
path: |
211+
${{ steps.cargo-dist.outputs.paths }}
212+
${{ env.BUILD_MANIFEST_NAME }}
213+
# Determines if we should publish/announce
214+
host:
215+
needs:
216+
- plan
217+
- build-local-artifacts
218+
- build-global-artifacts
219+
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
220+
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
221+
env:
222+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
223+
runs-on: "ubuntu-20.04"
224+
outputs:
225+
val: ${{ steps.host.outputs.manifest }}
226+
steps:
227+
- uses: actions/checkout@v4
228+
with:
229+
submodules: recursive
230+
- name: Install cached dist
231+
uses: actions/download-artifact@v4
232+
with:
233+
name: cargo-dist-cache
234+
path: ~/.cargo/bin/
235+
- run: chmod +x ~/.cargo/bin/dist
236+
# Fetch artifacts from scratch-storage
237+
- name: Fetch artifacts
238+
uses: actions/download-artifact@v4
239+
with:
240+
pattern: artifacts-*
241+
path: target/distrib/
242+
merge-multiple: true
243+
- id: host
244+
shell: bash
245+
run: |
246+
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
247+
echo "artifacts uploaded and released successfully"
248+
cat dist-manifest.json
249+
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
250+
- name: "Upload dist-manifest.json"
251+
uses: actions/upload-artifact@v4
252+
with:
253+
# Overwrite the previous copy
254+
name: artifacts-dist-manifest
255+
path: dist-manifest.json
256+
# Create a GitHub Release while uploading all files to it
257+
- name: "Download GitHub Artifacts"
258+
uses: actions/download-artifact@v4
259+
with:
260+
pattern: artifacts-*
261+
path: artifacts
262+
merge-multiple: true
263+
- name: Cleanup
264+
run: |
265+
# Remove the granular manifests
266+
rm -f artifacts/*-dist-manifest.json
267+
- name: Create GitHub Release
268+
env:
269+
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
270+
ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}"
271+
ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}"
272+
RELEASE_COMMIT: "${{ github.sha }}"
273+
run: |
274+
# Write and read notes from a file to avoid quoting breaking things
275+
echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt
276+
277+
gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
278+
279+
publish-homebrew-formula:
280+
needs:
281+
- plan
282+
- host
283+
runs-on: "ubuntu-20.04"
284+
env:
285+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
286+
PLAN: ${{ needs.plan.outputs.val }}
287+
GITHUB_USER: "axo bot"
288+
GITHUB_EMAIL: "[email protected]"
289+
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
290+
steps:
291+
- uses: actions/checkout@v4
292+
with:
293+
repository: "rustic-rs/homebrew-tap"
294+
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
295+
# So we have access to the formula
296+
- name: Fetch homebrew formulae
297+
uses: actions/download-artifact@v4
298+
with:
299+
pattern: artifacts-*
300+
path: Formula/
301+
merge-multiple: true
302+
# This is extra complex because you can make your Formula name not match your app name
303+
# so we need to find releases with a *.rb file, and publish with that filename.
304+
- name: Commit formula files
305+
run: |
306+
git config --global user.name "${GITHUB_USER}"
307+
git config --global user.email "${GITHUB_EMAIL}"
308+
309+
for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do
310+
filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output)
311+
name=$(echo "$filename" | sed "s/\.rb$//")
312+
version=$(echo "$release" | jq .app_version --raw-output)
313+
314+
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
315+
brew update
316+
# We avoid reformatting user-provided data such as the app description and homepage.
317+
brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true
318+
319+
git add "Formula/${filename}"
320+
git commit -m "${name} ${version}"
321+
done
322+
git push
323+
324+
announce:
325+
needs:
326+
- plan
327+
- host
328+
- publish-homebrew-formula
329+
# use "always() && ..." to allow us to wait for all publish jobs while
330+
# still allowing individual publish jobs to skip themselves (for prereleases).
331+
# "host" however must run to completion, no skipping allowed!
332+
if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') }}
333+
runs-on: "ubuntu-20.04"
334+
env:
335+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
336+
steps:
337+
- uses: actions/checkout@v4
338+
with:
339+
submodules: recursive

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,21 @@ lto = true
189189
debug-assertions = false
190190
codegen-units = 1
191191

192+
# The profile that 'dist' will build with
193+
[profile.dist]
194+
inherits = "release"
195+
lto = "thin"
196+
192197
# Allows quick RPM file generation, if "cargo-generate-rpm" is installed:
193198
# cargo build --release; cargo generate-rpm
194199
# will result in a file like target/generate-rpm/rustic-rs-0.6.1-1.x86_64.rpm
195200
[package.metadata.generate-rpm]
196201
assets = [
197202
{ source = "target/release/rustic", dest = "/usr/bin/rustic", mode = "0755", config = false, doc = false, user = "root", group = "root" },
198203
]
204+
205+
[package.metadata.wix]
206+
upgrade-guid = "39C94FD6-39A2-4626-B886-DF943C3FBE38"
207+
path-guid = "8278131B-7918-40A8-BBA3-4A4756597706"
208+
license = false
209+
eula = false

0 commit comments

Comments
 (0)