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

398 lines, 229 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
10
11set -o nounset
12set -o pipefail
13set -o errexit
14
15REPO_ROOT=$(cd "$(dirname $0)/.."; pwd)
16
17source soil/common.sh
18source test/tsv-lib.sh
19
20live-image-tag() {
21 ### image ID -> Docker tag name
22 local image_id=$1
23
24 case $image_id in
25 app-tests)
26 # update to Debian 12
27 echo 'v-2025-04-30b'
28 ;;
29 wild)
30 # update to Debian 12
31 echo 'v-2025-04-30b'
32 ;;
33 bloaty)
34 # update to Debian 12
35 echo 'v-2025-05-01'
36 ;;
37 benchmarks)
38 # Add 'pyte' terminal emulator library to py3-libs, for testing
39 echo 'v-2025-04-30b'
40 ;;
41 benchmarks2)
42 # refresh
43 echo 'v-2025-05-01'
44 ;;
45 cpp-spec)
46 # update to Debian 12
47 echo 'v-2025-05-01'
48 ;;
49 pea)
50 # update to Debian 12
51 echo 'v-2025-04-30b'
52 ;;
53 cpp-small)
54 # update to Debian 12
55 echo 'v-2025-05-01'
56 ;;
57 clang)
58 # update to Debian 12
59 echo 'v-2025-05-01'
60 ;;
61 ovm-tarball)
62 # update to Debian 12
63 echo 'v-2025-04-30b'
64 ;;
65 other-tests)
66 # update to Debian 12
67 echo 'v-2025-04-30b'
68 ;;
69 dummy)
70 # update to Debian 12
71 echo 'v-2025-04-30b'
72 ;;
73 dev-minimal)
74 # rebuild python 2 wedge with libreadline-dev in wedge-bootstrap-debian-12
75 echo 'v-2025-05-01'
76 ;;
77
78 # Not run directly
79 common)
80 # Rebuild with wedges
81 echo 'v-2023-02-28f'
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
196save-image-stats() {
197 local soil_dir=${1:-_tmp/soil}
198 local docker=${2:-docker}
199 local image=${3:-oilshell/soil-dummy}
200 local tag=${4:-latest}
201
202 # TODO: write image.json with the name and tag?
203
204 mkdir -p $soil_dir
205
206 # NOTE: Works on my dev machine, but produces an empty table on CI?
207 $docker images "$image:v-*" > $soil_dir/images-tagged.txt
208 log "Wrote $soil_dir/images-tagged.txt"
209
210 $docker history $image:$tag > $soil_dir/image-layers.txt
211 log "Wrote $soil_dir/image-layers.txt"
212
213 # NOTE: Works with docker but not podman! podman doesn't support --format ?
214 {
215 # --human=0 gives us raw bytes and ISO timestamps
216 # --no-trunc shows the full command line
217 echo $'num_bytes\tcreated_at\tcreated_by'
218 $docker history --no-trunc --human=0 --format '{{.Size}}\t{{.CreatedAt}}\t{{.CreatedBy}}' $image:$tag
219 } > $soil_dir/image-layers.tsv
220 log "Wrote $soil_dir/image-layers.tsv"
221
222 # TODO: sum into image-layers.json
223 # - total size
224 # - earliest and layer date?
225
226 here-schema-tsv >$soil_dir/image-layers.schema.tsv <<EOF
227column_name type
228num_bytes integer
229created_at string
230created_by string
231EOF
232
233 log "Wrote $soil_dir/image-layers.schema.tsv"
234}
235
236run-job-uke() {
237 local docker=$1 # docker or podman
238 local repo_root=$2
239 local job_name=$3 # e.g. dev-minimal
240 local debug_shell=${4:-}
241
242 log-context 'run-job-uke'
243
244 # Do this on the HOST because we write the pull time into it as well. It's
245 # shared between guest and host.
246 make-soil-dir
247 local soil_dir=$repo_root/_tmp/soil
248
249 local -a flags=()
250
251 local image_id=$job_name
252
253 # Some jobs don't have their own image, and some need docker -t
254 case $job_name in
255 app-tests)
256 # to run ble.sh tests
257 flags=( -t )
258 ;;
259 cpp-coverage)
260 image_id='clang'
261 ;;
262 cpp-tarball)
263 image_id='cpp-small'
264 ;;
265 benchmarks3)
266 # for test/syscall
267 #image_id='ovm-tarball'
268
269 # TODO: use wait-for-tarball
270 #image_id='benchmarks2'
271
272 # Ninja build
273 image_id='benchmarks'
274 ;;
275 interactive)
276 # to run 'interactive-osh' with job control enabled
277 flags=( -t )
278
279 # Reuse for now
280 image_id='benchmarks'
281 ;;
282 esac
283
284 local image="docker.io/oilshell/soil-$image_id"
285
286 local tag=$(live-image-tag $image_id)
287
288 local pull_status
289 # Use external time command in POSIX format, so it's consistent between hosts
290 set -o errexit
291 command time -p -o $soil_dir/image-pull-time.txt \
292 $docker pull $image:$tag
293 pull_status=$?
294 set +o errexit
295
296 if test $pull_status -ne 0; then
297 log "$docker pull failed with status $pull_status"
298
299 # Save status for a check later
300 mkdir -p _soil-jobs
301 echo "$pull_status" > _soil-jobs/$job_name.status.txt
302
303 # Return success
304 return
305 fi
306
307 save-image-stats $soil_dir $docker $image $tag
308
309 show-disk-info
310
311 podman-prune
312
313 local -a args
314 if test -n "$debug_shell"; then
315 # launch interactive shell
316 flags+=( -i -t )
317
318 # So we can run GDB
319 # https://stackoverflow.com/questions/35860527/warning-error-disabling-address-space-randomization-operation-not-permitted
320 flags+=( --cap-add SYS_PTRACE --security-opt seccomp=unconfined )
321
322 # can mount other tools for debugging, like clang
323 #local clang_dir=~/git/oilshell/oil_DEPS/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04
324 #flags+=( --mount "type=bind,source=$clang_dir,target=/home/uke/oil_DEPS/$(basename $clang_dir)" )
325
326 args=(bash)
327 else
328 args=(sh -c "cd /home/uke/oil; soil/worker.sh JOB-$job_name")
329 fi
330
331 $docker run "${flags[@]}" \
332 --mount "type=bind,source=$repo_root,target=/home/uke/oil" \
333 $image:$tag \
334 "${args[@]}"
335}
336
337did-all-succeed() {
338 ### Check if the given jobs succeeded
339
340 local max_status=0
341 for job_name in "$@"; do
342 local status
343 read status unused_job_id < "_soil-jobs/$job_name.status.txt"
344
345 echo "$job_name status: $status"
346 if test $status -gt $max_status; then
347 max_status=$status
348 fi
349 done
350
351 log ''
352 log "Exiting with max job status $max_status"
353
354 return "$max_status"
355}
356
357local-test-uke() {
358 ### Something I can run locally. This is fast.
359
360 # Simulate sourcehut with 'local-test-uke dummy dummy'
361 local job_name=${1:-dummy}
362 local job2=${2:-}
363 local debug_shell=${3:-} # add 'bash' to change it to a debug shell
364 local docker=${4:-docker}
365
366 local branch=$(git rev-parse --abbrev-ref HEAD)
367
368 local fresh_clone=/tmp/soil-$job_name
369 rm -r -f -v $fresh_clone
370
371 local this_repo=$PWD
372 git clone $this_repo $fresh_clone
373 cd $fresh_clone
374 git checkout $branch
375
376 sudo $0 mount-perms $fresh_clone
377 sudo $0 run-job-uke "$docker" $fresh_clone $job_name "$debug_shell"
378
379 # Run another job in the same container, to test interactions
380
381 if test -n "$job2"; then
382 $0 job-reset
383 sudo $0 run-job-uke "$docker" $fresh_clone $job2
384 fi
385}
386
387local-shell() {
388 local job_name=${1:-cpp}
389
390 # no job 2
391 local-test-uke $job_name '' bash
392}
393
394cleanup() {
395 sudo rm -r -f -v _tmp/soil /tmp/soil-*
396}
397
398"$@"