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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
# Cut a release binary when a v* tag is pushed.
#
# nimstatic builds itself: a throwaway host build of the tool produces the
# static one that ships, which means the release artifact is also the test that
# the tool works on a clean machine.
name: Release
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: Existing tag to (re)build and attach binaries to (default: the newest v*)
required: false
permissions:
contents: write # required — a job is read-only unless it asks
env:
NIM_VERSION: "2.2.4"
ZIG_VERSION: "0.15.1"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # tags, so a dispatch can find the latest one
# One place decides which tag is being released: the dispatch input, the
# tag that triggered the run, or — for a bare dispatch, which carries no
# inputs through the API — the newest v* tag. Anything else is a mistake
# worth stopping for, since the alternative is a release named after a
# branch.
- name: Resolve the tag
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if [ -n "${INPUT_TAG:-}" ]; then
TAG="$INPUT_TAG"
elif [ "$GITHUB_REF_TYPE" = tag ]; then
TAG="$GITHUB_REF_NAME"
else
TAG=$(git tag --list 'v*' --sort=-v:refname | head -1)
fi
case "$TAG" in
v*) ;;
*) echo "refusing to release '$TAG': not a v* tag" >&2; exit 1 ;;
esac
git checkout --detach "$TAG"
echo "releasing $TAG at $(git rev-parse --short HEAD)"
echo "TAG=$TAG" >> "$GITHUB_ENV"
# Toolchains come straight from upstream tarballs rather than setup
# actions: two curls, pinned versions, nothing else to trust.
- name: Install Nim and zig
run: |
set -euo pipefail
mkdir -p "$HOME/toolchains"
curl -sSfL "https://nim-lang.org/download/nim-${NIM_VERSION}-linux_x64.tar.xz" \
| tar -xJ -C "$HOME/toolchains"
curl -sSfL "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" \
| tar -xJ -C "$HOME/toolchains"
echo "$HOME/toolchains/nim-${NIM_VERSION}/bin" >> "$GITHUB_PATH"
echo "$HOME/toolchains/zig-x86_64-linux-${ZIG_VERSION}" >> "$GITHUB_PATH"
- name: Versions
run: |
nim --version | head -1
zig version
# Alpine packages are cached so a re-run does not re-download them.
- uses: actions/cache@v4
with:
path: ~/.cache/nimstatic
key: nimstatic-alpine-${{ runner.os }}-v3.21
- name: Test
id: test
run: nim c -d:ssl --hints:off -r tests/test_nimstatic.nim
- name: Bootstrap nimstatic on the host
run: nim c -d:release -d:ssl --hints:off -o:nimstatic-host src/nimstatic.nim
- name: Build the static binary with itself
run: |
set -euo pipefail
VERSION="${TAG#v}"
NAME="nimstatic-${VERSION}-x86_64-linux"
./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s
# `file` is not on the runner image; ldd answers the only question
# that matters, and running it proves the thing actually starts.
ldd "$NAME" 2>&1 | grep -q "not a dynamic executable"
./"$NAME" --help > /dev/null
xz -9e -k "$NAME"
sha256sum "$NAME" "$NAME.xz" > SHA256SUMS
cat SHA256SUMS
echo "NAME=$NAME" >> "$GITHUB_ENV"
# The release exists already (the tag was pushed, or it is being rebuilt),
# so this attaches assets to it rather than creating one. The upload host
# is whatever the API itself advertises in upload_url, which is the one
# value that cannot go stale.
# The release exists already (the tag was pushed, or it is being rebuilt),
# so this attaches assets to it rather than creating one. The upload host
# is whatever the API advertises in upload_url — the one value that
# cannot go stale.
- name: Attach the binaries to the release
run: |
set -euo pipefail
api() { curl -sSfL -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" "$@"; }
field() { python3 -c "import json,sys; print(json.load(sys.stdin).get(sys.argv[1],''))" "$1"; }
if ! api "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/$TAG" > release.json; then
api -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json
fi
upload_url=$(field upload_url < release.json | cut -d'{' -f1)
echo "uploading to $upload_url"
for asset in "$NAME" "$NAME.xz" SHA256SUMS; do
# Replace an asset of the same name, so a re-run is idempotent.
existing=$(python3 -c "import json,sys; print(next((a['url'] for a in (json.load(open('release.json')).get('assets') or []) if a['name']==sys.argv[1]),''))" "$asset")
if [ -n "$existing" ]; then
api -X DELETE "$existing" > /dev/null
fi
api -X POST "$upload_url?name=$asset" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$asset" > /dev/null
echo "uploaded $asset"
done
- uses: actions/upload-artifact@v4
with:
name: nimstatic-x86_64-linux
path: |
nimstatic-*-x86_64-linux
nimstatic-*-x86_64-linux.xz
SHA256SUMS
|