You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

18 KiB

suite_tcl90 - zig build of the Tcl 9.0 windows runtime (G-096, G-102, G-103)

The first real, tracked buildsuite: reproducibly builds tclsh90s.exe (static) and tclsh90szip.exe (self-contained zipfs) plus the TCLSH_PIPEREPL variants and the thread/vfs/tk extension dlls, tklib/tcllib installs and critcl-built tcllibc, from Tcl core-9-0-branch sources, using zig only (no MS toolchain, per the project toolchain policy) - and assembles them into the G-103 RUNTIME KIT FAMILY: verified self-contained batteries-attached runtimes (see "Runtime kit family" below). (vqtcl/vlerq was removed 2026-07-20 - an old-tclkit experiment; future tclkit investigation has TEMP_REFERENCE/metakit and TEMP_REFERENCE/KitCreator as reference material.)

Everything transient lives under src/buildsuites/_build/suite_tcl90/ (VCS-ignored via the repo's existing _build globs): staged recipe, source trees, zig cache, and the out/ install prefix. The tracked content here is the recipe and its drivers.

Two flows, one pipeline (G-102)

The PIPELINE - configure-product generation (overlays, source trees never written), compile, package installs, smokes, test gate - lives entirely in the zig recipe as build steps. Two ways to arrange sources and drive it:

No-tclsh bootstrap flow (pinned sources; nothing but the pinned zig required):

zig build stage       # fetch build.zig.zon-pinned sources + stage recipe -> ../_build/suite_tcl90
zig build bootstrap   # stage + run the staged pipeline end-to-end

run from THIS directory with the pinned zig. Sources come from build.zig.zon - content-hashed per-checkin tarball pins ('refresh' = bump a pin; zig fetch --save=<name> <url> edits the manifest). Steps for the nested staged build: -Dsteps=... (repeat the flag), default install install-libraries make-zipfs smoke tklib tcllib tcllibc kit-family kit-family-artifacts. The test gate: zig build bootstrap -Dsteps=test-gate (or invoke the staged build file directly, as suite.tcl does). zig extracts fetched packages into zig-pkg/ beside the manifest and .zig-cache/ for the outer invocation - both VCS-ignored.

Fossil dev flow (live checkouts; a plain tclsh drives fetch+staging):

tclsh suite.tcl build ?options?
tclsh suite.tcl test ?-testargs {...}?
tclsh suite.tcl clean

suite.tcl arranges LIVE fossil/git checkouts per sources.config (branch work, -refresh, -tclbranch overrides) into the same stage layout, then delegates to the same staged zig steps. An existing fossil/git checkout in the stage is never touched by the bootstrap staging (guarded twice), so the flows can be mixed.

See suite.tcl's header for options (-zig, -optimize, -tclbranch, -refresh, -steps). Zig resolution: -zig > $env(PUNK_ZIG) > the pinned toolchain at bin/tools/zig-x86_64-windows-0.16.0/zig.exe > zig on PATH.

Library test runs (G-107)

The external libraries the suite builds and ships run their OWN testsuites under the suite-built shell, as named recipe steps with a per-library policy tier - evidence for the exact zig-built combinations nobody upstream tests:

tclsh suite.tcl test                                  # core gate (test-gate), as before
tclsh suite.tcl test -steps {test-libraries}          # thread+tclvfs GATED, tcllib+tklib RECORDED
tclsh suite.tcl test -steps {test-thread}             # individual: test-thread test-tclvfs
                                                      #   test-tcllib test-tklib test-tk
tclsh suite.tcl test -steps {test-tcllib} -zigargs {-Dtestpolicy-tcllib=gate}

Policy tiers (-Dtestpolicy-<lib>=gate|record|skip, defaults in the recipe, overridable at invocation without recipe edits):

  • gate (default: thread, tclvfs) - parsed totals diffed against the tracked dispositioned baseline expected_test_failures_<lib>.txt (core-gate semantics including stale-entry notes). Small, load-bearing for every kit.
  • record (default: tcllib, tklib, tk) - the run must complete; failures are recorded, not fatal. Emits out/testreports/<lib>.log (full output) and out/testreports/<lib>.summary (line-record evidence summary - shape documented in goals/G-107-buildsuite-library-tests.md, consumed by the G-103 artifact metadata; the core gate writes tclcore.summary there too). Promote a library to gate once its census is dispositioned and two-run deterministic.
  • skip - the named step becomes a no-op (composite tuning).

EVERY mode fails the step when the run produces no parseable totals line - infrastructure breakage must never masquerade as recorded results. Per-library invocation overrides (recorded in the evidence summary): -Dtestargs-<lib>="..." (extra tcltest/driver args) and -Dtestnotfiles-<lib>="pat ..." (test-FILE glob patterns excluded from the run; a dedicated knob because -notfile needs a single list value that a flat -testargs cannot carry through the option chain).

test-libraries runs thread/tclvfs/tcllib/tklib. The full Tk suite is deliberately OUTSIDE it: test-tk is opt-in (needs an interactive desktop, maps real windows, steals focus, runs tens of minutes) - intended before PUBLISHING bi-family artifacts, not on every build. Tk excludes the native-dialog suites winDialog.test (72 cases) and winMsgbox.test (19) by default (default_notfiles): their message-injection automation does not drive the real OS dialogs under the batch/suite-built shell, so each case blocks waiting on a human and the census cannot complete unattended. To run them, -Dtestnotfiles-tk="" and dismiss each dialog by hand.

Drivers: thread/tclvfs/tk use their trees' own tests/all.tcl. tcllib/tklib are driven via their own support/devel/all.tcl (sak's underlying driver, bypassing sak's log format): per-file child interps, aggregated standard totals, and - because the installed tcllibc is on the shell's package path - the tcllib suites run WITH the critcl accelerators engaged (E tcllibc <v> markers in the log). A test file that errors out is recorded in the summary (errored_files / errorexit_files) as evidence; the gate criterion itself stays totals-vs-baseline. Known residual: the old-style thread/tclvfs drivers catch a mid-file abort without a structured marker, so such an abort shows up only as a failure-count drop plus stale-baseline notes.

Timing-sensitive suites (thread) must not run in parallel with heavy ones: suite.tcl test passes -j1 so combined step invocations serialize (zig otherwise runs independent steps concurrently).

Runtime kit family (G-103)

The kit-family step (in the default build pipeline) assembles the project's runtime kit family - runnable, self-contained executables whose info library AND core batteries live in the initially attached zip, depending on no external filesystem tree:

  • plain tclsh<patchlevel>.exe (e.g tclsh9.0.5.exe) - stock shell (no piperepl patch) + batteries: loadable Thread, tclvfs with the vfs::* packages, tcllib + tcllibc (critcl accelerators).
  • punk tclsh<patchlevel>-punk.exe - the same batteries on the TCLSH_PIPEREPL-patched shell, gate ENABLED BY DEFAULT (opt out with TCLSH_PIPEREPL=0; users wanting stock semantics take the plain kit).
  • punk-bi tclsh<patchlevel>-punk-bi.exe - punk + the batteries-included tier we build: Tk (dll + script library) and tklib. (tcltls joins when its zig-built crypto backend lands - see the G-103 goal notes.)

Attached-image layout (also the make.tcl kit contract - a future kit build extracts this image and merges its .vfs payload over it): tcl_library/ at the app root (the C-level zipfs boot looks only for /app/main.tcl and /app/tcl_library; no main.tcl is included, so the stock boot falls through to the ordinary interactive shell), tm modules at tcl9/<ver>/ beside it (tm.tcl anchors at the dirname of tcl_library), batteries under lib/<pkg>/ with their installed-shape pkgIndexes ($dir/../../bin dll references resolve to /app/bin), dlls under bin/, and a one-line stock pkgIndex hook at lib/pkgIndex.tcl joining lib/ to the package search (auto_path is [tcl_library, /app], and tclPkgUnknown re-scans auto_path growth mid-scan - the same mechanism tcllib's own top-level index uses). Battery dlls load out of zipfs via the core's copy-to-temp fallback - the TIP-741 temp-dir accumulation wart is characterized in the G-103 goal notes.

Every member is verified by tools/family_check.tcl before the step passes: the kit is copied ALONE into a scratch dir and probed from there with a scrubbed environment (no external Tcl visible) - patchlevel, zipfs tcl_library, tzdata/encodings, tm modules, functional Thread (cross-thread eval), tclvfs (vfs::zip mount round-trip), tcllib md5 with the tcllibc accelerator engaged, piperepl default/opt-out behaviour per variant, and Tk create/destroy + tklib for bi. Products land in out/family/.

The kit-family-artifacts step (also default; depends on the checks) emits the punkbin ARTIFACT tier into out/family/punkbin/win32-x86_64/: immutable -r<N>-named copies (revision via -Dfamilyrev=N, suite.tcl -zigargs {-Dfamilyrev=N}; default 1) plus per-artifact <name>.toml metadata (variant, working name, sha1/size, tcl patchlevel, piperepl policy, attached battery versions, source-checkout provenance, toolchain, G-107 test-evidence result lines) and a punkbin-format sha1sums.txt. The emission deliberately runs UNDER the plain family kit itself (sha1 via its attached tcllib) - every run re-proves the family runtime executes real tooling self-contained. Publication to the real punkbin repo remains a deliberate user step, deferred per the goal notes until the family shape is accepted.

Consumers: working-name runtimes are copied into bin/runtime/win32-x86_64/ and referenced by src/runtime/mapvfs.config (punk -> punk9wintk905.vfs as punk9_beta, punk-bi -> punk9win_for_tkruntime.vfs as punk9bi_beta, per the *_beta trial convention).

Pinned zig

The suite pin is zig 0.16.0 (current official release; adopted 2026-07-20 - the recipe was ported to the 0.16 build API and 0.14 support is deprecated; the 0.14-era recipe remains in git history and 0.14.1 remains archived on punkbin). The pinned archive zig-x86_64-windows-0.16.0.zip (+.minisig) is hosted on the punkbin artifact repo (win32-x86_64/tools/, sha1sums recorded) - the same mirror punk-getzig consults - and was minisign-verified against the zig release key before hosting. punk-getzig's own pin is the same 0.16.0 (installed to bin/tools/zig); a per-version fetch argument remains the intended enhancement for materializing arbitrary pinned versions into versioned dirs.

Zig object caches are kept per zig version (.zig-cache-<version> inside the staged build dir): caches must never be shared across zig versions (observed producing stale/duplicate artifacts at link time).

Layout

  • build905.zig - the zig recipe (verbatim lineage copy - see Lineage). Expects its build root's SIBLING dirs to hold sources (../tcl905, ../zlib, ../tclthread, ../tclvfs), which is why suite.tcl stages it into _build/suite_tcl90/build/ rather than building in place.
  • build_libtommath/ build_zlib/ build_tclvfs/ build_tclthread/ - helper build modules. The four tcl-dependent helpers take the tcl source dir as their tcldir param (a hardcoded ../tcl90/generic include, marked "todo - fix" upstream, was fixed when these copies were made). build_libtommath.zig still carries the hardcoded path but is not called by the current recipe (tcl's in-tree tommath is compiled into the core; the libtommath.dll seen in old output dirs was a stale cache-install artifact).
  • src/main.zig - stub root module that disables zig's own start symbols so the mingw CRT provides the exe entry.
  • tools/ - wrapfiletofile.zig (uuid-header overlay generation), stagetree.zig (bootstrap-mode source/recipe materializer), zipfs_mkzip.tcl / zipfs_mkimg.tcl (make-zipfs + kit-family wrap drivers), and the suite-built-shell step scripts test_gate.tcl / suite_smoke.tcl / pkg_smoke.tcl (G-102) plus family_check.tcl / family_artifacts.tcl (G-103: kit-family self-containment verification; punkbin-layout artifact + metadata emission). test_gate.tcl is the shared testsuite engine (G-107): gate/record modes, driver selection, evidence summaries.
  • expected_test_failures.txt (tcl core) / expected_test_failures_thread.txt / expected_test_failures_tclvfs.txt - tracked dispositioned gate baselines.
  • patches/ - recovered 2024 experiment patches, including TCLSH_PIPEREPL (see patches/README.md) - compiled into the SEPARATE tclsh90spr/ tclsh90sprzip products; the stock shells stay unpatched.
  • build.zig.zon - pinned source manifest for the bootstrap flow.
  • suite.tcl - the fossil dev driver: stage recipe -> arrange live checkouts -> delegate to the staged recipe's steps.

Sources

Source repositories, refs and staged checkout dirs are declared in sources.config (tcl, tclthread, tclvfs, tk, tklib, tcllib via fossil; critcl via git, version-pinned). That file is the tweak surface - its header documents the record format, the copy-and-tweak workflow, and how deep a tweak can go before it touches the zig recipe. One source is not in the config: zlib is copied from the tcl checkout's own compat/zlib (version-matched, includes contrib/minizip). The tcl ref can be overridden per-run with -tclbranch.

Fossil clones live in a punkshell-owned MACHINE-LEVEL store, ~/.punkshell/fossils (G-098 decisions 2026-07-20: hermetic - independent of any personal fossil collection - and outside deletable _build-class dirs; clone dbs are caches, not build output). Missing clones are seeded by READ-ONLY file copy from a local collection when available (-seedfossils, default ~/.fossils - the seed is never written; the copy is pulled current), else network-cloned. Store override: -repofolder or PUNK_FOSSIL_STORE (punk::config-registered - see help env). -refresh 1 pulls the store clones and updates checkouts. Checkouts use FOSSIL_HOME=<stage> so the user's global fossil config db records nothing (stage-local _fossil db). clean wipes the stage only; the store lives outside it.

Retargeting / copying the suite

For a developer (no punkshell knowledge required - the driver is dependency-free plain tclsh) who wants this build with different sources:

  1. Copy the whole tree: src/buildsuites/suite_tcl90 -> src/buildsuites/<yourname>.
  2. Edit <yourname>/sources.config (urls, refs, pins).
  3. tclsh suite.tcl build - the stage is derived from the suite folder name (_build/<yourname>), so copies build isolated from the original.

Same-major url/ref tweaks (mirror, fork, release tag) are config-only. Version-class changes additionally touch build905.zig and the driver's install steps (checkout dir names, version-derived tm/dll/manifest names) - see the depth-limit note in sources.config.

Fresh-checkout preparations (configure-products)

Normally products of configure/nmake that a bare checkout lacks (G-076 findings). Since G-102 these are generated INSIDE the zig recipe as build-cache OVERLAYS - source trees are never written (overlay include dirs added first so stale tree copies can never shadow; rc files that reference generated resources compile from overlay copies where rc-file-relative lookup would otherwise win):

  • tclUuid.h / tkUuid.h / threadUuid.h - from each tree's manifest.uuid (wrapfiletofile Run steps; consumed via overlay include dirs).
  • tclsh.exe.manifest / wish.exe.manifest - from their .in templates (*_WIN_VERSION per the trees' own formula: VERSION . releaselevel . patchlevel-stripped-of-'ab.').
  • thread.rc - overlay copy gains #include "tcl.h" resolved via rc include paths (the zig rc compile does not receive the makefile's include paths; historically an uncommitted checkout tweak, later a scripted edit, now an overlay - checkouts stay pristine).

Lineage

build905.zig descends from the validated ad-hoc builds in c:/buildtcl/2024zig/build_tcl90 (reference snapshot: TEMP_REFERENCE/2024zig): build90.zig (tcl 9.0.1 era, last built 2024-11-27, zig 0.14.0-dev.2074) plus the 2026-07-20 G-076 deltas - source folder ../tcl905, tclUuid.h copy-to-source targets derived from tcl_source_folder, and mingw_unicode_entry_point = true (9.0.5 sources dropped TCL_BROKEN_MAINARGS; tclAppInit's _tmain is wmain under -DUNICODE). Full provenance: goals/archive/G-076-tcl9-deadconsole-fix-adoption.md (2026-07-20 entries). Goal contract for this suite: goals/G-096-zig-buildsuite-piperepl.md.

zig 0.14.1 compat deltas (2026-07-20): b.host -> b.graph.host; the redundant second UNICODE tclMain add into the objs lib removed; the exe's ansi tclMain copy compiles with -UUNICODE -U_UNICODE -DTCL_ASCII_MAIN (the zig cc driver can supply windows-default defines, which made the "ansi" copy a duplicate UNICODE compile - upstream's TCL_ASCII_MAIN guard is the intended mechanism for the ascii variant).

zig 0.16.0 port (2026-07-20, adopted - recipe is now 0.16-API only): creation calls take .root_module = b.createModule(...) (addLibrary with .linkage replaces addShared/StaticLibrary); Compile method forwarders are gone - all addCSourceFile(s)/addIncludePath/addObjectFile/addLibraryPath/linkLibrary/ addWin32ResourceFile calls go through .root_module., linkLibC() becomes root_module.link_libc = true, linkSystemLibrary takes an options arg (linkSystemLibrary2 merged); std.ArrayList(T).init becomes std.array_list.Managed(T).init; build-script dir walks use std.Io.Dir.cwd().openDir(b.graph.io, ...) / it.next(b.graph.io); tools/wrapfiletofile.zig uses the 0.16 entry idiom (main(init: std.process.Init.Minimal), init.args.toSlice, an std.Io.Threaded io instance, Io.Dir.readFileAlloc/createFile, File.writeStreamingAll, cleanExit(io)). mingw_unicode_entry_point is unchanged in 0.16. Verified: full build PASS, shells report 9.0.5, tcl-test parity identical to the 0.14.1 build (557/357/200/0).