# G-102 buildsuite bootstrap without a pre-existing tclsh: zig-driver hybrid Status: achieved 2026-07-21 Scope: src/buildsuites/suite_tcl90/ (driver split: zig-layer fetch/generate/compile + suite-tclsh-run Tcl phases; sources.config -> zon-shaped manifest migration), src/buildsuites/ (the pattern future suites fork - interplay with G-099), bin/tools/ (pinned zig acquisition per the consent-gated mechanism), src/scriptapps/bin/ punk-getzig (acquisition layer, unchanged in role) Goal: a buildsuite is drivable end-to-end with only the pinned zig toolchain present - no pre-existing tclsh anywhere in the chain. Pre-tclsh phases (source fetch, configure-product generation, compile) move into the zig build layer, with sources declared in a content-hashed declarative manifest (build.zig.zon or a zon-shaped equivalent) that keeps the copy-and-tweak retargeting property; post-tclsh phases (tcllib/tklib installers, critcl accelerators, the parsed-totals test gate, smokes) remain Tcl scripts executed by the FRESHLY BUILT suite tclsh as build steps - Tcl stays the tool exactly where a tclsh is guaranteed to exist because the suite just built it. The plain-tclsh suite.tcl remains as an ergonomic dev driver during transition (or is reduced to a thin documented wrapper). Acceptance: from a clean checkout plus the pinned zig (acquired via the consent-gated download mechanism or self-built), a documented zig invocation performs the whole pipeline in an environment where no pre-existing tclsh is resolvable (PATH-scrubbed demonstration recorded in this file): fetch/stage sources per the declarative manifest (content-integrity pins for zig-fetched trees; the fossil-checkout flow retained as a documented dev alternative for live-branch work), generate the configure-products, build the runtime, then run the post-tclsh phases under the suite-built shell - tcllib install with tcllibc accelerators via critcl, tklib, tk - and the expected-failure-baselined test gate, with results matching the suite.tcl-driven equivalents; the manifest is the documented tweak surface and copied suite trees remain isolated (G-096 copy-and-tweak property preserved); the recipe pins the zig version it is written against (API-churn mitigation - the 0.14->0.16 migration cost is the recorded precedent); the bash+powershell polyglot alternative for this layer is recorded as considered and rejected, with polyglot craft remaining the ACQUISITION layer's mechanism (punk-getzig/getpunk first-contact story). ## Context Direction agreed 2026-07-20 (discussion recorded in the notes of G-096 (achieved 2026-07-20); user decisions: toml dropped for binary buildsuites, long-term no-tclsh bootstrap intent, zig acquisition accepted as the root dependency, the freshly built tclsh accepted as the legitimate intermediate for tcllib/tklib installers and critcl). Why zig fits this layer: - build.zig.zon is itself a declarative, content-hashed source manifest - fetch by url+hash gives declarative retargeting WITH integrity. Fossil sources pin as per-checkin tarball urls (branch-tip tarballs are not hash-stable); that is a provenance feature - '-refresh' becomes 'bump the pin' - with the fossil-checkout path kept for dev-friendly live-branch work. - The recipe already generates configure-products (tclUuid.h, manifests, rc edits) inside the zig layer - the boundary just extends to cover fetch. - zig Run steps execute artifacts they just built: 'zig build tcllib' = build tclsh -> run installer.tcl under it; 'zig build test-gate' = run the core suite under it and gate on parsed totals. The freshly-built-tclsh intermediate maps 1:1 onto the build graph. Why NOT a bash+powershell polyglot for this layer (considered and rejected): the polyglot's value is first contact - zero installed tools - but the suite driver runs AFTER zig acquisition by definition, so a capable tool is always already present. The driver's workload (manifest parsing, byte-exact file generation, totals parsing, child-process orchestration) is what dual shell implementations do worst: two codebases in lockstep or a fragile dual-parse dialect, divergent quoting/path semantics as a permanent bug farm. Polyglot craft stays where it already earns its keep in this repo: the acquisition layer (punk-getzig / getpunk cmd-or-powershell first-contact twins). Residual external tools, and their long-term story: fossil and git CLIs are used by the current fetch flow. Under the zon path they become optional (tarball fetch with pinned hashes needs neither); fossil itself is buildable with zig cc if live checkouts without a preinstalled fossil are ever wanted. Relationships: G-005 (this formalizes its zig-build-infrastructure intent for the buildsuite path), G-006 (consent-gated artifact/toolchain download - the zig acquisition gate), G-018 (superseded 2026-07-20 - the bare bootstrap-intermediate tclsh stays an accessible, unshowcased build product; the showcased 'plain' niche is G-103's plain-for-punk member), G-096 (origin discussion in its notes; the suite.tcl scaffolding this splits), G-099 (sequencing interplay below). SEQUENCING: proposed 2026-07-20. Natural slot is before or alongside G-099 activation so suite_tcl86 is born onto the chosen driver shape rather than ported later - but this goal must not block G-096's remaining items (piperepl matrix, punk-getzig per-version fetch), which close out on the current suite.tcl shape. sources.config records are deliberately name/url/ref-shaped (close to zon records) so the manifest migration is mechanical. ## Verification (acceptance evidence, 2026-07-21) Landed as increments on suite_tcl90: 84a1b225 (configure-products as build-cache OVERLAYS - tclUuid.h/tkUuid.h/threadUuid.h via generator include-dirs, the two exe manifests + thread.rc via overlay writefiles/rc copies; source trees never written, fossil checkouts stay pristine), 51fc727f (build.zig.zon pinned manifest + zig-native bootstrap staging: 'zig build stage|bootstrap' from the tracked suite dir, tools/stagetree.zig materializer with pin-marker invalidation and fossil/git checkout protection), e5db0513 (post-tclsh phases as recipe steps under the suite-built shell: smoke/tklib/tcllib/tcllibc/test-gate + package landing in the recipe; suite.tcl thinned to the fossil dev wrapper delegating to the same staged steps), acc6ab40 (build.zig default-entry shim; deterministic critcl target; comptime zig pin 0.16.0). PATH-scrubbed no-tclsh demonstration (clean checkout): - Clean git clone of the repo at e5db0513 to c:/T/g102 (shallow base). The clone carries no toolchain; the pinned zig is the acquisition layer's bin/tools/zig-x86_64-windows-0.16.0 (punk-getzig-fetched, minisign-verified hosting). - Environment: env -i with PATH = the pinned zig dir (plus C:\Windows\System32 + COMSPEC/PATHEXT/SystemDrive for the TEST-SUITE phase - base windows facilities, see findings) and TEMP/LOCALAPPDATA/USERPROFILE only. Probed: 'where zig.exe cmd.exe' resolve; 'where tclsh.exe tclsh90.exe fossil.exe git.exe' finds NOTHING. - Documented invocation 'zig build bootstrap' (build.zig shim -> build905.zig bootstrap mode) performed the whole pipeline: the seven build.zig.zon pinned per-checkin tarballs materialized into _build/suite_tcl90 (stagetree pin markers; packages verified against the manifest hashes by zig's fetch layer), recipe staged, and the nested staged build ran install + install-libraries + make-zipfs + smoke + tklib + tcllib + tcllibc. The clean SINGLE-INVOCATION composite (true exit 0, clone updated to 4d9c8a26 after the echo-banner finding below) produced all four shells in out/bin (tclsh90s/tclsh90spr/tclsh90szip/tclsh90sprzip) with every smoke green: suite_smoke static+zip (9.0.5; zip proves attached tzdata+autoload), tklib tooltip:2.0.4 under withdrawn Tk, tcllib md5:2.0.9 sha1:2.0.5 cmdline:1.5.3, tcllibc:2.0 with md5-critcl-accel:1 (critcl driving zig cc). Earlier scrubbed passes at e5db0513 had proven the same steps green individually while the composite was failing on the banner steps - the compile-only portion was also green with PATH = the zig dir ALONE, not even cmd.exe. tcl::build-info of the demo shell reports 9.0.5+1a9c3b9d... - IDENTICAL to the fossil-flow build: the zon pins are the exact checkouts the promoted punk905 runtime was built from. - Test gate ('zig build bootstrap -Dsteps=test-gate', scrubbed): totals 69552 run, 56075 passed, 13468 skipped, 9 failed - 8 baselined plus fCmd-30.3, which is a scrub artifact (the test's expected result assumes $env(SystemDrive) exists); with SystemDrive present the file reruns GATE PASS all-baselined. Same-day suite.tcl-driven equivalent (fossil flow, delegated to the same test-gate step): 69552 run, 56040 passed, 13503 skipped, 9 failed, GATE PASS - matching run counts, with the small passed<->skipped shift being env-dependent test constraints. - Copy-and-tweak preserved: exercised via a full suite copy (suite_tcl90zt) that bootstrapped from nothing but its tracked tree + pinned zig into its own _build/ stage; the stage dir derives from the suite folder name in both flows. The fossil-checkout dev flow is retained: suite.tcl fetches live checkouts per sources.config and delegates to the same staged steps, and the bootstrap staging never touches an existing fossil/git checkout (guarded in the recipe and in stagetree, tested against the fossil-populated stage). Environment findings (recorded for future suite work): - critcl chooses its build target by PROBING PATH for compilers: with a gcc visible the tracked config's defaults engaged (why the G-098-era flow always worked), but the scrubbed environment fell back to critcl's builtin msvc 'cl' target and all 25 accelerator modules failed with "couldn't execute cl". The config now declares an explicit win32-x86_64-zig target and the recipe passes -target win32-x86_64-zig - deterministic in both environments. - The tcl core test suite needs base windows facilities beyond the toolchain: without cmd.exe/COMSPEC/PATHEXT the winpipe/auto_execok/fCmd families fail spuriously (~52 extra), and fCmd-30.3 needs SystemDrive. These are properties of the tests, not the pipeline. - The recipe's five decorative 'echo --Installing package X--' banner steps (2024-era) spawned 'echo' as a program - a cmd BUILTIN that only resolved when a coreutils echo.exe happened to be on PATH. Scrubbed, the spawns failed and took the tm installs, install-libraries and (transitively) make-zipfs down - package steps green but no zip-wrapped shells. Removed 4d9c8a26; the pipeline is now spawn-clean of incidental external commands. - First socket listen by a newly built shell at a new path raises the windows firewall consent dialog (user-observed and accepted mid-run during the first demo gate) - socket-family flakes in a first gate run on a fresh clone can be firewall artifacts; post-accept runs are deterministic. An isolated http-4.14.2 connect-refused flake was also observed once on the fossil flow and passed on rerun (528/519/9/0). - Windows Run-step spawn semantics encoded in the recipe: cached (non-side-effect) zig Run steps spawn children with stdout IGNORED - a tcl child then has no stdout channel and any puts errors; such runs capture stdout. Built-shell artifact runs execute the CACHED exe (no ../lib/tcl9.0 beside it) and get TCL_LIBRARY pointed at the source tree's script library (the zip smoke deliberately excluded - its attached library is under test). Residual: out/bin/tclsh90sprzip.exe in the MAIN repo's fossil-flow stage is RESOLVED 2026-07-21: the user application released the file and the pending copy completed - all four shells current in both the fossil-flow stage and the demo clone. The G-104 make.tcl surface will front these flows; -Dtestargs passthrough on bootstrap mode (currently a staged-invocation option only) is a G-104-adjacent nicety. ## Notes - 2026-07-20 (user requirements batch): the build-graph products this driver shape must express grew concrete - the G-103 runtime kit FAMILY (plain/punk/bi kits with attached batteries) become named build outputs, G-105 adds -target as a build dimension, and G-104's make.tcl surface fronts whichever driver shape wins (its suite self-description contract must survive the migration). The shape that won (this goal, achieved): pipeline entirely in the zig recipe as steps; two source flows (zon pins / fossil checkouts) converging on one staged invocation.