-
Notifications
You must be signed in to change notification settings - Fork 5
262 lines (239 loc) · 12.7 KB
/
Copy pathtermux.yml
File metadata and controls
262 lines (239 loc) · 12.7 KB
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
name: Termux (Android arm64)
on:
push:
branches: [main]
tags: ['[0-9]+.[0-9]+.[0-9]+']
workflow_dispatch:
env:
# The NDK does all C compilation for the android binary (the host
# compiler is unused), so pinning it pins the toolchain. The URL is
# versioned but Google re-serves it: pinned by sha256 so a refresh is
# a conscious bump, not silent drift.
NDK_SHA256: 6d6e659834d28bb24ba7ae66148ad05115ebbad7dabed1af9b3265674774fcf6
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: jiro4989/setup-nim-action@v2
with:
# Exact version, not `stable`: the same commit must compile with
# the same compiler forever. Bump deliberately, together with
# regenerating nimble.lock (its `nim` entry records the version).
nim-version: 2.2.12
repo-token: ${{ secrets.GITHUB_TOKEN }}
parent-nim-install-directory: $HOME
nim-install-directory: nim-toolchain
- name: Install dependencies
# nimble.lock pins every dependency to an exact revision + checksum;
# install honors it, so rebuilds of the same commit get identical
# dependency trees (bump requires -> `nimble lock` to update).
run: |
nimble install -y --depsOnly
- name: Generate nimble.paths for direct nim invocations
# The committed nimble.lock makes nim disable its pkgs2 package scan
# (see windows.yml), and the cross-compiles below are direct `nim c`
# calls that get no --path flags from nimble. nimble.paths
# (gitignored, included by config.nims) restores the exact paths the
# disabled scan would have used.
run: |
{ echo '--noNimblePath'
for d in "$HOME"/.nimble/pkgs2/*/; do echo "--path:\"$d\""; done
} > nimble.paths
cat nimble.paths
- name: Download Android NDK
# The main binary builds for Termux via NDK cross-compile. No
# -d:termux: that links -landroid-glob (a Termux package for
# pre-API-28 devices); targeting API 28+ gets glob natively.
# OpenSSL is dlopen'ed at runtime (Termux's openssl package
# provides libssl.so.3), so no TLS libs are linked here.
run: |
curl -fsSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-r26c-linux.zip
echo "${NDK_SHA256} ndk.zip" | sha256sum -c -
unzip -q ndk.zip
echo "NDK_CLANG=$PWD/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang" >> "$GITHUB_ENV"
- name: Compute build flags
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
# autoupdate on releases only, like the other platform builds
echo "BUILD_FLAGS=-d:autoUpdate" >> "$GITHUB_ENV"
fi
- name: Build (cross, --os:android --cpu:arm64)
run: |
nim c -d:release --os:android --cpu:arm64 -d:ssl -d:testPlainHttp ${{ env.BUILD_FLAGS }} \
--clang.exe:"$NDK_CLANG" --clang.linkerexe:"$NDK_CLANG" \
-o:3code-android src/threecode.nim
file 3code-android
# TLS check for the smoke job, cross-compiled here: installing nim
# inside the emulated container (pkg install under qemu) can
# outlive any reasonable job budget, so the container only RUNS
# binaries now. Same flags; config.nims adds the Termux RUNPATH.
nim c -d:release --os:android --cpu:arm64 -d:ssl -d:testPlainHttp \
--clang.exe:"$NDK_CLANG" --clang.linkerexe:"$NDK_CLANG" \
-o:tlscheck-android tests/android_tlscheck.nim
- name: Verify Termux RUNPATH
# Nim's openssl wrapper dlopens libssl.so.3/libcrypto.so.3 at
# module init; Android's linker only finds them via the binary's
# DT_RUNPATH (config.nims bakes in the Termux prefix). The android
# binary can't run on this x86 host, so guard the runpath here;
# without it the binary dies on startup with "could not import:
# SSL_CTX_ctrl".
run: |
readelf -d 3code-android | grep -q 'RUNPATH.*\[/data/data/com.termux/files/usr/lib\]'
- name: Package
# SOURCE_DATE_EPOCH anchors all archive mtimes to the commit, so
# the tarball is byte-identical across rebuilds of one commit.
run: |
export SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)"
mkdir -p 3code-termux-arm64
cp 3code-android 3code-termux-arm64/3code
cp tlscheck-android 3code-termux-arm64/tlscheck
# Can't run the android binary on the x86 host; version comes
# from the nimble file (tag releases) or the short SHA.
grep -m1 '^version' threecode.nimble | cut -d'"' -f2 > 3code-termux-arm64/VERSION
git rev-parse --short=8 HEAD >> 3code-termux-arm64/VERSION
{
nim --version | head -1
"${NDK_CLANG}" --version | head -1
sha256sum 3code-android | cut -d' ' -f1
echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"
} > 3code-termux-arm64/BUILD_INFO
cp README.md LICENSE 3code-termux-arm64/
pack() {
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner \
-cf - 3code-termux-arm64 | gzip -n > 3code-termux-arm64.tar.gz
}
pack
h1=$(sha256sum 3code-termux-arm64.tar.gz | cut -d' ' -f1)
find 3code-termux-arm64 ! -type l -exec touch {} + # perturb mtimes (not symlinks: touch would create dangling targets)
pack
h2=$(sha256sum 3code-termux-arm64.tar.gz | cut -d' ' -f1)
[ "$h1" = "$h2" ] || { echo "archive not reproducible: $h1 vs $h2" >&2; exit 1; }
- name: Upload artifact (GitHub Actions)
uses: actions/upload-artifact@v4
with:
name: 3code-termux-arm64
path: 3code-termux-arm64.tar.gz
smoke:
# Run the cross-built binary inside an emulated Termux (qemu-aarch64
# + bionic, provided by termux/termux-docker). The x86 runner can't
# execute the binary natively, so this container is the only way to
# catch startup failures (dlopen paths, tmpdir, TLS) without a phone.
needs: build
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: 3code-termux-arm64
- name: Smoke test in emulated Termux
run: |
set -e
tar xzf 3code-termux-arm64.tar.gz
# Register qemu binfmt handlers; runner docker can't exec arm64
# images without this (locally docker-desktop does it for you).
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes > /dev/null
# The image's Termux tree is owned by uid/gid 1000 (system); run
# the container as that user so pkg and $HOME behave.
CID=$(docker create -t --platform linux/arm64 --user 1000:1000 \
termux/termux-docker:latest sleep 300)
docker start $CID >/dev/null
trap 'docker rm -f $CID >/dev/null 2>&1 || true' EXIT
docker exec $CID mkdir -p /data/data/com.termux/files/usr/tmp
docker cp 3code-termux-arm64/3code $CID:/data/data/com.termux/files/usr/tmp/3code
docker exec -t -e THREECODE_ALLOW_ROOT=1 $CID \
/data/data/com.termux/files/usr/tmp/3code --version
# --help exits 2 (ExitUsage) by design; assert it runs at all.
docker exec -t -e THREECODE_ALLOW_ROOT=1 $CID sh -c \
'/data/data/com.termux/files/usr/tmp/3code --help > /dev/null; \
test $? -eq 2'
# TLS init is the fragile part on Termux: the openssl wrapper
# dlopens libssl.so.3/libcrypto.so.3 at module init, and it only
# works when DT_RUNPATH (asserted in the build job) points at the
# Termux lib dir. A bare --version doesn't touch TLS; the
# cross-compiled tlscheck does a verified handshake the same way
# api.nim does. No -t: output must pipe into grep.
docker cp 3code-termux-arm64/tlscheck $CID:/data/data/com.termux/files/usr/tmp/tlscheck
docker exec $CID /data/data/com.termux/files/usr/tmp/tlscheck \
| grep -q 'tls handshake ok'
- name: Upload artifact to 3code.capocasa.dev
if: github.ref == 'refs/heads/main'
run: |
# --retry: 5xx is transient on the upload endpoint
curl -f --retry 5 --retry-delay 5 -H "Authorization: Bearer ${{ secrets.RELEASE_SECRET }}" -F "file=@3code-termux-arm64.tar.gz" \
https://3code.capocasa.dev/main/builds/upload.nim
yrc-stress:
# YRC is Nim's experimental thread-safe ORC (shared heap, atomic RC,
# concurrent cycle collector), only in devel (>= 2.3.1). 3code keeps a
# GUI animation thread painting a footer from a shared lock-protected
# frame model; stock ORC's non-atomic refcount races corrupt the heap,
# which glibc tolerates but Android's hardened_malloc aborts on
# ("write after free", the termux freeze). YRC makes those cross-thread
# RC ops safe. This job cross-compiles the tty stress tests + a stub
# 3code to arm64 with --mm:yrc and runs them in emulated Termux
# (termux-docker) to confirm the freeze is gone on the target that
# actually aborts. Requires Nim devel, so it uses nim-version: devel
# (the build/smoke jobs stay on stable for the shipped binary).
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: jiro4989/setup-nim-action@v2
with:
nim-version: devel
repo-token: ${{ secrets.GITHUB_TOKEN }}
parent-nim-install-directory: $HOME
nim-install-directory: nim-toolchain
- name: Install dependencies
# nimble.lock pins deps for the shipped builds; this experimental
# stress job rides devel Nim on purpose and keeps the same lock.
run: |
nimble install -y --depsOnly
- name: Generate nimble.paths for direct nim invocations
# Same as the build job: the lock disables nim's pkgs2 scan and the
# stress-test cross-compiles are direct `nim c` calls.
run: |
{ echo '--noNimblePath'
for d in "$HOME"/.nimble/pkgs2/*/; do echo "--path:\"$d\""; done
} > nimble.paths
cat nimble.paths
- name: Download Android NDK
run: |
curl -fsSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-r26c-linux.zip
echo "${NDK_SHA256} ndk.zip" | sha256sum -c -
unzip -q ndk.zip
echo "NDK_CLANG=$PWD/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang" >> "$GITHUB_ENV"
- name: Cross-compile yrc stub + stress tests (arm64)
# The stub and the tty stress tests are built for android/arm64
# with --mm:yrc so they run under bionic in termux-docker. The
# stress tests drive the stub through a PTY, so THREECODE_TEST_STUB_BINARY
# points the test harness at the prebuilt arm64 stub (it can't run
# `nim` at test time in the container).
run: |
set -e
mkdir -p build
COMMON="-d:release --os:android --cpu:arm64 --mm:yrc -d:ssl -d:testPlainHttp -d:providerStub -d:fastStubRetries --threads:on --path:src --path:tests --clang.exe:$NDK_CLANG --clang.linkerexe:$NDK_CLANG"
nim c $COMMON --nimcache:build/yrc_stub_cache -o:build/3code_stub_android src/threecode.nim
for t in test_spinner_race_stress test_resize_ticker test_gui_join_freeze; do
nim c $COMMON --nimcache:build/yrc_${t}_cache -o:build/${t}_android tests/tty/${t}.nim
done
file build/3code_stub_android
- name: Run yrc stress tests in emulated Termux
run: |
set -e
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes > /dev/null
CID=$(docker create -t --platform linux/arm64 --user 1000:1000 \
termux/termux-docker:latest sleep 1800)
docker start $CID >/dev/null
trap 'docker rm -f $CID >/dev/null 2>&1 || true' EXIT
W=/data/data/com.termux/files/usr/tmp/work
docker exec $CID mkdir -p $W
docker cp build/3code_stub_android $CID:$W/3code_stub
docker cp build/test_spinner_race_stress_android $CID:$W/test_spinner_race_stress
docker cp build/test_resize_ticker_android $CID:$W/test_resize_ticker
docker cp build/test_gui_join_freeze_android $CID:$W/test_gui_join_freeze
for t in test_spinner_race_stress test_resize_ticker test_gui_join_freeze; do
docker exec -t \
-e THREECODE_TEST_STUB_BINARY=$W/3code_stub \
-e THREECODE_ALLOW_ROOT=1 \
$CID sh -c "cd $W && ./$t"
done