this repo has no description
1#!/usr/bin/env bash
2# build-trivial.sh — Attempt to build trivial Nix derivations inside Darling
3#
4# This script exercises Phase 4.1 of the plan: building progressively more
5# complex derivations inside a Darling prefix to validate that the full Nix
6# build pipeline works (posix_spawn → sandbox-exec → builder → store).
7#
8# Usage:
9# ./scripts/build-trivial.sh [OPTIONS]
10#
11# Options:
12# --prefix <path> Darling prefix path (default: ~/.darling or $DPREFIX)
13# --level <N> Run only derivation level N (1-5, default: all)
14# --keep Don't garbage-collect built derivations
15# --verbose Show full nix-build output
16# --debug Pass -vvvv --debug to nix-build (very verbose)
17# --help Show this help message
18#
19# Derivation Levels:
20# 1 — Echo to $out (no deps, /bin/bash only)
21# 2 — Multi-line builder script writing to $out
22# 3 — Derivation that reads and transforms input files
23# 4 — Derivation with a dependency on another derivation
24# 5 — Fetch a file from the binary cache (requires network)
25#
26# Exit codes:
27# 0 — all attempted levels passed
28# 1 — one or more levels failed
29# 2 — infrastructure error
30#
31# See: plan/06-phase4-building.md (Task 4.1)
32
33set -euo pipefail
34
35SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
36
37# ── Defaults ────────────────────────────────────────────────────────────────
38
39DARLING_PREFIX="${DPREFIX:-$HOME/.darling}"
40SELECTED_LEVEL=""
41KEEP_BUILDS=0
42VERBOSE=0
43DEBUG=0
44
45# ── Colors ──────────────────────────────────────────────────────────────────
46
47if [ -t 1 ]; then
48 RED='\033[0;31m'
49 GREEN='\033[0;32m'
50 YELLOW='\033[0;33m'
51 BLUE='\033[0;34m'
52 BOLD='\033[1m'
53 DIM='\033[2m'
54 RESET='\033[0m'
55else
56 RED='' GREEN='' YELLOW='' BLUE='' BOLD='' DIM='' RESET=''
57fi
58
59# ── Helpers ─────────────────────────────────────────────────────────────────
60
61log() { echo -e "${GREEN}[build-trivial]${RESET} $*"; }
62warn() { echo -e "${YELLOW}[build-trivial] WARNING:${RESET} $*" >&2; }
63err() { echo -e "${RED}[build-trivial] ERROR:${RESET} $*" >&2; }
64fatal() { err "$@"; exit 2; }
65
66dsh() {
67 darling shell "$@"
68}
69
70# Run a command inside Darling with Nix on PATH
71dsh_nix() {
72 local nix_flags=""
73 if [ "$DEBUG" -eq 1 ]; then
74 nix_flags="-vvvv --debug"
75 fi
76
77 darling shell bash -lc "
78 # Source Nix profile
79 if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
80 . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
81 elif [ -e \"\\\$HOME/.nix-profile/etc/profile.d/nix.sh\" ]; then
82 . \"\\\$HOME/.nix-profile/etc/profile.d/nix.sh\"
83 elif [ -e '/etc/profile.d/nix-darling.sh' ]; then
84 . '/etc/profile.d/nix-darling.sh'
85 fi
86 $*
87 " 2>&1
88}
89
90usage() {
91 cat <<'EOF'
92Usage: ./scripts/build-trivial.sh [OPTIONS]
93
94Build progressively more complex Nix derivations inside Darling to validate
95the full build pipeline.
96
97Options:
98 --prefix <path> Darling prefix (default: ~/.darling or $DPREFIX)
99 --level <N> Run only level N (1-5; default: all)
100 --keep Don't garbage-collect built derivations
101 --verbose Show full nix-build output
102 --debug Pass -vvvv --debug to nix-build
103 --help Show this help
104
105Levels:
106 1 Echo to $out — minimal: /bin/bash writes a one-liner to $out
107 2 Multi-line builder — bash script with variables, loops, file I/O
108 3 Input transformation — derivation that reads from an input file
109 4 Derivation dependency — one derivation depends on another
110 5 Binary substitution — fetch a pre-built path from cache.nixos.org
111
112Each level exercises more of the Nix + Darling stack. If a level fails,
113the script prints debugging hints specific to that level's failure mode.
114EOF
115 exit 0
116}
117
118# ── Argument Parsing ────────────────────────────────────────────────────────
119
120while [ $# -gt 0 ]; do
121 case "$1" in
122 --prefix)
123 [ $# -ge 2 ] || fatal "--prefix requires an argument"
124 DARLING_PREFIX="$2"
125 shift 2
126 ;;
127 --level)
128 [ $# -ge 2 ] || fatal "--level requires an argument"
129 SELECTED_LEVEL="$2"
130 if ! [[ "$SELECTED_LEVEL" =~ ^[1-5]$ ]]; then
131 fatal "--level must be 1-5, got: $SELECTED_LEVEL"
132 fi
133 shift 2
134 ;;
135 --keep)
136 KEEP_BUILDS=1
137 shift
138 ;;
139 --verbose|-v)
140 VERBOSE=1
141 shift
142 ;;
143 --debug)
144 DEBUG=1
145 VERBOSE=1
146 shift
147 ;;
148 --help|-h)
149 usage
150 ;;
151 *)
152 fatal "Unknown option: $1 (try --help)"
153 ;;
154 esac
155done
156
157# ── Preflight ───────────────────────────────────────────────────────────────
158
159log "${BOLD}Preflight checks...${RESET}"
160
161if ! command -v darling &>/dev/null; then
162 fatal "darling is not in PATH"
163fi
164
165if [ ! -d "$DARLING_PREFIX" ]; then
166 fatal "Darling prefix not found at $DARLING_PREFIX"
167fi
168
169if ! dsh echo ok &>/dev/null; then
170 fatal "darling shell is not functional"
171fi
172
173# Check Nix is installed
174nix_version=""
175nix_version=$(dsh_nix "nix --version" 2>&1) || true
176if [ -z "$nix_version" ] || ! echo "$nix_version" | grep -qi "nix"; then
177 fatal "Nix does not appear to be installed in the Darling prefix.\n" \
178 " Run: ./scripts/install-nix-in-darling.sh"
179fi
180
181log " Prefix: $DARLING_PREFIX"
182log " Nix: $nix_version"
183
184# ── Level Definitions ───────────────────────────────────────────────────────
185
186# Each level is a function that:
187# - Prints what it exercises
188# - Runs the build
189# - Returns 0 on success, 1 on failure
190
191level1_echo_to_out() {
192 cat <<'DESC'
193 Level 1: Echo to $out
194 Exercises: posix_spawn, sandbox-exec stub, /bin/bash, file creation
195 in /nix/store, store path registration, chmod/lchflags on
196 store path.
197DESC
198
199 local expr='derivation {
200 name = "darling-test-1-echo";
201 builder = "/bin/bash";
202 args = [ "-c" "echo Hello from Darling > $out" ];
203 system = "x86_64-darwin";
204 }'
205
206 local nix_flags=""
207 if [ "$DEBUG" -eq 1 ]; then
208 nix_flags="-vvvv"
209 fi
210
211 local output=""
212 local exit_code=0
213 output=$(dsh_nix "nix-build --no-out-link $nix_flags --expr '$expr' 2>&1") || exit_code=$?
214
215 if [ "$VERBOSE" -eq 1 ]; then
216 echo "$output" | sed 's/^/ /'
217 fi
218
219 if [ "$exit_code" -ne 0 ]; then
220 echo "$output" | tail -20 | sed 's/^/ /'
221 echo ""
222 echo " Debugging hints for Level 1 failure:"
223 echo " • 'clearing flags of path': lchflags still broken (Phase 1.1)"
224 echo " • 'Bad file descriptor' / ENOEXEC: sandbox-exec stub issue (Phase 2)"
225 echo " • 'sandbox profile' write fail: check /tmp is writable inside prefix"
226 echo " • Builder hangs: posix_spawn with POSIX_SPAWN_SETEXEC broken"
227 echo " • 'Unimplemented syscall': check plan/syscall-triage.md"
228 echo ""
229 echo " Manual reproduction:"
230 echo " darling shell bash -lc 'nix-build -vvvv --no-out-link --expr \"$expr\"'"
231 echo " darling shell /usr/bin/sandbox-exec -f /dev/null /bin/bash -c 'echo ok'"
232 return 1
233 fi
234
235 # Verify the output exists and has the right content
236 local store_path
237 store_path=$(echo "$output" | grep '^/nix/store/' | tail -1)
238 if [ -z "$store_path" ]; then
239 echo " Build appeared to succeed but no store path in output."
240 echo " Output: $output"
241 return 1
242 fi
243
244 local content
245 content=$(dsh_nix "cat '$store_path'" 2>&1) || true
246 if echo "$content" | grep -q "Hello from Darling"; then
247 echo " Store path: $store_path"
248 echo " Content: $(echo "$content" | head -1)"
249 return 0
250 else
251 echo " Store path content mismatch."
252 echo " Expected: 'Hello from Darling'"
253 echo " Got: '$content'"
254 return 1
255 fi
256}
257
258level2_multiline_builder() {
259 cat <<'DESC'
260 Level 2: Multi-line builder script
261 Exercises: bash scripting, variables, loops, file I/O, mkdir, directory
262 output (vs single file), multiple files in $out.
263DESC
264
265 local expr='derivation {
266 name = "darling-test-2-multiline";
267 builder = "/bin/bash";
268 args = [ "-c" "
269 set -e
270 mkdir -p $out/bin $out/share
271 echo \"#!/bin/bash\" > $out/bin/hello
272 echo \"echo Hello from Darling build\" >> $out/bin/hello
273 chmod +x $out/bin/hello
274 for i in 1 2 3; do
275 echo \"Item $i\" > $out/share/item-$i.txt
276 done
277 echo done > $out/share/status.txt
278 " ];
279 system = "x86_64-darwin";
280 }'
281
282 local nix_flags=""
283 if [ "$DEBUG" -eq 1 ]; then
284 nix_flags="-vvvv"
285 fi
286
287 local output=""
288 local exit_code=0
289 output=$(dsh_nix "nix-build --no-out-link $nix_flags --expr '$expr' 2>&1") || exit_code=$?
290
291 if [ "$VERBOSE" -eq 1 ]; then
292 echo "$output" | sed 's/^/ /'
293 fi
294
295 if [ "$exit_code" -ne 0 ]; then
296 echo "$output" | tail -20 | sed 's/^/ /'
297 echo ""
298 echo " Debugging hints for Level 2 failure:"
299 echo " • mkdir/chmod failures: filesystem or syscall issue"
300 echo " • 'for' loop issues: bash not fully working in sandbox"
301 echo " • Same as Level 1 hints if Level 1 also failed"
302 return 1
303 fi
304
305 local store_path
306 store_path=$(echo "$output" | grep '^/nix/store/' | tail -1)
307 if [ -z "$store_path" ]; then
308 echo " No store path in output."
309 return 1
310 fi
311
312 # Verify directory structure
313 local verify_exit=0
314 dsh_nix "
315 test -x '$store_path/bin/hello' &&
316 test -f '$store_path/share/item-1.txt' &&
317 test -f '$store_path/share/item-2.txt' &&
318 test -f '$store_path/share/item-3.txt' &&
319 test -f '$store_path/share/status.txt' &&
320 grep -q 'done' '$store_path/share/status.txt'
321 " >/dev/null 2>&1 || verify_exit=$?
322
323 if [ "$verify_exit" -eq 0 ]; then
324 echo " Store path: $store_path"
325 echo " Verified: bin/hello (executable), share/item-{1,2,3}.txt, share/status.txt"
326 return 0
327 else
328 echo " Output verification failed."
329 echo " Store path: $store_path"
330 dsh_nix "find '$store_path' -type f 2>/dev/null" | sed 's/^/ /' || true
331 return 1
332 fi
333}
334
335level3_input_transform() {
336 cat <<'DESC'
337 Level 3: Input transformation
338 Exercises: builtins.toFile, passing string context to a derivation,
339 reading input files, text processing (wc, sort).
340DESC
341
342 local expr='let
343 input = builtins.toFile "input.txt" "apple\nbanana\ncherry\ndate\nelderberry\n";
344 in derivation {
345 name = "darling-test-3-transform";
346 builder = "/bin/bash";
347 args = [ "-c" "
348 set -e
349 mkdir -p $out
350 cp ${input} $out/original.txt
351 sort ${input} > $out/sorted.txt
352 wc -l < ${input} | tr -d \" \" > $out/count.txt
353 " ];
354 system = "x86_64-darwin";
355 inherit input;
356 }'
357
358 local nix_flags=""
359 if [ "$DEBUG" -eq 1 ]; then
360 nix_flags="-vvvv"
361 fi
362
363 local output=""
364 local exit_code=0
365 output=$(dsh_nix "nix-build --no-out-link $nix_flags --expr '$expr' 2>&1") || exit_code=$?
366
367 if [ "$VERBOSE" -eq 1 ]; then
368 echo "$output" | sed 's/^/ /'
369 fi
370
371 if [ "$exit_code" -ne 0 ]; then
372 echo "$output" | tail -20 | sed 's/^/ /'
373 echo ""
374 echo " Debugging hints for Level 3 failure:"
375 echo " • builtins.toFile failure: store write issue"
376 echo " • sort/wc not found: PATH issue inside build sandbox"
377 echo " • Input file not readable: store path access issue"
378 return 1
379 fi
380
381 local store_path
382 store_path=$(echo "$output" | grep '^/nix/store/' | tail -1)
383 if [ -z "$store_path" ]; then
384 echo " No store path in output."
385 return 1
386 fi
387
388 local count
389 count=$(dsh_nix "cat '$store_path/count.txt'" 2>&1 | tr -d '[:space:]')
390 if [ "$count" = "5" ]; then
391 echo " Store path: $store_path"
392 echo " Line count: $count (correct)"
393 return 0
394 else
395 echo " Count verification failed: expected '5', got '$count'"
396 return 1
397 fi
398}
399
400level4_derivation_dependency() {
401 cat <<'DESC'
402 Level 4: Derivation dependency
403 Exercises: one derivation depending on another's output, Nix's dependency
404 tracking, incremental builds, store path references.
405DESC
406
407 local expr='let
408 dep = derivation {
409 name = "darling-test-4-dep";
410 builder = "/bin/bash";
411 args = [ "-c" "echo DEPENDENCY_OUTPUT > $out" ];
412 system = "x86_64-darwin";
413 };
414 in derivation {
415 name = "darling-test-4-consumer";
416 builder = "/bin/bash";
417 args = [ "-c" "
418 set -e
419 mkdir -p $out
420 content=\$(cat ${dep})
421 echo \"Read from dep: \$content\" > $out/result.txt
422 echo ${dep} > $out/dep-path.txt
423 " ];
424 system = "x86_64-darwin";
425 inherit dep;
426 }'
427
428 local nix_flags=""
429 if [ "$DEBUG" -eq 1 ]; then
430 nix_flags="-vvvv"
431 fi
432
433 local output=""
434 local exit_code=0
435 output=$(dsh_nix "nix-build --no-out-link $nix_flags --expr '$expr' 2>&1") || exit_code=$?
436
437 if [ "$VERBOSE" -eq 1 ]; then
438 echo "$output" | sed 's/^/ /'
439 fi
440
441 if [ "$exit_code" -ne 0 ]; then
442 echo "$output" | tail -20 | sed 's/^/ /'
443 echo ""
444 echo " Debugging hints for Level 4 failure:"
445 echo " • If dep itself failed: same as Level 1/2 hints"
446 echo " • If consumer failed reading dep: store path access issue"
447 echo " • Build order issue: Nix should build dep first automatically"
448 return 1
449 fi
450
451 local store_path
452 store_path=$(echo "$output" | grep '^/nix/store/' | tail -1)
453 if [ -z "$store_path" ]; then
454 echo " No store path in output."
455 return 1
456 fi
457
458 local result
459 result=$(dsh_nix "cat '$store_path/result.txt'" 2>&1)
460 if echo "$result" | grep -q "DEPENDENCY_OUTPUT"; then
461 echo " Store path: $store_path"
462 echo " Result: $result"
463 echo " Dep path: $(dsh_nix "cat '$store_path/dep-path.txt'" 2>&1 | head -1)"
464 return 0
465 else
466 echo " Dependency content not found in result."
467 echo " Expected to contain: 'DEPENDENCY_OUTPUT'"
468 echo " Got: '$result'"
469 return 1
470 fi
471}
472
473level5_binary_substitution() {
474 cat <<'DESC'
475 Level 5: Binary substitution (requires network)
476 Exercises: curl/TLS, binary cache access, NAR download, signature
477 verification, store path registration from remote.
478DESC
479
480 # Check if we can reach the cache first
481 local cache_check=""
482 cache_check=$(dsh_nix "curl -sfI https://cache.nixos.org/nix-cache-info 2>&1 | head -1") || true
483 if ! echo "$cache_check" | grep -qi "200"; then
484 echo " Cannot reach cache.nixos.org — skipping Level 5."
485 echo " (This level requires network access.)"
486 echo " cache check result: $cache_check"
487 return 2 # special: skip, not fail
488 fi
489
490 # Try to realise a simple, very small store path from the cache.
491 # We use `nix-store -r` on a known path, or `nix build` with --substituters.
492 # The safest approach: evaluate a nixpkgs attr and fetch it.
493 local output=""
494 local exit_code=0
495 output=$(dsh_nix "
496 # Try to fetch a tiny package from the cache.
497 # 'hello' is small and almost always cached.
498 nix build --no-link --print-out-paths \\
499 --expr '(import <nixpkgs> {}).hello' \\
500 --substituters https://cache.nixos.org \\
501 --trusted-public-keys 'cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=' \\
502 2>&1
503 ") || exit_code=$?
504
505 if [ "$VERBOSE" -eq 1 ]; then
506 echo "$output" | sed 's/^/ /'
507 fi
508
509 if [ "$exit_code" -ne 0 ]; then
510 echo "$output" | tail -20 | sed 's/^/ /'
511 echo ""
512 echo " Debugging hints for Level 5 failure:"
513 echo " • 'SSL' / 'TLS' error: Darling's certificate bundle may be outdated"
514 echo " • 'cannot connect': DNS or network issue inside Darling"
515 echo " • '<nixpkgs>' not found: channels not set up — run nix-channel --update"
516 echo " • NAR download failure: curl or decompression issue"
517 echo " • Signature mismatch: trusted-public-keys not configured"
518 return 1
519 fi
520
521 local store_path
522 store_path=$(echo "$output" | grep '^/nix/store/' | tail -1)
523 if [ -z "$store_path" ]; then
524 echo " Build output did not contain a store path."
525 echo " Output: $(echo "$output" | tail -5)"
526 return 1
527 fi
528
529 # Try running the fetched hello binary
530 local hello_output
531 hello_output=$(dsh_nix "'$store_path/bin/hello'" 2>&1) || true
532 if echo "$hello_output" | grep -qi "hello"; then
533 echo " Store path: $store_path"
534 echo " Output: $hello_output"
535 return 0
536 else
537 echo " Store path: $store_path"
538 echo " hello binary did not produce expected output: $hello_output"
539 echo " (This may be OK — the binary was fetched, which is the main test.)"
540 return 0
541 fi
542}
543
544# ── Determine which levels to run ──────────────────────────────────────────
545
546ALL_LEVELS=(1 2 3 4 5)
547
548if [ -n "$SELECTED_LEVEL" ]; then
549 RUN_LEVELS=("$SELECTED_LEVEL")
550else
551 RUN_LEVELS=("${ALL_LEVELS[@]}")
552fi
553
554# ── Execute ─────────────────────────────────────────────────────────────────
555
556echo ""
557log "${BOLD}═══════════════════════════════════════════════════════════${RESET}"
558log "${BOLD} Trivial Derivation Build Tests (Phase 4.1)${RESET}"
559log "${BOLD}═══════════════════════════════════════════════════════════${RESET}"
560echo ""
561
562LEVELS_PASSED=0
563LEVELS_FAILED=0
564LEVELS_SKIPPED=0
565
566declare -A LEVEL_RESULT # "pass", "fail", "skip"
567LEVEL_NAMES=(
568 [1]="Echo to \$out"
569 [2]="Multi-line builder"
570 [3]="Input transformation"
571 [4]="Derivation dependency"
572 [5]="Binary substitution (network)"
573)
574
575LEVEL_FUNCS=(
576 [1]=level1_echo_to_out
577 [2]=level2_multiline_builder
578 [3]=level3_input_transform
579 [4]=level4_derivation_dependency
580 [5]=level5_binary_substitution
581)
582
583for level in "${RUN_LEVELS[@]}"; do
584 echo -e "${BLUE}━━━ Level $level: ${LEVEL_NAMES[$level]} ━━━${RESET}"
585 echo ""
586
587 exit_code=0
588 ${LEVEL_FUNCS[$level]} || exit_code=$?
589
590 echo ""
591
592 if [ "$exit_code" -eq 0 ]; then
593 LEVELS_PASSED=$((LEVELS_PASSED + 1))
594 LEVEL_RESULT[$level]="pass"
595 echo -e " ${GREEN}✓ Level $level PASSED${RESET}"
596 elif [ "$exit_code" -eq 2 ]; then
597 LEVELS_SKIPPED=$((LEVELS_SKIPPED + 1))
598 LEVEL_RESULT[$level]="skip"
599 echo -e " ${YELLOW}⊘ Level $level SKIPPED${RESET}"
600 else
601 LEVELS_FAILED=$((LEVELS_FAILED + 1))
602 LEVEL_RESULT[$level]="fail"
603 echo -e " ${RED}✗ Level $level FAILED${RESET}"
604
605 # If an early level fails, later levels will almost certainly fail too
606 if [ "$level" -le 2 ] && [ -z "$SELECTED_LEVEL" ]; then
607 warn "Level $level failed — skipping remaining levels (they depend on this)."
608 for remaining in "${RUN_LEVELS[@]}"; do
609 if [ "$remaining" -gt "$level" ] && [ -z "${LEVEL_RESULT[$remaining]+x}" ]; then
610 LEVELS_SKIPPED=$((LEVELS_SKIPPED + 1))
611 LEVEL_RESULT[$remaining]="skip"
612 fi
613 done
614 break
615 fi
616 fi
617
618 echo ""
619done
620
621# ── Garbage Collection ──────────────────────────────────────────────────────
622
623if [ "$KEEP_BUILDS" -eq 0 ] && [ "$LEVELS_PASSED" -gt 0 ]; then
624 log "Cleaning up test derivations..."
625 dsh_nix "nix-collect-garbage 2>/dev/null" >/dev/null 2>&1 || true
626else
627 if [ "$KEEP_BUILDS" -eq 1 ]; then
628 log "Keeping built derivations (--keep)."
629 fi
630fi
631
632# ── Summary ─────────────────────────────────────────────────────────────────
633
634TOTAL=$((LEVELS_PASSED + LEVELS_FAILED + LEVELS_SKIPPED))
635
636echo ""
637log "${BOLD}═══════════════════════════════════════════════════════════${RESET}"
638log "${BOLD} Build Test Summary${RESET}"
639log "${BOLD}═══════════════════════════════════════════════════════════${RESET}"
640echo ""
641
642printf " %-5s %-35s %s\n" "Level" "Name" "Result"
643printf " %-5s %-35s %s\n" "─────" "───────────────────────────────────" "──────"
644
645for level in "${ALL_LEVELS[@]}"; do
646 result="${LEVEL_RESULT[$level]:-skip}"
647 case "$result" in
648 pass) printf " %-5s %-35s ${GREEN}✓ PASS${RESET}\n" "$level" "${LEVEL_NAMES[$level]}" ;;
649 fail) printf " %-5s %-35s ${RED}✗ FAIL${RESET}\n" "$level" "${LEVEL_NAMES[$level]}" ;;
650 skip) printf " %-5s %-35s ${YELLOW}⊘ SKIP${RESET}\n" "$level" "${LEVEL_NAMES[$level]}" ;;
651 esac
652done
653
654echo ""
655printf " Total: %d levels\n" "$TOTAL"
656printf " Passed: ${GREEN}%d${RESET}\n" "$LEVELS_PASSED"
657printf " Failed: ${RED}%d${RESET}\n" "$LEVELS_FAILED"
658printf " Skipped: ${YELLOW}%d${RESET}\n" "$LEVELS_SKIPPED"
659echo ""
660
661if [ "$LEVELS_FAILED" -gt 0 ]; then
662 err "Some build levels failed."
663 echo "" >&2
664 echo "Next steps:" >&2
665 echo " • Run a single level: $0 --level N --debug" >&2
666 echo " • Manual build: darling shell bash -lc 'nix-build -vvvv --expr \"...\"'" >&2
667 echo " • Manual sandbox test: darling shell /usr/bin/sandbox-exec -f /dev/null /bin/bash -c 'echo ok'" >&2
668 echo " • Check syscalls: ./scripts/triage-syscalls.sh" >&2
669 echo " • Host-side trace: strace -f -p \$(pidof darlingserver) 2>&1 | head -500" >&2
670 echo " • Darling xtrace: DARLING_XTRACE=1 darling shell bash -lc 'nix-build --expr ...'" >&2
671 echo "" >&2
672 exit 1
673elif [ "$LEVELS_PASSED" -eq 0 ]; then
674 warn "No levels were attempted — check that Nix is installed."
675 exit 2
676else
677 log "${GREEN}All build levels passed!${RESET}"
678 if [ "$LEVELS_SKIPPED" -gt 0 ]; then
679 log "${DIM}($LEVELS_SKIPPED levels skipped — re-run with --level N to target them)${RESET}"
680 fi
681 log ""
682 log "Phase 4.1 is complete! Next steps:"
683 log " • Phase 4.2: Test bash in build sandboxes (more complex scripts)"
684 log " • Phase 4.5: Build a C program with Darwin stdenv"
685 log " • See: plan/06-phase4-building.md"
686 exit 0
687fi