AtlatestRepositorycore-channel
core-channel / tree / toolchainsetup.sh
1
#!/bin/sh2
set -eu4
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)5
archive="$root/zig-x86_64-linux-0.16.0.tar.xz"6
expected=$(awk 'NF && $1 !~ /^#/ { print $1; exit }' "$root/MANIFEST")7
actual=$(sha256sum "$archive" | awk '{print $1}')8
[ "$actual" = "$expected" ] || { echo "zig sha256 mismatch" >&2; exit 1; }10
rm -rf "$root/zig-x86_64-linux-0.16.0" "$root/bin"11
tar -C "$root" -xf "$archive"12
mkdir -p "$root/bin"14
# ---------------------------------------------------------------------------15
# THE EXPLICIT-TARGET GUARD (P1.3)16
#17
# `bin/zig` used to be a symlink to the zig binary. It is now a guard that18
# REFUSES a compile or a link with no explicit `-target`, and only then execs19
# the real binary. The rule it enforces is normative and thrice-measured:20
# targets are always explicit inside the build sandbox; loaders are inputs, not21
# ambient. Any "native" invocation inside the sandbox is a bug BY DEFINITION.22
#23
# WHY A REFUSAL AND NOT A DEFAULT. Zig does not fail when it has nothing to24
# detect -- it GUESSES, and the guess is silent. Measured 2026-07-31 on this25
# toolchain: bare `zig cc` bakes the interpreter26
# `/gnu/store/<hash>-glibc-2.41/lib/ld-linux-x86-64.so.2` into every executable27
# it links. That is a host-specific, guix-generation-specific absolute path28
# which is not a declared input, so inside the namespace it does not exist and29
# GNU hello dies at AC_PROG_CC. The spike measured the same guess landing30
# somewhere far worse: a SEGFAULTING `temacs`, with no error at all.31
#32
# WHAT THIS GUARD IS AND IS NOT. It is a defect detector on the invocation, so33
# the diagnostic names the cause instead of surfacing as an ENOENT or a34
# segfault hours later. It is NOT a security boundary: a build that reaches35
# past $PATH for `../zig-x86_64-linux-0.16.0/zig` is not stopped by it. The36
# structural backstop for "however the condition arrives" is the post-link37
# artifact check (test/integration/elf-dynsym-collapse-gate.sh in the38
# monorepo), which reads the produced ELF rather than the command line.39
#40
# The guard sits on the HOT path: the musl wrappers below exec it too, so every41
# compile in every build exercises it. A guard nothing routes through is the42
# vacuous-gate family one step earlier.43
cat > "$root/bin/zig" <<'EOF'44
#!/bin/sh45
set -eu46
here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)47
real="$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 dangling51
# zig would exec-fail with 127, which reads like a compiler error rather than52
# 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." >&256
echo "zig-guard: this is a BROKEN TOOLCHAIN, not a compile failure." >&257
echo "zig-guard: re-run toolchain/setup.sh." >&258
exit 7059
}61
# `case` rather than `[ ... ] && exec`: under `set -e` a failing `[` is the62
# status of the whole AND-OR list, so the guard would exit 1 on the FIRST63
# non-matching argument and never reach the refusal. Removing the construct64
# beats escaping it correctly.65
case "${1:-}" in66
cc|c++|build-exe|build-lib|build-obj|translate-c)67
for arg in "$@"; do68
case "$arg" in69
-target|--target=*) exec "$real" "$@" ;;70
esac71
done72
echo "zig-guard: REFUSING 'zig $1' with no explicit -target." >&273
echo "zig-guard:" >&274
echo "zig-guard: Inside the build sandbox there is nothing for zig's native" >&275
echo "zig-guard: detection to probe, so it does not fail -- it guesses, and" >&276
echo "zig-guard: bakes a host /gnu/store glibc loader into the output. That" >&277
echo "zig-guard: loader is not a declared input and does not exist in the" >&278
echo "zig-guard: namespace, so the result fails at exec or segfaults." >&279
echo "zig-guard:" >&280
echo "zig-guard: Use the wrappers -- x86_64-linux-musl-cc / -ar / -ranlib --" >&281
echo "zig-guard: or pass -target explicitly." >&282
exit 7883
;;84
esac85
exec "$real" "$@"86
EOF87
chmod +x "$root/bin/zig"88
cat > "$root/bin/x86_64-linux-musl-cc" <<'EOF'89
#!/bin/sh90
set -eu91
here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)92
export ZIG_GLOBAL_CACHE_DIR=${ZIG_GLOBAL_CACHE_DIR:-/tmp/sigil-channel/zig-global-cache}93
export ZIG_LOCAL_CACHE_DIR=${ZIG_LOCAL_CACHE_DIR:-/tmp/sigil-channel/zig-local-cache}94
exec "$here/zig" cc -target x86_64-linux-musl "$@"95
EOF96
chmod +x "$root/bin/x86_64-linux-musl-cc"97
cat > "$root/bin/x86_64-linux-musl-ar" <<'EOF'98
#!/bin/sh99
set -eu100
here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)101
exec "$here/zig" ar "$@"102
EOF103
cat > "$root/bin/x86_64-linux-musl-ranlib" <<'EOF'104
#!/bin/sh105
set -eu106
here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)107
exec "$here/zig" ranlib "$@"108
EOF109
chmod +x "$root/bin/x86_64-linux-musl-ar" "$root/bin/x86_64-linux-musl-ranlib"110
ln -s x86_64-linux-musl-ar "$root/bin/ar"111
ln -s x86_64-linux-musl-ranlib "$root/bin/ranlib"112
cat > "$root/bin/ld" <<'EOF'113
#!/bin/sh114
set -eu115
here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)116
exec "$here/zig" ld.lld "$@"117
EOF118
chmod +x "$root/bin/ld"119
cat > "$root/bin/copy-strip" <<'EOF'120
#!/bin/sh121
set -eu122
out=123
input=124
while [ "$#" -gt 0 ]; do125
case "$1" in126
-o) out=$2; shift 2 ;;127
-*) shift ;;128
*) input=$1; shift ;;129
esac130
done131
[ -n "$input" ]132
if [ -n "$out" ]; then cp "$input" "$out"; fi133
EOF134
chmod +x "$root/bin/copy-strip"135
"$root/bin/zig" version