From f89bd58c319eab7e43d9ca2b862b573b829523fa Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sun, 26 Jul 2026 13:37:53 +1000 Subject: [PATCH] G-100 achieved: suite_tcl86 dependency set (Tk 8.6, tklib, tcllib + tcllibc) and an 8.6 runtests census Extends the 8.6 buildsuite with the remaining pieces punkshell's Tcl 8 testing depends on, all built from source with the pinned zig 0.16.0 toolchain only. Tk 8.6.17 (build_tk86/build_tk86.zig) is a RE-DERIVATION from the staged 8.6 win/makefile.vc + rules.vc, not a port of suite_tcl90's build_tk.zig: - no generic/tkPkgConfig.c in 8.6 -> no CFG_* defines at all - rules.vc TCL_MAJOR_VERSION==8 arm -> tk86t.dll + lib/tk8.6 (vs tcl9tk90.dll) - @TK_WIN_VERSION@ substitutes $(DOTVERSION).0.0, not the tcl-style .. formula - 8.6 TKOBJS carries no tkIcu/tkImgListFormat/tkImgSVGnano/tkWinGDI/tkWinIco/ tkWinSysTray; XLIBOBJS is a first-class makefile macro here (the 9.0 derivation had to recover those files by hand) USE_TCL_STUBS + USE_TCLOO_STUBS against libtclstub86.a is the stock rules.vc arrangement and keeps the dll loadable by both suite shells. tkMain.c is compiled twice per makefile.vc, the ansi copy forced with -UUNICODE/-U_UNICODE (verified the pinned zig cc defines neither for x86_64-windows-gnu, so the 8.6 core's ANSI/TCHAR arrangement is matched exactly). tklib 0.9 and tcllib 2.0 install via their own installer.tcl (batch, packages only, explicit -pkg-path so the installer's tcl_pkgPath-derived default can never write off-tree); tcllibc is built by critcl 3.3.1 driving zig cc (tracked critcl_zig.config, target pinned win32-x86_64-zig - critcl otherwise probes PATH and falls back to its builtin msvc target). critcl compiles against its own bundled headers chosen by the driving shell's version, which is why one config serves both generations. New evidence tools, run on every build: tools/tk_smoke.tcl maps a real toplevel with classic + themed widgets, updates, then destroys it - a load-only check passes even with no usable display tools/accel_bench.tcl same payload through sha1's critcl and pure-Tcl implementations with the digests compared: 4MB -> critcl 5.7ms vs pure-tcl 2736ms = 477x Test tiers (G-107 policy pattern): test-tcllib/test-tklib on the record tier inside test-libraries, plus an opt-in test-tk (winDialog/winMsgbox excluded by default - their message-injection automation blocks on a human under a batch shell). First censuses on tclsh86ts.exe 8.6.18: thread 142 run / 0 failed GATE PASS tklib 796 run / 0 failed record tcllib 64259 run / 41 failed record (names in the summary artifact) tclvfs 51 run / 4 failed GATE PASS after baseline extension The tclvfs baseline grew vfsFtp-1.1..1.3: they sit behind an 'ftp' constraint needing tcllib's ftp package - absent at the G-099 capture, satisfied now that the suite installs tcllib - so they run and reach the retired ftp.tcl.tk, the same external-service class already baselined for vfsUrl-1.2/2.1. punkshell runtests census (both runs 2026-07-26, identical flags, default single-process sequential - 8.6 has no tcl::process so -jobs re-convoys): native Tcl 8.6.17 1003 total / 986 passed / 10 skipped / 7 failed suite tclsh86ts 1003 total / 987 passed / 10 skipped / 6 failed runtests_parity.tcl finds exactly ONE difference: punkcheck concurrency_two_writers_no_corruption failed in the BASELINE run and passed on the suite runtime - a full-suite-load flake (6/6 twice in isolation on both runtimes). Zero suite-runtime-only failures. Tk-dependent suites ran unskipped (repl consolebackends 3/3, including the tkconsole backend on the suite Tk). Two defects fixed in passing: - scrubTclEnv never cleared TCL__TM_PATH, so suite shells silently preferred machine/repo tm modules over what the suite had just installed (a repo-built sha1-2.0.4.tm shadowed tcllib's 2.0.5, md5 2.0.8 shadowed 2.0.9) - any census through such a shell measures the machine. The scrub list now covers TCL8_0..8_7 + TCL9_0/9_1 in all three sites (per-step, suite.tcl process-level, bootstrap nested build). suite_tcl90 has the same exposure via TCL9_0_TM_PATH and is deliberately untouched here (out of this goal's scope) - pointers recorded in G-108 and G-116. - build.zig.zon's .fingerprint was hand-authored, so 'zig build' from the tracked suite dir died with 'invalid fingerprint' - the no-tclsh bootstrap flow could never have run. Corrected to zig's computed value (its low half is a checksum of the package name); 'zig build stage' now verified. Goal flipped to achieved 2026-07-26 and archived; reference sweep updated G-005, G-101, G-105 and ARCHITECTURE.md, and the archive-time xref pair G-101 <-> G-116 (whose only bridges were the archived G-100/G-103) gained a direct Related pointer. No project-version bump - arm's-length build tooling, not shipped shell behaviour. Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com --- ARCHITECTURE.md | 4 +- GOALS-archive.md | 4 + GOALS.md | 4 - goals/G-005-zig-build-infrastructure.md | 2 +- goals/G-100-suite-tcl86-tk-tcllib.md | 57 ---- goals/G-101-tcl86-kit-container-strategy.md | 18 +- goals/G-105-buildsuite-cross-target.md | 5 +- goals/G-108-buildsuite-debug-tier.md | 6 + goals/G-116-suite-built-tcltls.md | 7 + goals/archive/G-100-suite-tcl86-tk-tcllib.md | 210 ++++++++++++++ src/buildsuites/AGENTS.md | 13 +- src/buildsuites/suite_tcl86/README.md | 144 ++++++++-- src/buildsuites/suite_tcl86/build.zig.zon | 48 +++- src/buildsuites/suite_tcl86/build86.zig | 232 ++++++++++++++-- src/buildsuites/suite_tcl86/build_common.zig | 64 ++++- .../suite_tcl86/build_tk86/build_tk86.zig | 260 ++++++++++++++++++ src/buildsuites/suite_tcl86/critcl_zig.config | 36 +++ .../expected_test_failures_tclvfs.txt | 23 +- src/buildsuites/suite_tcl86/sources.config | 24 +- src/buildsuites/suite_tcl86/suite.tcl | 44 ++- .../suite_tcl86/tools/accel_bench.tcl | 50 ++++ .../suite_tcl86/tools/tk_smoke.tcl | 64 +++++ 22 files changed, 1184 insertions(+), 135 deletions(-) delete mode 100644 goals/G-100-suite-tcl86-tk-tcllib.md create mode 100644 goals/archive/G-100-suite-tcl86-tk-tcllib.md create mode 100644 src/buildsuites/suite_tcl86/build_tk86/build_tk86.zig create mode 100644 src/buildsuites/suite_tcl86/critcl_zig.config create mode 100644 src/buildsuites/suite_tcl86/tools/accel_bench.tcl create mode 100644 src/buildsuites/suite_tcl86/tools/tk_smoke.tcl diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index fa81e6fe..18903239 100644 --- a/ARCHITECTURE.md +++ b/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__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 diff --git a/GOALS-archive.md b/GOALS-archive.md index 8e549928..02c27118 100644 --- a/GOALS-archive.md +++ b/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. diff --git a/GOALS.md b/GOALS.md index 15b26988..0a02589e 100644 --- a/GOALS.md +++ b/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 diff --git a/goals/G-005-zig-build-infrastructure.md b/goals/G-005-zig-build-infrastructure.md index b32f4091..bd5c9055 100644 --- a/goals/G-005-zig-build-infrastructure.md +++ b/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 diff --git a/goals/G-100-suite-tcl86-tk-tcllib.md b/goals/G-100-suite-tcl86-tk-tcllib.md deleted file mode 100644 index f0ff496b..00000000 --- a/goals/G-100-suite-tcl86-tk-tcllib.md +++ /dev/null @@ -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). diff --git a/goals/G-101-tcl86-kit-container-strategy.md b/goals/G-101-tcl86-kit-container-strategy.md index 9dceefae..2ab2350e 100644 --- a/goals/G-101-tcl86-kit-container-strategy.md +++ b/goals/G-101-tcl86-kit-container-strategy.md @@ -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 diff --git a/goals/G-105-buildsuite-cross-target.md b/goals/G-105-buildsuite-cross-target.md index d65c92bd..be924f77 100644 --- a/goals/G-105-buildsuite-cross-target.md +++ b/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 diff --git a/goals/G-108-buildsuite-debug-tier.md b/goals/G-108-buildsuite-debug-tier.md index a02e691a..fd32aa07 100644 --- a/goals/G-108-buildsuite-debug-tier.md +++ b/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__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 diff --git a/goals/G-116-suite-built-tcltls.md b/goals/G-116-suite-built-tcltls.md index 7f113acb..3142af8c 100644 --- a/goals/G-116-suite-built-tcltls.md +++ b/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__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 diff --git a/goals/archive/G-100-suite-tcl86-tk-tcllib.md b/goals/archive/G-100-suite-tcl86-tk-tcllib.md new file mode 100644 index 00000000..9b925453 --- /dev/null +++ b/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__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: ` 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__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 ` 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/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). diff --git a/src/buildsuites/AGENTS.md b/src/buildsuites/AGENTS.md index f3bb8d08..f8122a93 100644 --- a/src/buildsuites/AGENTS.md +++ b/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__TM_PATH must be scrubbed + alongside TCLLIBPATH/TCL_LIBRARY or a census measures the machine); no child + AGENTS.md yet diff --git a/src/buildsuites/suite_tcl86/README.md b/src/buildsuites/suite_tcl86/README.md index 6ac816d7..dea23621 100644 --- a/src/buildsuites/suite_tcl86/README.md +++ b/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 + `..` 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__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`. diff --git a/src/buildsuites/suite_tcl86/build.zig.zon b/src/buildsuites/suite_tcl86/build.zig.zon index 483263e2..aab23274 100644 --- a/src/buildsuites/suite_tcl86/build.zig.zon +++ b/src/buildsuites/suite_tcl86/build.zig.zon @@ -5,16 +5,23 @@ //tarball urls + zig package hashes; 'zig fetch ' 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 = .{""}, } diff --git a/src/buildsuites/suite_tcl86/build86.zig b/src/buildsuites/suite_tcl86/build86.zig index 193da51a..07bf240f 100644 --- a/src/buildsuites/suite_tcl86/build86.zig +++ b/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 /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-=gate|record|skip -Dtestargs-="..." // -Dtestnotfiles-="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__TM_PATH module roots) + for (common.tcl_env_vars) |ev| { nested.removeEnvironmentVariable(ev); } nested.step.dependOn(stage_step); diff --git a/src/buildsuites/suite_tcl86/build_common.zig b/src/buildsuites/suite_tcl86/build_common.zig index 00ffe985..e00e40b5 100644 --- a/src/buildsuites/suite_tcl86/build_common.zig +++ b/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__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 ' 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: //.., releaselevel //0=alpha 1=beta 2=final (e.g 8.6 + 8.6.18 -> 8.6.2.8618) diff --git a/src/buildsuites/suite_tcl86/build_tk86/build_tk86.zig b/src/buildsuites/suite_tcl86/build_tk86/build_tk86.zig new file mode 100644 index 00000000..8bd7feac --- /dev/null +++ b/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 +// .. 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 -> /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 }; +} diff --git a/src/buildsuites/suite_tcl86/critcl_zig.config b/src/buildsuites/suite_tcl86/critcl_zig.config new file mode 100644 index 00000000..74a0a7ed --- /dev/null +++ b/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 diff --git a/src/buildsuites/suite_tcl86/expected_test_failures_tclvfs.txt b/src/buildsuites/suite_tcl86/expected_test_failures_tclvfs.txt index 1e834aa9..09c66533 100644 --- a/src/buildsuites/suite_tcl86/expected_test_failures_tclvfs.txt +++ b/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 diff --git a/src/buildsuites/suite_tcl86/sources.config b/src/buildsuites/suite_tcl86/sources.config index 8d550191..59d805a4 100644 --- a/src/buildsuites/suite_tcl86/sources.config +++ b/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 diff --git a/src/buildsuites/suite_tcl86/suite.tcl b/src/buildsuites/suite_tcl86/suite.tcl index ba3cacd3..ad001bb7 100644 --- a/src/buildsuites/suite_tcl86/suite.tcl +++ b/src/buildsuites/suite_tcl86/suite.tcl @@ -24,11 +24,16 @@ # -seedfossils read-only seed collection for missing clones (default ~/.fossils; "" disables) # -all <0|1> with 'clean': reserved (the clone store lives outside the stage) # -steps 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 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__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]]} } } diff --git a/src/buildsuites/suite_tcl86/tools/accel_bench.tcl b/src/buildsuites/suite_tcl86/tools/accel_bench.tcl new file mode 100644 index 00000000..2882937e --- /dev/null +++ b/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 ? (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 diff --git a/src/buildsuites/suite_tcl86/tools/tk_smoke.tcl b/src/buildsuites/suite_tcl86/tools/tk_smoke.tcl new file mode 100644 index 00000000..a1168ff3 --- /dev/null +++ b/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 ? +#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