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
|
# The build, on rickub. The same `nix flake check` and the same three objects
# GitLab builds next door, for the case where a push reaches this host and not
# that one.
#
# It does not compile here, and could not: a rickub runner is a Linux x86-64
# container, and this graph is ~800 vendored crates behind libcosmic, iced,
# wgpu and aws-lc-sys. What the runner does is evaluate, dispatch and fetch —
# eu.nixbuild.net holds every vendor path from previous runs and does the
# compiling, exactly as it does for GitLab. So the two hosts share a cache
# rather than duplicating an hour of work, and a warm pipeline here is a
# pipeline that mostly substitutes.
#
# What it deliberately does NOT do is publish. The tarballs go to GitLab's
# generic package registry, and the token for that is CI_JOB_TOKEN — a GitLab
# job's own credential, issued to the job, not something to mint and carry
# here as a long-lived secret. One publisher and one place a consumer pins
# from is also just simpler to reason about than two. This host builds, checks
# and hands the tarballs back as run artifacts; `main` on GitLab is what moves
# `latest`.
#
# Lives in .rickub/workflows/ rather than .github/workflows/ because rickub
# reads one or the other and never both — see
# https://rickub.com/docs/actions and https://rickub.com/docs/migrating-from-github
name: build
on:
push:
pull_request:
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
# A cold run vendors the world and compiles the workspace. The builder does
# the compiling, but this side waits on it, and waits on the fetch of what
# comes back. GitLab gives the same job 3h.
timeout-minutes: 180
steps:
# The flake is fetched from the checkout, and nix wants the git tree
# rather than a shallow single commit to resolve `self` from.
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: cachix/install-nix-action@v27
with:
extra_nix_config: |
experimental-features = nix-command flakes
# Everything the GitLab job's before_script sets up, and for the same
# reasons — the comments there are the long version. The short one:
#
# max-jobs must NOT be zero. At zero the runner refuses to build
# anything at all, which sends the fixed-output fetches (rust
# toolchain tarballs, every crate source) to nixbuild, where the build
# sandbox has no network and they can only fail. nproc keeps those and
# crane's hundreds of trivial cargo-src derivations local and parallel;
# the expensive builds still go remote.
#
# The explicit priority on nixbuild matters. nix picks a substituter by
# priority rather than by the order listed, and an ssh-ng store
# defaults to 0 against cache.nixos.org's 40 — so without it nixbuild
# wins even for stock nixpkgs paths and they crawl down one SSH
# connection instead of coming off the CDN.
#
# Keep the builder's job count modest: nixbuild's sshd caps concurrent
# sessions and refuses the excess, which nix reports as the thoroughly
# misleading "Nix daemon disconnected unexpectedly (maybe it crashed?)".
- name: Point nix at eu.nixbuild.net
env:
NIXBUILD_SSH_KEY: ${{ secrets.NIXBUILD_SSH_KEY }}
run: |
set -eu
# ROOT's ~/.ssh, and this is the one thing that differs from the
# GitLab job. There the whole job runs as root in a nixos/nix image;
# here install-nix-action leaves a multi-user install, so the process
# that opens the SSH connection to the builder is nix-daemon running
# as root. A key under the runner user's home is a key it never
# reads, and the failure is a build that silently stays local —
# which on this graph means a runner trying to compile libcosmic.
sudo mkdir -p /root/.ssh
# base64 because a CI variable can only carry a single line.
printf '%s' "$NIXBUILD_SSH_KEY" | base64 -d | sudo tee /root/.ssh/nixbuild >/dev/null
sudo chmod 600 /root/.ssh/nixbuild
printf 'Host eu.nixbuild.net\n PubkeyAcceptedKeyTypes ssh-ed25519\n IdentityFile /root/.ssh/nixbuild\n IdentitiesOnly yes\n' | sudo tee /root/.ssh/config >/dev/null
printf 'eu.nixbuild.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIQCZc54poJ8vqawd8TraNryQeJnvH1eLpIDgbiqymM\n' | sudo tee /root/.ssh/known_hosts >/dev/null
# nixbuild's paths are signed, but the key is per-account and only
# readable from its web UI, so rather than carry a second secret we
# lean on the transport — the store is reached over SSH with our own
# key. cache.nixos.org is verified by its own key either way;
# require-sigs only relaxes nixbuild.
sudo tee -a /etc/nix/nix.conf >/dev/null <<EOF
builders = ssh-ng://eu.nixbuild.net x86_64-linux - 16 1 big-parallel,benchmark
builders-use-substitutes = true
substituters = https://cache.nixos.org ssh-ng://eu.nixbuild.net?priority=50
require-sigs = false
max-jobs = $(nproc)
EOF
# The daemon reads nix.conf at start, so settings appended after
# install-nix-action has already started it do nothing until it is
# restarted. Without this the job runs with none of the above and
# the first symptom is an hour of local compilation.
sudo systemctl restart nix-daemon
nix config show builders
nix config show max-jobs
# fmt, clippy and the test suite, plus all four desktop objects — the
# checks build them. This is the step that has historically failed, and
# it fails fast: fmt costs seconds and runs before anything is compiled.
- run: nix flake check -L
# Store paths are read-only symlink farms; the runner uploads plain
# files, so dereference them into trees it can zip.
- name: Stage the objects
run: |
set -eu
nix build -L --no-link --print-out-paths .#libs .#android > /tmp/outs
mkdir -p artifacts && xargs -a /tmp/outs -I{} cp -rL {}/. artifacts/
# Staged beside artifacts/ and not into it: libsPortable holds the
# same four sonames with a different RUNPATH, so merging the two
# trees would leave whichever was copied last under both names.
nix build -L --no-link --print-out-paths .#libsPortable > /tmp/portable
mkdir -p portable && xargs -a /tmp/portable -I{} cp -rL {}/. portable/
# The same three tarballs GitLab publishes, rooted at lib/ and include/
# so a consumer's `flake = false` input resolves to ${input}/lib/… with
# nothing in between. Here they are only artifacts of the run — see the
# note at the top about why this host does not upload them anywhere.
- name: Tar them the way a consumer takes them
run: |
set -eu
tar czf x86_64-linux.tar.gz -C artifacts include \
lib/libvidya.so lib/libjolttui.so lib/libjoltmoq.so lib/libjoltcosmic.so
# `-C portable lib include` and not a file list: the point of this one
# is the libraries that came along, and naming the four we know about
# would drop them.
tar czf x86_64-linux-portable.tar.gz -C portable lib include
tar czf android-arm64-v8a.tar.gz -C artifacts include lib/arm64-v8a
- uses: actions/upload-artifact@v4
with:
name: jolt-native-${{ github.sha }}
path: |
x86_64-linux.tar.gz
x86_64-linux-portable.tar.gz
android-arm64-v8a.tar.gz
if-no-files-found: error
|