OILS / soil / host-shim.sh View on Github | oils.pub

446 lines, 255 significant
1#!/usr/bin/env bash
2#
3# Shell functions run on the host machine, OUTSIDE the container.
4#
5# Usage:
6# soil/host-shim.sh <function name>
7#
8# Examples:
9# soil/host-shim.sh local-test-uke cpp-spec # run a job
10# soil/host-shim.sh local-test-uke cpp-spec '' T # get shell
11
12set -o nounset
13set -o pipefail
14set -o errexit
15
16REPO_ROOT=$(cd "$(dirname $0)/.."; pwd)
17
18source soil/common.sh
19source test/tsv-lib.sh
20
21readonly REGISTRY='ghcr.io'
22
23live-image-tag() {
24 ### image ID -> Docker tag name
25 local image_id=$1
26
27
28 case $image_id in
29 dummy)
30 # re2c to version 4.3.1
31 echo 'v-2026-01-13'
32 ;;
33 app-tests|benchmarks|clang|cpp-small|cpp-spec|ovm-tarball|wild)
34 # re2c to version 4.3.1
35 echo 'v-2026-01-13'
36 ;;
37 *)
38 # ghcr.io migration
39 echo 'v-2025-12-21'
40 ;;
41
42 #
43 # UNUSED
44 #
45
46 app-tests)
47 # wedges 2025
48 echo 'v-2025-10-28'
49 ;;
50 wild)
51 # wedges 2025
52 echo 'v-2025-10-28'
53 ;;
54 bloaty)
55 # wedges 2025
56 echo 'v-2025-10-28'
57 ;;
58 benchmarks)
59 # wedges 2025
60 echo 'v-2025-10-28'
61 ;;
62 benchmarks2)
63 # wedges 2025
64 # rebuild cmark, NOT uftrace
65 echo 'v-2025-10-28'
66 ;;
67 cpp-spec)
68 # wedges 2025
69 echo 'v-2025-10-28'
70 ;;
71 pea)
72 # wedges 2025
73 echo 'v-2025-10-28'
74 ;;
75 cpp-small)
76 # wedges 2025
77 echo 'v-2025-10-28'
78 ;;
79 clang)
80 # wedges 2025
81 echo 'v-2025-10-28'
82 ;;
83 ovm-tarball)
84 # wedges 2025
85 echo 'v-2025-10-28'
86 ;;
87 other-tests)
88 # wedges 2025
89 echo 'v-2025-10-28'
90 ;;
91 dev-minimal)
92 # wedges 2025
93 echo 'v-2025-10-28'
94 ;;
95 *)
96 die "Invalid image $image"
97 ;;
98 esac
99}
100
101make-soil-dir() {
102 log-context 'make-soil-dir'
103
104 mkdir --verbose -p _tmp/soil
105 ls -l -d . _tmp _tmp/soil
106
107 # Match what mount-perms does
108 chmod --changes 777 _tmp _tmp/soil
109 ls -l -d . _tmp _tmp/soil
110}
111
112show-disk-info() {
113 # Debug 'no space left on device' issue
114 echo 'DISKS'
115 df -h
116 echo
117
118 # Useful but many permissions errors
119 if false; then
120 echo 'SPACE FOR IMAGES?'
121 du --si -s ~/.local/share/ || true
122 echo
123 fi
124}
125
126podman-prune() {
127 ### Should this work on Debian?
128
129 if ! command -v podman; then
130 echo 'no podman'
131 return
132 fi
133
134 echo 'IMAGES'
135 podman images --all
136 echo
137
138 if false; then
139 # This causes an interactive prompt
140 echo 'PRUNE'
141 podman system prune || true
142 echo
143
144 show-disk-info
145
146 echo 'PRUNE AS ROOT'
147 sudo podman system prune || true
148 echo
149
150 show-disk-info
151 fi
152}
153
154mount-perms() {
155 ### Ensure that the guest can write to bind mount
156
157 local repo_root=$1
158
159 #show-disk-info
160
161 log-context 'mount-perms'
162
163 # We have to chmod all dirs because 'build/py.sh all' creates
164 # build/temp.linux-*, for example. Also can't exclude .git/ because
165 # submodules need it.
166 time find "$repo_root" -type d -a -print \
167 | xargs -d $'\n' -- chmod --changes 777 \
168 | wc -l
169 echo
170}
171
172job-reset() {
173 ### Called between jobs
174
175 #show-disk-info
176
177 log-context 'job-reset'
178
179 # The VM runs as the 'build' user on sourcehut. The podman container runs as
180 # 'uke' user, which apparently gets UID 100999.
181 #
182 # Running as 'build', we can't remove files created by the guest, so use
183 # 'sudo'.
184 #
185 # It's really these three dirs.
186 # ls -l -d _tmp/soil _tmp/soil/logs _devbuild/bin || true
187
188 sudo $0 mount-perms $PWD
189 echo
190
191 git status .
192 echo
193
194 # Similar to functions in 'build/clean.sh'
195 local -a dirs=(_tmp _bin _build _devbuild _test)
196 #local -a dirs=(_tmp)
197
198 log 'Removing temp dirs'
199 log ''
200
201 du --si -s "${dirs[@]}" || true
202 rm -r -f "${dirs[@]}"
203 echo
204
205 show-disk-info
206}
207
208image-layers-tsv() {
209 local docker=${1:-podman}
210 local image=${2:-oils-for-unix/soil-dummy}
211 local tag=${3:-latest}
212
213 # --human=0 gives us raw bytes and ISO timestamps
214 # --no-trunc shows the full command line
215
216 # Removed .CreatedAt because of this issue
217 # https://github.com/containers/podman/issues/17738
218 #echo $'num_bytes\tcreated_at\tcreated_by'
219
220 echo $'num_bytes\tcreated_by'
221
222 $docker history \
223 --no-trunc --human=0 \
224 --format json \
225 $REGISTRY/$image:$tag | python3 -c '
226import sys, json
227array = json.load(sys.stdin)
228for row in array:
229 print("%s\t%s" % (row["size"], row["CreatedBy"]))
230'
231}
232
233save-image-stats() {
234 local soil_dir=${1:-_tmp/soil}
235 local docker=${2:-podman}
236 local image=${3:-oils-for-unix/soil-dummy}
237 local tag=${4:-latest}
238
239 log "save-image-stats with $(which $docker)"
240 $docker --version
241 echo
242
243 mkdir -p $soil_dir
244
245 # NOTE: Works on my dev machine, but produces an empty table on CI?
246 $docker images "$REGISTRY/$image:v-*" > $soil_dir/images-tagged.txt
247 log "Wrote $soil_dir/images-tagged.txt"
248
249 $docker history $REGISTRY/$image:$tag > $soil_dir/image-layers.txt
250 log "Wrote $soil_dir/image-layers.txt"
251
252 image-layers-tsv $docker $image $tag > $soil_dir/image-layers.tsv
253 log "Wrote $soil_dir/image-layers.tsv"
254
255 # TODO: sum into image-layers.json
256 # - total size
257 # - earliest and layer date?
258
259 here-schema-tsv >$soil_dir/image-layers.schema.tsv <<EOF
260column_name type
261num_bytes integer
262created_at string
263created_by string
264EOF
265
266 log "Wrote $soil_dir/image-layers.schema.tsv"
267}
268
269# for both ASAN/LeakSanitizer and GDB
270readonly PTRACE_FLAGS=( --cap-add=SYS_PTRACE --security-opt seccomp=unconfined )
271
272run-job-uke() {
273 local docker=$1 # docker or podman
274 local repo_root=$2
275 local job_name=$3 # e.g. dev-minimal
276 local debug_shell=${4:-}
277
278 log-context 'run-job-uke'
279
280 # Do this on the HOST because we write the pull time into it as well. It's
281 # shared between guest and host.
282 make-soil-dir
283 local soil_dir=$repo_root/_tmp/soil
284
285 local image_id=$job_name
286 # Some jobs don't have their own image, and some need docker -t
287 case $job_name in
288 app-tests)
289 ;;
290 cpp-coverage)
291 image_id='clang'
292 ;;
293 cpp-tarball)
294 image_id='cpp-small'
295 ;;
296 benchmarks3)
297 # for test/syscall
298 #image_id='ovm-tarball'
299
300 # TODO: use wait-for-tarball
301 #image_id='benchmarks2'
302
303 # Ninja build
304 image_id='benchmarks'
305 ;;
306 interactive)
307 # Reuse for now
308 image_id='benchmarks'
309 ;;
310 esac
311
312 local -a flags=()
313 case $job_name in
314 app-tests|interactive)
315 # app-tests: to run ble.sh tests
316 # interactive: to run 'interactive-osh' with job control enabled
317 flags+=( -t )
318 ;;
319 esac
320
321 case $job_name in
322 cpp-small|cpp-spec|cpp-tarball|ovm-tarball)
323 # podman requires an additional flag for ASAN, so it can use ptrace()
324 # Otherwise we get:
325 # ==1194==LeakSanitizer has encountered a fatal error.
326 # ==1194==HINT: LeakSanitizer does not work under ptrace (strace, gdb, etc)
327 #
328 # Note: somehow this isn't necessary locally: on podamn 4.3.1 on Debian 12
329 if test $docker = podman; then
330 flags+=( "${PTRACE_FLAGS[@]}" )
331 fi
332 esac
333
334 local image="ghcr.io/oils-for-unix/soil-$image_id"
335
336 local tag=$(live-image-tag $image_id)
337
338 local pull_status
339 # Use external time command in POSIX format, so it's consistent between hosts
340 set -o errexit
341 command time -p -o $soil_dir/image-pull-time.txt \
342 $docker pull $REGISTRY/$image:$tag
343 pull_status=$?
344 set +o errexit
345
346 if test $pull_status -ne 0; then
347 log "$docker pull failed with status $pull_status"
348
349 # Save status for a check later
350 mkdir -p _soil-jobs
351 echo "$pull_status" > _soil-jobs/$job_name.status.txt
352
353 # Return success
354 return
355 fi
356
357 save-image-stats $soil_dir $docker $image $tag
358
359 show-disk-info
360
361 podman-prune
362
363 local -a args
364 if test -n "$debug_shell"; then
365 # launch interactive shell
366 flags+=( -i -t )
367
368 # So we can run GDB
369 # https://stackoverflow.com/questions/35860527/warning-error-disabling-address-space-randomization-operation-not-permitted
370 flags+=( "${PTRACE_FLAGS[@]}" )
371
372 args=(bash)
373 else
374 args=(sh -c "cd /home/uke/oil; soil/worker.sh JOB-$job_name")
375 fi
376
377 set -x
378 $docker run "${flags[@]}" \
379 --mount "type=bind,source=$repo_root,target=/home/uke/oil" \
380 $REGISTRY/$image:$tag \
381 "${args[@]}"
382 set +x
383}
384
385did-all-succeed() {
386 ### Check if the given jobs succeeded
387
388 local max_status=0
389 for job_name in "$@"; do
390 local status
391 read status unused_job_id < "_soil-jobs/$job_name.status.txt"
392
393 echo "$job_name status: $status"
394 if test $status -gt $max_status; then
395 max_status=$status
396 fi
397 done
398
399 log ''
400 log "Exiting with max job status $max_status"
401
402 return "$max_status"
403}
404
405local-test-uke() {
406 ### Something I can run locally. This is fast.
407
408 # Simulate sourcehut with 'local-test-uke dummy dummy'
409 local job_name=${1:-dummy}
410 local job2=${2:-}
411 local debug_shell=${3:-} # add 'bash' to change it to a debug shell
412 local docker=${4:-podman}
413
414 local branch=$(git rev-parse --abbrev-ref HEAD)
415
416 local fresh_clone=/tmp/soil-$job_name
417 rm -r -f -v $fresh_clone
418
419 local this_repo=$PWD
420 git clone $this_repo $fresh_clone
421 cd $fresh_clone
422 git checkout $branch
423
424 sudo $0 mount-perms $fresh_clone
425 sudo $0 run-job-uke "$docker" $fresh_clone $job_name "$debug_shell"
426
427 # Run another job in the same container, to test interactions
428
429 if test -n "$job2"; then
430 $0 job-reset
431 sudo $0 run-job-uke "$docker" $fresh_clone $job2
432 fi
433}
434
435local-shell() {
436 local job_name=${1:-cpp}
437
438 # no job 2
439 local-test-uke $job_name '' bash
440}
441
442cleanup() {
443 sudo rm -r -f -v _tmp/soil /tmp/soil-*
444}
445
446"$@"