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

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