From 48a1d0a97f77d54030e9dcef642358bf936a6aba Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Mon, 20 Jul 2026 07:13:06 +1000 Subject: [PATCH] G-098 increment 3: tcllib installed; tcllibc via critcl 3.3.1 + zig cc; sha1 248x evidence tcllib pure side via its own installer (explicit -pkg-path; driver now unsets TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY for hermetic child shells after the default libdir followed inherited TCLLIBPATH to C:/TCLPKGS - additive pollution verified and removed). install-libraries now installs the whole tcl library tree (its own pkgIndex references msgcat/opt/... subdirs the piecemeal install missed). critcl pinned 3.3.1 (git) + tracked critcl_zig.config (zig cc -target x86_64-windows-gnu); driver replicates sak's direct critcl invocation over the 21 declared files -> tcllibc.dll at out/lib/tcllibc (provide 2.0). Evidence: md5 accel(critcl)=1; sha1 5MB critcl 12.3ms vs pure 3.06s (~248x) - the punkcheck hashing pain quantified and fixed. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- goals/G-098-suite-tk-tcllib-critcl.md | 41 ++++++++- src/buildsuites/suite_tcl90/build905.zig | 11 +++ src/buildsuites/suite_tcl90/critcl_zig.config | 25 ++++++ src/buildsuites/suite_tcl90/suite.tcl | 88 ++++++++++++++++++- 4 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/buildsuites/suite_tcl90/critcl_zig.config diff --git a/goals/G-098-suite-tk-tcllib-critcl.md b/goals/G-098-suite-tk-tcllib-critcl.md index d428c8de..8483bfc9 100644 --- a/goals/G-098-suite-tk-tcllib-critcl.md +++ b/goals/G-098-suite-tk-tcllib-critcl.md @@ -86,12 +86,49 @@ user direction 2026-07-20: - SMOKE PASS: in the suite shell, package require Tk + package require tooltip -> tooltip 2.0.4 (a Tk-dependent tklib package resolving through the aggregate index). +### 2026-07-20 increment 3: tcllib installed; tcllibc built via critcl 3.3.1 + zig cc; 248x hashing evidence + +- tcllib pure-tcl side installs via its own installer (same family as tklib: critcl + DSL stubbed, exclusions, aggregating pkgIndex; batch packages-only, suite shell). + INCIDENT: the installer's default libdir derives from the running shell's + tcl_pkgPath, which included the user's machine-global C:/TCLPKGS via inherited + TCLLIBPATH - the first run installed tcllib2.0 THERE. Verified pure additive + (all content fresh-dated, no pre-existing tcllib at that path) and removed. + Fixes: the driver now UNSETS TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY for all child + shells (hermetic; also removes false-positive smoke risk via machine packages), + and both tklib/tcllib installs pass explicit -pkg-path (version parsed from each + project's support/installation/version.tcl). Smoke: md5 2.0.9, sha1 2.0.5, + cmdline 1.5.3 from out/lib/tcllib2.0. +- install-libraries recipe fix surfaced by critcl's package interception: tcl's + installed library/pkgIndex.tcl references subdir packages (msgcat/ opt/ + cookiejar/ ...) the piecemeal top-level install never provided - phantom ifneeded + entries. The recipe now installs the whole library tree to lib/tcl9.0 + (stock-install parity) alongside the tm installs. +- tcllibc: critcl git-cloned and pinned at tag 3.3.1 under _build (hermetic + git-mirror treatment analogous to the fossil store = noted refinement); tracked + critcl_zig.config overrides critcl's gcc defaults with + `zig cc -target x86_64-windows-gnu` compile/link (driver prepends the pinned zig + dir to PATH). sak.tcl's windows path wants a tclkitsh+starkit combo, so the + driver replicates its underlying invocation directly: critcl main.tcl -config + ... -cache ... -force -libdir out/lib -pkg tcllibc + the 21 critcl files parsed + from tcllib's own support/installation/version.tcl declarations (critcl_main + first; brace-grouped lists handled). RESULT: tcllibc.dll built and placed at + out/lib/tcllibc (provide tcllibc 2.0). +- Evidence: package require tcllibc -> 2.0; tcllib md5 auto-engages the critcl + accelerator (accel(critcl)=1). Hashing comparison (punkcheck-relevant): sha1 -hex + over ~5MB in the suite shell - critcl 12.3ms vs pure-tcl 3.06s (pure measured + with tcllibc moved OUTSIDE lib/ - child pkgIndex scanning ignores dir names, so + an in-place rename does not hide a package) = ~248x speedup, confirming the + user-reported punkcheck hashing pain and its fix. + Remaining for acceptance: -- tcllib install via installer.tcl/sak.tcl (behaviour check) + critcl-built tcllibc - (critcl git clone/pin driving zig cc) + hashing comparison evidence. + - suite.tcl test gate: parse all.tcl totals vs a recorded expected-failure baseline; disposition the 72 current failures. - punkshell runtests against the suite-built runtime with Tk suites unskipped. +- (superseded note kept for history below: the clone store subsequently moved from + the suite-owned _build location to the machine-level ~/.punkshell/fossils - see + increment 1 and the suite README for current truth) - Fetch conventions follow suite_tcl90, with the hermetic clone store adopted at this goal's activation (user decision 2026-07-20): clones live in the suite-owned _build/suite_tcl90/fossils store (seeded READ-ONLY from ~/.fossils when present, diff --git a/src/buildsuites/suite_tcl90/build905.zig b/src/buildsuites/suite_tcl90/build905.zig index 0fc62784..45ae1840 100644 --- a/src/buildsuites/suite_tcl90/build905.zig +++ b/src/buildsuites/suite_tcl90/build905.zig @@ -1818,6 +1818,17 @@ pub fn build(b: *std.Build) !void { install_libraries.dependOn(&dde_dll.step); } + //Whole library tree -> lib/tcl9.0 (stock-install parity, G-098 finding: the + //library's own pkgIndex.tcl references subdir packages such as msgcat/ opt/ + //cookiejar/ - installing only top-level .tcl files leaves phantom ifneeded + //entries pointing at files that were never installed). + const install_library_tree = b.addInstallDirectory(.{ + .source_dir = b.path(tcl_source_folder ++ "/library"), + .install_dir = .prefix, + .install_subdir = "lib/tcl" ++ tcl_dot_version, + }); + install_libraries.dependOn(&install_library_tree.step); + //Install library files - non recursive var library_dir = try std.Io.Dir.cwd().openDir(b.graph.io, tcl_source_folder ++ "/library", .{ .iterate = true }); defer library_dir.close(b.graph.io); diff --git a/src/buildsuites/suite_tcl90/critcl_zig.config b/src/buildsuites/suite_tcl90/critcl_zig.config new file mode 100644 index 00000000..12af67b5 --- /dev/null +++ b/src/buildsuites/suite_tcl90/critcl_zig.config @@ -0,0 +1,25 @@ +# suite_tcl90 critcl configuration: zig cc as the C toolchain (G-098). +# Overrides critcl's gcc-style defaults; 'zig' is resolved from PATH - the suite +# driver 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). + +platform win32-x86_64 +compile zig cc -target x86_64-windows-gnu -c -fPIC +version zig version +link zig cc -target x86_64-windows-gnu -shared +include -I +libinclude -L +object .o +output -o [list $outfile] +optimize -O2 +noassert -DNDEBUG +tclstubs -DUSE_TCL_STUBS +tkstubs -DUSE_TK_STUBS +debug_symbols -g +strip -Wl,-s +link_debug +link_release +embed_manifest +ldoutput +link_preload +sharedlibext .dll diff --git a/src/buildsuites/suite_tcl90/suite.tcl b/src/buildsuites/suite_tcl90/suite.tcl index e68dd5fd..64df2fc8 100644 --- a/src/buildsuites/suite_tcl90/suite.tcl +++ b/src/buildsuites/suite_tcl90/suite.tcl @@ -132,6 +132,16 @@ file mkdir $stage set env(FOSSIL_HOME) $stage file mkdir $fossildir +#Hermetic child shells: the suite-built tclsh must not see the user's machine-level +#package paths (TCLLIBPATH pushed e.g C:/TCLPKGS into tcl_pkgPath here - misdirecting +#installer default paths and risking false-positive smokes via machine packages). +foreach ev {TCLLIBPATH TCL_LIBRARY TK_LIBRARY} { + if {[info exists env($ev)]} { + log "unsetting inherited \$env($ev) for hermetic child shells" + unset env($ev) + } +} + proc fossil_source {name url branch dir} { #ensure /.fossil exists (read-only seed copy, else network clone) and #open it at branch in dir @@ -295,11 +305,20 @@ foreach {label path script} [list \ #generation of the aggregating pkgIndex.tcl (gen_main_index). Run it in batch mode #(packages only) with the suite-built shell; default paths derive from that shell, #landing the packages under /lib/. +proc installer_version {dir} { + #tcllib-family: support/installation/version.tcl carries 'package_version ' + set f [open [file join $dir support installation version.tcl] r] + set data [read $f]; close $f + if {![regexp {package_version\s+(\S+)} $data -> v]} {error "no package_version in $dir"} + return $v +} if {![llength [glob -nocomplain [file join $stage out lib tklib*]]]} { - log "installing tklib (batch, packages only) via its own installer" + set tklibver [installer_version [file join $stage tklib]] + log "installing tklib $tklibver (batch, packages only) via its own installer" set savedpwd [pwd] cd [file join $stage tklib] - run $exe installer.tcl -no-gui -no-wait -pkgs -no-apps -no-html -no-nroff -no-examples + run $exe installer.tcl -no-gui -no-wait -no-apps -no-html -no-nroff -no-examples \ + -pkg-path [file join $stage out lib tklib$tklibver] cd $savedpwd } set tkliblanded [glob -nocomplain [file join $stage out lib tklib*]] @@ -307,4 +326,69 @@ if {![llength $tkliblanded]} {fail "tklib install did not land under [file join set tksmoke [exec $exe << {package require Tk; wm withdraw .; puts "tooltip: [package require tooltip]"; destroy .; exit}] log "tklib: [file tail [lindex $tkliblanded 0]] installed; smoke $tksmoke" +#-- tcllib install (G-098) ------------------------------------------------- +#Same installer family as tklib (module exclusions + aggregating pkgIndex; critcl DSL +#stubbed to no-ops, so this is the PURE-TCL side only - the critcl-built tcllibc +#accelerators are a separate step). Batch, packages only, suite-built shell. +if {![llength [glob -nocomplain [file join $stage out lib tcllib*]]]} { + set tcllibver [installer_version [file join $stage tcllib]] + log "installing tcllib $tcllibver (batch, packages only, pure-tcl side) via its own installer" + set savedpwd [pwd] + cd [file join $stage tcllib] + run $exe installer.tcl -no-gui -no-wait -no-apps -no-html -no-nroff -no-examples \ + -pkg-path [file join $stage out lib tcllib$tcllibver] + cd $savedpwd +} +set tclliblanded [glob -nocomplain [file join $stage out lib tcllib*]] +if {![llength $tclliblanded]} {fail "tcllib install did not land under [file join $stage out lib]"} +set tcllibsmoke [exec $exe << {puts "md5:[package require md5] sha1:[package require sha1] cmdline:[package require cmdline]"; exit}] +log "tcllib: [file tail [lindex $tclliblanded 0]] installed; smoke $tcllibsmoke" + +#-- tcllibc accelerators via critcl + zig cc (G-098) ----------------------- +#sak.tcl's critcl target expects a tclkitsh+starkit on windows; we replicate its +#underlying invocation directly (critcl -cache ... -force -libdir -pkg tcllibc +#) using the critcl source checkout's main.tcl app under the +#suite shell, with the suite's zig on PATH and the tracked critcl_zig.config. +set critcldir [file join $stage critcl] +set critclpin 3.3.1 +if {![file isdirectory [file join $critcldir .git]]} { + log "cloning critcl (git) -> $critcldir" + run git clone --quiet https://github.com/andreas-kupries/critcl $critcldir +} +run git -C $critcldir checkout --quiet $critclpin +if {![llength [glob -nocomplain [file join $stage out lib tcllibc*]]]} { + #module list from tcllib's own declarations (support/installation/version.tcl): + #critcl_main's file first, then every critcl module's files (sak.tcl semantics) + set f [open [file join $stage tcllib support installation version.tcl] r] + set vdata [read $f]; close $f + set mainfiles {} + set modfiles {} + foreach line [split $vdata \n] { + if {[regexp {^critcl_main\s+\S+\s+(.+)$} $line -> flist]} { + foreach ff [concat {*}$flist] {lappend mainfiles $ff} + } elseif {[regexp {^critcl\s+\S+\s+(.+)$} $line -> flist]} { + foreach ff [concat {*}$flist] {lappend modfiles $ff} + } + } + if {![llength $mainfiles]} {fail "no critcl_main declaration found in tcllib version.tcl"} + set critclfiles {} + foreach ff [concat $mainfiles $modfiles] { + lappend critclfiles [file join $stage tcllib modules $ff] + } + log "building tcllibc via critcl $critclpin + zig cc ([llength $critclfiles] critcl files)" + set zigdir [file dirname $opt(-zig)] + set savedpath $env(PATH) + set env(PATH) "[file nativename $zigdir];$env(PATH)" + set savedpwd [pwd] + cd [file join $stage tcllib] + run $exe [file join $critcldir main.tcl] \ + -config [file join $suiteroot critcl_zig.config] \ + -cache [file join $stage .critcl] -force \ + -libdir [file join $stage out lib] -pkg tcllibc {*}$critclfiles + cd $savedpwd + set env(PATH) $savedpath +} +set tcllibcsmoke [exec $exe << {puts "tcllibc:[package require tcllibc]"; package require md5; puts "md5-critcl-accel:$::md5::accel(critcl)"; exit}] +log "tcllibc: $tcllibcsmoke" + log "PASS - built $patchlevel (checkout [string range $uuid 0 11]..., zig $zigv) -> $stage/out"