Compare commits

...

2 Commits

Author SHA1 Message Date
Julian Noble f4031b209f G-101 pre-activation survey: reference inventory + sdx wrap.tcl provenance capture 1 week ago
Julian Noble f89bd58c31 G-100 achieved: suite_tcl86 dependency set (Tk 8.6, tklib, tcllib + tcllibc) and an 8.6 runtests census 1 week ago
  1. 4
      ARCHITECTURE.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 2
      goals/G-005-zig-build-infrastructure.md
  5. 57
      goals/G-100-suite-tcl86-tk-tcllib.md
  6. 216
      goals/G-101-tcl86-kit-container-strategy.md
  7. 5
      goals/G-105-buildsuite-cross-target.md
  8. 6
      goals/G-108-buildsuite-debug-tier.md
  9. 7
      goals/G-116-suite-built-tcltls.md
  10. 210
      goals/archive/G-100-suite-tcl86-tk-tcllib.md
  11. 13
      src/buildsuites/AGENTS.md
  12. 144
      src/buildsuites/suite_tcl86/README.md
  13. 48
      src/buildsuites/suite_tcl86/build.zig.zon
  14. 232
      src/buildsuites/suite_tcl86/build86.zig
  15. 64
      src/buildsuites/suite_tcl86/build_common.zig
  16. 260
      src/buildsuites/suite_tcl86/build_tk86/build_tk86.zig
  17. 36
      src/buildsuites/suite_tcl86/critcl_zig.config
  18. 23
      src/buildsuites/suite_tcl86/expected_test_failures_tclvfs.txt
  19. 109
      src/buildsuites/suite_tcl86/patches/README.md
  20. 344
      src/buildsuites/suite_tcl86/patches/sdx-2011.03.17-wrap.tcl
  21. 12
      src/buildsuites/suite_tcl86/patches/sdx-wrap.tcl.tclkitinf-binary.patch
  22. 345
      src/buildsuites/suite_tcl86/patches/sdx-wrap_tclkitinf_binary.tcl
  23. 24
      src/buildsuites/suite_tcl86/sources.config
  24. 44
      src/buildsuites/suite_tcl86/suite.tcl
  25. 50
      src/buildsuites/suite_tcl86/tools/accel_bench.tcl
  26. 64
      src/buildsuites/suite_tcl86/tools/tk_smoke.tcl

4
ARCHITECTURE.md

@ -83,7 +83,7 @@ Two layers with a deliberate dependency direction (the class never depends on th
- **`src/make.tcl` is the single build entry.** Key subcommands: `modules`/`libs`/`packages` (repo-root build outputs), `vfscommonupdate` (regenerate `_vfscommon.vfs`), `bake` (kit assembly into `bin/`; optional kit names confine the run to those kits - G-121), `bakelist` (report the configured kit matrix with runtime/vfs presence and deployed state), `bin`, `bakehouse` (packages + bake for a clean checkout; `project`/`vfs` remain as deprecated aliases - G-112 rename, achieved), `bootsupport` (snapshot refresh), `vendorupdate`, `check`, `workflow`, `projectversion`. Dispatch and help dogfood `punk::args` (G-030, achieved) and degrade to plain scan/help when bootsupport `punk::args` is stale (`PUNKBOOT_PLAIN=1` forces the degraded mode). Source: `src/AGENTS.md`.
- **punkcheck** records every install/delete as events in per-folder `.punkcheck` directories - the basis for skip/copy change detection, superseded-module pruning and provenance. Single OO record lifecycle (G-094, achieved) with atomic saves and advisory event-scoped locking for concurrent writers (G-095, achieved).
- **Provenance gates.** Build/promotion commands warn on uncommitted `src/` changes (column-0 `PROVENANCE-WARNING:` token, `-dirty-abort` for strict mode); `vendorupdate` warns for dirty source-project checkouts.
- **Buildsuites and the kit family.** `src/buildsuites/suite_tcl90/` builds Tcl/Tk/tcllib from source with a pinned zig toolchain and produces the runtime kit family (plain / punk / bi) plus artifact metadata (G-096-G-117 era: see archived goals G-096, G-098, G-102, G-103, G-107); artifacts publish to the punkbin repo that `bin/punk-runtime.cmd` fetches from. `src/buildsuites/suite_tcl86/` is the 8.6 sibling (G-099, achieved): a Tcl 8.6 windows runtime (static + dynamic shells, `tcl86t.dll`, on-disk lib tree - no zipfs, so no self-contained kit) with thread 2.8 + tclvfs companions, gated against the 8.6 core/thread/tclvfs testsuites. The 8.6 dependency set (Tk/tcllib) and kit container strategy remain in-flux - see "In-flux areas".
- **Buildsuites and the kit family.** `src/buildsuites/suite_tcl90/` builds Tcl/Tk/tcllib from source with a pinned zig toolchain and produces the runtime kit family (plain / punk / bi) plus artifact metadata (G-096-G-117 era: see archived goals G-096, G-098, G-102, G-103, G-107); artifacts publish to the punkbin repo that `bin/punk-runtime.cmd` fetches from. `src/buildsuites/suite_tcl86/` is the 8.6 sibling (G-099 + G-100, both achieved): a Tcl 8.6 windows runtime (static + dynamic shells, `tcl86t.dll`, on-disk lib tree - no zipfs, so no self-contained kit) with thread 2.8 + tclvfs + Tk 8.6 + tklib + tcllib(+tcllibc critcl accelerators) companions, gated against the 8.6 core/thread/tclvfs testsuites with tcllib/tklib on a record tier and an opt-in `test-tk`; punkshell's own runtests is censused on that runtime at parity with a same-day native-8.6 baseline. Suite child shells scrub `TCL<major>_<minor>_TM_PATH` as well as TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY - without it a census measures the machine's module trees. The 8.6 kit container strategy remains in-flux - see "In-flux areas".
- **Project generation.** `dev project.new` composes thin layouts from `src/project_layouts/` (overlay chain with `.anti` deletion markers and `name@base` derivation - G-087, achieved) and injects bootsupport modules from the generating shell at generation time.
- **Workflow overview.** `tclsh src/make.tcl workflow` prints the embedded ASCII data-flow overview of the build/release pipeline, with its own update contract in `src/AGENTS.md`. This section deliberately summarises rather than copies it.
@ -103,7 +103,7 @@ Mechanics here are documented as pointers only - detailed prose would be obsolet
- Stored configuration and help: G-014 (punk::config toml), G-042 (subshell help topics), G-043 (subshell definition plugins).
- Console err channel: G-011.
- Launch surface: G-023 (version-named binaries), G-033 (`proj:` package mode), G-077 (`-e` one-liners), G-089 (scriptlib in kits).
- 8.6 buildsuite family: G-099 (core runtime, achieved 2026-07-26), G-100 (dependency set), G-101 (kit container).
- 8.6 buildsuite family: G-101 (kit container) - the core runtime (G-099) and the Tk/tklib/tcllib dependency set (G-100) are achieved 2026-07-26.
## Doc map - which lane holds what

4
GOALS-archive.md

@ -190,3 +190,7 @@ Acceptance: `make.tcl bakelist` under a plain tclsh and under a punk kit executa
### G-099 [achieved 2026-07-26] suite_tcl86: zig buildsuite for core-8-6-branch (runtime, thread, tclvfs, core-test gate) → detail: goals/archive/G-099-suite-tcl86-buildsuite.md
Scope: src/buildsuites/suite_tcl86/ (new: suite.tcl driver, 8.6 zig recipe + helper modules, expected_test_failures baseline); src/buildsuites/_build/ (untracked sources/outputs via existing globs); bin/tools/ (pinned zig as consumed); PUNK_FOSSIL_STORE / ~/.punkshell/fossils (hermetic clone store shared with suite_tcl90)
Acceptance: from the pinned zig and fetched core-8-6-branch sources (hermetic clone store, no MS toolchain) the suite produces a working tclsh86 executable and tcl86 dll with an installed lib tree the shell resolves without external Tcl installations (TCLLIBPATH/TCL_LIBRARY unset in all suite child invocations); an 8.6-compatible thread package and tclvfs build and load in that shell (versions recorded); suite.tcl test runs the 8.6 core testsuite, parses totals (exit codes not trusted), and gates against a tracked expected_test_failures baseline with every entry dispositioned with a reason - a full-run PASS deterministic across two consecutive runs is recorded in this file; an interactive-smoke record (script execution, package require checks) is captured here.
### G-100 [achieved 2026-07-26] suite_tcl86 dependency set: Tk 8.6 + tklib, tcllib with critcl accelerators, punkshell runtests on the 8.6 runtime → detail: goals/archive/G-100-suite-tcl86-tk-tcllib.md
Scope: src/buildsuites/suite_tcl86/ (recipe extensions, driver steps); src/buildsuites/_build/ (tk/tklib/tcllib/critcl sources and outputs); bin/tools/ (pinned zig as consumed); src/tests/ (punkshell suites exercised against the suite-built 8.6 runtime)
Acceptance: from the pinned zig and fetched sources the suite produces a Tk 8.6 the suite tclsh loads (package require Tk with a create/destroy-window verification recorded here) and a matching tklib whose packages resolve (a representative require recorded); a tcllib install via its installer whose accelerators load (package require tcllibc) with the accelerated-vs-pure comparison recorded; punkshell runtests runs against the suite 8.6 runtime and its census is recorded here against a same-day native/kit 8.6 baseline run (same include set), with every difference dispositioned - Tk-dependent suites run unskipped, and any 8.6-runtime-only failures are root-caused or baselined with reasons.

4
GOALS.md

@ -349,10 +349,6 @@ Detail: goals/G-089-scriptlib-kits-and-modes.md
Scope: src/modules/shellfilter-999999.0a1.0.tm (stack placement, insert_transform, junctions); src/modules/shellthread-999999.0a1.0.tm (worker park/adoption/termination); src/tests/modules/ (new characterization suites); README.md ('very unripe parts' note)
Detail: goals/G-090-shellfilter-shellthread-audit.md
### G-100 [proposed] suite_tcl86 dependency set: Tk 8.6 + tklib, tcllib with critcl accelerators, punkshell runtests on the 8.6 runtime
Scope: src/buildsuites/suite_tcl86/ (recipe extensions, driver steps); src/buildsuites/_build/ (tk/tklib/tcllib/critcl sources and outputs); bin/tools/ (pinned zig as consumed); src/tests/ (punkshell suites exercised against the suite-built 8.6 runtime)
Detail: goals/G-100-suite-tcl86-tk-tcllib.md
### G-101 [proposed] kit container strategy for zig-built Tcl 8.6 runtimes (no zipfs)
Scope: investigation + decision record under src/buildsuites/suite_tcl86/ (or successor mechanism); TEMP_REFERENCE/metakit + TEMP_REFERENCE/KitCreator (read-only guidance); src/runtime/mapvfs.config ('kit' type consumers); src/make.tcl kit-wrap path (as consumer)
Detail: goals/G-101-tcl86-kit-container-strategy.md

2
goals/G-005-zig-build-infrastructure.md

@ -51,7 +51,7 @@ This goal is the primary mechanism for G-004's outcome. It is independent of and
respect - see goals/archive/G-058-static-runtime-packages.md.
- Buildsuite-arc relationship (recorded 2026-07-24 after overlap review; neither this
file nor the buildsuite goals previously referenced each other): the zig buildsuite
system - achieved G-096/G-098/G-099/G-102/G-103/G-104/G-107, live G-100/G-105/G-108/
system - achieved G-096/G-098/G-099/G-100/G-102/G-103/G-104/G-107, live G-105/G-108/
G-116 - is the arm's-length runtime factory this goal's direction became, sharing the
pinned-zig / zig-only toolchain policy. This goal's distinct remainder: building the
VENDORED native extensions (twapi, tcllibc class) and wiring missing-binary retrieval

57
goals/G-100-suite-tcl86-tk-tcllib.md

@ -1,57 +0,0 @@
# G-100 suite_tcl86 dependency set: Tk 8.6 + tklib, tcllib with critcl accelerators, punkshell runtests on the 8.6 runtime
Status: proposed
Scope: src/buildsuites/suite_tcl86/ (recipe extensions, driver steps); src/buildsuites/_build/ (tk/tklib/tcllib/critcl sources and outputs); bin/tools/ (pinned zig as consumed); src/tests/ (punkshell suites exercised against the suite-built 8.6 runtime)
Goal: suite_tcl86 builds/installs the remaining runtime pieces punkshell's Tcl 8 testing depends on - Tk from fossil core-8-6-branch loadable by the suite's 8.6 windows runtime together with a matching tklib (installer.tcl where it does more than a straight copy), and tcllib installed via its documented installer machinery WITH its critcl-built C accelerators (tcllibc; critcl driving zig cc, bootstrapped by the suite-built tclsh86 - no MS toolchain) - and punkshell's own runtests runs against the suite-built 8.6 runtime with Tk-dependent suites unskipped.
Acceptance: from the pinned zig and fetched sources the suite produces a Tk 8.6 the suite tclsh loads (package require Tk with a create/destroy-window verification recorded here) and a matching tklib whose packages resolve (a representative require recorded); a tcllib install via its installer whose accelerators load (package require tcllibc) with the accelerated-vs-pure comparison recorded; punkshell runtests runs against the suite 8.6 runtime and its census is recorded here against a same-day native/kit 8.6 baseline run (same include set), with every difference dispositioned - Tk-dependent suites run unskipped, and any 8.6-runtime-only failures are root-caused or baselined with reasons.
## Context
Mirror of G-098 (suite_tcl90 dependency set, achieved 2026-07-20) for the 8.6 arc.
Depends on G-099 (suite_tcl86 core buildsuite) - ACHIEVED 2026-07-26 (see
goals/archive/G-099-suite-tcl86-buildsuite.md); the 8.6 runtime (tclsh86ts/
tclsh86t + tcl86t.dll + installed lib tree), thread 2.8.13, tclvfs 1.4.2 and
the core/thread/tclvfs test-gate this goal extends now exist, so the dependency
is met and G-100 is unblocked.
8.6-specific expectations (verify at activation):
- Tk core-8-6-branch pairs with the 8.6 runtime; the 9.0 build_tk zig module was
derived from tk9 makefile.vc object lists - the 8.6 lists/xlib arrangement differ,
so expect a re-derivation rather than reuse.
- tcllib trunk supports 8.5+; the same vendored critcl mechanism applies, with
critcl driven by the suite tclsh86. tcllibc built per-generation (the punk9wintk903
vfs precedent carries tcl8/tcl9 tcllibc side by side under lib_tcl8/lib_tcl9).
- runtests under 8.6: no tcl::process, so -jobs child spawning falls back to
blocking execs which CONVOY on windows (documented) - run -singleproc 1 or accept
serialized child walls for the census run; record which mode produced the census.
- Census baseline: compare against the punk86-class kit runtime results on the same
day/include-set (forward-slash path conventions on 8.6 kits noted in project
memory) rather than against the 9.x census.
- The kit-child 'src' package_mode launch added to runtests for punk kits (G-098
increment 7) applies to 8.6 punk kits too - a plain suite tclsh86 host takes the
native path.
SEQUENCING: as for G-099 - proposed now, activation recommended after suite_tcl90's
G-096 (achieved 2026-07-20) remainder closed and punk905_beta trial concludes.
## Notes
- G-116 relationship (recorded 2026-07-24 after overlap review): G-116's zig-built
crypto-backend + static tcltls pattern (suite_tcl90) is the reference if the 8.6
dependency set later grows a tls battery - the bi-family payload contract it
extends is generation-shaped, not 9-only.
- G-005 relationship: same zig toolchain-policy lineage as G-099's note - G-005
owns the make.tcl-integrated vendored-extension build direction; the suites are
its arm's-length proving ground.
- G-099 (achieved, see goals/archive/G-099-suite-tcl86-buildsuite.md) delivered
the patterns this goal extends: the build86.zig recipe + build_tclthread86/
build_tclvfs86 helpers (TEA-shape companion builds against the 8.6 stub lib),
the whole-tree library install + tm-module derivation, and the suite_tcl90
G-107 (achieved - goals/archive/G-107-buildsuite-library-tests.md) policy-tier
test-gate (test_gate.tcl) - a Tk/tklib/tcllib gate follows the same shape. Two 8.6-specific traps recorded there carry forward: the
load-bearing MP_FIXED_CUTOFFS define and TCL_BROKEN_MAINARGS+console-subsystem
entry handling (build86.zig comments + README DERIVATION). Also inherited from
G-099 Notes: the G-106 powershell console-mode disableraw-via-server path is
only tcl9-verified - now that a twapi-less 8.6 runtime exists, run the G-106
recipe against it (goals/archive/G-106-powershell-consolemode-fallback.md).

216
goals/G-101-tcl86-kit-container-strategy.md

@ -1,7 +1,7 @@
# G-101 kit container strategy for zig-built Tcl 8.6 runtimes (no zipfs)
Status: proposed
Scope: investigation + decision record under src/buildsuites/suite_tcl86/ (or successor mechanism); TEMP_REFERENCE/metakit + TEMP_REFERENCE/KitCreator (read-only guidance); src/runtime/mapvfs.config ('kit' type consumers); src/make.tcl kit-wrap path (as consumer)
Scope: investigation + decision record under src/buildsuites/suite_tcl86/ (or successor mechanism); TEMP_REFERENCE/{metakit,KitCreator,kbs,sdx,tcl9} (read-only guidance - inventory in 'Reference material' below); src/runtime/mapvfs.config ('kit' type consumers); src/make.tcl kit-wrap path (as consumer); bin/sdx.kit (the tracked wrapping tool - provenance is part of this goal, see 'sdx: provenance' below)
Goal: a decided, demonstrated strategy for wrapping zig-built 8.6 runtimes into self-contained punk86-class kit executables in the absence of zipfs - candidate containers: the metakit/mk4vfs tclkit lineage, cookfs, and zipvfs-over-tclvfs boot - so the prebuilt third-party kit runtimes (tclkit86bi.exe, tclkit-win64-dyn.exe) can eventually be replaced by suite-built, provenance-carrying ones.
Acceptance: the candidate containers are assessed and recorded here against punkshell's kit expectations (compatibility with make.tcl's 'kit' wrap path or a documented replacement path; boot-time vfs mount without an installed Tcl; loading binary dlls from or via the container; buildable from source under the zig-only policy); one strategy is demonstrated end-to-end - a kit wrapped from the suite-built 8.6 runtime boots on a path without any Tcl installation and runs a script from inside its vfs; the decision, rationale, and the follow-on work needed to reach punk86-kit parity are recorded in this file.
@ -37,12 +37,24 @@ well-understood ports of the 9.0 work, while this is open investigation - keepin
it out of their acceptance keeps the 8.6 suite arc incrementally landable.
SEQUENCING: proposed; naturally last in the 8.6 arc (needs the G-099 runtime to
wrap). G-099 landed 2026-07-26 (achieved - see
goals/archive/G-099-suite-tcl86-buildsuite.md), so the runtime this goal wraps
now exists; activation still waits on G-100 (the dependency set) landing.
wrap). Both predecessors have landed - G-099 (achieved 2026-07-26 - see
goals/archive/G-099-suite-tcl86-buildsuite.md) built the runtime this goal wraps,
and G-100 (achieved 2026-07-26 - see goals/archive/G-100-suite-tcl86-tk-tcllib.md)
completed the dependency set that becomes this container's payload. The arc is
unblocked; activation is the user's call.
## Notes
- Related: G-116 (suite-built tcltls with a zig-built crypto backend) - the
payload question is shared: whatever container this goal picks carries the
battery set, and G-116's bi-family payload contract is generation-shaped, not
9-only. Recorded 2026-07-26 at the G-100 archive sweep, where the pair's only
bridges (G-100, G-103) both left the live tier.
- Payload inventory as of 2026-07-26: G-100 (achieved - see
goals/archive/G-100-suite-tcl86-tk-tcllib.md) completed the 8.6 dependency set
this container would wrap - Tk 8.6.17 (tk86t.dll + lib/tk8.6), tklib 0.9,
tcllib 2.0 + tcllibc accelerators, alongside G-099's thread 2.8.13 / tclvfs
1.4.2 / registry / dde.
- 2026-07-21: the prebuilt runtimes this goal replaces (tclkit86bi.exe,
tclkit-win64-dyn.exe) are Metakit tclkits - self-contained (Tcl + VFS
embedded) yet DYNAMICALLY linked to libc. That dynamic libc is what lets them
@ -100,3 +112,199 @@ now exists; activation still waits on G-100 (the dependency set) landing.
future zero-flash launcher takes the w-suffix convention (punkw) rather than
-gui/-cli (which in kit lineage encode Tk linkage, not subsystem).
- G-121 (archived) built bakelist/selective-bake over the parsed mapping without assuming zip-only types ('kit'-type entries list and bake by name), so 8.6 container entries join that surface automatically - see goals/archive/G-121-bakelist-selective-bake.md
- 2026-07-26: pre-activation survey added four sections below - reference-material
inventory (with the boot-chain reading order), sdx provenance + the
characterize-before-rebuild ordering, the self-mount shadowing gotcha (verified
live, container-agnostic), and metakit-under-Tcl-9 feasibility. Two of them
carry proposed acceptance riders. The first - capture the sdx wrap.tcl patch
somewhere versioned before any sdx rebuild - was DONE the same day
(`src/buildsuites/suite_tcl86/patches/`, with a back-pointer added to
TEMP_REFERENCE/AGENTS.md so the survey is not repeated). The second stands:
make the end-to-end demonstration wrap a SEPARATE COPY of the runtime so the
self-mount shadowing path is actually exercised rather than accidentally
avoided.
## Reference material (TEMP_REFERENCE, machine-local)
Recorded 2026-07-26 from an agent survey. CAVEAT: TEMP_REFERENCE is ignored by
BOTH git and fossil (root .gitignore:59, .fossil-settings/ignore-glob:58-59), as
is scratch* (.gitignore:35). Everything named here is unversioned and machine-local
- it can vanish to a `git clean -xdf`. Findings derived from it belong in THIS
file, not in notes left under that tree.
Metakit boot chain, in reading order (this is the mechanism the mk4 candidate
would reproduce for a suite-built 8.6 runtime):
- `kbs/kbskit/sources/kbskit0.4/generic/kitInit.c` - the clearest single
description, and the closest to an 8.6 target. `TclKit_AppInit` registers
Mk4tcl/rechan/vfs/tclkitpath via `Tcl_StaticPackage` (:170-177), then installs
a pre-init script (:95-135) that opens the exe AS a metakit datafile
(`mk::file open exe $::tcl::kitpath -readonly`), selects `boot.tcl` out of
`exe.dirs!0.files`, zlib-inflates it when stored size != content length, and
evals it. Comment at :88-93 explains why it must be re-entrant: it runs for
every interp, including child and thread interps.
- `kbs/kbskit/sources/kbskit0.4/library/boot.tcl` - the chicken-and-egg step.
VFS is not mounted yet, so it hand-evaluates `vfsUtils/vfslib/mk4vfs` out of
`lib/vfs1*` with raw `mk::select` (:24-38), mounts
`::vfs::mk4::handler exe`, then UNMOUNTS AND REMOUNTS after fixing the system
encoding (:109-116). Note :40-44 - "8.6 core zlib does not support this for
mk4vfs" forces `mk4vfs::zstreamed 0`, i.e. whole-blob decompression via
`vfs::memchan` rather than streaming (the two branches are at
`kbs/kbskit/sources/vfs1.4/library/mk4vfs.tcl:167-180`). Directly relevant to
this goal's memory/latency profile for a battery-carrying 8.6 kit.
- `kbs/kbskit/sources/kbskit0.4/library/setupvfs.tcl:143-200` - how the kit gets
WRITTEN (the counterpart to sdx wrap).
- `metakit/tcl/mk4tcl.cpp` (2621 lines) - `Mk4tcl_Init` -> `Mktcl_Cmds`
(:2559-2597): stubs at "8.1", registers the mkProperty/mkCursor obj types, one
`MkWorkspace` per interp with assoc-data delproc + exit handler + event source,
defines the nine `mk::*` commands. 8.6 compat is a one-line shim at :27-29;
`CHANGES` dates that work to 2007-06-25. The storage engine (`metakit/src/`)
is Tcl-agnostic - only `tcl/mk4tcl.cpp` + `mk4tcl.h` are Tcl-coupled.
- `KitCreator/kitsh/buildsrc/kitsh-0.0/{kitInit.c,boot.tcl}` - same shape,
generalised: storage backend is a compile-time switch
(`KIT_STORAGE_MK4`/`_ZIP`/`_CVFS`) driving `$::tclKitStorage`, and boot.tcl
branches on it (:18-90). Best reference for separating metakit-specific steps
from generic starkit boot - i.e. for scoping how much of the boot survives if
this goal picks cookfs or zipvfs instead. It also re-roots `tcl::tm`
(:158-161), which kbskit's does not and which THIS goal's payload needs.
- `KitCreator/mk4tcl/build.sh` + `KitCreator/mk4tcl/patches/all/` - the build
recipe and EIGHT patches against metakit 2.4.9.7 (64-bit filespace, 32/64 fix,
`unregisterchans`, `fixeventchannels`, shared-obj linker, ranlib/ar). The
`TEMP_REFERENCE/metakit` checkout is UNPATCHED upstream
(`github.com/jnorthrup/metakit`, last commit "URL fix"), and kbs.tcl:2813-2827
pulls that same unpatched master zip. If mk4 is chosen, deciding which patch
lineage to carry is a required step, not a detail.
Other trees in scope: `tcl9/` is the fossil Tcl core checkout (currently 9.1b1
per generic/tcl.h - NOT the 9.0.5 the suites build, so init-path claims must be
re-verified against 9.0.5); `tcl-sfe/` is the cookfs-lineage prior art named in
Context; `sdx/` is covered below.
## sdx: provenance and the wrapping-tool question
Recorded 2026-07-26. The user raised this before activation; findings are
concrete enough to shape acceptance.
- THE PROVENANCE GAP, precisely. There are two distinct sdx binaries on this
machine, differing by 121 bytes:
- `bin/sdx.kit` == `TEMP_REFERENCE/sdx/sdx.kit`, sha256 `5d4aa629...`,
119191 bytes. TRACKED IN GIT. This is the one make.tcl resolves.
- `scratch/sdx.kit` == `scratch/sdx-20110317.kit`, sha256 `04dc85d4...`,
119312 bytes. UNTRACKED AND GITIGNORED.
Unwrapping both (the `.vfs` trees sit beside them) shows the trees are
identical at 64 files each EXCEPT ONE: `lib/app-sdx/wrap.tcl`. The whole delta
is a two-line local patch in the Windows version-resource path:
`open [file join $idir tclkit.inf]` becomes `open ... "r"` plus
`fconfigure $fd -translation binary`, immediately before
`::stringfileinfo::writeStringInfo` (wrap.tcl:232-239 in the patched copy).
Plausibly a real fix for CRLF corruption of `tclkit.inf` on Windows.
So the tracked tool is a locally-patched sdx whose patch is recorded NOWHERE
in version control, and the stock baseline that proves what the patch IS
exists only in two ignored directories on this one machine. RESOLVED
2026-07-26: both sides are now captured in tracked form at
`src/buildsuites/suite_tcl86/patches/` (stock baseline, patched file, diff, and
a README with the hashes and the reconstruction rules) - a rebuild can no
longer silently drop or silently re-invent the fix. Note the patched path is
DORMANT for present builds: it fires only when `-runtime` is given AND the
runtime is a `.exe` AND `tclkit.inf` exists in the source `.vfs`, and no
`tclkit.inf` exists anywhere in this repo. Whether the fix is CORRECT is
unverified - no reproducer was recorded with it; confirming or retiring it is
part of this goal's characterization work.
- CHARACTERIZATION PRIMITIVE ALREADY EXISTS. sdx ships `version`
(`lib/app-sdx/version.tcl`), which walks the mounted kit and prints
`<mtime> <crc-hi>-<crc-lo>`. Verified live under
`bin/runtime/win32-x86_64/tclkit86bi.exe`: the tracked kit reports
`2025/06/04 22:56:36 69170-33997`, the stock one `2011/03/17 23:39:41
50768-16003` - and the former's timestamp is exactly the patched wrap.tcl's
mtime, independently confirming the patch is inside the tracked kit. TRAP for
any rebuild-and-compare: `traverse` hashes file NAME + MTIME + SIZE, not
content, so the signature is not reproducible across a re-wrap unless mtimes
are normalised (sdx has `version -fixtime`). A content-level characterization
battery needs its own comparison (unwrap both, diff trees; plus behavioural
wrap/unwrap/mksplit round-trips), not just this signature.
- BUNDLED LIBRARIES ARE 2011-ERA, and mostly irrelevant to wrapping: base64
2.4.1, md5 1.4.2, uri 1.2.1, autoproxy 1.5.1, ftpd 0.4, plus an entire Wikit
GUI + httpd/ftpd/rexecd server stack. Only `app-sdx` (wrap/unwrap/mksplit/
mkpack/mkinfo/lsk/qwrap) and `stringfileinfo` are on this goal's path. That
argues a rebuild should be a REDUCTION (drop the server/wiki payload) rather
than a library refresh - smaller attack surface, smaller characterization
battery, and the dropped packages have no bearing on kit wrapping. ChangeLog
ends 2011-03-17 (Pat Thoyts); upstream was `svn://svn.equi4.com/sdx/trunk`.
- BOOTSTRAP CIRCULARITY. sdx.kit is itself a metakit starkit (mk4 schema string
`dirs[name:S,parent:I,files[...]]` visible in its tail bytes; contents stored
uncompressed - plaintext greps hit inside the kit). Running it needs an
existing tclkit, so the tool that would wrap a suite-built 8.6 runtime
currently depends on a prebuilt third-party runtime of exactly the kind this
goal exists to replace. make.tcl:5048-5097 encodes that fallback ladder and
ends at `$binfolder/sdx.kit` + a tclkit. Breaking the circularity is a real
acceptance question: either (a) run sdx under the suite-built dynamic
`tclsh86t` with Mk4tcl loaded as a package rather than under a tclkit, or
(b) replace sdx's wrap step with a suite-owned writer (setupvfs.tcl above is
the reference for what writing a kit actually requires). Option (a) is the
cheaper first probe and also tests whether a suite-built Mk4tcl works at all.
- STALE WRAPPER: `bin/sdx1.bat` (and its `TEMP_REFERENCE/sdx/sdx.bat` twin)
invokes `%~dp0..\src\runtime\tclkit86bi.exe`, a path that no longer exists -
runtimes moved to `bin/runtime/<os-arch>/`. bin/ has no `sdx` wrapper under
that exact name either, so make.tcl's `auto_execok $binfolder/sdx` probe
cannot hit it and resolution falls through to the sdx.kit + tclkit last
resort. Cosmetic, but it means the documented `sdx` entry point in
bin/AGENTS.md:54 (external tools keep their own name) is currently unmet.
## Self-mount shadowing (the same-path gotcha)
Recorded 2026-07-26, verified live rather than inferred. A running tclkit mounts
its VFS AT ITS OWN EXECUTABLE PATH, so from inside itself the exe is a DIRECTORY:
probing `bin/runtime/win32-x86_64/tclkit86bi.exe` gives
`[file type [info nameofexecutable]] == directory`, `[file isfile]` 0,
`[vfs::filesystem info]` listing that same exe path, and
`glob $exe/*` yielding `bin boot.tcl lib tclkit.ico`.
Consequences this goal inherits whichever container it picks (zipfs has the same
shape via its /app anchor, so this is not an mk4-specific wart):
- A kit cannot read or copy ITSELF as a file - so it cannot wrap itself, and
cannot be handed to sdx as `-runtime`. sdx's `-runtime` path does
`LoadHeader $pfile` (wrap.tcl:109-115), a plain file read that fails on a
shadowed exe.
- make.tcl already works around this and the workaround should be preserved as a
requirement, not rediscovered: :4995-5005 detects `[file type $runtimefile] eq
"directory"` and substitutes a `_BUILDCOPY` (comment at :4996 states the rule),
and :4980-4981 records that sdx objects when the wrapping shell and the
`-runtime` argument are the same file.
- Acceptance rider: whichever container lands, the demonstration should include
a runtime wrapping a DIFFERENT copy of itself, so the shadowing path is
exercised rather than accidentally avoided.
## Metakit under Tcl 9 (bearing on container choice)
Recorded 2026-07-26 from an API survey against TEMP_REFERENCE/tcl9 (9.1b1).
Relevant because it decides whether mk4 is an 8.6-only stopgap or ONE container
format across both runtime lines - which is the strongest argument for choosing
it over cookfs/zipvfs, since Tcl 9 already has zipfs and needs no help.
- The BOOT hook is easier on 9 than on 8.6: `Tcl_SetPreInitScript` is public API
in 9.1 (generic/tcl.h:2306, run at generic/tclInterp.c:837), where 8.6 forced
kbskit to hand-declare the internal `TclSetPreInitScript` (kitInit.c:50). And
`LocatePreInitScript` honours `$tcl_library` first and exclusively
(tclInterp.c:487-490), so a preinit that mounts the container and sets
tcl_library gets init.tcl sourced from inside it.
- BUT the `proc tclInit {}` override - which is what boot.tcl IS - is dead in
9.1: tclInterp.c:619-622 says so explicitly, and `tcl_libPath` is gone
(:441-444). A dual-generation design must be preinit-shaped from the start.
UNVERIFIED: whether 9.0.5 (what suite_tcl90 builds) exports
`Tcl_SetPreInitScript`; the reference tree is 9.1b1 and its comments describe
9.0 as still tclInit-driven. Check before relying on this.
- Mk4tcl PORT COST is bounded but real: ~23 `Tcl_GetStringFromObj` +
list/bytearray call sites need `Tcl_Size*` not `int*`; stubs bump to "9.0";
one `Tcl_ChannelType` (`mk::channel`) against TCL_CHANNEL_VERSION_5 is the
fiddliest piece; five `Tcl_ObjType` initialisers gain a version + abstract-list
tail (compile clean under C++ aggregate init, worth making explicit).
`Tcl_ObjCmdProc` still takes `int objc` (tcl.h:566) unless built
`-DTCL_NO_DEPRECATED`, so the dispatcher signatures survive.
`Tcl_StaticPackage` -> `Tcl_StaticLibrary` (tcl.h:2309).
- tclvfs is largely FREE on 9: the `forTcl9` branch (1.5.0) already exists
upstream and is already recorded as the tcl9 pin in
src/buildsuites/suite_tcl86/sources.config:28-29 (user decision 2026-07-22).
`Tcl_FSRegister` / TCL_FILESYSTEM_VERSION_1 unchanged in 9.1. Core `zlib push`
+ `chan create` on 9 would also let mk4vfs use real streaming, which the 8.6
note above says it cannot.

5
goals/G-105-buildsuite-cross-target.md

@ -33,8 +33,9 @@ INVERTED):
musl-dynamic).
Relationships: G-060 (qemu cross-platform matrix - WSL is the cheap first rung;
qemu covers what WSL cannot), G-099/G-100 (the 8.6 suite inherits the target
dimension), G-102 (achieved 2026-07-21 - driver shape settled; target becomes a build-graph dimension), G-103 (achieved 2026-07-22 - see goals/archive/G-103-runtime-kit-family.md)
qemu covers what WSL cannot), G-099/G-100 (both achieved 2026-07-26 - the 8.6
suite and its Tk/tklib/tcllib dependency set inherit the target dimension; see
goals/archive/G-100-suite-tcl86-tk-tcllib.md), G-102 (achieved 2026-07-21 - driver shape settled; target becomes a build-graph dimension), G-103 (achieved 2026-07-22 - see goals/archive/G-103-runtime-kit-family.md)
(family x target = the punkbin artifact matrix), G-006 (punkbin artifact consent).
## Notes

6
goals/G-108-buildsuite-debug-tier.md

@ -55,6 +55,12 @@ TCL_COMPILE_STATS similarly must be absent (not =0) for release performance.
## Notes
- G-100 (archived) recorded a census-hygiene finding this goal's recorded test
runs depend on: `TCL<major>_<minor>_TM_PATH` env vars seed tm.tcl's module
roots and were NOT part of the suite scrub list, so a suite shell can silently
prefer machine/repo tm modules over what the suite installed. Fixed for
suite_tcl86; suite_tcl90 still has the exposure via `TCL9_0_TM_PATH` - see
goals/archive/G-100-suite-tcl86-tk-tcllib.md.
- G-104 (archived) recorded this goal as a surface consumer: 'make.tcl
buildsuite build' forwards driver args (-steps/-zigargs) untouched and
list/info read the sources.config self-description records, so the -dbg<n>

7
goals/G-116-suite-built-tcltls.md

@ -44,6 +44,13 @@ extends).
## Notes
- G-100 (achieved 2026-07-26 - goals/archive/G-100-suite-tcl86-tk-tcllib.md)
recorded two things this goal will hit when it adds its own sources: zig
0.16's `zig fetch` resolves a BUILD ROOT before fetching (run it from a
directory with a build.zig, e.g. the suite dir, or it dies with a misleading
"no build.zig file found"), and the suite env scrub must include
`TCL<major>_<minor>_TM_PATH` or a battery run measures the machine's module
trees instead of the suite's (suite_tcl90 still has that exposure).
- G-117 relationship (G-117 achieved 2026-07-24 - see
goals/archive/G-117-self-describing-runtimes.md; recorded 2026-07-24 after overlap review; neither file
previously referenced the other): this goal's Acceptance extends the artifact

210
goals/archive/G-100-suite-tcl86-tk-tcllib.md

@ -0,0 +1,210 @@
# G-100 suite_tcl86 dependency set: Tk 8.6 + tklib, tcllib with critcl accelerators, punkshell runtests on the 8.6 runtime
Status: achieved 2026-07-26
Scope: src/buildsuites/suite_tcl86/ (recipe extensions, driver steps); src/buildsuites/_build/ (tk/tklib/tcllib/critcl sources and outputs); bin/tools/ (pinned zig as consumed); src/tests/ (punkshell suites exercised against the suite-built 8.6 runtime)
Goal: suite_tcl86 builds/installs the remaining runtime pieces punkshell's Tcl 8 testing depends on - Tk from fossil core-8-6-branch loadable by the suite's 8.6 windows runtime together with a matching tklib (installer.tcl where it does more than a straight copy), and tcllib installed via its documented installer machinery WITH its critcl-built C accelerators (tcllibc; critcl driving zig cc, bootstrapped by the suite-built tclsh86 - no MS toolchain) - and punkshell's own runtests runs against the suite-built 8.6 runtime with Tk-dependent suites unskipped.
Acceptance: from the pinned zig and fetched sources the suite produces a Tk 8.6 the suite tclsh loads (package require Tk with a create/destroy-window verification recorded here) and a matching tklib whose packages resolve (a representative require recorded); a tcllib install via its installer whose accelerators load (package require tcllibc) with the accelerated-vs-pure comparison recorded; punkshell runtests runs against the suite 8.6 runtime and its census is recorded here against a same-day native/kit 8.6 baseline run (same include set), with every difference dispositioned - Tk-dependent suites run unskipped, and any 8.6-runtime-only failures are root-caused or baselined with reasons.
## Context
Mirror of G-098 (suite_tcl90 dependency set, achieved 2026-07-20) for the 8.6 arc.
Depends on G-099 (suite_tcl86 core buildsuite) - ACHIEVED 2026-07-26 (see
goals/archive/G-099-suite-tcl86-buildsuite.md); the 8.6 runtime (tclsh86ts/
tclsh86t + tcl86t.dll + installed lib tree), thread 2.8.13, tclvfs 1.4.2 and
the core/thread/tclvfs test-gate this goal extends now exist, so the dependency
is met and G-100 is unblocked.
8.6-specific expectations (verify at activation):
- Tk core-8-6-branch pairs with the 8.6 runtime; the 9.0 build_tk zig module was
derived from tk9 makefile.vc object lists - the 8.6 lists/xlib arrangement differ,
so expect a re-derivation rather than reuse.
- tcllib trunk supports 8.5+; the same vendored critcl mechanism applies, with
critcl driven by the suite tclsh86. tcllibc built per-generation (the punk9wintk903
vfs precedent carries tcl8/tcl9 tcllibc side by side under lib_tcl8/lib_tcl9).
- runtests under 8.6: no tcl::process, so -jobs child spawning falls back to
blocking execs which CONVOY on windows (documented) - run -singleproc 1 or accept
serialized child walls for the census run; record which mode produced the census.
- Census baseline: compare against the punk86-class kit runtime results on the same
day/include-set (forward-slash path conventions on 8.6 kits noted in project
memory) rather than against the 9.x census.
- The kit-child 'src' package_mode launch added to runtests for punk kits (G-098
increment 7) applies to 8.6 punk kits too - a plain suite tclsh86 host takes the
native path.
SEQUENCING: as for G-099 - proposed now, activation recommended after suite_tcl90's
G-096 (achieved 2026-07-20) remainder closed and punk905_beta trial concludes.
## Progress
### 2026-07-26 activation + increment 1 (dependency set built, census taken)
- ACTIVATED by the user. Sources wired into both flows: `sources.config` gains
tk (`core-8-6-branch` -> dir `tk86`), tklib/tcllib (trunk) and critcl (git
3.3.1); `build.zig.zon` gains the matching pins - tk freshly `zig fetch`ed
(checkin 9986fdc267, 2025-03-17 = Tk 8.6.17), tklib/tcllib/critcl re-fetched
and verified BYTE-IDENTICAL to suite_tcl90's pins (generation-neutral
upstreams; what differs per suite is the shell running the installers and the
critcl target). suite.tcl gains git-source support + the new staging items;
the bootstrap driver's `source_dirs`/`recipe_items` match.
- TK 8.6 RE-DERIVED (`build_tk86/build_tk86.zig`) from the staged 8.6
`win/makefile.vc` + `rules.vc`, not ported from the 9.0 module - the goal
Context's expectation held. Full derivation record in
`src/buildsuites/suite_tcl86/README.md`; the load-bearing differences:
no `tkPkgConfig.c` (so NO `CFG_*` defines at all), the
TCL_MAJOR_VERSION==8 naming arm (`tk86t.dll`, `lib/tk8.6`), the
`@TK_WIN_VERSION@` = `$(DOTVERSION).0.0` manifest substitution (not the
tcl-style releaselevel formula), and an 8.6 object inventory without
tkIcu/tkImgListFormat/tkImgSVGnano/tkWinGDI/tkWinIco/tkWinSysTray. Products:
`bin/tk86t.dll` + `lib/tk86t.lib` + the `lib/tk8.6` script library +
makefile-shaped `pkgIndex.tcl`.
- TK ACCEPTANCE EVIDENCE (repeatable - `tools/tk_smoke.tcl` runs in the `smoke`
step on every build, and a load-only check would pass with no usable display):
`tk_smoke OK: Tk 8.6.17 (Ttk 8.6.17) windowingsystem win32 screen 3840x2160
depth 32 widgets Label/TButton/Toplevel create+map+destroy clean`. Both shells
load it: `pkg_smoke OK: Thread:2.8.13 vfs:1.4.2 registry:1.3.5 dde:1.4.6
Tk:8.6.17` (static AND dynamic).
- TKLIB via its OWN installer (batch, packages-only, explicit `-pkg-path` so the
installer's tcl_pkgPath-derived default can never place files off-tree) ->
`lib/tklib0.9`. Representative require recorded: `pkg_smoke OK: tooltip:2.0.4`
- a Tk-DEPENDENT package resolving through the installer-generated aggregate
pkgIndex.
- TCLLIB 2.0 installed the same way (`md5:2.0.9 sha1:2.0.5 cmdline:1.5.3`), and
TCLLIBC built by critcl 3.3.1 driving `zig cc` under the suite-built 8.6 shell
(tracked `critcl_zig.config`, target pinned `win32-x86_64-zig` - critcl
otherwise probes PATH and falls back to its builtin msvc target). Accelerator
evidence, both on every build: `pkg_smoke OK: tcllibc:2.0 md5-critcl-accel:1`
and the new `tools/accel_bench.tcl` -
`accel_bench OK: sha1 over 4198400 bytes - critcl 5.9ms vs pure-tcl 2661.4ms
= 452.3x (digests identical, sha1 2.0.5, tcllibc 2.0)`. The bench loads BOTH
implementations via `::sha1::LoadAccelerator` and switches with
`::sha1::SwitchTo`, so the comparison is in-process and the digests are
compared rather than assumed.
- TEST TIERS extended on the G-107 policy pattern: `test-tcllib` / `test-tklib`
(record tier, inside `test-libraries`) and the OPT-IN `test-tk` (excluded from
`test-libraries`; `winDialog.test`/`winMsgbox.test` excluded by default -
their message-injection automation blocks on a human under a batch shell).
- HERMETIC-ENVIRONMENT FINDING (see Notes): `TCL<major>_<minor>_TM_PATH` was
never scrubbed, so the suite shells were silently preferring repo-built tm
modules over the tcllib the suite had just installed. Fixed in all three
scrub sites before the census was taken.
#### punkshell runtests census (2026-07-26, both runs same day, same include set)
Mode: DEFAULT single-process sequential (`-singleproc 1`; no `-jobs` - 8.6 has
no `tcl::process`, so parallel child spawning re-convoys on windows, per the
goal Context). Identical invocation for both runtimes, full source-tree suite,
no include filter: `<shell> src/tests/runtests.tcl -report json`.
- BASELINE - native Tcl 8.6.17 (`C:/Users/sleek/AppData/Local/Apps/Tcl86`,
which carries Tk 8.6.17, Thread 2.8.12, tcllibc 2.0, twapi): 101 files,
Total 1003 / Passed 986 / Skipped 10 / Failed 7, 657s. Files failed:
`core/tcl/.../exec.test` (ERROR `invalid command name "tcl::build-info"` - a
9-era core test under an 8.6 shell), `modules/punk/ns/.../nslist.test` (4),
`modules/punk/ns/.../nsprimitives.test` (2 - the `:::odd` namespace-lookup
class), `modules/punkcheck/.../concurrency.test` (1).
- SUITE RUNTIME - `out/bin/tclsh86ts.exe` (8.6.18, this suite's static shell):
101 files, Total 1003 / Passed 987 / Skipped 10 / Failed 6, 601s.
- DIFFERENCE SET (authoritative, `scriptlib/developer/runtests_parity.tcl`):
exactly ONE - `punkcheck concurrency_two_writers_no_corruption` ERRORed in the
BASELINE run and PASSED on the suite runtime. Every other per-file status,
tally, failure identity, skip identity+reason and warning code is identical.
DISPOSITION: flake under full-suite load, not a runtime difference - the file
passes 6/6 in isolation on BOTH runtimes, twice each (verified same day).
ZERO suite-runtime-only failures; nothing needed baselining.
- TK-DEPENDENT SUITES RAN UNSKIPPED on the suite runtime:
`modules/punk/repl/testsuites/repl/consolebackends.test` 3/3 PASS (its
tkconsole backend drives a child repl on the suite-built Tk) and
`modules/opunk/console/testsuites/console/backends.test` 7 passed / 1 skipped.
That single skip is `backends_tkconsole_widget_size_and_eof`, whose
`tkavailable` constraint requires the `PUNK_TEST_TK` OPT-IN env var (not Tk
availability); it skips identically on the native baseline, and the opt-in's
own pre-existing quirk is recorded in G-098's increment 6 notes.
- Runtime-difference note (does not change any result): the suite runtime has no
twapi, so its runs open with `is_interactive cannot detect console ... without
twapi` and a `punk::du` slower-disk-operations warning that the native
baseline (twapi present) does not print.
#### library test tiers - first censuses (2026-07-26, suite tclsh86ts.exe 8.6.18)
Beyond the acceptance text; run because the goal Context asks the G-107
policy-tier shape to follow for the new libraries.
- thread (gate): 142 run / 126 passed / 16 skipped / 0 failed - GATE PASS
(baseline still empty; constraint-driven skips only).
- tklib (RECORD, new tier): 796 run / 684 passed / 112 skipped / 0 failed. Its
Tk-dependent suites exercise the suite-built Tk, and nothing failed.
- tcllib (RECORD, new tier): 64259 run / 61186 passed / 3032 skipped / 41
failed - names in `out/testreports/tcllib.summary`. Dominant classes:
network-reaching suites (picoirc 20, httpd-proxy/cgi 4), installer/path
assumptions (install-2.1/2.2, find-sf-*/find-4.0, multi/multiop-5.2), plus
matrix-16.7/16.8, tar-add, uri-4.1, example-*, zipfile-encode-6.4/6.5. A
record tier is evidence, not a gate criterion - no baseline is tracked and the
step never fails the build on these.
- tclvfs (gate): the tcllib install CHANGED this suite's census - vfsFtp-1.1..
1.3 sit behind an `ftp` constraint that needs tcllib's ftp package (absent at
the G-099 capture: 3 constraint skips), so they now RUN and reach the retired
`ftp://ftp.tcl.tk`, the same external-service class already baselined for
vfsUrl-1.2/2.1 (skips 8 -> 5 accordingly). `expected_test_failures_tclvfs.txt`
extended with the three names and the cause; re-run confirms GATE PASS
(51 run / 42 passed / 5 skipped / 4 failed, all baselined; vfsUrl-1.2 noted as
a stale entry - which members of the family fail is network-timing dependent,
so stale notes are expected and never fatal).
Remains for acceptance: nothing - the four acceptance clauses (Tk loadable +
create/destroy verification, matching tklib with a representative require,
tcllib installed by its own machinery with loading accelerators and a recorded
accelerated-vs-pure comparison, and a dispositioned runtests census against a
same-day 8.6 baseline with Tk suites unskipped) are all recorded above.
## Notes
- Hermetic-environment finding 2026-07-26 (fixed here, exposure recorded for
suite_tcl90): `scrubTclEnv` cleared only TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY,
but `tm.tcl` also seeds module roots from `env(TCL<major>_<minor>_TM_PATH)`
down through every lower minor. This machine points `TCL8_6_TM_PATH` at
`C:/repo/jn/shellspy/modules` among others, so suite shells preferred a
repo-built `sha1-2.0.4.tm` / `md5 2.0.8` over the tcllib 2.0.5 / 2.0.9 the
suite had just installed - a census through such a shell measures the machine.
Scrub list now covers `TCL8_0..8_7_TM_PATH` + `TCL9_0/9_1_TM_PATH` in the
per-step scrub (`build_common.zig` `tcl_env_vars`), `suite.tcl`'s
process-level unset and the bootstrap nested-build scrub. suite_tcl90 has the
same exposure via `TCL9_0_TM_PATH` and was deliberately left untouched (out of
this goal's scope) - worth a suite_tcl90-scoped fix.
- zig 0.16 trap (recorded while pinning): `zig fetch <url>` resolves a BUILD
ROOT before fetching, so it must be run from a directory containing a
`build.zig`; from anywhere else it dies with the misleading "no build.zig file
found, in the current directory or any parent directories". Also in the
`build.zig.zon` header.
- Activation freshness 2026-07-26: goals_xref.tcl score G-100 re-run at the
proposed->active flip (user-directed activation). Top pairs G-105 / G-101 /
G-116 / G-108 reviewed; the two that were only one-directional get the
back-pointers below, G-116 was already recorded here, and the remaining hits
are term noise - no new relationships.
- Related: G-105 (buildsuite cross-target) - it names this arc as a target-
dimension consumer; the tk/tklib/tcllib/critcl steps this goal adds must not
preclude a per-target dimension (no per-target tree copies, per-target out
prefixes).
- Related: G-101 (8.6 kit container strategy) - the downstream consumer of this
dependency set: whatever container it picks carries the Tk/tklib/tcllib/
tcllibc payload this goal produces.
- Related: G-108 (debug buildsuite tier) - sibling suite-arc goal queued behind
this arc; its -dbg<n>/tcl::test variant pattern is suite_tcl90-scoped today.
- G-116 relationship (recorded 2026-07-24 after overlap review): G-116's zig-built
crypto-backend + static tcltls pattern (suite_tcl90) is the reference if the 8.6
dependency set later grows a tls battery - the bi-family payload contract it
extends is generation-shaped, not 9-only.
- G-005 relationship: same zig toolchain-policy lineage as G-099's note - G-005
owns the make.tcl-integrated vendored-extension build direction; the suites are
its arm's-length proving ground.
- G-099 (achieved, see goals/archive/G-099-suite-tcl86-buildsuite.md) delivered
the patterns this goal extends: the build86.zig recipe + build_tclthread86/
build_tclvfs86 helpers (TEA-shape companion builds against the 8.6 stub lib),
the whole-tree library install + tm-module derivation, and the suite_tcl90
G-107 (achieved - goals/archive/G-107-buildsuite-library-tests.md) policy-tier
test-gate (test_gate.tcl) - a Tk/tklib/tcllib gate follows the same shape. Two 8.6-specific traps recorded there carry forward: the
load-bearing MP_FIXED_CUTOFFS define and TCL_BROKEN_MAINARGS+console-subsystem
entry handling (build86.zig comments + README DERIVATION). Also inherited from
G-099 Notes: the G-106 powershell console-mode disableraw-via-server path is
only tcl9-verified - now that a twapi-less 8.6 runtime exists, run the G-106
recipe against it (goals/archive/G-106-powershell-consolemode-fallback.md).

13
src/buildsuites/AGENTS.md

@ -40,8 +40,11 @@ this project's `src/` tree.
- `suite_tcl90/` - Tcl 9.0 suite (its README.md carries lineage/detail; no
child AGENTS.md yet)
- `suite_tcl86/` - Tcl 8.6 suite, G-099 arc (recipe landed 2026-07-26:
tclsh86ts/tclsh86t + tcl86t.dll + lib tree, thread 2.8.13 + tclvfs 1.4.2,
smokes green; test censuses/baselines in progress - its README.md carries
the deliverables, source selections and the makefile.vc DERIVATION record;
no child AGENTS.md yet)
- `suite_tcl86/` - Tcl 8.6 suite (G-099 achieved 2026-07-26: tclsh86ts/tclsh86t
+ tcl86t.dll + lib tree, thread 2.8.13 + tclvfs 1.4.2, core/thread/tclvfs
gates; G-100 active: Tk 8.6.17 + tklib 0.9 + tcllib 2.0/tcllibc, and
punkshell's own runtests censused on this runtime). Its README.md carries the
deliverables, source selections, both makefile.vc DERIVATION records and the
hermetic-environment finding (TCL<major>_<minor>_TM_PATH must be scrubbed
alongside TCLLIBPATH/TCL_LIBRARY or a census measures the machine); no child
AGENTS.md yet

144
src/buildsuites/suite_tcl86/README.md

@ -1,18 +1,18 @@
# suite_tcl86 - zig buildsuite for Tcl core-8-6-branch (G-099)
# suite_tcl86 - zig buildsuite for Tcl core-8-6-branch (G-099, G-100)
Builds a Tcl 8.6 windows runtime from fossil core-8-6-branch sources with the
pinned zig toolchain only (zig-only build policy; msys/mingw and MS toolchains
remain test/validation-only), together with the core binary companions
punkshell's Tcl 8 targets need - thread 2.8 and tclvfs - gated against the 8.6
core testsuite. Forked from `suite_tcl90` (the shape reference); shared
scaffolding stays duplicated-with-a-note until this second consumer makes the
right factoring obvious (G-099 Context).
STATUS: recipe LANDED 2026-07-26 - full `suite.tcl build` PASS (build 8.6.18 +
install + install-libraries + smokes on both shells, hermetic resolution
proven). Core/thread/tclvfs test censuses + dispositioned baselines are the
remaining G-099 acceptance items. Progress ledger:
`goals/G-099-suite-tcl86-buildsuite.md`.
remain test/validation-only), together with the binary and script companions
punkshell's Tcl 8 targets need - thread 2.8, tclvfs, Tk 8.6, tklib, and tcllib
with its critcl-built accelerators - gated against the 8.6 core testsuite.
Forked from `suite_tcl90` (the shape reference); shared scaffolding stays
duplicated-with-a-note until this second consumer makes the right factoring
obvious (G-099 Context).
STATUS: G-099 ACHIEVED 2026-07-26 (runtime + thread/tclvfs + core-test gate -
see `goals/archive/G-099-suite-tcl86-buildsuite.md`). G-100 (the dependency set:
Tk + tklib + tcllib/tcllibc, and punkshell's own runtests on this runtime) is
ACTIVE - see `goals/G-100-suite-tcl86-tk-tcllib.md` for the progress ledger.
## Deliverables (per the G-099 acceptance; stock threaded-release naming)
@ -33,11 +33,38 @@ remaining G-099 acceptance items. Progress ledger:
library pkgIndex files (http 2.9.9, msgcat 1.6.1, tcltest 2.5.11,
platform 1.1.1, platform::shell 1.1.4).
- `suite.tcl test` runs the 8.6 core testsuite, parses totals (exit codes not
trusted), and gates against a tracked `expected_test_failures.txt` baseline -
the FIRST CENSUS is in flight (record mode; the 9.0.5 census does not
transfer; machine traits dispositioned there will likely reappear under 8.6
test names). Same census flow for the thread/tclvfs suites
(`expected_test_failures_thread.txt` / `_tclvfs.txt`).
trusted), and gates against the tracked dispositioned `expected_test_failures.txt`
baseline (2 entries: the windows admin-share self-globbing machine trait).
Same flow for the thread/tclvfs suites (`expected_test_failures_thread.txt`
empty / `_tclvfs.txt` 2 external-FTP entries).
## Deliverables (G-100 dependency set)
- Tk **8.6.17** from `core-8-6-branch`: `bin/tk86t.dll` (+ `lib/tk86t.lib`
import lib) and the script library at `lib/tk8.6` with the makefile-shaped
`pkgIndex.tcl` - `package require Tk` loads in BOTH shells, and the `smoke`
step's `tools/tk_smoke.tcl` maps a real toplevel with classic + themed
widgets, updates, and destroys it (a load-only check would pass with no
usable display).
- tklib **0.9** installed by its OWN `installer.tcl` (batch, packages only,
explicit `-pkg-path`): not a straight copy - module exclusions, app-script
generation and the aggregating top-level `pkgIndex.tcl` that makes the
two-levels-down module indexes resolvable. Smoke: `package require tooltip`
(a Tk-DEPENDENT tklib package) through that aggregate index.
- tcllib **2.0** installed by its own installer (same family; its critcl DSL
procs are stubbed to no-ops there - that is the pure-tcl side), plus
**tcllibc** built by critcl 3.3.1 driving `zig cc` (`critcl_zig.config`,
target pinned `win32-x86_64-zig`) under the suite-built 8.6 shell. Evidence
on every build: `pkg_smoke -accel md5 tcllibc` (asserts
`::md5::accel(critcl)`) and `tools/accel_bench.tcl` - sha1 over 4MB,
**critcl 5.9ms vs pure-tcl 2661ms = ~450x**, digests identical.
- Library test tiers extended (G-107 policy pattern): `test-tcllib` /
`test-tklib` on the RECORD tier inside `test-libraries`, plus the OPT-IN
`test-tk` (excluded from `test-libraries`: it maps windows on the interactive
desktop for tens of minutes; the native-dialog suites `winDialog.test` /
`winMsgbox.test` are excluded by default because their message-injection
automation blocks waiting on a human under a batch shell - re-include with
`-Dtestnotfiles-tk=""`).
## Sources (both flows carry the same identity)
@ -59,6 +86,19 @@ Selections (2026-07-25, staged and verified):
(1.5.0) is tcl9-only (user decision 2026-07-22, recorded in suite_tcl90's
sources.config).
Selections (2026-07-26, G-100 dependency set):
- tk: `core-8-6-branch` -> dir `tk86` - tip checkin 9986fdc267 (2025-03-17) =
**8.6.17** (TK_PATCH_LEVEL in the staged tree). The 8.6 line is quiet
upstream; it pairs with the 8.6.18 core without complaint.
- tklib / tcllib / critcl: GENERATION-NEUTRAL - the same trunk refs and the
same content-hashed zon pins suite_tcl90 carries (re-fetched 2026-07-26 and
verified byte-identical). What differs per suite is the SHELL that runs the
installers and the critcl target that builds tcllibc; critcl compiles against
its own bundled headers, chosen by the version of the shell running it
(`lib/critcl/critcl_c/tcl8.6/` here), which is why one config file serves
both generations.
## DERIVATION (build86.zig from the staged 8.6.18 win/makefile.vc)
Recorded findings from the staged tree (assess-not-assume items from the G-099
@ -112,12 +152,78 @@ tclvfs test gates. Two extra findings surfaced during the build (recorded in
the DERIVATION list above): the load-bearing MP_FIXED_CUTOFFS, and the
vfs142t.dll TEA name that lets Tcl's load-time name guessing derive Vfs_Init.
## DERIVATION (build_tk86 from the staged tk 8.6 win/makefile.vc)
A RE-DERIVATION, not a port of suite_tcl90's `build_tk.zig` (the G-100 Context
predicted this). Verified against the staged tree 2026-07-26:
- Object inventory: 8.6 `TKOBJS` has no `tkIcu` / `tkImgListFormat` /
`tkImgSVGnano` / `tkPkgConfig`, and no `tkWinGDI` / `tkWinIco` /
`tkWinSysTray`. `XLIBOBJS` is the same 5-file X emulation set but is a
first-class makefile macro here (in the 9.0 derivation those files had to be
recovered by hand after a name-pattern extraction missed them). Every name in
the staged makefile resolved to exactly one source dir - the recipe's lists
are that mapping.
- NO `generic/tkPkgConfig.c` in 8.6 -> the recipe passes no `CFG_*` defines at
all (the 9.0 module carries a full `CFG_{RUNTIME,INSTALL}_*` set for it).
- Naming: `rules.vc` TCL_MAJOR_VERSION==8 arm gives `TKLIBNAME` = **tk86t.dll**
(SUFX `t` survives while TCL_VERSION <= 86); the 9.0 arm produces the
`tcl9tk90.dll` form. Script install dir is `lib/tk8.6`.
- `wish.exe.manifest`: 8.6's makefile substitutes `@TK_WIN_VERSION@` with
`$(DOTVERSION).0.0` = "8.6.0.0" - NOT the tcl-style
`<dotversion>.<releaselevel>.<patchlevel>` formula (`common.winResourceVersion`)
the 9.0 tree uses. `win/rc/tk.rc` embeds the manifest as RT_MANIFEST in the
DLL itself (that is what enables comctl32 v6 themed scrollbars), so it is
generated into the build cache and delivered through the rc include paths.
- `tkUuid.h`: 8.6 DOES generate it (`win/tkUuid.h.in` + `manifest.uuid`,
makefile.vc:541) and `tkWindow.c` includes it - same overlay treatment as the
tcl side.
- Stubs: `rules.vc` sets `USE_STUBS_DEFS` (`/DUSE_TCL_STUBS /DUSE_TCLOO_STUBS`)
for every project except tcl itself, so building Tk against the suite's
`libtclstub86.a` IS the stock arrangement - and it keeps the dll unbound to a
particular tcl dll name, so both the static and the dynamic suite shell load it.
- `tkMain.c` compiled twice (`tkMain.obj` + `tkMain2.obj` with UNICODE). The
ansi copy is forced with `-UUNICODE -U_UNICODE`; verified 2026-07-26 that the
pinned zig cc defines NEITHER for `x86_64-windows-gnu`, so the stock
ANSI/TCHAR arrangement of the 8.6 core is matched exactly (TCHAR resolution in
8.6 depends on `_UNICODE` at each TU's compile - tk and tcl must agree).
- Deviation from stock, recorded: the generated `pkgIndex.tcl` keeps only the
windows arm of the makefile's two-branch text (the unix DISPLAY branch cannot
apply to a windows-only suite) and wraps the load path in `file normalize`
(as the 9.0 recipe does).
## Hermetic environment (G-100 finding, 2026-07-26)
`scrubTclEnv` cleared only TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY. `tm.tcl` ALSO
seeds its module roots from `env(TCL<major>_<minor>_TM_PATH)` - for an 8.6
shell, `TCL8_6_TM_PATH` down through every lower minor - and this dev machine
points `TCL8_6_TM_PATH` at repo/foreign module trees. A suite shell therefore
preferred a repo-built `sha1-2.0.4.tm` over the tcllib 2.0.5 the suite had just
installed (and `md5 2.0.8` over 2.0.9): any census taken through such a shell
measures the machine, not the suite. The scrub list now covers
`TCL8_0..8_7_TM_PATH` and `TCL9_0/9_1_TM_PATH` in all three places - the
per-step scrub (`build_common.zig` `tcl_env_vars`), `suite.tcl`'s process-level
unset, and the bootstrap driver's nested-build scrub. Keep the two lists in
step. NOTE: suite_tcl90 has the same exposure via `TCL9_0_TM_PATH` and is
deliberately untouched here (out of this goal's scope).
## Flows
- FOSSIL DEV FLOW: `tclsh suite.tcl build|test|clean` (or via the make.tcl
surface: `tclsh src/make.tcl buildsuite build suite_tcl86 ?driver-args?`).
- NO-TCLSH FLOW: `zig build stage|bootstrap` - lands with the recipe;
`build.zig.zon` pins are already recorded.
Default build steps: `install install-libraries smoke tklib tcllib tcllibc`.
- NO-TCLSH FLOW: `zig build --build-file build86.zig stage|bootstrap` from THIS
tracked directory (materializes the `build.zig.zon` pins for any source that
has no checkout in the stage, then drives the staged recipe). `stage` verified
2026-07-26 - it skips the existing fossil/git checkouts and stages the recipe
items (including `build_tk86/` and `critcl_zig.config`).
- FINGERPRINT TRAP (fixed 2026-07-26): `build.zig.zon`'s `.fingerprint` low
half is a checksum OF THE PACKAGE NAME, so a hand-authored value is rejected
at configure time ("invalid fingerprint ... if this is a new or forked
package, use this value: ..."). The G-099-era value was hand-authored and
survived unnoticed because the staged `suite.tcl` flow never reads this file
- the first real `zig build` from the tracked dir surfaced it. If you fork
this suite, take the value zig prints rather than inventing one.
The suite self-description contract (records in `sources.config` read by
`make.tcl buildsuite list/info`) is documented in `src/buildsuites/README.md`.

48
src/buildsuites/suite_tcl86/build.zig.zon

@ -5,16 +5,23 @@
//tarball urls + zig package hashes; 'zig fetch <url>' prints a hash, branch-tip
//urls are deliberately NOT used - the pin IS the provenance record).
//
//The bootstrap build.zig driver lands with the recipe (build86.zig is a
//pending-derivation skeleton); these records pin the 8.6 sources from the
//suite's FIRST COMMIT so both flows carry the same source identity
//These records pin the 8.6 sources so both flows carry the same source identity
//(sources.config = live dev-flow refs on the same repositories/branches).
//Hashes below were computed with the pinned zig 0.16.0 ('zig fetch') on
//2026-07-25.
//Hashes computed with the pinned zig 0.16.0: the core set 2026-07-25, the
//G-100 dependency set 2026-07-26. TRAP: 0.16's 'zig fetch' resolves a build
//root BEFORE fetching, so it must be run from a directory with a build.zig
//(this suite dir does) - from anywhere else it dies with the misleading
//"no build.zig file found, in the current directory or any parent directories".
.{
.name = .suite_tcl86,
.version = "0.0.1",
.fingerprint = 0x9fe14fa771ae8617,
//zig-computed for the package name .suite_tcl86 - its low half is a checksum
//OF THAT NAME, so a hand-authored value is rejected at configure time
//('invalid fingerprint ... use this value: ...'). Corrected 2026-07-26 after
//the first real 'zig build --build-file build86.zig' from this tracked dir
//(the staged suite.tcl flow never reads this file, which is why the G-099-era
//hand-authored value survived unnoticed).
.fingerprint = 0x5c82bff30d9af9ad,
.minimum_zig_version = "0.16.0",
.dependencies = .{
//tcl core-8-6-branch checkout 1d1d5cbd91 2026-07-14 (8.6.17+ maintenance era)
@ -37,6 +44,35 @@
.hash = "N-V-__8AAIaeDQBJ9V3M96hpQSxemxghwLBG8fB1s0rqh4SQ",
.lazy = true,
},
//-- G-100 dependency set ------------------------------------------
//tk core-8-6-branch checkout 9986fdc267 2025-03-17 (8.6 maintenance line,
//pairing with the 8.6 runtime; the 9.0 suite pins core-9-0-branch instead)
.tk = .{
.url = "https://core.tcl-lang.org/tk/tarball/9986fdc26786cecc8d513e05373fce5bd6d7b2377d8e3b6d17fcb8f09b2d81fb/tk.tar.gz",
.hash = "N-V-__8AANjEKQE7NM2LsFNsAzQl7mtH8R82KTFHnPrCkBuH",
.lazy = true,
},
//tklib/tcllib/critcl are generation-neutral: the SAME pins suite_tcl90
//carries (verified identical by re-fetch 2026-07-26). What differs per
//suite is the shell that runs the installers and the critcl target.
//tklib trunk checkout 2d53beafc0 2026-07-03 (0.9 era)
.tklib = .{
.url = "https://core.tcl-lang.org/tklib/tarball/2d53beafc0e8621dacc75b3c809898cc9f1f92f9/tklib.tar.gz",
.hash = "N-V-__8AABomLAFd2U5OW96AfpQLoLm3Q9swpQZEwslrEJx3",
.lazy = true,
},
//tcllib trunk checkout c784f6fda4 2026-06-30 (2.0 era)
.tcllib = .{
.url = "https://core.tcl-lang.org/tcllib/tarball/c784f6fda4516f676453a2785b3c56f1ca5999f4/tcllib.tar.gz",
.hash = "N-V-__8AAFvlBARw4fIY107nOJmfxdwAjr1Vr89cYx98czpF",
.lazy = true,
},
//critcl 3.3.1 (tag commit 254bdffe2a)
.critcl = .{
.url = "https://github.com/andreas-kupries/critcl/archive/254bdffe2afc78148f79f92df8d6823a24f971bf.tar.gz",
.hash = "N-V-__8AAF8-bABDlxChmtnyalKgofVfN3kzcZdmZ7pjEmE0",
.lazy = true,
},
},
.paths = .{""},
}

232
src/buildsuites/suite_tcl86/build86.zig

@ -56,6 +56,7 @@ const common = @import("build_common.zig");
const build_zlib86 = @import("build_zlib86/build_zlib86.zig").build_zlib86;
const build_tclthread86 = @import("build_tclthread86/build_tclthread86.zig").build_tclthread86;
const tclvfs86 = @import("build_tclvfs86/build_tclvfs86.zig");
const tk86 = @import("build_tk86/build_tk86.zig");
const tcl_dot_version = "8.6";
const tcl_nodot_version = "86";
@ -616,6 +617,12 @@ pub fn build(b: *std.Build) !void {
build_tclvfs_step.dependOn(&tclvfs_compile.step);
try tclvfs86.install_tclvfs_package("../tclvfs", b, tclvfs_compile);
//-- Tk 8.6 (G-100): tk86t.dll + the tk script library + pkgIndex, all hooked
//onto the default install step inside the helper (as the companions above).
const tk_build = try tk86.build_tk86(tcl_source_folder, "../tk86", b, target, optimize, finalstublib);
const build_tk_step = b.step("build-tk", "build the Tk 8.6 loadable extension (tk86t.dll) + tk script library install");
build_tk_step.dependOn(&tk_build.lib.step);
// ================================================================
//install-libraries: the whole script library tree -> lib/tcl8.6 (each
//subdir package keeps its own pkgIndex; encoding/tzdata/msgs ride inside),
@ -682,18 +689,168 @@ pub fn build(b: *std.Build) !void {
//package-resolution smoke: the built companions + the library-dir
//packages load in each shell (Thread/vfs via lib/, registry/dde via
//the script library's package dirs - static registrations in the
//static shell, the built dlls in the dynamic one)
//static shell, the built dlls in the dynamic one; Tk via lib/tk8.6
//under a withdrawn root, so no window flashes here)
const pkgs = b.addSystemCommand(&.{sr.exe});
common.scrubTclEnv(pkgs);
pkgs.has_side_effects = true;
pkgs.setName(b.fmt("pkg_smoke {s}", .{sr.label}));
pkgs.addFileArg(b.path("tools/pkg_smoke.tcl"));
pkgs.addArgs(&.{ "Thread", "vfs", "registry", "dde" });
pkgs.addArgs(&.{ "-gui", "1", "Thread", "vfs", "registry", "dde", "Tk" });
pkgs.step.dependOn(b.getInstallStep());
pkgs.step.dependOn(install_libraries);
smoke_step.dependOn(&pkgs.step);
}
//-- Tk window-station smoke (G-100 acceptance evidence): maps a real
//toplevel with classic+themed widgets, updates, then destroys - a
//load-only check would pass without a usable display. Static shell only:
//the pkg_smoke runs above already prove Tk LOADS in both.
const tksmoke = b.addSystemCommand(&.{installed_static});
common.scrubTclEnv(tksmoke);
tksmoke.has_side_effects = true;
tksmoke.setName("tk_smoke static");
tksmoke.addFileArg(b.path("tools/tk_smoke.tcl"));
tksmoke.addArgs(&.{ "-expect", tk_build.patchlevel });
tksmoke.step.dependOn(b.getInstallStep());
tksmoke.step.dependOn(install_libraries);
smoke_step.dependOn(&tksmoke.step);
// ============ G-100 script-library dependency set ============
//Two shell roles here, deliberately split:
// INSTALLER/CRITCL runs use the build-cache artifact (addRunArtifact) so
// the step is CACHED on the shell's own hash and can own output dirs.
// That exe has no ../lib/tcl8.6 beside it, so those runs (and only
// those) get TCL_LIBRARY pointed at the staged source library.
// SMOKE runs use the INSTALLED shell with no TCL_LIBRARY, exactly as the
// smokes above: package resolution then comes from <prefix>/lib via the
// exe-relative auto_path entry - which is also what proves the install
// layout, not just the build.
const tcl_library_src = common.replaceAll(b, b.pathFromRoot(tcl_source_folder ++ "/library"), "\\", "/");
//-- tklib: pure-tcl, but its installer is NOT a straight copy (module
//exclusion list, app-script generation, and generation of the aggregating
//top-level pkgIndex.tcl that makes the two-levels-down module indexes
//resolvable). Batch mode, packages only; -pkg-path is a cached output dir
//installed into the prefix, so the installer's own default (derived from
//the RUNNING shell's tcl_pkgPath) can never place files off-tree.
const tklib_ver = common.installerPackageVersion(b, "../tklib");
const tklib_step = b.step("tklib", "install tklib via its own installer under the suite-built 8.6 shell (+Tk-dependent package smoke)");
const tklib_run = b.addRunArtifact(tclsh_static_exe);
common.scrubTclEnv(tklib_run);
tklib_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src);
//cached (non-side-effect) Runs spawn with stdout IGNORED - on windows the
//child then has no stdout handle and tcl errors on any puts ('can not find
//channel named "stdout"'). Capturing gives it a real pipe.
_ = tklib_run.captureStdOut(.{});
tklib_run.setCwd(b.path("../tklib"));
tklib_run.addFileArg(b.path("../tklib/installer.tcl"));
tklib_run.addArgs(&.{ "-no-gui", "-no-wait", "-no-apps", "-no-html", "-no-nroff", "-no-examples", "-pkg-path" });
const tklib_out = tklib_run.addOutputDirectoryArg("tklib-pkg");
const tklib_install = b.addInstallDirectory(.{
.source_dir = tklib_out,
.install_dir = .prefix,
.install_subdir = b.fmt("lib/tklib{s}", .{tklib_ver}),
});
const tklib_smoke = b.addSystemCommand(&.{installed_static});
common.scrubTclEnv(tklib_smoke);
tklib_smoke.has_side_effects = true;
tklib_smoke.setName("tklib pkg_smoke");
tklib_smoke.addFileArg(b.path("tools/pkg_smoke.tcl"));
tklib_smoke.addArgs(&.{ "-gui", "1", "tooltip" }); //a Tk-DEPENDENT tklib package
tklib_smoke.step.dependOn(&tklib_install.step);
tklib_smoke.step.dependOn(b.getInstallStep()); //Tk dll + pkgIndex + script library
tklib_smoke.step.dependOn(install_libraries);
tklib_step.dependOn(&tklib_smoke.step);
//-- tcllib: same installer family (its critcl DSL procs are stubbed to
//no-ops there - this is the PURE-TCL side; the accelerators are the
//tcllibc step below).
const tcllib_ver = common.installerPackageVersion(b, "../tcllib");
const tcllib_step = b.step("tcllib", "install tcllib (pure-tcl side) via its own installer under the suite-built 8.6 shell (+smoke)");
const tcllib_run = b.addRunArtifact(tclsh_static_exe);
common.scrubTclEnv(tcllib_run);
tcllib_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src);
_ = tcllib_run.captureStdOut(.{});
tcllib_run.setCwd(b.path("../tcllib"));
tcllib_run.addFileArg(b.path("../tcllib/installer.tcl"));
tcllib_run.addArgs(&.{ "-no-gui", "-no-wait", "-no-apps", "-no-html", "-no-nroff", "-no-examples", "-pkg-path" });
const tcllib_out = tcllib_run.addOutputDirectoryArg("tcllib-pkg");
const tcllib_install = b.addInstallDirectory(.{
.source_dir = tcllib_out,
.install_dir = .prefix,
.install_subdir = b.fmt("lib/tcllib{s}", .{tcllib_ver}),
});
const tcllib_smoke = b.addSystemCommand(&.{installed_static});
common.scrubTclEnv(tcllib_smoke);
tcllib_smoke.has_side_effects = true;
tcllib_smoke.setName("tcllib pkg_smoke");
tcllib_smoke.addFileArg(b.path("tools/pkg_smoke.tcl"));
tcllib_smoke.addArgs(&.{ "md5", "sha1", "cmdline" });
tcllib_smoke.step.dependOn(&tcllib_install.step);
tcllib_smoke.step.dependOn(b.getInstallStep());
tcllib_smoke.step.dependOn(install_libraries);
tcllib_step.dependOn(&tcllib_smoke.step);
//-- tcllibc: tcllib's critcl accelerators, critcl driving zig cc (the
//tracked critcl_zig.config; zig resolved from PATH with our own dir
//prepended). critcl compiles against ITS OWN bundled headers, chosen by
//the version of the shell running it (critcl_c/tcl8.6 here) - which is
//why the generation split works with one config file. sak.tcl's critcl
//target expects a tclkitsh on windows, so this replicates its underlying
//invocation directly, as suite_tcl90 does.
const tcllibc_step = b.step("tcllibc", "build+install tcllibc (critcl accelerators, zig cc) under the suite-built 8.6 shell (+accel smoke/bench)");
const critcl_run = b.addRunArtifact(tclsh_static_exe);
common.scrubTclEnv(critcl_run);
critcl_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src);
_ = critcl_run.captureStdOut(.{});
critcl_run.setCwd(b.path("../tcllib"));
critcl_run.addFileArg(b.path("../critcl/main.tcl"));
critcl_run.addArg("-config");
critcl_run.addFileArg(b.path("critcl_zig.config"));
//pin the build target to the config's explicit section: critcl otherwise
//GUESSES its target by probing PATH for compilers, and a scrubbed
//environment falls back to critcl's builtin msvc 'cl' target (G-102
//finding, inherited here).
critcl_run.addArgs(&.{ "-target", "win32-x86_64-zig" });
critcl_run.addArg("-cache");
_ = critcl_run.addOutputDirectoryArg("critcl-cache");
critcl_run.addArg("-force");
critcl_run.addArg("-libdir");
const critcl_libout = critcl_run.addOutputDirectoryArg("critcl-lib");
critcl_run.addArgs(&.{ "-pkg", "tcllibc" });
for (common.tcllibCritclFiles(b, "../tcllib")) |cf| {
critcl_run.addFileArg(b.path(b.pathJoin(&.{ "../tcllib/modules", cf })));
}
const zigdir = std.fs.path.dirname(b.graph.zig_exe) orelse ".";
const inherited_path = b.graph.environ_map.get("PATH") orelse "";
critcl_run.setEnvironmentVariable("PATH", b.fmt("{s}{c}{s}", .{ zigdir, std.fs.path.delimiter, inherited_path }));
const tcllibc_install = b.addInstallDirectory(.{
.source_dir = critcl_libout,
.install_dir = .prefix,
.install_subdir = "lib",
});
const tcllibc_smoke = b.addSystemCommand(&.{installed_static});
common.scrubTclEnv(tcllibc_smoke);
tcllibc_smoke.has_side_effects = true;
tcllibc_smoke.setName("tcllibc accel smoke");
tcllibc_smoke.addFileArg(b.path("tools/pkg_smoke.tcl"));
tcllibc_smoke.addArgs(&.{ "-accel", "md5", "tcllibc" });
tcllibc_smoke.step.dependOn(&tcllibc_install.step);
tcllibc_smoke.step.dependOn(&tcllib_install.step); //accel host packages come from tcllib
tcllibc_smoke.step.dependOn(b.getInstallStep());
tcllibc_smoke.step.dependOn(install_libraries);
//accelerated-vs-pure evidence (G-100 acceptance): same payload through
//sha1's critcl and pure-Tcl implementations, digests compared, ratio
//printed - the punkcheck-relevant hashing comparison.
const accel_bench = b.addSystemCommand(&.{installed_static});
common.scrubTclEnv(accel_bench);
accel_bench.has_side_effects = true;
accel_bench.setName("tcllibc accel bench");
accel_bench.addFileArg(b.path("tools/accel_bench.tcl"));
accel_bench.step.dependOn(&tcllibc_smoke.step);
tcllibc_step.dependOn(&accel_bench.step);
//-- test-gate: the 8.6 core testsuite under the installed static shell,
//judged on PARSED totals (all.tcl exit codes lie) vs the tracked
//dispositioned baseline. First census: run with -Dtestmode=record (the
@ -731,32 +888,57 @@ pub fn build(b: *std.Build) !void {
gate_run.step.dependOn(install_libraries);
gate_step.dependOn(&gate_run.step);
//-- per-library testsuites (suite_tcl90 G-107 pattern, thread+tclvfs
//arm): policy tiers gate|record|skip resolved at invocation.
//-- per-library testsuites (suite_tcl90 G-107 pattern): policy tiers
//gate|record|skip resolved at invocation.
// -Dtestpolicy-<lib>=gate|record|skip -Dtestargs-<lib>="..."
// -Dtestnotfiles-<lib>="a.test b.test"
//Baselines expected_test_failures_thread.txt / _tclvfs.txt are created
//from dispositioned record-mode censuses (same first-census flow as the
//core gate).
//core gate); the G-100 script-library suites ride the RECORD tier (their
//censuses are evidence, not a gate criterion - the same disposition
//suite_tcl90 made).
//NOTE zig runs independent steps concurrently - drive combined test
//invocations with -j1 (suite.tcl's test action does) so timing-sensitive
//suites never run beside heavy ones.
const LibSuite = struct {
name: []const u8,
testsdir: []const u8,
driver: []const u8,
name: []const u8, //step suffix, policy/testargs option suffix, report basename
testsdir: []const u8, //run cwd (from build root)
driver: []const u8, //driver script, relative to testsdir
default_policy: []const u8,
baseline: []const u8,
default_notfiles: []const u8,
baseline: []const u8, //tracked baseline filename ("" = none tracked yet)
default_notfiles: []const u8, //default driver -notfile exclusions ("" = none)
deps: []const *std.Build.Step, //beyond the top-level install step
};
const thread_test_deps = [_]*std.Build.Step{install_libraries};
const tclvfs_test_deps = [_]*std.Build.Step{install_libraries};
const tcllib_test_deps = [_]*std.Build.Step{ install_libraries, &tcllib_install.step, &tcllibc_install.step };
const tklib_test_deps = [_]*std.Build.Step{ install_libraries, &tklib_install.step, &tcllib_install.step };
const tk_test_deps = [_]*std.Build.Step{install_libraries};
const lib_suites = [_]LibSuite{
.{ .name = "thread", .testsdir = "../tclthread/tests", .driver = "all.tcl", .default_policy = "gate", .baseline = "expected_test_failures_thread.txt", .default_notfiles = "" },
.{ .name = "tclvfs", .testsdir = "../tclvfs/tests", .driver = "all.tcl", .default_policy = "gate", .baseline = "expected_test_failures_tclvfs.txt", .default_notfiles = "" },
.{ .name = "thread", .testsdir = "../tclthread/tests", .driver = "all.tcl", .default_policy = "gate", .baseline = "expected_test_failures_thread.txt", .default_notfiles = "", .deps = &thread_test_deps },
.{ .name = "tclvfs", .testsdir = "../tclvfs/tests", .driver = "all.tcl", .default_policy = "gate", .baseline = "expected_test_failures_tclvfs.txt", .default_notfiles = "", .deps = &tclvfs_test_deps },
//tcllib via its own support/devel/all.tcl (sak's underlying driver).
//The installed tcllibc is on the shell's package path, so
//accelerator-aware suites engage the critcl accelerators - the
//highest-value run of the set for an 8.6 runtime nobody else builds.
.{ .name = "tcllib", .testsdir = "../tcllib", .driver = "support/devel/all.tcl", .default_policy = "record", .baseline = "", .default_notfiles = "", .deps = &tcllib_test_deps },
.{ .name = "tklib", .testsdir = "../tklib", .driver = "support/devel/all.tcl", .default_policy = "record", .baseline = "", .default_notfiles = "", .deps = &tklib_test_deps },
//tk: OPT-IN (not part of test-libraries) - it maps windows on the
//interactive desktop for tens of minutes. The native-dialog suites are
//excluded by default: their message-injection automation does not
//drive the real OS dialogs under a batch shell, so each case blocks
//waiting on a human. Re-include with -Dtestnotfiles-tk="" for a
//manual interactive run.
.{ .name = "tk", .testsdir = "../tk86/tests", .driver = "all.tcl", .default_policy = "record", .baseline = "", .default_notfiles = "winDialog.test winMsgbox.test", .deps = &tk_test_deps },
};
const libtests_step = b.step("test-libraries", "library testsuites (thread, tclvfs) under the installed static shell per policy tier");
const libtests_step = b.step("test-libraries", "library testsuites under the installed static shell per policy tier (default: thread+tclvfs gate, tcllib+tklib record; tk excluded - see test-tk)");
for (lib_suites) |ls| {
const policy = b.option([]const u8, b.fmt("testpolicy-{s}", .{ls.name}), b.fmt("{s} testsuite policy: gate|record|skip (default {s})", .{ ls.name, ls.default_policy })) orelse ls.default_policy;
const lib_testargs = b.option([]const u8, b.fmt("testargs-{s}", .{ls.name}), b.fmt("extra tcltest/driver args for the {s} testsuite", .{ls.name})) orelse "";
const lib_notfiles = b.option([]const u8, b.fmt("testnotfiles-{s}", .{ls.name}), b.fmt("test-file glob patterns excluded from the {s} run (default \"{s}\")", .{ ls.name, ls.default_notfiles })) orelse ls.default_notfiles;
const is_tk = std.mem.eql(u8, ls.name, "tk");
const step = b.step(b.fmt("test-{s}", .{ls.name}), b.fmt("{s} testsuite under the installed static shell (policy: {s})", .{ ls.name, policy }));
libtests_step.dependOn(step);
if (!is_tk) libtests_step.dependOn(step);
if (std.mem.eql(u8, policy, "skip")) continue;
if (!std.mem.eql(u8, policy, "gate") and !std.mem.eql(u8, policy, "record")) {
std.debug.panic("-Dtestpolicy-{s}: unknown policy '{s}' (expected gate|record|skip)", .{ ls.name, policy });
@ -772,10 +954,13 @@ pub fn build(b: *std.Build) !void {
run.addArgs(&.{ "-driver", ls.driver });
if (std.mem.eql(u8, policy, "gate")) {
run.addArg("-baseline");
if (common.pathExists(b, ls.baseline)) {
if (ls.baseline.len != 0 and common.pathExists(b, ls.baseline)) {
run.addFileArg(b.path(ls.baseline));
} else {
run.addArg(common.replaceAll(b, b.pathFromRoot(ls.baseline), "\\", "/"));
//no tracked baseline (yet): pass the conventional name so the
//engine's missing-baseline message names the file to create
const conv = if (ls.baseline.len != 0) ls.baseline else b.fmt("expected_test_failures_{s}.txt", .{ls.name});
run.addArg(common.replaceAll(b, b.pathFromRoot(conv), "\\", "/"));
}
}
run.addArg("-logfile");
@ -820,6 +1005,11 @@ fn bootstrapMode(b: *std.Build) !void {
.{ "tcl", "tcl86" },
.{ "tclthread", "tclthread" },
.{ "tclvfs", "tclvfs" },
//G-100 dependency set
.{ "tk", "tk86" },
.{ "tklib", "tklib" },
.{ "tcllib", "tcllib" },
.{ "critcl", "critcl" },
};
var deps_pending = false;
for (source_dirs) |sd| {
@ -857,6 +1047,8 @@ fn bootstrapMode(b: *std.Build) !void {
"build_zlib86",
"build_tclthread86",
"build_tclvfs86",
"build_tk86",
"critcl_zig.config",
"expected_test_failures.txt",
"expected_test_failures_thread.txt",
"expected_test_failures_tclvfs.txt",
@ -874,7 +1066,8 @@ fn bootstrapMode(b: *std.Build) !void {
//bootstrap: stage, then drive the staged recipe (per-zig-version cache dir -
//object caches must never be shared across zig versions; forward-slash paths
//throughout: the prefix flows into -D macros as C string literals)
const steps_opt = b.option([]const []const u8, "steps", "steps for the staged build (repeat the flag; default: install install-libraries smoke)") orelse @as([]const []const u8, &.{ "install", "install-libraries", "smoke" });
//default mirrors suite.tcl's build default (keep the two in step)
const steps_opt = b.option([]const []const u8, "steps", "steps for the staged build (repeat the flag; default: install install-libraries smoke tklib tcllib tcllibc)") orelse @as([]const []const u8, &.{ "install", "install-libraries", "smoke", "tklib", "tcllib", "tcllibc" });
var cachever: []const u8 = builtin.zig_version_string;
cachever = common.replaceAll(b, cachever, "+", "_");
cachever = common.replaceAll(b, cachever, "/", "_");
@ -895,8 +1088,9 @@ fn bootstrapMode(b: *std.Build) !void {
nested.has_side_effects = true;
//cwd = staged build dir (suite.tcl parity)
nested.setCwd(.{ .cwd_relative = b.pathFromRoot(b.pathJoin(&.{ stage_rel, "build" })) });
//hermetic child shells (parity with suite.tcl)
for ([_][]const u8{ "TCLLIBPATH", "TCL_LIBRARY", "TK_LIBRARY" }) |ev| {
//hermetic child shells (parity with suite.tcl - same list as the per-step
//scrub, incl. the TCL<major>_<minor>_TM_PATH module roots)
for (common.tcl_env_vars) |ev| {
nested.removeEnvironmentVariable(ev);
}
nested.step.dependOn(stage_step);

64
src/buildsuites/suite_tcl86/build_common.zig

@ -53,11 +53,28 @@ pub fn replaceAll(b: *std.Build, input: []const u8, needle: []const u8, replacem
//never sets TCL_LIBRARY (built-shell runs execute the INSTALLED shell beside its
//installed lib tree, proving hermetic exe-relative resolution).
pub fn scrubTclEnv(run: *std.Build.Step.Run) void {
for ([_][]const u8{ "TCLLIBPATH", "TCL_LIBRARY", "TK_LIBRARY" }) |ev| {
for (tcl_env_vars) |ev| {
run.removeEnvironmentVariable(ev);
}
}
//The full set of tcl-environment vars a suite child must not inherit.
//TCL<major>_<minor>_TM_PATH is the one that bites quietly (G-100 finding
//2026-07-26): tm.tcl seeds its module roots from env(TCL8_6_TM_PATH) down
//through every lower minor of the major, and this dev machine points
//TCL8_6_TM_PATH at C:/repo/jn/shellspy/modules among others - so a suite shell
//silently preferred a repo-built sha1-2.0.4.tm over the tcllib 2.0.5 the suite
//had just installed. Any census taken through such a shell measures the
//machine, not the suite. Scrubbed for every generation, not just this suite's:
//the vars are cheap to clear and a copied suite inherits the protection.
pub const tcl_env_vars = [_][]const u8{
"TCLLIBPATH", "TCL_LIBRARY", "TK_LIBRARY",
"TCL8_0_TM_PATH", "TCL8_1_TM_PATH", "TCL8_2_TM_PATH",
"TCL8_3_TM_PATH", "TCL8_4_TM_PATH", "TCL8_5_TM_PATH",
"TCL8_6_TM_PATH", "TCL8_7_TM_PATH", "TCL9_0_TM_PATH",
"TCL9_1_TM_PATH",
};
//checkout uuid from a tree's manifest.uuid when the repo materializes it (a
//per-repo fossil 'manifest' setting; suite.tcl forces it on for staged
//checkouts), else "unrecorded" so provenance emission stays total.
@ -122,6 +139,51 @@ pub fn pkgIfneededVersion(b: *std.Build, pkgindex_from_root: []const u8, pkgname
std.debug.panic("no 'package ifneeded {s}' in {s}", .{ pkgname, pkgindex_from_root });
}
//tcllib-family installer trees carry 'package_version <v>' in
//support/installation/version.tcl (tklib and tcllib alike - both installers
//derive their default install dir name from it, and the suite passes it
//explicitly as -pkg-path so a machine-global tcl_pkgPath can never be chosen).
pub fn installerPackageVersion(b: *std.Build, tree_from_root: []const u8) []const u8 {
const data = readSourceFile(b, b.fmt("{s}/support/installation/version.tcl", .{tree_from_root}));
var lines = std.mem.splitScalar(u8, data, '\n');
while (lines.next()) |line| {
var toks = std.mem.tokenizeAny(u8, line, " \t\r");
const first = toks.next() orelse continue;
if (!std.mem.eql(u8, first, "package_version")) continue;
const ver = toks.next() orelse continue;
return b.dupe(ver);
}
std.debug.panic("no package_version in {s}/support/installation/version.tcl", .{tree_from_root});
}
//tcllib's critcl module files from its version.tcl declarations: the critcl_main
//module's files first, then every 'critcl' module's files (sak.tcl semantics).
//Returned paths are relative to the tcllib tree's modules/ dir. Tcl brace-list
//tokens are unwrapped by brace stripping (the declared file names contain no
//spaces).
pub fn tcllibCritclFiles(b: *std.Build, tcllib_from_root: []const u8) []const []const u8 {
const data = readSourceFile(b, b.fmt("{s}/support/installation/version.tcl", .{tcllib_from_root}));
var mains = std.array_list.Managed([]const u8).init(b.allocator);
var mods = std.array_list.Managed([]const u8).init(b.allocator);
var lines = std.mem.splitScalar(u8, data, '\n');
while (lines.next()) |line| {
var toks = std.mem.tokenizeAny(u8, line, " \t\r");
const first = toks.next() orelse continue;
const is_main = std.mem.eql(u8, first, "critcl_main");
if (!is_main and !std.mem.eql(u8, first, "critcl")) continue;
_ = toks.next() orelse continue; //module name
while (toks.next()) |tok| {
var f = tok;
f = std.mem.trim(u8, f, "{}");
if (f.len == 0) continue;
(if (is_main) &mains else &mods).append(b.dupe(f)) catch @panic("OOM");
}
}
if (mains.items.len == 0) @panic("no critcl_main declaration found in tcllib version.tcl");
mains.appendSlice(mods.items) catch @panic("OOM");
return mains.items;
}
//TCL_WIN_VERSION as the makefiles derive it:
//<dotversion>.<releaselevel>.<patchlevel with a/b/. stripped>, releaselevel
//0=alpha 1=beta 2=final (e.g 8.6 + 8.6.18 -> 8.6.2.8618)

260
src/buildsuites/suite_tcl86/build_tk86/build_tk86.zig

@ -0,0 +1,260 @@
const std = @import("std");
const common = @import("../build_common.zig");
//G-100: Tk 8.6 windows loadable extension (tk86t.dll) from stock core-8-6-branch
//sources, plus the tk script library installed to lib/tk8.6.
//
//DERIVED from the staged tk 8.6 win/makefile.vc + win/rules.vc - a re-derivation,
//not a port of the 9.0 build_tk.zig (the goal Context predicted this: the object
//lists and xlib arrangement differ). Differences from the 9.0 module, all
//makefile-authorised:
// - object inventory: 8.6 TKOBJS has no tkIcu / tkImgListFormat / tkImgSVGnano /
// tkPkgConfig, and no tkWinGDI / tkWinIco / tkWinSysTray. XLIBOBJS is the same
// 5-file emulation set but is a first-class makefile macro here (in 9.0 the
// xlib files had to be recovered by hand - the name-pattern extraction missed
// them).
// - NO tkPkgConfig.c in 8.6 -> no CFG_* configure-era defines at all (the 9.0
// module carries a full CFG_{RUNTIME,INSTALL}_* set for it).
// - dll naming: rules.vc's TCL_MAJOR_VERSION==8 arm gives TKLIBNAME
// tk86t.dll (SUFX 't' survives while TCL_VERSION <= 86); the 9.0 arm produces
// the tcl9tk90.dll form.
// - wish.exe.manifest version: 8.6's makefile substitutes @TK_WIN_VERSION@ with
// $(DOTVERSION).0.0 = "8.6.0.0" - NOT the tcl-style
// <dotversion>.<releaselevel>.<patchlevel> formula the 9.0 tree uses.
// - pkgIndex: the 8.6 install-binaries text provides ONLY 'Tk' (no lowercase
// 'tk' alias) and guards with 'package vsatisfies [package provide Tcl] 8.6.0'.
//
//Kept from the 9.0 module (same reasons):
// - USE_TCL_STUBS/USE_TCLOO_STUBS + link against the suite's libtclstub86.a:
// that IS the stock arrangement (rules.vc sets USE_STUBS_DEFS for every
// project except tcl itself), and it keeps the dll unbound to a particular
// tcl dll name so both the static and dynamic suite shells can load it.
// - tkMain.c compiled twice (makefile: tkMain.obj + tkMain2.obj). The ansi copy
// gets explicit -UUNICODE/-U_UNICODE so a driver-side UNICODE default could
// never turn it into a duplicate UNICODE compile (G-096 finding; verified
// 2026-07-26 that the pinned zig cc defines neither for x86_64-windows-gnu,
// so the guard is belt-and-braces and the stock ANSI/TCHAR arrangement of the
// 8.6 core is matched exactly).
// - tkUuid.h generated into the build cache (tkWindow.c #includes it) and
// consumed via an include-dir overlay added FIRST - source trees are never
// written (G-102 convention).
//win/makefile.vc TKOBJS, split by the directory each source actually lives in
//(verified against the staged tree - every name resolved to exactly one dir).
const generic_names = [_][]const u8{
"tkConsole", "tk3d", "tkArgv", "tkAtom", "tkBind",
"tkBitmap", "tkBusy", "tkButton", "tkCanvArc", "tkCanvBmap",
"tkCanvImg", "tkCanvLine", "tkCanvPoly", "tkCanvPs", "tkCanvText",
"tkCanvUtil", "tkCanvWind", "tkCanvas", "tkClipboard", "tkCmds",
"tkColor", "tkConfig", "tkCursor", "tkEntry", "tkError",
"tkEvent", "tkFileFilter", "tkFocus", "tkFont", "tkFrame",
"tkGC", "tkGeometry", "tkGet", "tkGrab", "tkGrid",
"tkImage", "tkImgBmap", "tkImgGIF", "tkImgPNG", "tkImgPPM",
"tkImgPhoto", "tkImgPhInstance", "tkImgUtil", "tkListbox", "tkMacWinMenu",
"tkMenu", "tkMenubutton", "tkMenuDraw", "tkMessage", "tkPanedWindow",
"tkObj", "tkOldConfig", "tkOption", "tkPack", "tkPlace",
"tkPointer", "tkRectOval", "tkScale", "tkScrollbar", "tkSelect",
"tkStyle", "tkText", "tkTextBTree", "tkTextDisp", "tkTextImage",
"tkTextIndex", "tkTextMark", "tkTextTag", "tkTextWind", "tkTrig",
"tkUndo", "tkUtil", "tkVisual", "tkStubInit", "tkWindow",
};
const unix_names = [_][]const u8{ "tkUnixMenubu", "tkUnixScale" };
const xlib_names = [_][]const u8{ "xcolors", "xdraw", "xgc", "ximage", "xutil" };
const win_names = [_][]const u8{
"tkWin3d", "tkWin32Dll", "tkWinButton", "tkWinClipboard", "tkWinColor",
"tkWinConfig", "tkWinCursor", "tkWinDialog", "tkWinDraw", "tkWinEmbed",
"tkWinFont", "tkWinImage", "tkWinInit", "tkWinKey", "tkWinMenu",
"tkWinPixmap", "tkWinPointer", "tkWinRegion", "tkWinScrlbr", "tkWinSend",
"tkWinSendCom", "tkWinWindow", "tkWinWm", "tkWinX", "stubs",
"ttkWinMonitor", "ttkWinTheme", "ttkWinXPTheme",
};
const ttk_names = [_][]const u8{
"ttkBlink", "ttkButton", "ttkCache", "ttkClamTheme", "ttkClassicTheme",
"ttkDefaultTheme", "ttkElements", "ttkEntry", "ttkFrame", "ttkImage",
"ttkInit", "ttkLabel", "ttkLayout", "ttkManager", "ttkNotebook",
"ttkPanedwindow", "ttkProgress", "ttkScale", "ttkScrollbar", "ttkScroll",
"ttkSeparator", "ttkSquare", "ttkState", "ttkTagSet", "ttkTheme",
"ttkTrace", "ttkTrack", "ttkTreeview", "ttkWidget", "ttkStubInit",
};
pub const TkBuild = struct {
lib: *std.Build.Step.Compile,
pkgidx: std.Build.LazyPath,
patchlevel: []const u8, //e.g "8.6.17" (tk.h TK_PATCH_LEVEL)
libdir: []const u8, //install dir name, e.g "tk8.6"
dll_file: []const u8, //e.g "tk86t.dll"
};
pub fn build_tk86(comptime tcldir: []const u8, comptime subdir: []const u8, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, stublib: *std.Build.Step.Compile) !TkBuild {
//-- version facts from the staged tree (never hardcoded)
const tkh_content = common.readSourceFile(b, subdir ++ "/generic/tk.h");
const tk_h_version = common.parseDefineString(tkh_content, "TK_VERSION") orelse @panic("TK_VERSION not found in tk.h");
const tk_h_patchlevel = common.parseDefineString(tkh_content, "TK_PATCH_LEVEL") orelse @panic("TK_PATCH_LEVEL not found in tk.h");
if (!std.mem.eql(u8, tk_h_version, "8.6")) {
std.debug.panic("staged tk tree is {s} but this recipe expresses Tk 8.6 (sources.config tk ref / dir mismatch?)", .{tk_h_version});
}
const tk_nodot = "86";
const dll_name = "tk" ++ tk_nodot ++ "t"; //rules.vc TKLIBNAME, TCL_MAJOR_VERSION==8 arm
const dll_file = dll_name ++ ".dll";
const script_dir_rel = "lib/tk8.6"; //rules.vc SCRIPT_INSTALL_DIR for DOING_TK
const lib = b.addLibrary(.{
.linkage = .dynamic,
.name = dll_name,
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
//-- flag classes (rules.vc pkgcflags for PROJECT=tk: cflags + INCLUDES +
//TCL_DEFINES + PRJ_DEFINES + OPTDEFINES + USE_STUBS_DEFS + /DBUILD_tk)
var flags = std.array_list.Managed([]const u8).init(b.allocator);
defer flags.deinit();
try flags.appendSlice(&.{
"-Wall",
"-O3",
"-fomit-frame-pointer",
"-finput-charset=UTF-8",
//the tcl 8.6 recipe's finding, kept here: clang UBSan traps must stay
//fully off on windows
"-fno-sanitize=undefined",
"-fno-sanitize-trap=undefined",
//project defines: makefile.vc PRJ_DEFINES (/DBUILD_ttk + CONFIG_DEFS;
//inline=__inline and the _CRT_*_NO_DEPRECATE pair are MSVC-isms)
"-DBUILD_tk",
"-DBUILD_ttk",
"-DSTDC_HEADERS=1",
"-DHAVE_SYS_TYPES_H=1",
"-DHAVE_SYS_STAT_H=1",
"-DHAVE_STDLIB_H=1",
"-DHAVE_STRING_H=1",
"-DHAVE_MEMORY_H=1",
"-DHAVE_STRINGS_H=1",
"-DHAVE_UXTHEME_H=1", //makefile default (OPTS=noxp turns it off)
//rules.vc USE_STUBS_DEFS for a non-tcl project
"-DUSE_TCL_STUBS=1",
"-DUSE_TCLOO_STUBS=1",
//OPTDEFINES equivalents, matching the flag set the 8.6 core was built with
"-DTCL_THREADS=1",
"-DNDEBUG=1",
"-DTCL_CFG_OPTIMIZED=1",
"-DTCL_CFG_DO64BIT=1",
"-DHAVE_NO_SEH=1", //gcc-class: no __try/__except
});
var sources = std.array_list.Managed([]const u8).init(b.allocator);
inline for (generic_names) |n| try sources.append(subdir ++ "/generic/" ++ n ++ ".c");
inline for (unix_names) |n| try sources.append(subdir ++ "/unix/" ++ n ++ ".c");
inline for (xlib_names) |n| try sources.append(subdir ++ "/xlib/" ++ n ++ ".c");
inline for (win_names) |n| try sources.append(subdir ++ "/win/" ++ n ++ ".c");
inline for (ttk_names) |n| try sources.append(subdir ++ "/generic/ttk/" ++ n ++ ".c");
//tkUuid.h overlay: makefile.vc concatenates win/tkUuid.h.in + the checkout's
//manifest.uuid; wrapfiletofile reproduces exactly that.
const wrapfilecmd = b.addExecutable(.{
.name = "wrapfiletofile",
.root_module = b.createModule(.{
.root_source_file = b.path("tools/wrapfiletofile.zig"),
.target = b.graph.host,
}),
});
const uuidcmd_step = b.addRunArtifact(wrapfilecmd);
uuidcmd_step.addArgs(&.{
"-prefix",
"#define TK_VERSION_UUID \\",
"-prefixnl",
"1",
"-input",
});
uuidcmd_step.addFileArg(b.path(subdir ++ "/manifest.uuid"));
uuidcmd_step.addArg("-output");
const tkuuid_file = uuidcmd_step.addOutputFileArg("tkUuid.h");
lib.root_module.addIncludePath(tkuuid_file.dirname()); //overlay first
lib.root_module.addIncludePath(b.path(subdir ++ "/generic"));
lib.root_module.addIncludePath(b.path(subdir ++ "/generic/ttk"));
lib.root_module.addIncludePath(b.path(subdir ++ "/win"));
lib.root_module.addIncludePath(b.path(subdir ++ "/xlib")); //PRJ_INCLUDES
lib.root_module.addIncludePath(b.path(subdir ++ "/bitmaps")); //PRJ_INCLUDES
lib.root_module.addIncludePath(b.path(tcldir ++ "/generic"));
lib.root_module.addIncludePath(b.path(tcldir ++ "/win"));
lib.root_module.addCSourceFiles(.{
.files = sources.items,
.flags = flags.items,
});
//tkMain.c twice - ansi (forced) + UNICODE 'tkMain2' (makefile.vc parity)
var mainansi_flags = std.array_list.Managed([]const u8).init(b.allocator);
defer mainansi_flags.deinit();
try mainansi_flags.appendSlice(flags.items);
try mainansi_flags.appendSlice(&.{ "-UUNICODE", "-U_UNICODE" });
lib.root_module.addCSourceFile(.{
.file = b.path(subdir ++ "/generic/tkMain.c"),
.flags = mainansi_flags.items,
});
var mainw_flags = std.array_list.Managed([]const u8).init(b.allocator);
defer mainw_flags.deinit();
try mainw_flags.appendSlice(flags.items);
try mainw_flags.appendSlice(&.{ "-DUNICODE", "-D_UNICODE" });
lib.root_module.addCSourceFile(.{
.file = b.path(subdir ++ "/generic/tkMain.c"),
.flags = mainw_flags.items,
});
//wish.exe.manifest overlay: win/rc/tk.rc embeds it as RT_MANIFEST (that is
//what enables comctl32 v6 themed scrollbars in the DLL), and RT_MANIFEST
//file references resolve through the rc include paths. 8.6's makefile
//substitutes @TK_WIN_VERSION@ with $(DOTVERSION).0.0.
const tk_win_version = b.fmt("{s}.0.0", .{tk_h_version});
const wish_manifest_content = common.replaceAll(b, common.replaceAll(b, common.readSourceFile(b, subdir ++ "/win/wish.exe.manifest.in"), "@TK_WIN_VERSION@", tk_win_version), "@MACHINE@", "AMD64");
const wf_tkrc = b.addWriteFiles();
_ = wf_tkrc.add("wish.exe.manifest", wish_manifest_content);
lib.root_module.addWin32ResourceFile(.{
.file = b.path(subdir ++ "/win/rc/tk.rc"),
//TCL_THREADS drives the rc's OriginalFilename suffix (tk86t.dll)
.flags = &.{"-DTCL_THREADS=1"},
.include_paths = &.{
wf_tkrc.getDirectory(),
b.path(subdir ++ "/win"),
b.path(subdir ++ "/win/rc"),
b.path(subdir ++ "/generic"),
b.path(tcldir ++ "/generic"),
},
});
lib.root_module.linkLibrary(stublib);
lib.root_module.link_libc = true;
//rules.vc winlibs + makefile.vc PRJ_LIBS, plus the OLE/dialog/IME imports the
//win sources pull in (tkWinSendCom, tkWinDialog, tkWinX)
inline for (.{ "netapi32", "gdi32", "user32", "userenv", "winspool", "shell32", "ole32", "oleaut32", "uuid", "comctl32", "comdlg32", "imm32", "uxtheme", "advapi32", "kernel32" }) |l| {
lib.root_module.linkSystemLibrary(l, .{});
}
b.installArtifact(lib); //bin/tk86t.dll (+ tk86t.lib import lib -> lib/)
//tk script library -> <prefix>/lib/tk8.6 (whole-tree copy: stock splits the
//demos out to a separate dir, this keeps them inside - same simplification
//the tcl-side library install makes)
const install_tk_library = b.addInstallDirectory(.{
.source_dir = b.path(subdir ++ "/library"),
.install_dir = .prefix,
.install_subdir = script_dir_rel,
});
b.getInstallStep().dependOn(&install_tk_library.step);
//pkgIndex.tcl per makefile.vc install-binaries (windows arm of its two-branch
//text - the unix DISPLAY branch cannot apply to a windows-only suite;
//'file normalize' added as in the 9.0 recipe so the load path is cwd-proof).
const wf_pkgidx = b.addWriteFiles();
const pkgidx = wf_pkgidx.add("pkgIndex.tcl", b.fmt(
\\if {{![package vsatisfies [package provide Tcl] 8.6.0]}} return
\\package ifneeded Tk {s} [list load [file normalize [file join $dir .. .. bin {s}]]]
\\
, .{ tk_h_patchlevel, dll_file }));
const pkgidx_install = b.addInstallFileWithDir(pkgidx, .prefix, script_dir_rel ++ "/pkgIndex.tcl");
b.getInstallStep().dependOn(&pkgidx_install.step);
return .{ .lib = lib, .pkgidx = pkgidx, .patchlevel = tk_h_patchlevel, .libdir = "tk8.6", .dll_file = dll_file };
}

36
src/buildsuites/suite_tcl86/critcl_zig.config

@ -0,0 +1,36 @@
# suite_tcl86 critcl configuration: zig cc as the C toolchain (G-100).
# Forked from suite_tcl90/critcl_zig.config - generation-neutral content: critcl
# compiles against whichever tcl headers/stubs the DRIVING shell reports, so the
# 8.6 suite needs no flag differences here. 'zig' is resolved from PATH - the
# suite recipe prepends the pinned bin/tools zig dir for the critcl invocation.
# Target is the native windows-gnu triple (matches the suite's tcl runtime ABI).
#
# The settings live under an EXPLICIT target, win32-x86_64-zig, and the recipe
# invokes critcl with '-target win32-x86_64-zig': critcl otherwise chooses its
# build target by PROBING the environment for compilers, which made the selection
# depend on what happened to be on PATH (a visible gcc landed on the config
# defaults; a scrubbed environment fell back to critcl's builtin msvc 'cl'
# target and never used these settings - suite_tcl90 G-102 finding).
platform win32-x86_64
win32-x86_64-zig platform win32-x86_64
win32-x86_64-zig compile zig cc -target x86_64-windows-gnu -c -fPIC
win32-x86_64-zig version zig version
win32-x86_64-zig link zig cc -target x86_64-windows-gnu -shared
win32-x86_64-zig include -I
win32-x86_64-zig libinclude -L
win32-x86_64-zig object .o
win32-x86_64-zig output -o [list $outfile]
win32-x86_64-zig optimize -O2
win32-x86_64-zig noassert -DNDEBUG
win32-x86_64-zig tclstubs -DUSE_TCL_STUBS
win32-x86_64-zig tkstubs -DUSE_TK_STUBS
win32-x86_64-zig debug_symbols -g
win32-x86_64-zig strip -Wl,-s
win32-x86_64-zig link_debug
win32-x86_64-zig link_release
win32-x86_64-zig embed_manifest
win32-x86_64-zig ldoutput
win32-x86_64-zig link_preload
win32-x86_64-zig sharedlibext .dll

23
src/buildsuites/suite_tcl86/expected_test_failures_tclvfs.txt

@ -15,12 +15,25 @@
# against the retired external FTP host - the same 1.4.2-era entries that
# suite_tcl90's tclvfs baseline note records from its 2026-07-21 capture.
# -- external-service dependency: both tests reach a public FTP server
# -- external-service dependency: these tests reach a public FTP server
# (ftp://ftp.tcl.tk) the Tcl project retired years ago, so they fail on any
# modern machine/network regardless of the build (vfsUrl-1.2 asserts
# 'file exists ftp://ftp.tcl.tk' == 1; vfsUrl-2.1 copies a file from it). Not a
# suite defect - the tclvfs URL handlers themselves are exercised by the passing
# vfsUrl mount/parse cases. (The file's other ftp cases are gated behind the
# 'ftp' constraint and skip; these two are not constraint-gated.)
# 'file exists ftp://ftp.tcl.tk' == 1; vfsUrl-2.1 copies a file from it;
# vfsFtp-1.1..1.3 mount/list/stat that host). Not a suite defect - the tclvfs
# URL/FTP handlers themselves are exercised by the passing mount/parse cases.
#
# G-100 EXTENSION 2026-07-26: the vfsFtp entries joined the baseline when the
# suite grew tcllib. They are gated behind the 'ftp' constraint, which needs
# tcllib's ftp package - absent at the G-099 capture (3 constraint skips),
# satisfied once 'suite.tcl build' installs tcllib 2.0, so they now RUN and
# reach the retired host. Skips accordingly dropped 8 -> 5.
#
# WHICH members of this family fail is network-timing dependent (a connect that
# times out vs one refused fast can flip a case between fail and error-then-fail
# across runs), so the gate is expected to report some of these as 'stale
# baseline' entries on any given run - stale entries are noted, never fatal.
vfsUrl-1.2
vfsUrl-2.1
vfsFtp-1.1
vfsFtp-1.2
vfsFtp-1.3

109
src/buildsuites/suite_tcl86/patches/README.md

@ -0,0 +1,109 @@
# suite_tcl86 patches
## sdx wrap.tcl - tclkit.inf binary-read fix (G-101)
Recovered into tracked form 2026-07-26. Same rationale as the suite_tcl90 patch
recovery: these files previously existed ONLY in VCS-ignored on-disk copies
(`scratch/` and `TEMP_REFERENCE/` are ignored by both git and fossil - root
`.gitignore:35,59` and `.fossil-settings/ignore-glob:35-36,58-59`), so a
`git clean -xdf` would have destroyed the only evidence of what the patch is.
### What this documents
`bin/sdx.kit` is TRACKED and is the tool `src/make.tcl` resolves for the 'kit'
wrap path (discovery ladder at src/make.tcl:5048-5097). It is NOT stock sdx: it
is a locally-patched build whose patch was recorded nowhere in version control.
Two distinct sdx binaries were found on the author's machine, differing by 121
bytes:
| copy | sha256 | size | VCS |
| --- | --- | --- | --- |
| `bin/sdx.kit` (== `TEMP_REFERENCE/sdx/sdx.kit`) | `5d4aa629495bb68b737dfe7280363ceb0a6653938380e8c9ccc9063a3864f531` | 119191 | tracked |
| `scratch/sdx.kit` (== `scratch/sdx-20110317.kit`) | `04dc85d4503c037f26c2b8719e45e4e20385674fbc7404481f3c171fce0a26c3` | 119312 | ignored |
Unwrapped, both are 64 files and are IDENTICAL except `lib/app-sdx/wrap.tcl`.
That one file is the entire provenance delta, and it is the two-line change
captured here.
### The change
In the Windows version-resource customization path (wrap.tcl:232-239 in the
patched copy), reading `tclkit.inf`:
```
- set fd [open [file join $idir tclkit.inf]]
+ set fd [open [file join $idir tclkit.inf] "r"]
+ fconfigure $fd -translation binary
```
The array read from that file is handed straight to
`::stringfileinfo::writeStringInfo`, which patches the PE version resource of the
output executable. Without binary translation the read is line-ending-translated,
so this reads as a fix for CRLF corruption of `tclkit.inf` content on Windows.
NOT VERIFIED against a failing case - the fix is plausible and small, but no
reproducer was recorded with it. Confirming (or retiring) it is part of the
G-101 sdx characterization work.
Note the patch only fires when ALL of: `-runtime` was given, the runtime has a
`.exe` extension, and `tclkit.inf` exists in the source `.vfs`. No `tclkit.inf`
currently exists anywhere in this repo, so the patched path is DORMANT for
present builds - it matters only if/when version-stamping of wrapped executables
is turned on.
### Files
- `sdx-2011.03.17-wrap.tcl` - the STOCK baseline, byte-exact
(sha256 `41e669dce6da5b102f4924a4c177501b0ab8f3f62bf0657d88bdea73089c0a75`).
Kept in full because upstream is GONE: sdx's home was
`svn://svn.equi4.com/sdx/trunk` (per its ChangeLog, last entry 2011-03-17 by
Pat Thoyts) and it cannot be re-fetched. This is the irreplaceable artifact
here - the patched version is independently recoverable by unwrapping the
tracked `bin/sdx.kit`.
- `sdx-wrap_tclkitinf_binary.tcl` - the PATCHED file as it exists inside
`bin/sdx.kit`, byte-exact
(sha256 `7a77009be04e0da5fb750d86a6c1e7141bf712422434d0908456e40766b847be`).
Kept as a drop-in per the suite_tcl90 convention (full patched file alongside
the diff), so a rebuild does not depend on a working `patch` tool.
- `sdx-wrap.tcl.tclkitinf-binary.patch` - the diff between the two. Round-trip
verified 2026-07-26: applying it to the stock baseline reproduces the patched
file byte for byte. Apply with `patch --binary` on Windows - without it the
CRLF context lines will not match.
LINE ENDINGS: the two `.tcl` files are CRLF and MUST STAY CRLF. They are vendored
fidelity artifacts whose bytes must match the kit contents; the repo's usual
LF-on-edit preference does not apply to them. Do not run a line-ending normalizer
over this directory. This README is LF.
### Verifying the capture
`sdx version` walks a mounted kit and prints `<mtime> <crc-hi>-<crc-lo>`
(`lib/app-sdx/version.tcl`). Observed under
`bin/runtime/win32-x86_64/tclkit86bi.exe`:
```
bin/sdx.kit -> 2025/06/04 22:56:36 69170-33997
scratch/sdx.kit -> 2011/03/17 23:39:41 50768-16003
```
The tracked kit's timestamp is exactly the patched `wrap.tcl` mtime, which is
what confirms the patch is inside the tracked kit rather than only on disk.
TRAP for any rebuild-and-compare: `traverse` hashes file NAME + MTIME + SIZE, not
content, so this signature is NOT reproducible across a re-wrap unless mtimes are
normalised (`sdx version -fixtime`). Content-level characterization needs its own
comparison - unwrap and diff trees, plus behavioural wrap/unwrap/mksplit
round-trips - not this signature alone.
### Why this lives here
Placed under the suite that owns the question rather than beside the binary:
`bin/` is documented as build outputs and tooling (bin/AGENTS.md), not source
material. G-101 (kit container strategy for zig-built 8.6 runtimes) owns the sdx
provenance and rebuild decision, and sdx is the kit-wrapping tool that goal must
either keep, rebuild, or replace. If G-101 replaces sdx with a suite-owned kit
writer, this capture stays as the record of what the retired tool did.
Full findings - including the bootstrap circularity (sdx.kit is itself a metakit
starkit and needs a tclkit to run), the 2011-era bundled library set, and the
stale `bin/sdx1.bat` wrapper - are in `goals/G-101-tcl86-kit-container-strategy.md`.

344
src/buildsuites/suite_tcl86/patches/sdx-2011.03.17-wrap.tcl

@ -0,0 +1,344 @@
# wrap - convert file system tree to a starkit
# by Matt Newman <matt@sensus.org> and Jean-Claude Wippler <jcw@equi4.com>
#
# 20020607 jcw added -writeable, switched to starkit package
# the default is now to creates a read-only starkit
set header \
{#!/bin/sh
# %
exec @PROG@ "$0" ${1+"$@"}
package require starkit
starkit::header @TYPE@ @OPTS@
}
append header \32
regsub % $header \\ header
if {[llength $argv] < 1} {
puts stderr "Usage: $argv0 outfile ?-option ...?"
exit 1
}
set origtime [clock seconds]
proc readfile {name} {
set fd [open $name]
fconfigure $fd -translation binary
set data [read $fd]
close $fd
return $data
}
proc writefile {name data} {
set fd [open $name w]
fconfigure $fd -translation binary
puts -nonewline $fd $data
close $fd
}
# decode Windows .ICO file contents into the individual bit maps
# Each icon header has:
# byte : width in pixels (0 for 256 px)
# byte : height in pixels (0 for 256px)
# byte : number of colors in palette (0 for truecolor)
# byte : reserved (0)
# short: number of color planes (0 or 1)
# short: number of bits per pixel
# long : size of the bitmap data in bytes
# long : offset to the start of bitmap data
proc decICO {dat} {
set result {}
binary scan $dat sss - type count
for {set pos 6} {[incr count -1] >= 0} {incr pos 16} {
binary scan $dat @${pos}ccccssii w h cc - p bc bir io
if {$cc == 0} { set cc 256 }
#puts "pos $pos w $w h $h cc $cc p $p bc $bc bir $bir io $io"
binary scan $dat @${io}a$bir image
lappend result ${w}x${h}/$bc $image
}
return $result
}
proc LoadHeader {filename} {
if {[file normalize $filename] == [info nameofexe]} {
puts stderr "file in use, cannot be prefix: [file normalize $filename]"
exit 1
}
set size [file size $filename]
catch {
package require vfs::mk4
vfs::mk4::Mount $filename hdr -readonly
# we only look for an icon if the runtime is called *.exe (!)
if {[string tolower [file extension $filename]] == ".exe"} {
catch { set ::origicon [readfile hdr/tclkit.ico] }
}
}
catch { vfs::unmount $filename }
return [readfile $filename]
}
set out [lindex $argv 0]
set base [file root [file tail $out]]
set idir $base.vfs
if {![file isdirectory $idir]} {
if {[file isdirectory $base] && [file exists $base/main.tcl]} {
set idir $base
if {[lindex [split $out .] 0] eq $out} {
# append kit if user hasn't specified an extension
set out $base.kit
}
}
}
set compress 1
set verbose 0
set ropts -readonly
set prefix 0
set reusefile 0
set prog tclkit
set type mk4
set explist {}
set syncopts {}
set a [lindex $argv 1]
while {[string match -* $a]} {
switch -- $a {
-interp {
set prog [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
-runtime {
set pfile [lindex $argv 2]
if {$pfile == $out} {
set reusefile 1
} else {
set header [LoadHeader $pfile]
}
set argv [lreplace $argv 1 2]
set prefix 1
}
-writable -
-writeable {
#set ropts "-nocommit"
set ropts ""
set argv [lreplace $argv 1 1]
}
-nocomp -
-nocompress {
set compress 0
set argv [lreplace $argv 1 1]
}
-verbose {
set verbose 1
set argv [lreplace $argv 1 1]
}
-uncomp {
lappend explist [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
-vfs {
set idir [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
default {
lappend syncopts [lindex $argv 1] [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
}
set a [lindex $argv 1]
}
if {![file isdir $idir]} {
puts stderr "Input directory not found: $idir"
exit 1
}
if {!$prefix} {
regsub @PROG@ $header $prog header
regsub @OPTS@ $header $ropts header
regsub @TYPE@ $header $type header
set n [string length $header]
while {$n <= 240} {
append header ################
incr n 16
}
set slop [expr { 15 - (($n + 15) % 16) }]
for {set i 0} {$i < $slop} {incr i} {
append header #
}
set n [string length $header]
if {$n % 16 != 0} {
error "Header size is $n, should be a multiple of 16"
}
}
if {!$reusefile} {
writefile $out $header
}
set origsize [file size $out]
switch $tcl_platform(platform) {
unix {
catch {file attributes $out -permissions +x}
}
windows {
set batfile [file root $out].bat
# 2005-03-18 don't create a batfile if "-runtime" is specified
if {![file exists $batfile] && ![info exists pfile]} {
set fd [open $batfile w]
puts -nonewline $fd \
"@$prog [file tail $out] %1 %2 %3 %4 %5 %6 %7 %8 %9"
close $fd
}
}
macintosh {
catch {file attributes $out -creator TKd4}
}
}
# 2003-02-08: added code to patch icon in windows executable
# triggered by existence of tclkit.ico in vfs dir *and* tclkit.ico in orig
# careful: this applies only to windows executables, but the
# icon replacement can in fact take place on any platform...
if {[info exists origicon] && [file exists [file join $idir tclkit.ico]]} {
puts " customizing tclkit.ico in executable"
set custicon [readfile [file join $idir tclkit.ico]]
array set newimg [decICO $custicon]
foreach {k v} [decICO $origicon] {
if {[info exists newimg($k)]} {
set len [string length $v]
set pos [string first $v $header]
if {$pos < 0} {
puts " icon $k: NOT FOUND"
} elseif {[string length $newimg($k)] != $len} {
puts " icon $k: NOT SAME SIZE"
} else {
binary scan $header a${pos}a${len}a* prefix - suffix
set header "$prefix$newimg($k)$suffix"
puts " icon $k: replaced"
}
}
}
writefile $out $header
}
# 2005-03-15 added AF's code to customize version/description strings in exe's
if {[info exists pfile] &&
[string tolower [file extension $pfile]] == ".exe" &&
[file exists [file join $idir tclkit.inf]]} {
puts " customizing strings in executable"
package require stringfileinfo
set fd [open [file join $idir tclkit.inf]]
array set strinfo [read $fd]
close $fd
::stringfileinfo::writeStringInfo $out strinfo
}
proc tclLog {msg} { puts $msg }
proc mkFileStart {filename} {
set end [file size $filename]
if {$end < 27} {
fail "file too small, cannot be a datafile"
}
set fd [open $filename]
fconfigure $fd -translation binary
seek $fd -16 end
binary scan [read $fd 16] IIII a b c d
close $fd
#puts [format %x-%d-%x-%d $a $b $c $d]
if {($c >> 24) != -128} {
error "this is not a Metakit datafile"
}
# avoid negative sign / overflow issues
if {[format %x [expr {$a & 0xffffffff}]] eq "80000000"} {
set start [expr {$end - 16 - $b}]
} else {
# if the file is in commit-progress state, we need to do more
error "this code needs to be finished..."
}
return $start
}
if {![catch { package require Mk4tcl }]} {
vfs::mk4::Mount $out $out
set argv $syncopts
lappend argv -compress $compress -verbose $verbose -noerror 0 $idir $out
source [file join [file dirname [info script]] sync.tcl]
# 2003-06-19: new "-uncomp name" option to store specific file(s)
# in uncompressed form, even if the rest is compressed
set o $vfs::mk4::compress
set vfs::mk4::compress 0
foreach f $explist {
file delete -force [file join $out $f]
file copy [file join $idir $f] [file join $out $f]
}
set vfs::mk4::compress $o
vfs::unmount $out
} elseif {![catch { package require vlerq }]} {
package require vfs::m2m 1.8
if {$verbose} {
puts "Mk4tcl not found, switching to the Vlerq driver."
}
# 2006-12-06, must do a bit more work for starpacks when there is no Mk4tcl:
# - split the tail off
# - truncate the file so only the head remains
# - copy the original vfs contents to m2m, to be overwritten/extended later
set outsize [file size $out]
if {![catch { mkFileStart $out } mkpos]} {
set fd [open $out]
fconfigure $fd -translation binary
set outhead [read $fd $mkpos]
set origvfs [read $fd]
close $fd
set fd [open $out w]
fconfigure $fd -translation binary
puts -nonewline $fd $outhead
close $fd
}
vfs::m2m::Mount $out $out
if {[info exists origvfs]} {
set fd [open $out.tmp w]
fconfigure $fd -translation binary
puts -nonewline $fd $origvfs
close $fd
package require vfs::mkcl
vfs::mkcl::Mount $out.tmp $out.tmp
set argv [list -verbose 0 $out.tmp $out]
source [file join [file dirname [info script]] sync.tcl]
vfs::unmount $out.tmp
file delete $out.tmp
}
set argv $syncopts
lappend argv -verbose $verbose -noerror 0 $idir $out
source [file join [file dirname [info script]] sync.tcl]
vfs::unmount $out
} else {
puts stderr "cannot find required packages (Mk4tcl or Vlerq)"
exit 1
}

12
src/buildsuites/suite_tcl86/patches/sdx-wrap.tcl.tclkitinf-binary.patch

@ -0,0 +1,12 @@
--- a/lib/app-sdx/wrap.tcl (sdx 2011-03-17 stock)
+++ b/lib/app-sdx/wrap.tcl (bin/sdx.kit patched)
@@ -232,7 +232,8 @@
[file exists [file join $idir tclkit.inf]]} {
puts " customizing strings in executable"
package require stringfileinfo
- set fd [open [file join $idir tclkit.inf]]
+ set fd [open [file join $idir tclkit.inf] "r"]
+ fconfigure $fd -translation binary
array set strinfo [read $fd]
close $fd
::stringfileinfo::writeStringInfo $out strinfo

345
src/buildsuites/suite_tcl86/patches/sdx-wrap_tclkitinf_binary.tcl

@ -0,0 +1,345 @@
# wrap - convert file system tree to a starkit
# by Matt Newman <matt@sensus.org> and Jean-Claude Wippler <jcw@equi4.com>
#
# 20020607 jcw added -writeable, switched to starkit package
# the default is now to creates a read-only starkit
set header \
{#!/bin/sh
# %
exec @PROG@ "$0" ${1+"$@"}
package require starkit
starkit::header @TYPE@ @OPTS@
}
append header \32
regsub % $header \\ header
if {[llength $argv] < 1} {
puts stderr "Usage: $argv0 outfile ?-option ...?"
exit 1
}
set origtime [clock seconds]
proc readfile {name} {
set fd [open $name]
fconfigure $fd -translation binary
set data [read $fd]
close $fd
return $data
}
proc writefile {name data} {
set fd [open $name w]
fconfigure $fd -translation binary
puts -nonewline $fd $data
close $fd
}
# decode Windows .ICO file contents into the individual bit maps
# Each icon header has:
# byte : width in pixels (0 for 256 px)
# byte : height in pixels (0 for 256px)
# byte : number of colors in palette (0 for truecolor)
# byte : reserved (0)
# short: number of color planes (0 or 1)
# short: number of bits per pixel
# long : size of the bitmap data in bytes
# long : offset to the start of bitmap data
proc decICO {dat} {
set result {}
binary scan $dat sss - type count
for {set pos 6} {[incr count -1] >= 0} {incr pos 16} {
binary scan $dat @${pos}ccccssii w h cc - p bc bir io
if {$cc == 0} { set cc 256 }
#puts "pos $pos w $w h $h cc $cc p $p bc $bc bir $bir io $io"
binary scan $dat @${io}a$bir image
lappend result ${w}x${h}/$bc $image
}
return $result
}
proc LoadHeader {filename} {
if {[file normalize $filename] == [info nameofexe]} {
puts stderr "file in use, cannot be prefix: [file normalize $filename]"
exit 1
}
set size [file size $filename]
catch {
package require vfs::mk4
vfs::mk4::Mount $filename hdr -readonly
# we only look for an icon if the runtime is called *.exe (!)
if {[string tolower [file extension $filename]] == ".exe"} {
catch { set ::origicon [readfile hdr/tclkit.ico] }
}
}
catch { vfs::unmount $filename }
return [readfile $filename]
}
set out [lindex $argv 0]
set base [file root [file tail $out]]
set idir $base.vfs
if {![file isdirectory $idir]} {
if {[file isdirectory $base] && [file exists $base/main.tcl]} {
set idir $base
if {[lindex [split $out .] 0] eq $out} {
# append kit if user hasn't specified an extension
set out $base.kit
}
}
}
set compress 1
set verbose 0
set ropts -readonly
set prefix 0
set reusefile 0
set prog tclkit
set type mk4
set explist {}
set syncopts {}
set a [lindex $argv 1]
while {[string match -* $a]} {
switch -- $a {
-interp {
set prog [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
-runtime {
set pfile [lindex $argv 2]
if {$pfile == $out} {
set reusefile 1
} else {
set header [LoadHeader $pfile]
}
set argv [lreplace $argv 1 2]
set prefix 1
}
-writable -
-writeable {
#set ropts "-nocommit"
set ropts ""
set argv [lreplace $argv 1 1]
}
-nocomp -
-nocompress {
set compress 0
set argv [lreplace $argv 1 1]
}
-verbose {
set verbose 1
set argv [lreplace $argv 1 1]
}
-uncomp {
lappend explist [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
-vfs {
set idir [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
default {
lappend syncopts [lindex $argv 1] [lindex $argv 2]
set argv [lreplace $argv 1 2]
}
}
set a [lindex $argv 1]
}
if {![file isdir $idir]} {
puts stderr "Input directory not found: $idir"
exit 1
}
if {!$prefix} {
regsub @PROG@ $header $prog header
regsub @OPTS@ $header $ropts header
regsub @TYPE@ $header $type header
set n [string length $header]
while {$n <= 240} {
append header ################
incr n 16
}
set slop [expr { 15 - (($n + 15) % 16) }]
for {set i 0} {$i < $slop} {incr i} {
append header #
}
set n [string length $header]
if {$n % 16 != 0} {
error "Header size is $n, should be a multiple of 16"
}
}
if {!$reusefile} {
writefile $out $header
}
set origsize [file size $out]
switch $tcl_platform(platform) {
unix {
catch {file attributes $out -permissions +x}
}
windows {
set batfile [file root $out].bat
# 2005-03-18 don't create a batfile if "-runtime" is specified
if {![file exists $batfile] && ![info exists pfile]} {
set fd [open $batfile w]
puts -nonewline $fd \
"@$prog [file tail $out] %1 %2 %3 %4 %5 %6 %7 %8 %9"
close $fd
}
}
macintosh {
catch {file attributes $out -creator TKd4}
}
}
# 2003-02-08: added code to patch icon in windows executable
# triggered by existence of tclkit.ico in vfs dir *and* tclkit.ico in orig
# careful: this applies only to windows executables, but the
# icon replacement can in fact take place on any platform...
if {[info exists origicon] && [file exists [file join $idir tclkit.ico]]} {
puts " customizing tclkit.ico in executable"
set custicon [readfile [file join $idir tclkit.ico]]
array set newimg [decICO $custicon]
foreach {k v} [decICO $origicon] {
if {[info exists newimg($k)]} {
set len [string length $v]
set pos [string first $v $header]
if {$pos < 0} {
puts " icon $k: NOT FOUND"
} elseif {[string length $newimg($k)] != $len} {
puts " icon $k: NOT SAME SIZE"
} else {
binary scan $header a${pos}a${len}a* prefix - suffix
set header "$prefix$newimg($k)$suffix"
puts " icon $k: replaced"
}
}
}
writefile $out $header
}
# 2005-03-15 added AF's code to customize version/description strings in exe's
if {[info exists pfile] &&
[string tolower [file extension $pfile]] == ".exe" &&
[file exists [file join $idir tclkit.inf]]} {
puts " customizing strings in executable"
package require stringfileinfo
set fd [open [file join $idir tclkit.inf] "r"]
fconfigure $fd -translation binary
array set strinfo [read $fd]
close $fd
::stringfileinfo::writeStringInfo $out strinfo
}
proc tclLog {msg} { puts $msg }
proc mkFileStart {filename} {
set end [file size $filename]
if {$end < 27} {
fail "file too small, cannot be a datafile"
}
set fd [open $filename]
fconfigure $fd -translation binary
seek $fd -16 end
binary scan [read $fd 16] IIII a b c d
close $fd
#puts [format %x-%d-%x-%d $a $b $c $d]
if {($c >> 24) != -128} {
error "this is not a Metakit datafile"
}
# avoid negative sign / overflow issues
if {[format %x [expr {$a & 0xffffffff}]] eq "80000000"} {
set start [expr {$end - 16 - $b}]
} else {
# if the file is in commit-progress state, we need to do more
error "this code needs to be finished..."
}
return $start
}
if {![catch { package require Mk4tcl }]} {
vfs::mk4::Mount $out $out
set argv $syncopts
lappend argv -compress $compress -verbose $verbose -noerror 0 $idir $out
source [file join [file dirname [info script]] sync.tcl]
# 2003-06-19: new "-uncomp name" option to store specific file(s)
# in uncompressed form, even if the rest is compressed
set o $vfs::mk4::compress
set vfs::mk4::compress 0
foreach f $explist {
file delete -force [file join $out $f]
file copy [file join $idir $f] [file join $out $f]
}
set vfs::mk4::compress $o
vfs::unmount $out
} elseif {![catch { package require vlerq }]} {
package require vfs::m2m 1.8
if {$verbose} {
puts "Mk4tcl not found, switching to the Vlerq driver."
}
# 2006-12-06, must do a bit more work for starpacks when there is no Mk4tcl:
# - split the tail off
# - truncate the file so only the head remains
# - copy the original vfs contents to m2m, to be overwritten/extended later
set outsize [file size $out]
if {![catch { mkFileStart $out } mkpos]} {
set fd [open $out]
fconfigure $fd -translation binary
set outhead [read $fd $mkpos]
set origvfs [read $fd]
close $fd
set fd [open $out w]
fconfigure $fd -translation binary
puts -nonewline $fd $outhead
close $fd
}
vfs::m2m::Mount $out $out
if {[info exists origvfs]} {
set fd [open $out.tmp w]
fconfigure $fd -translation binary
puts -nonewline $fd $origvfs
close $fd
package require vfs::mkcl
vfs::mkcl::Mount $out.tmp $out.tmp
set argv [list -verbose 0 $out.tmp $out]
source [file join [file dirname [info script]] sync.tcl]
vfs::unmount $out.tmp
file delete $out.tmp
}
set argv $syncopts
lappend argv -verbose $verbose -noerror 0 $idir $out
source [file join [file dirname [info script]] sync.tcl]
vfs::unmount $out
} else {
puts stderr "cannot find required packages (Mk4tcl or Vlerq)"
exit 1
}

24
src/buildsuites/suite_tcl86/sources.config

@ -27,14 +27,34 @@
# (2.8.13, 2026-04-30) - selection recorded in goals G-099.
# - tclvfs keeps TRUNK (1.4.2 era): the forTcl9 branch (1.5.0) is for tcl9
# suites only (user decision 2026-07-22, recorded in suite_tcl90).
#
# G-100 dependency-set notes:
# - tk rides core-8-6-branch (the 8.6 maintenance line, pairing with the 8.6
# runtime) into dir tk86 - the 9.0 recipe's tk9 dir is version-named the
# same way. The object lists/xlib arrangement differ from 9.0, so
# build_tk86 is a re-derivation from the 8.6 win/makefile.vc, not a reuse.
# - tklib/tcllib/critcl are GENERATION-NEUTRAL upstreams: the same trunk refs
# (and the same zon pins) as suite_tcl90 - what differs is the shell that
# runs their installers and the critcl target that builds tcllibc. Both
# libraries install per-generation (tcl8 vs tcl9 tcllibc side by side is the
# established punk kit payload shape).
description Zig-built Tcl 8.6 windows runtime (core-8-6-branch): tclsh86ts/tclsh86t shells + tcl86t.dll + installed lib tree, thread 2.8 + tclvfs, core-test gate (G-099 arc)
description Zig-built Tcl 8.6 windows runtime (core-8-6-branch): tclsh86ts/tclsh86t shells + tcl86t.dll + installed lib tree, thread 2.8 + tclvfs + Tk 8.6 + tklib/tcllib(+tcllibc), core-test gate (G-099/G-100 arc)
product tclsh86ts.exe static shell + tclsh86t.exe/tcl86t.dll dynamic pair with installed lib/tcl8.6 tree + tm modules (no zipfs in 8.6 - no self-contained szip runtime; kit containment is G-101 territory)
product thread 2.8.13 (thread2813t.dll) and tclvfs 1.4.2 (vfs142t.dll) + registry/dde package dlls, all loadable in both shells
product core + thread + tclvfs testsuite gates vs tracked expected_test_failures baselines (parsed all.tcl totals; first censuses in flight - see README)
product Tk 8.6.17 (tk86t.dll + lib/tk8.6 script library) with tklib 0.9 and tcllib 2.0 installed by their own installers, plus tcllibc critcl accelerators built with zig cc
product core + thread + tclvfs testsuite gates vs tracked expected_test_failures baselines (parsed all.tcl totals), tcllib/tklib record tiers and an opt-in test-tk
doc README.md
zigpin bin/tools/zig-x86_64-windows-0.16.0/zig.exe
source tcl fossil https://core.tcl-lang.org/tcl core-8-6-branch tcl86
source tclthread fossil https://core.tcl-lang.org/thread thread-2-8-branch tclthread
source tclvfs fossil https://core.tcl-lang.org/tclvfs trunk tclvfs
# G-100: Tk + companion script libraries. tklib is pure-tcl (installed by its own
# installer where that matters); tcllib's accelerators (tcllibc) are built via critcl.
source tk fossil https://core.tcl-lang.org/tk core-8-6-branch tk86
source tklib fossil https://core.tcl-lang.org/tklib trunk tklib
source tcllib fossil https://core.tcl-lang.org/tcllib trunk tcllib
source critcl git https://github.com/andreas-kupries/critcl 3.3.1 critcl

44
src/buildsuites/suite_tcl86/suite.tcl

@ -24,11 +24,16 @@
# -seedfossils <dir> read-only seed collection for missing clones (default ~/.fossils; "" disables)
# -all <0|1> with 'clean': reserved (the clone store lives outside the stage)
# -steps <list> zig build steps. 'build' default: {install install-libraries
# smoke} (runtimes + companions + script library + smokes).
# smoke tklib tcllib tcllibc} (runtimes + companions + Tk +
# script library + smokes, then the G-100 dependency set:
# tklib/tcllib installed by their own installers and the
# critcl-built tcllibc accelerators).
# 'test' default: {test-gate} (the 8.6 core suite gate vs the
# tracked expected_test_failures baseline; first census via
# -zigargs {-Dtestmode=record}). Also: test-thread, test-tclvfs,
# test-libraries (G-107-pattern policy tiers).
# test-libraries (G-107-pattern policy tiers), test-tcllib,
# test-tklib, and the OPT-IN test-tk (full Tk suite: maps
# windows on the interactive desktop, tens of minutes).
# -zigargs <list> extra args appended to the zig build invocation
#
#The build area lives entirely under src/buildsuites/_build (VCS-ignored). Fossil
@ -119,7 +124,7 @@ array set opt {
-optimize ReleaseFast
-tclbranch {}
-refresh 0
-steps {install install-libraries smoke}
-steps {install install-libraries smoke tklib tcllib tcllibc}
-testargs {}
-zigargs {}
-repofolder {}
@ -170,9 +175,15 @@ if {$opt(-repofolder) ne ""} {
#Hermetic child shells: the suite-built tclsh must not see the user's machine-level
#package paths (TCLLIBPATH/TCL_LIBRARY misdirect installer paths and risk
#false-positive smokes). The zig recipe additionally scrubs these per built-shell
#Run step (suite_tcl90 G-102 convention, kept for the 8.6 recipe).
foreach ev {TCLLIBPATH TCL_LIBRARY TK_LIBRARY} {
#false-positive smokes; TCL<major>_<minor>_TM_PATH seeds tm.tcl's module roots and
#is the quiet one - this machine points TCL8_6_TM_PATH at repo/foreign module trees,
#which shadowed suite-installed tcllib packages until the G-100 work caught it).
#The zig recipe scrubs the same list per built-shell Run step (build_common.zig
#tcl_env_vars - keep the two lists in step).
foreach ev {TCLLIBPATH TCL_LIBRARY TK_LIBRARY
TCL8_0_TM_PATH TCL8_1_TM_PATH TCL8_2_TM_PATH TCL8_3_TM_PATH
TCL8_4_TM_PATH TCL8_5_TM_PATH TCL8_6_TM_PATH TCL8_7_TM_PATH
TCL9_0_TM_PATH TCL9_1_TM_PATH} {
if {[info exists env($ev)]} {
log "unsetting inherited \$env($ev) for hermetic child shells"
unset env($ev)
@ -276,19 +287,33 @@ proc fossil_source {name url branch dir} {
cd $savedpwd
}
proc git_source {name url ref dir} {
#clone (or leave) a git checkout pinned at ref (critcl - the recipe's tcllibc
#step consumes it from the stage; suite_tcl90 convention)
global opt
if {![file isdirectory [file join $dir .git]]} {
log "cloning $name (git) -> $dir"
run git clone --quiet $url $dir
} elseif {$opt(-refresh)} {
run git -C $dir fetch --quiet
}
run git -C $dir checkout --quiet $ref
}
#-- stage the recipe ------------------------------------------------------
#Per-item sync (not a wholesale delete of the build dir): zig cache dirs live in
#the build dir and are kept (per-zig-version; windows can hold transient locks on
#recently-used cache trees).
#recently-used cache trees). Item list mirrors the zig bootstrap mode's
#recipe_items (build86.zig bootstrapMode).
log "staging recipe -> $builddir"
file mkdir $builddir
foreach item {build86.zig build_common.zig build_zlib86 build_tclthread86 build_tclvfs86 expected_test_failures.txt expected_test_failures_thread.txt expected_test_failures_tclvfs.txt src tools} {
foreach item {build86.zig build_common.zig build_zlib86 build_tclthread86 build_tclvfs86 build_tk86 critcl_zig.config expected_test_failures.txt expected_test_failures_thread.txt expected_test_failures_tclvfs.txt src tools} {
file delete -force [file join $builddir $item]
file copy [file join $suiteroot $item] $builddir
}
#-- sources ---------------------------------------------------------------
foreach required {tcl tclthread tclvfs} {
foreach required {tcl tclthread tclvfs tk tklib tcllib critcl} {
if {![dict exists $sources $required]} {
fail "sources.config: missing required source record '$required'"
}
@ -301,6 +326,7 @@ dict for {name s} $sources {
}
switch -- [dict get $s kind] {
fossil {fossil_source $name [dict get $s url] $ref [file join $stage [dict get $s dir]]}
git {git_source $name [dict get $s url] $ref [file join $stage [dict get $s dir]]}
}
}

50
src/buildsuites/suite_tcl86/tools/accel_bench.tcl

@ -0,0 +1,50 @@
#accel_bench.tcl (G-100): tcllibc accelerator evidence under a suite-built shell.
#Hashes the same payload twice through tcllib's sha1 - once on the critcl
#accelerator (tcllibc), once on the pure-Tcl implementation - asserts the two
#digests match and prints the timings plus the speedup ratio. This is the
#punkcheck-relevant comparison (punkcheck hashing is the workload that made the
#accelerators worth building).
#
#Both implementations are made available with ::sha1::LoadAccelerator (the
#package itself stops loading at the FIRST working accelerator, so 'tcl' is
#normally not registered when critcl won) and selected with ::sha1::SwitchTo.
#
#args: ?-mb <payload megabytes>? (default 4)
proc fail {msg} {puts stderr "accel_bench FAIL: $msg"; flush stderr; exit 1}
set mb 4
for {set i 0} {$i < [llength $argv]} {incr i} {
switch -- [lindex $argv $i] {
-mb {incr i; set mb [lindex $argv $i]}
default {fail "unknown argument: [lindex $argv $i]"}
}
}
if {[catch {package require sha1} sha1ver]} {fail "package require sha1: $sha1ver"}
if {[catch {package require tcllibc} tcllibcver]} {fail "package require tcllibc: $tcllibcver"}
if {![::sha1::LoadAccelerator critcl]} {fail "sha1 critcl accelerator not available (tcllibc $tcllibcver loaded but sha1c missing?)"}
if {![::sha1::LoadAccelerator tcl]} {fail "sha1 pure-tcl implementation not available"}
#payload: incompressible-ish repeated block, built once, hashed by both
set block [string repeat "punkshell-suite_tcl86-accel-bench-0123456789abcdef" 1024]
set data ""
set target [expr {$mb * 1024 * 1024}]
while {[string length $data] < $target} {append data $block}
set bytes [string length $data]
::sha1::SwitchTo critcl
set t_critcl [lindex [time {set d_critcl [::sha1::sha1 -hex -- $data]}] 0]
::sha1::SwitchTo tcl
set t_tcl [lindex [time {set d_tcl [::sha1::sha1 -hex -- $data]}] 0]
if {$d_critcl ne $d_tcl} {fail "digest mismatch: critcl $d_critcl vs pure-tcl $d_tcl"}
if {$t_critcl <= 0} {set t_critcl 1}
set speedup [format %.1f [expr {double($t_tcl) / double($t_critcl)}]]
set ms_critcl [format %.1f [expr {$t_critcl / 1000.0}]]
set ms_tcl [format %.1f [expr {$t_tcl / 1000.0}]]
puts "accel_bench OK: sha1 over $bytes bytes - critcl ${ms_critcl}ms vs pure-tcl ${ms_tcl}ms = ${speedup}x (digests identical, sha1 $sha1ver, tcllibc $tcllibcver)"
flush stdout
exit 0

64
src/buildsuites/suite_tcl86/tools/tk_smoke.tcl

@ -0,0 +1,64 @@
#tk_smoke.tcl (G-100): Tk window-station smoke under a suite-built shell.
#Goes beyond 'package require Tk' (a load-only check passes even when no usable
#display exists): it maps a real toplevel with a widget in it, forces a display
#update, then destroys it - the create/destroy-window verification the G-100
#acceptance asks for, run on every build rather than once by hand.
#
#args: ?-expect <tk patchlevel>?
#Exits 1 with a 'tk_smoke FAIL:' line on any failure; prints one evidence line
#on success (version, screen geometry/depth, windowingsystem, widget classes).
proc fail {msg} {puts stderr "tk_smoke FAIL: $msg"; flush stderr; exit 1}
set expect {}
for {set i 0} {$i < [llength $argv]} {incr i} {
switch -- [lindex $argv $i] {
-expect {incr i; set expect [lindex $argv $i]}
default {fail "unknown argument: [lindex $argv $i]"}
}
}
if {[catch {package require Tk} tkver]} {fail "package require Tk: $tkver"}
if {$expect ne "" && $tkver ne $expect} {fail "Tk version $tkver, expected $expect"}
if {[catch {package require Ttk} ttkver]} {set ttkver "-"}
#the root window itself: geometry facts prove a real window station/display
if {[catch {
wm title . "tk_smoke"
wm geometry . 320x120+80+80
set screen "[winfo screenwidth .]x[winfo screenheight .] depth [winfo screendepth .]"
set wsys [tk windowingsystem]
} err]} {fail "root window setup: $err"}
#create: a classic widget, a themed widget and a child toplevel
if {[catch {
label .l -text "tk_smoke"
pack .l
ttk::button .b -text "ok"
pack .b
toplevel .t
wm title .t "tk_smoke child"
wm geometry .t 200x80+420+80
label .t.l -text "child"
pack .t.l
update
} err]} {fail "widget create/map: $err"}
foreach {label w} {root . classic .l themed .b child .t} {
if {![winfo ismapped $w]} {fail "window not mapped after update: $label ($w)"}
}
set classes [list [winfo class .l] [winfo class .b] [winfo class .t]]
#destroy: the child toplevel first, then every root child - a clean teardown
if {[catch {
destroy .t
update
if {[winfo exists .t]} {fail "child toplevel survived destroy"}
destroy .l .b
update
if {[llength [winfo children .]]} {fail "root children survived destroy: [winfo children .]"}
} err]} {fail "widget destroy: $err"}
puts "tk_smoke OK: Tk $tkver (Ttk $ttkver) windowingsystem $wsys screen $screen widgets [join $classes /] create+map+destroy clean"
flush stdout
exit 0
Loading…
Cancel
Save