AtlatestRepositorycore-channel
1#!/bin/sh
2set -eu
3
4root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
5archive="$root/zig-x86_64-linux-0.16.0.tar.xz"
6expected=$(awk 'NF && $1 !~ /^#/ { print $1; exit }' "$root/MANIFEST")
7actual=$(sha256sum "$archive" | awk '{print $1}')
8[ "$actual" = "$expected" ] || { echo "zig sha256 mismatch" >&2; exit 1; }
9
10rm -rf "$root/zig-x86_64-linux-0.16.0" "$root/bin"
11tar -C "$root" -xf "$archive"
12mkdir -p "$root/bin"
14# ---------------------------------------------------------------------------
15# THE EXPLICIT-TARGET GUARD (P1.3)
17# `bin/zig` used to be a symlink to the zig binary. It is now a guard that
18# REFUSES a compile or a link with no explicit `-target`, and only then execs
19# the real binary. The rule it enforces is normative and thrice-measured:
20# targets are always explicit inside the build sandbox; loaders are inputs, not
21# ambient. Any "native" invocation inside the sandbox is a bug BY DEFINITION.
23# WHY A REFUSAL AND NOT A DEFAULT. Zig does not fail when it has nothing to
24# detect -- it GUESSES, and the guess is silent. Measured 2026-07-31 on this
25# toolchain: bare `zig cc` bakes the interpreter
26# `/gnu/store/<hash>-glibc-2.41/lib/ld-linux-x86-64.so.2` into every executable
27# it links. That is a host-specific, guix-generation-specific absolute path
28# which is not a declared input, so inside the namespace it does not exist and
29# GNU hello dies at AC_PROG_CC. The spike measured the same guess landing
30# somewhere far worse: a SEGFAULTING `temacs`, with no error at all.
32# WHAT THIS GUARD IS AND IS NOT. It is a defect detector on the invocation, so
33# the diagnostic names the cause instead of surfacing as an ENOENT or a
34# segfault hours later. It is NOT a security boundary: a build that reaches
35# past $PATH for `../zig-x86_64-linux-0.16.0/zig` is not stopped by it. The
36# structural backstop for "however the condition arrives" is the post-link
37# artifact check (test/integration/elf-dynsym-collapse-gate.sh in the
38# monorepo), which reads the produced ELF rather than the command line.
40# The guard sits on the HOT path: the musl wrappers below exec it too, so every
41# compile in every build exercises it. A guard nothing routes through is the
42# vacuous-gate family one step earlier.
43cat > "$root/bin/zig" <<'EOF'
44#!/bin/sh
45set -eu
46here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
47real="$here/../zig-x86_64-linux-0.16.0/zig"
49# ASSERT THE INSTRUMENT BEFORE INTERPRETING ANYTHING. `-x` resolves symlinks,
50# so it is false for a dangling one. Without this check a missing or dangling
51# zig would exec-fail with 127, which reads like a compiler error rather than
52# like a broken toolchain -- the exact conflation ("the world cannot do this"
53# vs "I could not ask") that silently degraded every build once already.
54[ -x "$real" ] || {
55 echo "zig-guard: the zig binary is missing or unusable at $real." >&2
56 echo "zig-guard: this is a BROKEN TOOLCHAIN, not a compile failure." >&2
57 echo "zig-guard: re-run toolchain/setup.sh." >&2
58 exit 70
61# `case` rather than `[ ... ] && exec`: under `set -e` a failing `[` is the
62# status of the whole AND-OR list, so the guard would exit 1 on the FIRST
63# non-matching argument and never reach the refusal. Removing the construct
64# beats escaping it correctly.
65case "${1:-}" in
66 cc|c++|build-exe|build-lib|build-obj|translate-c)
67 for arg in "$@"; do
68 case "$arg" in
69 -target|--target=*) exec "$real" "$@" ;;
70 esac
71 done
72 echo "zig-guard: REFUSING 'zig $1' with no explicit -target." >&2
73 echo "zig-guard:" >&2
74 echo "zig-guard: Inside the build sandbox there is nothing for zig's native" >&2
75 echo "zig-guard: detection to probe, so it does not fail -- it guesses, and" >&2
76 echo "zig-guard: bakes a host /gnu/store glibc loader into the output. That" >&2
77 echo "zig-guard: loader is not a declared input and does not exist in the" >&2
78 echo "zig-guard: namespace, so the result fails at exec or segfaults." >&2
79 echo "zig-guard:" >&2
80 echo "zig-guard: Use the wrappers -- x86_64-linux-musl-cc / -ar / -ranlib --" >&2
81 echo "zig-guard: or pass -target explicitly." >&2
82 exit 78
83 ;;
84esac
85exec "$real" "$@"
86EOF
87chmod +x "$root/bin/zig"
88cat > "$root/bin/x86_64-linux-musl-cc" <<'EOF'
89#!/bin/sh
90set -eu
91here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
92export ZIG_GLOBAL_CACHE_DIR=${ZIG_GLOBAL_CACHE_DIR:-/tmp/sigil-channel/zig-global-cache}
93export ZIG_LOCAL_CACHE_DIR=${ZIG_LOCAL_CACHE_DIR:-/tmp/sigil-channel/zig-local-cache}
94exec "$here/zig" cc -target x86_64-linux-musl "$@"
95EOF
96chmod +x "$root/bin/x86_64-linux-musl-cc"
97cat > "$root/bin/x86_64-linux-musl-ar" <<'EOF'
98#!/bin/sh
99set -eu
100here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
101exec "$here/zig" ar "$@"
102EOF
103cat > "$root/bin/x86_64-linux-musl-ranlib" <<'EOF'
104#!/bin/sh
105set -eu
106here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
107exec "$here/zig" ranlib "$@"
108EOF
109chmod +x "$root/bin/x86_64-linux-musl-ar" "$root/bin/x86_64-linux-musl-ranlib"
110ln -s x86_64-linux-musl-ar "$root/bin/ar"
111ln -s x86_64-linux-musl-ranlib "$root/bin/ranlib"
112cat > "$root/bin/ld" <<'EOF'
113#!/bin/sh
114set -eu
115here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
116exec "$here/zig" ld.lld "$@"
117EOF
118chmod +x "$root/bin/ld"
119cat > "$root/bin/copy-strip" <<'EOF'
120#!/bin/sh
121set -eu
122out=
123input=
124while [ "$#" -gt 0 ]; do
125 case "$1" in
126 -o) out=$2; shift 2 ;;
127 -*) shift ;;
128 *) input=$1; shift ;;
129 esac
130done
131[ -n "$input" ]
132if [ -n "$out" ]; then cp "$input" "$out"; fi
133EOF
134chmod +x "$root/bin/copy-strip"
135"$root/bin/zig" version