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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
# The build, on rickub. GitLab CI next door reads source and no more —
# check-common on every push — and deliberately builds nothing. This is the
# other half: the web bundle, actually compiled.
#
# It is not compiled *here*. The job hands the work to Modal exactly as a
# person at a terminal would, and the Sandbox does it against the `devshell`
# volume. What a runner contributes is a checkout, a python, and somewhere to
# put the result afterwards. The reason it goes to Modal is the volume the
# toolchain is cached on, not the size of the build.
#
# Lives in .rickub/workflows/ rather than .github/workflows/ because rickub
# reads one or the other and never both: with this directory present, a
# .github/workflows/ added later would be silently ignored. There is none
# today, so nothing is being shadowed — see
# https://rickub.com/docs/actions and https://rickub.com/docs/migrating-from-github
name: build
on:
push:
workflow_dispatch:
jobs:
# The same read-only check GitLab runs, for the same reason: common/ compiles
# for two targets, so a host-specific call in shared code breaks one of them
# at a namespace nobody touched. Seconds, no toolchain. Worth having on both
# hosts rather than depending on which one a given push reaches.
check-common:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python3 tools/check-common.py common
# The Nim core: its own suite, and the library the Dart job needs.
#
# NOT in a `container:`, and that is the whole reason this job is shaped the
# way it is. `actions/upload-artifact` is a node20 action and JS actions run
# inside the job container, so `nimlang/nim` — which carries no node — failed
# the upload step with `node: command not found` after everything real had
# already passed. The runner image has node; Nim is what it lacks, and Nim is
# the easier of the two to bring.
#
# Pinned by sha256 rather than taken from apt, which is this repo's habit
# elsewhere — see `tools/toolchain.sh`, which fetches Flutter, a JDK and the
# Clojure CLI the same way. An apt Nim is whatever the distro froze, and
# `nim/nim.cfg` needs >= 2.0.
nim-test:
runs-on: ubuntu-latest
env:
NIM_VERSION: "2.2.10"
NIM_SHA256: "0a3a38752e97e9d44aa479b3a7b37336dfe0176daf22ee5b5218ad0991ecd211"
steps:
- uses: actions/checkout@v4
# libssl-dev because `nim/nim.cfg` sets `-d:ssl`: std/net wants OpenSSL
# for the TLS on :6697 and `frq.atproto` uses httpclient over the same.
- name: OpenSSL headers
run: sudo apt-get update -qq && sudo apt-get install -y -qq libssl-dev
- name: Nim ${{ env.NIM_VERSION }}
run: |
set -euo pipefail
url="https://nim-lang.org/download/nim-${NIM_VERSION}-linux_x64.tar.xz"
curl -fsSL -o /tmp/nim.tar.xz "$url"
echo "${NIM_SHA256} /tmp/nim.tar.xz" | sha256sum -c -
mkdir -p /opt/nim && tar -xJf /tmp/nim.tar.xz -C /opt/nim --strip-components=1
echo "/opt/nim/bin" >> "$GITHUB_PATH"
- name: The Nim suite
run: cd nim && for t in tests/t*.nim; do nim c -r --hints:off --path:src "$t"; done
- name: Build libfrqcore.so
run: |
cd nim && nim c --app:lib --mm:orc -d:release --hints:off --path:src \
--out:../build/nim/libfrqcore.so src/frq_core.nim
# Diagnostic, not a gate. Worth reading: Nim resolves OpenSSL through
# dlopen rather than a link-time NEEDED, so libssl will not appear
# here and the Dart job still has to have one installed.
objdump -p ../build/nim/libfrqcore.so | grep NEEDED || true
- uses: actions/upload-artifact@v4
with:
name: libfrqcore
path: build/nim/libfrqcore.so
if-no-files-found: error
# The Dart side of the same boundary, on the plain VM — no Flutter, no
# emulator, which is what makes it a second to run.
#
# No container here either, for the same node reason: `download-artifact` is
# a JS action too. Dart comes from its own setup action instead.
#
# `libssl3` because the .so dlopens OpenSSL at startup and the Dart SDK
# carries its own BoringSSL rather than bringing one.
dart-test:
runs-on: ubuntu-latest
needs: [nim-test]
env:
DART_VERSION: "3.13.4"
DART_SHA256: "6487a10df5eab890d746d14a55f4c70bec3c1c0633f51804eb504cbc0fc395bb"
steps:
- uses: actions/checkout@v4
# The SDK by sha256 rather than `dart-lang/setup-dart`, for the reason
# the Nim job pins its tarball: a third-party action is one more thing
# that has to resolve on this host, and this one does not have to.
- name: Dart ${{ env.DART_VERSION }}
run: |
set -euo pipefail
url="https://storage.googleapis.com/dart-archive/channels/stable/release/${DART_VERSION}/sdk/dartsdk-linux-x64-release.zip"
curl -fsSL -o /tmp/dart.zip "$url"
echo "${DART_SHA256} /tmp/dart.zip" | sha256sum -c -
sudo unzip -q /tmp/dart.zip -d /opt
echo "/opt/dart-sdk/bin" >> "$GITHUB_PATH"
- run: sudo apt-get update -qq && sudo apt-get install -y -qq libssl3
- uses: actions/download-artifact@v4
with:
name: libfrqcore
path: build/nim
- run: cd dart/frq_core && dart pub get && dart test -r expanded
web:
runs-on: ubuntu-latest
needs: [check-common, nim-test, dart-test]
# What it spends its time on is the ClojureDart compile and, on a cold
# toolchain, fetching the pinned Flutter/JDK/Clojure tarballs.
timeout-minutes: 30
steps:
# The container copies `.` — the whole working tree, uncommitted edits
# included. On a runner that is whatever the checkout left, so it wants
# to be the commit and not a shallow surprise.
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install --disable-pip-version-check modal
# Two secrets, set under Settings -> Secrets and variables. A Modal
# token is the whole of this job's configuration: no nix, no builder,
# no cache of its own.
- name: Build the web bundle, on Modal
env:
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
# Unpiped on purpose. The image build streams to this client and
# nowhere else, and `modal app logs` cannot reach an ephemeral run —
# so this terminal is the only place the build is visible. tee, not
# tail: a run killed mid-pipe through tail takes its output with it.
run: modal run .modal/web/container.py 2>&1 | tee /tmp/frq-build.log
# The Sandbox leaves the bundle on the devshell volume rather than
# anywhere a runner can see, so fetch it back out.
- name: Fetch the bundle out of the volume
env:
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
# Into a directory that already exists, which `modal volume get` then
# creates `web/` inside — the same shape `just serve` uses. Naming
# `web` as the destination itself is what failed, with `[Errno 21] Is a
# directory`, after the Modal build had already succeeded.
run: |
mkdir -p dist
modal volume get --force devshell \
frq-web/flutter/build/web dist
- uses: actions/upload-artifact@v4
with:
name: frq-web-${{ github.sha }}
path: dist/web
if-no-files-found: error
if-no-files-found: error
# Kept whether or not the build succeeded: a failed run's log is the
# one most worth reading, and it is gone with the runner otherwise.
- uses: actions/upload-artifact@v4
if: always()
with:
name: build-log
path: /tmp/frq-build.log
if-no-files-found: ignore
|