From 79b36ff121758adfaff28e2f02cbd3b81d429147 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sun, 2 Aug 2026 07:36:22 +1000 Subject: [PATCH] G-101 increments 3+4: mk4 kit-head + end-to-end demonstration -> achieved Increment 3 - the kit-head (opt-in suite step "kithead"): - build_kithead86/kithead.c: static Mk4tcl+vfs registration, preinit reading boot.tcl from the exe's own mk4 file, post-init main.tcl pivot (kbskit kitInit.c reduced to the 8.6 mk4 case; the 8.6.18 headers have no Tcl_StaticLibrary - stock tclAppInit.c's file-local compat define mirrored). - build_kithead86/boot.tcl: kbskit boot.tcl mk4-only/8.6-only (tclInit override, raw mk::select hand-eval of vfsUtils/vfslib/mk4vfs, mount + encoding remount, in-kit init.tcl, auto_path reset). - tools/mk4kit_attach.tcl: setupvfs role under the suite dynamic shell (mount rw, lib trees + lf-normalized boot.tcl, tclvfs scripts with the pkgIndex vfs entry rewritten to "load {} vfs"). - build86.zig: kithead exe (core+reg/dde+stubs as tclsh86ts, two-anchor tclAppInit overlay, engine+binding via build_mk4tcl86 pub lists, static tclvfs w/ STATIC_BUILD); artifact bin/tclsh8.6.18-mk4-r1.exe; smoke asserts info library INSIDE the exe. No rechan needed (vfslib memchan rides 8.6 reflected channels). Increment 4 - the demonstration (hermetic env, PATH=System32, bare dir): - head boots with library inside itself on a Tcl-free path; - the RUNNING head ran sdx and wrapped a SEPARATE COPY of itself (self-mount rider as specified); - the starpack runs main.tcl from inside, reads vfs data, loads a kit-only package, and loads thread2813t.dll FROM the vfs (dll clause measured, not inherited). Goal detail: Progress 3+4, Assessment (four candidates vs the four kit expectations), Decision (mk4 = the 8.6 container and punk86 default; zipfs backport stays the recorded G-131 second arm), Follow-on-to-parity list; Status -> achieved 2026-08-02. Archive move follows separately. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- goals/G-101-tcl86-kit-container-strategy.md | 152 +++++++++++++++- src/buildsuites/suite_tcl86/build86.zig | 172 ++++++++++++++++++ .../suite_tcl86/build_kithead86/boot.tcl | 87 +++++++++ .../suite_tcl86/build_kithead86/kithead.c | 123 +++++++++++++ .../build_mk4tcl86/build_mk4tcl86.zig | 16 +- src/buildsuites/suite_tcl86/suite.tcl | 2 +- .../suite_tcl86/tools/kithead_smoke.tcl | 24 +++ .../suite_tcl86/tools/mk4kit_attach.tcl | 116 ++++++++++++ 8 files changed, 681 insertions(+), 11 deletions(-) create mode 100644 src/buildsuites/suite_tcl86/build_kithead86/boot.tcl create mode 100644 src/buildsuites/suite_tcl86/build_kithead86/kithead.c create mode 100644 src/buildsuites/suite_tcl86/tools/kithead_smoke.tcl create mode 100644 src/buildsuites/suite_tcl86/tools/mk4kit_attach.tcl diff --git a/goals/G-101-tcl86-kit-container-strategy.md b/goals/G-101-tcl86-kit-container-strategy.md index 652a99bc..bfca8c66 100644 --- a/goals/G-101-tcl86-kit-container-strategy.md +++ b/goals/G-101-tcl86-kit-container-strategy.md @@ -1,6 +1,6 @@ # G-101 kit container strategy for zig-built Tcl 8.6 runtimes (no zipfs) -Status: active +Status: achieved 2026-08-02 Scope: investigation + decision record under src/buildsuites/suite_tcl86/ (or successor mechanism); src/buildsuites/suite_tcl86/patches/ (tracked sdx wrap.tcl provenance capture); TEMP_REFERENCE/{metakit_jnorthrup,KitCreator,kbs,sdx,tcl9} (read-only guidance - inventory in the detail file); src/runtime/mapvfs.toml ('kit' type consumers); src/make.tcl kit-wrap path (as consumer); bin/sdx.kit (the tracked wrapping tool - provenance is part of this goal) Goal: a decided, demonstrated strategy for wrapping zig-built 8.6 runtimes into self-contained punk86-class kit executables in the absence of zipfs - candidate containers: the metakit/mk4vfs tclkit lineage, cookfs, and zipvfs-over-tclvfs boot - so the prebuilt third-party kit runtimes (tclkit86bi.exe, tclkit-win64-dyn.exe) can eventually be replaced by suite-built, provenance-carrying ones. Acceptance: the candidate containers are assessed and recorded here against punkshell's kit expectations (compatibility with make.tcl's 'kit' wrap path or a documented replacement path; boot-time vfs mount without an installed Tcl; loading binary dlls from or via the container; buildable from source under the zig-only policy); one strategy is demonstrated end-to-end - a kit wrapped from the suite-built 8.6 runtime boots on a path without any Tcl installation and runs a script from inside its vfs; the decision, rationale, and the follow-on work needed to reach punk86-kit parity are recorded in this file. @@ -254,12 +254,150 @@ for 'kit' bakes on this machine class; the sdx -runtime path (executable-head wrapping) still needs the kit-capable HEAD this goal's demonstration arm will build, and stays unexercised until then. -Remaining for acceptance: the candidate-container assessments recorded against -the kit expectations (mk4 / cookfs / zipvfs-over-tclvfs, with the -zipfs-backport arm's bearing); one strategy demonstrated end-to-end (kit -wrapped from the suite-built runtime boots with no installed Tcl and runs a -script from inside its vfs, wrapping a SEPARATE copy per the self-mount -rider); decision + rationale + follow-on-to-parity record in this file. +Increment 3 landed 2026-08-02: the mk4 KIT-HEAD exists - suite_tcl86 gained an +opt-in `kithead` step building a second static shell that boots from its own +attached Metakit container. + +- build_kithead86/kithead.c: static registrations (Mk4tcl + vfs) + the + pre-init script reading boot.tcl out of the exe's own mk4 file + the + post-init attached-main.tcl startup pivot - kbskit 0.4 kitInit.c reduced to + the 8.6 mk4 case (derivation and reductions in the file header; one 8.6 + fact found the hard way: Tcl_StaticLibrary is NOT in the 8.6.18 headers - + stock tclAppInit.c carries its own file-local `#define Tcl_StaticLibrary + Tcl_StaticPackage` shim at win/tclAppInit.c:26-29, now mirrored). +- build_kithead86/boot.tcl: the kbskit boot.tcl reduced to mk4-only/8.6-only + (tclInit override; raw mk::select hand-eval of vfsUtils/vfslib/mk4vfs from + lib/vfs1*; mount, encoding fix + remount, config.tcl hook, in-kit init.tcl, + auto_path reset). Rides IN the attached vfs per the tclkit convention. +- tools/mk4kit_attach.tcl: the setupvfs.tcl role under the suite's own + dynamic shell (no bootstrap kit needed) - copies the raw head, mounts it rw + via vfs::mk4, writes boot.tcl + lib/tcl8.6 + lib/tcl8 + the tclvfs SCRIPTS + with the pkgIndex 'vfs' entry rewritten to `load {} vfs` (the setupvfs + .static convention), lf-normalizes text files (setupvfs's crucial rule), + verifies root boot.tcl by raw mk::select before declaring OK. +- build86.zig: kithead section (tcl core + tclWinReg/Dde + stub objs as + tclsh86ts, tclAppInit overlaid at two exact stock anchors, metakit engine + + binding via the build_mk4tcl86 pub lists, tclvfs C core static with the + build_tclvfs86 define set minus BUILD_vfs plus STATIC_BUILD and the same + __stat64 shim); artifact per the recorded trial naming: + bin/tclsh-mk4-r1.exe (raw un-attached head stays in cache). +- Facts for the record: no rechan needed (tclvfs 1.4.2 vfslib implements + memchan over 8.6 reflected channels - the kbskit-era rechan package is a + pre-8.5 fallback); no zlib static package (8.6 core carries the command, + available at preinit time); mk4vfs::zstreamed stays default-0 on 8.6. +- VERIFIED: `suite.tcl build -steps kithead` builds, attaches and smokes - + kithead_smoke asserts `info library` == `/lib/tcl8.6` (in-EXE, while + sitting BESIDE a real out/lib tree), init.tcl visible inside, static vfs + via the in-kit index, static Mk4tcl, platform from the in-kit tm tree, and + auto_load through the in-kit library. Transcript: "kithead_smoke OK: 8.6.18 + library-in-exe vfs:1.4.2 Mk4tcl:2.4.9.8 platform:1.1.1". + +Increment 4 landed 2026-08-02: the END-TO-END DEMONSTRATION - acceptance's +demonstration clause met, all steps in a hermetic environment (TCL* env vars +unset, PATH cut to C:\Windows\System32;C:\Windows) in a bare scratch dir with +no Tcl anywhere near it: + +- BARE-PATH BOOT: a copy of tclsh8.6.18-mk4-r1.exe boots and reports + `info library` = `/lib/tcl8.6` ("bareboot OK: library resolved + inside the executable, no Tcl on disk here"). +- SELF-MOUNT RIDER EXERCISED AS SPECIFIED: the RUNNING head (its own vfs + mounted at its exe path) ran bin/sdx.kit and wrapped a SEPARATE COPY of + itself: `head.exe sdx.kit wrap demo -runtime head-copy.exe` - the shadowed + self was the wrapping shell, the plain-file copy was the -runtime read. +- THE STARPACK RUNS ITS PAYLOAD: demo.exe (head 4,123,434 bytes + app vfs = + 4,128,032) boots on the same bare path, starkit::startup resolves topdir to + the exe, main.tcl runs from inside, reads data/payload.txt from inside, and + loads demopkg 1.0 - a package existing ONLY inside the kit. +- DLL-FROM-CONTAINER MEASURED (not inherited from references): the suite's + thread2.8.13 package dir (thread2813t.dll) wrapped into the vfs; + `package require Thread` inside the starpack loads the dll through + mk4vfs's temp-copy mechanism - "Thread 2.8.13 dll loaded FROM the vfs + (thread id tid...)". + +All Progress remaining-work items are resolved by the Assessment, Decision +and Follow-on sections below; acceptance verified in full 2026-08-02. + +## Assessment (acceptance record, 2026-08-02) + +Each candidate against the four kit expectations (make.tcl 'kit' wrap-path +compatibility or documented replacement; boot-time vfs mount without an +installed Tcl; loading binary dlls from or via the container; buildable from +source under the zig-only policy): + +- mk4 (metakit/mk4vfs tclkit lineage) - ALL FOUR MEASURED on suite-built + bytes (Progress increments 1-4): sdx wrapping runs under the suite shells + and under the head itself, so make.tcl's existing sdx-driving 'kit' path is + compatible as-is (the resolution-ladder rung is follow-on item 1); the + kithead boots from its own container on a bare path; thread2813t.dll loads + from inside a wrapped kit; every byte in the chain is suite-built or + fork-built under the pinned zig (core/shells G-099, tclvfs G-099, Tk G-100, + Mk4tcl + engine from the metakit_punk fork pin 6a92b479 per G-153, the head + from build_kithead86). The only non-suite tool is bin/sdx.kit itself - + 2011-era, tracked, provenance captured in patches/, replaceable per option + (b) in the sdx section if ever needed. +- cookfs - assessed, NOT PURSUED: buildable in principle (C extension) but + every expectation starts from zero (no punkshell boot machinery, no wrap + tooling, no reference runtime in the repo), the tcl-sfe lineage carries + none of punkshell's provenance record, and it offers no capability mk4 + lacks for 8.6. Existing cookfs branches in the boot/make surfaces stay + untouched (the G-131 exotics posture). +- zipvfs-over-tclvfs - assessed, NOT PURSUED as the 8.6 container: solving + its bootstrap chicken-and-egg (vfs::zip from inside the archive) requires + exactly the static-vfs + boot-script machinery mk4 needed anyway, PLUS a + zip read path available at preinit time; mk4 got that machinery first and + the zip READ half outside the boot is already covered (G-124 punk::zip, + G-126 accelerator). Nothing blocks a future zip arm; it was not the one to + demonstrate. +- zipfs backport (androwish, surveyed in its own section above) - the SECOND + ARM, not a rejected candidate: additive core-patch shape, C not C++, and + the G-131 pairing argument stands. Its open items are unchanged from the + 2026-07-27 survey (8.6.18 applicability, zig buildability, self-mount hook + location) and move to G-131-era work. + +## Decision (2026-08-02) + +mk4 is the 8.6 kit container and the default for punk86-class kits. +Rationale: it is the one candidate demonstrated end-to-end on suite-built +bytes; it is carried by punkshell's own fork-as-upstream metakit repo (G-153, +pin 6a92b479) rather than a third-party binary lineage; it is continuous with +the kit ecosystem punkshell already ships (the prebuilt punk86 runtimes ARE +mk4 tclkits, bin/sdx.kit and make.tcl's 'kit' path already speak it); the +format is interchange-verified dual-arch (G-153) serving the G-130 ix86 lane; +and post-hoc stamping support is already measured move-safe (G-128 note +above). Per the DOWNSTREAM/G-131 note, this decision names the DEFAULT +without closing the container question: the zipfs backport remains the +recorded second arm so a future both-capability runtime can make the +container a bake-time choice. The one-byte writer-arch varint difference +(offset 169, G-153 interchange) is a recorded physical-encoding observation +with no read-back consequence. + +## Follow-on to punk86-kit parity (acceptance record) + +1. make.tcl sdx resolution ladder (:5048-5097): add the suite-shell rung + (tclsh86t or the head + installed batteries) ahead of the tclkit last + resort, ending the third-party-tclkit dependency for 'kit' bakes; retire + or repair the stale bin/sdx1.bat wrapper while there. +2. Family variants of the head (G-103 shapes): -punk/-bi battery payloads + (thread/tclvfs/Tk/tcllib in the runtime vfs), naming per the recorded + trial convention; the container tag drops from artifact names once + container trials end. +3. punk86-kit parity bake: 'kit'-type mapvfs entries wrapping punk .vfs + payloads with the suite head through make.tcl (G-121 bakelist surface); + verify the G-125 gate's vfs_boot_library_report location list against the + tclkit shape at first bake. +4. win32-ix86 head: G-105-style cross-target parameterization of suite_tcl86 + (or the G-130 lane's own suite) - the mk4 pieces are dual-arch-proven. +5. Publication: runtime-tier records (G-123 schema v2) when a head ships to + punkbin; punkres icon/version stamping at the established seam (G-057 / + G-137) - metakit payloads measured move-safe for post-hoc stamping. +6. G-131 dual-container runtime: the zipfs-backport arm's open items plus + boot-side container detection; this goal's kithead.c/boot.tcl are the mk4 + half of that detection, and the Tcl 9 story is preinit-shaped per the + Metakit-under-Tcl-9 section (mk4tcl 9-port costed there; tclInit override + dies in 9.1). +7. sdx option (b) if ever warranted: a suite-owned kit writer (setupvfs.tcl + is the reference; the wrap surface needed is small) - the recorded + reduction-not-refresh posture applies to any sdx rebuild. ## Reference material (TEMP_REFERENCE, machine-local) diff --git a/src/buildsuites/suite_tcl86/build86.zig b/src/buildsuites/suite_tcl86/build86.zig index 85e93573..d57df0d2 100644 --- a/src/buildsuites/suite_tcl86/build86.zig +++ b/src/buildsuites/suite_tcl86/build86.zig @@ -1092,6 +1092,177 @@ pub fn build(b: *std.Build) !void { run.step.dependOn(install_libraries); step.dependOn(&run.step); } + + // ================================================================ + //mk4 kit-head (G-101 demonstration arm): a SECOND static shell that + //boots from its own attached Metakit container - engine + Mk4tcl/ + //mk4too + the tclvfs C core compiled in, tclAppInit overlaid with the + //build_kithead86/kithead.c hooks, boot.tcl riding IN the attached vfs + //(tclkit convention; the setupvfs role is played by + //tools/mk4kit_attach.tcl under the installed dynamic shell). + //Artifact name per the recorded trial convention (G-103 family name + + //container tag riding the modifier chain while container trials run): + //bin/tclsh-mk4-r1.exe. The raw un-attached head stays a + //cache artifact - only the attached runtime installs. Opt-in step + //('kithead'), not part of the default build. + const kithead_raw = b.addExecutable(.{ + .name = "kithead86raw", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + kithead_raw.stack_size = 2300000; //makefile.vc CONEXECMD parity (as tclsh86ts) + kithead_raw.subsystem = .Console; + core_include(kithead_raw.root_module, b, tcluuid_include_dir); + for (core_sources.items) |src| { + const flags = if (std.mem.eql(u8, std.fs.path.stem(src), "tclMain")) mainw_static_flags.items else pkg_static_flags.items; + kithead_raw.root_module.addCSourceFile(.{ .file = b.path(src), .flags = flags }); + } + kithead_raw.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/generic/tclMain.c"), + .flags = pkg_static_flags.items, + }); + kithead_raw.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/win/tclWinReg.c"), + .flags = stubext_static_flags.items, + }); + kithead_raw.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/win/tclWinDde.c"), + .flags = stubext_static_flags.items, + }); + //tclAppInit overlay - two injections around the stock file: the + //kithead pre-hook (static registrations + TclSetPreInitScript) BEFORE + //Tcl_Init, the post-hook (attached-main.tcl startup pivot) before the + //rcFileName epilogue. Anchors are exact stock-8.6.18 lines; a missed + //anchor leaves the file unchanged and the kithead smoke below fails + //on the missing ::tcl::kitpath - that smoke is the overlay's guard. + const kithead_pre_anchor = " if ((Tcl_Init)(interp) == TCL_ERROR) {"; + const kithead_pre_inject = + \\ /* mk4 kit-head boot hooks (punkshell G-101; build_kithead86/kithead.c) */ + \\ { + \\ extern int TclKitHead_PreTclInit(Tcl_Interp *); + \\ if (TclKitHead_PreTclInit(interp) != TCL_OK) { + \\ return TCL_ERROR; + \\ } + \\ } + \\ + ; + const kithead_post_anchor = " (Tcl_ObjSetVar2)(interp, Tcl_NewStringObj(\"tcl_rcFileName\", -1), NULL,"; + const kithead_post_inject = + \\ /* mk4 kit-head startup pivot (punkshell G-101) */ + \\ { + \\ extern int TclKitHead_PostTclInit(Tcl_Interp *); + \\ TclKitHead_PostTclInit(interp); + \\ } + \\ + ; + var kithead_appinit_src = common.readSourceFile(b, tcl_source_folder ++ "/win/tclAppInit.c"); + kithead_appinit_src = common.replaceAll(b, kithead_appinit_src, kithead_pre_anchor, kithead_pre_inject ++ kithead_pre_anchor); + kithead_appinit_src = common.replaceAll(b, kithead_appinit_src, kithead_post_anchor, kithead_post_inject ++ kithead_post_anchor); + const wf_kithead_appinit = b.addWriteFiles(); + const kithead_appinit_overlay = wf_kithead_appinit.add("tclAppInit.c", kithead_appinit_src); + kithead_raw.root_module.addCSourceFile(.{ + .file = kithead_appinit_overlay, + .flags = appinit_static_flags.items, + }); + kithead_raw.root_module.addCSourceFile(.{ + .file = b.path("build_kithead86/kithead.c"), + .flags = appinit_static_flags.items, + }); + inline for (.{ "tclStubLib", "tclTomMathStubLib", "tclOOStubLib" }) |nm| { + kithead_raw.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/generic/" ++ nm ++ ".c"), + .flags = stub_flags.items, + }); + } + //metakit engine + binding, compiled in - the same source lists the + //build_mk4tcl86 dll module carries (pub there so the two stay in + //step); the binding uses the static flag pair the G-153 experiment + //proved (USE_TCL_STUBS + STATIC_BUILD) + kithead_raw.root_module.addIncludePath(b.path("../metakit/src/include")); + kithead_raw.root_module.addIncludePath(b.path("../metakit/src/core")); + inline for (mk4tcl86.core_sources) |nm| { + kithead_raw.root_module.addCSourceFile(.{ + .file = b.path("../metakit/src/core/" ++ nm), + .flags = &mk4tcl86.engine_flags, + }); + } + inline for (.{ "mk4tcl.cpp", "mk4too.cpp" }) |nm| { + kithead_raw.root_module.addCSourceFile(.{ + .file = b.path("../metakit/src/tcl/" ++ nm), + .flags = &mk4tcl86.binding_static_flags, + }); + } + //tclvfs C core, static: build_tclvfs86's define set with STATIC_BUILD + //in place of BUILD_vfs, same include-order shim (that module's + //__stat64 note) + const kithead_vfsinfo = tclvfs86.versionInfo(b, "../tclvfs"); + const wf_kithead_vfs = b.addWriteFiles(); + const kithead_vfs_shim = wf_kithead_vfs.add("vfs_statorder_shim.c", + \\/* generated include-order shim - see build_tclvfs86.zig */ + \\#include + \\#include "vfs.c" + \\ + ); + kithead_raw.root_module.addCSourceFile(.{ + .file = kithead_vfs_shim, + .flags = &.{ + "-DPACKAGE_NAME=\"vfs\"", + "-DPACKAGE_TARNAME=\"vfs\"", + b.fmt("-DPACKAGE_VERSION=\"{s}\"", .{kithead_vfsinfo.dotversion}), + b.fmt("-DPACKAGE_STRING=\"vfs {s}\"", .{kithead_vfsinfo.dotversion}), + "-DPACKAGE_BUGREPORT=\"\"", + "-DPACKAGE_URL=\"\"", + "-DHAVE_SYS_STAT_H=1", + "-DTCL_CFG_OPTIMIZED=1", + "-DUSE_TCL_STUBS=1", + "-DSTATIC_BUILD", + }, + }); + kithead_raw.root_module.addIncludePath(b.path("../tclvfs")); + kithead_raw.root_module.addIncludePath(b.path("../tclvfs/generic")); + kithead_raw.root_module.linkLibrary(zlib_lib_compile); + win_syslibs(kithead_raw.root_module); + kithead_raw.root_module.link_libc = true; + kithead_raw.root_module.link_libcpp = true; + kithead_raw.root_module.addWin32ResourceFile(.{ + .file = tclshrc_overlay, + .include_paths = &.{ + b.path(tcl_source_folder ++ "/generic"), + b.path(tcl_source_folder ++ "/win"), + }, + }); + + //attach the runtime vfs (boot.tcl + lib/tcl8.6 + lib/tcl8 + tclvfs + //scripts w/ static-adjusted pkgIndex) -> the installed head artifact + const kithead_name = b.fmt("tclsh{s}-mk4-r1.exe", .{tcl_h_patchlevel}); + const kithead_out = common.replaceAll(b, b.pathJoin(&.{ bindir, kithead_name }), "\\", "/"); + const kithead_attach = b.addSystemCommand(&.{installed_dyn}); + common.scrubTclEnv(kithead_attach); + kithead_attach.has_side_effects = true; + kithead_attach.setName("mk4kit attach"); + kithead_attach.addFileArg(b.path("tools/mk4kit_attach.tcl")); + kithead_attach.addArg("-exe"); + kithead_attach.addArtifactArg(kithead_raw); + kithead_attach.addArgs(&.{ "-prefix", prefix, "-out", kithead_out }); + kithead_attach.addArg("-boot"); + kithead_attach.addFileArg(b.path("build_kithead86/boot.tcl")); + kithead_attach.step.dependOn(b.getInstallStep()); + kithead_attach.step.dependOn(install_libraries); + + //the attached head proves in-exe library resolution + package loads + //(runs beside a REAL ../lib/tcl8.6 - the assertions are all in-exe) + const kithead_smoke = b.addSystemCommand(&.{kithead_out}); + common.scrubTclEnv(kithead_smoke); + kithead_smoke.has_side_effects = true; + kithead_smoke.setName("kithead smoke"); + kithead_smoke.addFileArg(b.path("tools/kithead_smoke.tcl")); + kithead_smoke.step.dependOn(&kithead_attach.step); + + const kithead_step = b.step("kithead", "G-101: build the mk4 kit-head, attach its runtime vfs (boot.tcl + lib trees) and smoke the self-mounted boot"); + kithead_step.dependOn(&kithead_smoke.step); } } @@ -1168,6 +1339,7 @@ fn bootstrapMode(b: *std.Build) !void { "build_tclvfs86", "build_tk86", "build_mk4tcl86", + "build_kithead86", "critcl_zig.config", "expected_test_failures.txt", "expected_test_failures_thread.txt", diff --git a/src/buildsuites/suite_tcl86/build_kithead86/boot.tcl b/src/buildsuites/suite_tcl86/build_kithead86/boot.tcl new file mode 100644 index 00000000..63855542 --- /dev/null +++ b/src/buildsuites/suite_tcl86/build_kithead86/boot.tcl @@ -0,0 +1,87 @@ +# boot.tcl - punkshell suite_tcl86 mk4 kit-head boot script (G-101). +# +# Lives at the ROOT of the kit-head's attached Metakit vfs (written there by +# tools/mk4kit_attach.tcl); the pre-init script compiled into the head +# (build_kithead86/kithead.c) extracts and evaluates it before init.tcl, for +# every interp. +# +# Derived from the Tclkit lineage's boot.tcl as carried by kbskit 0.4 +# (Tcl-style license.terms), reduced to the 8.6 mk4 case: no vlerq/mkcl +# branch, no 8.4 pwb/librarypath branch. The proc tclInit override below is +# the 8.6 boot hook; Tcl 9 removes the tclInit override entirely +# (preinit-only) - the cross-generation generalisation is goals G-131 +# territory, recorded in goals/G-101-tcl86-kit-container-strategy.md. + +proc tclInit {} { + rename tclInit {} + + global auto_path tcl_library tcl_libPath tcl_version tclkit_system_encoding + + set noe $::tcl::kitpath + # resolve symlinks + set noe [file dirname [file normalize [file join $noe __dummy__]]] + set tcl_library [file join $noe lib tcl$tcl_version] + set tcl_libPath [list $tcl_library [file join $noe lib]] + + # get rid of a build residue + unset -nocomplain ::tclDefaultLibrary + + # Mount only when this interp does not already see the exe as a + # directory - happens once per thread; child interps see the mount. + if {![file isdirectory $noe]} { + load {} vfs + + # source lib/vfs1*/{vfsUtils,vfslib,mk4vfs}.tcl out of the still + # unmounted container with raw Metakit calls (VFS not in place yet) + set d [mk::select exe.dirs parent 0 name lib] + set d [mk::select exe.dirs parent $d -glob name vfs1*] + + foreach x {vfsUtils vfslib mk4vfs} { + set n [mk::select exe.dirs!$d.files name $x.tcl] + if {[llength $n] != 1} { error "$x: cannot find startup script" } + set s [mk::get exe.dirs!$d.files!$n contents] + catch {set s [zlib decompress $s]} + uplevel #0 $s + } + # 8.6 keeps mk4vfs::zstreamed at its default 0: core zlib does not + # provide the streamed-channel shape mk4vfs would need (kbskit + # boot.tcl note); compressed reads go whole-blob through + # vfs::memchan, which vfslib implements over 8.6 reflected channels. + + # mount the executable - all runtime files become visible + vfs::filesystem mount $noe [list ::vfs::mk4::handler exe] + + # encodings live inside the freshly mounted library + encoding dirs [list [file join [info library] encoding]] ;# TIP 258 + # apply the environment encoding recorded by the C side, now that + # the encoding files are reachable + if {[info exists tclkit_system_encoding]} { + catch {encoding system $tclkit_system_encoding} + unset tclkit_system_encoding + } + # fix system encoding if it never got set up (identity residue) + if {[encoding system] eq "identity"} { + switch $::tcl_platform(platform) { + windows { encoding system cp1252 } + default { encoding system iso8859-1 } + } + } + + # remount so path handling reflects the corrected system encoding + vfs::filesystem unmount $noe + set noe $::tcl::kitpath + set noe [file dirname [file normalize [file join $noe __dummy__]]] + set tcl_library [file join $noe lib tcl$tcl_version] + set tcl_libPath [list $tcl_library [file join $noe lib]] + vfs::filesystem mount $noe [list ::vfs::mk4::handler exe] + } + + # optional per-kit config hook (tclkit convention) + namespace eval ::vfs { variable tclkit_version 1 } + catch { uplevel #0 [list source [file join $noe config.tcl]] } + + uplevel #0 [list source [file join $tcl_library init.tcl]] + + # reset auto_path so init.tcl's search outside the kit is cancelled + set auto_path $tcl_libPath +} diff --git a/src/buildsuites/suite_tcl86/build_kithead86/kithead.c b/src/buildsuites/suite_tcl86/build_kithead86/kithead.c new file mode 100644 index 00000000..f0256e30 --- /dev/null +++ b/src/buildsuites/suite_tcl86/build_kithead86/kithead.c @@ -0,0 +1,123 @@ +/* kithead.c - punkshell suite_tcl86 mk4 kit-head boot support (G-101). + * + * Static-package registration plus the pre-init script that reads boot.tcl + * out of the executable's own attached Metakit file, and the post-init + * startup pivot to an attached main.tcl (starpack case). Compiled ONLY into + * the kit-head executable (see build86.zig kit-head section); the plain + * shells stay stock. The tclAppInit overlay calls the two entry points + * around Tcl_Init. + * + * Derived from the Tclkit lineage's kitInit.c as carried by kbskit 0.4 + * (Jean-Claude Wippler, ActiveState, Pat Thoyts, Rene Zaumseil; Tcl-style + * license.terms), reduced to the 8.6 mk4 case: + * - no vlerq/KIT_LITE branch, no 8.4/8.5 compat, no Tk console hooks + * - no zlib static package (8.6 core carries the zlib command) + * - no rechan (tclvfs 1.4.2 vfslib rides 8.6 reflected channels) + * - ::tcl::kitpath is set by the pre-init script from + * [info nameofexecutable] rather than via the tclkitpath C package + * (no stardll ambitions; revisit if a dll-shaped head ever appears) + * - std-channel repair dropped: the console shells this head derives from + * always have std channels; a windowed variant would need it back + */ + +#include +#include + +/* 8.6 spells it Tcl_StaticPackage; the alias below is stock tclAppInit.c's + * own forward-compat shim (win/tclAppInit.c:26-29 in the 8.6.18 tree). */ +#if TCL_MAJOR_VERSION < 9 +# define Tcl_StaticLibrary Tcl_StaticPackage +#endif + +/* Internal routine, no stub entry: resolves at link time inside the static + * shell (the same hand-declaration kitInit.c:50 uses on 8.6-era cores). */ +extern char *TclSetPreInitScript(char *); + +extern int Mk4tcl_Init(Tcl_Interp *interp); +extern int Vfs_Init(Tcl_Interp *interp); + +/* Runs for EVERY new interp (child and thread interps included) in place of + * the stock init-library search; boot.tcl's tclInit override guards + * re-mounting (kitInit.c's re-entrancy comment). A failure surfaces on + * stderr through Tcl_Main's init-failure report rather than silently. */ +static char preInitCmd[] = +"proc tclKitPreInit {} {\n" + "rename tclKitPreInit {}\n" + "set ::tcl::kitpath [info nameofexecutable]\n" + "load {} Mk4tcl\n" + "mk::file open exe $::tcl::kitpath -readonly\n" + "set n [mk::select exe.dirs!0.files name boot.tcl]\n" + "if {[llength $n] == 1} {\n" + "array set a [mk::get exe.dirs!0.files!$n]\n" + "if {![info exists a(contents)]} { error {no boot.tcl file} }\n" + "if {$a(size) != [string length $a(contents)]} {\n" + "set a(contents) [zlib decompress $a(contents)]\n" + "}\n" + "if {$a(contents) eq \"\"} { error {empty boot.tcl} }\n" + "uplevel #0 $a(contents)\n" + "} else {\n" + "error \"\n $::tcl::kitpath has no VFS data to start up\"\n" + "}\n" +"}\n" +"tclKitPreInit" +; + +/* kitInit.c's initScript: pivot the startup script to an attached main.tcl + * when one exists (starpack). A bare runtime carries no main.tcl and the + * head behaves as a plain tclsh; the trailing bare 'continue' errors the + * eval on purpose so no pivot happens. */ +static const char initScript[] = +"if {[file isfile [file join $::tcl::kitpath main.tcl]]} {\n" + "set tcl_interactive 0\n" + "incr argc\n" + "set argv [linsert $argv 0 $argv0]\n" + "set argv0 [file join $::tcl::kitpath main.tcl]\n" +"} else continue\n" +; + +int +TclKitHead_PreTclInit( + Tcl_Interp *interp) +{ + Tcl_DString encodingName; + + Tcl_StaticLibrary(NULL, "Mk4tcl", Mk4tcl_Init, NULL); + Tcl_StaticLibrary(NULL, "vfs", Vfs_Init, NULL); + + /* kitInit.c encoding dance: align the system encoding with the + * environment before any vfs path is decoded; boot.tcl re-applies the + * recorded name once the mounted library's encoding dir is available + * and remounts (the name may be unavailable until then). */ + Tcl_GetEncodingNameFromEnvironment(&encodingName); + if (strcmp(Tcl_DStringValue(&encodingName), Tcl_GetEncodingName(NULL))) { + Tcl_SetSystemEncoding(NULL, Tcl_DStringValue(&encodingName)); + } + Tcl_SetVar2(interp, "tclkit_system_encoding", NULL, + Tcl_DStringValue(&encodingName), 0); + Tcl_DStringFree(&encodingName); + + TclSetPreInitScript(preInitCmd); + return TCL_OK; +} + +int +TclKitHead_PostTclInit( + Tcl_Interp *interp) +{ + /* kitInit.c's "messy" pivot: Tcl_Main chose the startup script before + * Tcl_AppInit ran, so adopt initScript's result as the startup script + * here; when no script argument was given, drop the argv slot the + * pivot inserted. */ + if (Tcl_EvalEx(interp, initScript, -1, TCL_EVAL_GLOBAL) == TCL_OK) { + const char *encoding = NULL; + Tcl_Obj *path = Tcl_GetStartupScript(&encoding); + + Tcl_SetStartupScript(Tcl_GetObjResult(interp), encoding); + if (path == NULL) { + Tcl_Eval(interp, "incr argc -1; set argv [lrange $argv 1 end]"); + } + } + Tcl_SetVar2(interp, "errorInfo", NULL, "", TCL_GLOBAL_ONLY); + Tcl_ResetResult(interp); + return TCL_OK; +} diff --git a/src/buildsuites/suite_tcl86/build_mk4tcl86/build_mk4tcl86.zig b/src/buildsuites/suite_tcl86/build_mk4tcl86/build_mk4tcl86.zig index 9ce67ca8..25553657 100644 --- a/src/buildsuites/suite_tcl86/build_mk4tcl86/build_mk4tcl86.zig +++ b/src/buildsuites/suite_tcl86/build_mk4tcl86/build_mk4tcl86.zig @@ -31,14 +31,14 @@ const common = @import("../build_common.zig"); // digits, the historical Mk_Init quirk). //upstream src/CMakeLists.txt metakit_SOURCES (q4_UNIV default set) - same list -//as the fork build.zig core_sources -const core_sources = [_][]const u8{ +//as the fork build.zig core_sources (pub: the kit-head embeds the same set) +pub const core_sources = [_][]const u8{ "column.cpp", "custom.cpp", "derived.cpp", "field.cpp", "fileio.cpp", "format.cpp", "handler.cpp", "persist.cpp", "remap.cpp", "store.cpp", "string.cpp", "table.cpp", "univ.cpp", "view.cpp", "viewx.cpp", }; -const engine_flags = [_][]const u8{ +pub const engine_flags = [_][]const u8{ "-fno-exceptions", "-fno-rtti", "-fno-sanitize=undefined", @@ -52,6 +52,16 @@ const binding_flags = [_][]const u8{ "-fno-sanitize-trap=undefined", }; +//binding compiled INTO a static shell (the kit-head): STATIC_BUILD instead of +//BUILD_Mk4tcl (no dllexport decoration; the G-153 static-embed experiment's +//proven flag pair) +pub const binding_static_flags = [_][]const u8{ + "-DUSE_TCL_STUBS=1", + "-DSTATIC_BUILD", + "-fno-sanitize=undefined", + "-fno-sanitize-trap=undefined", +}; + //package version fact from the staged fork tree (never hardcoded): the //Tcl_PkgProvide call in mk4tcl.cpp is the authority for what the built dll //actually provides ('package provide stays 2.4.9.8' is the fork's recorded diff --git a/src/buildsuites/suite_tcl86/suite.tcl b/src/buildsuites/suite_tcl86/suite.tcl index c53ad86d..8be183c0 100644 --- a/src/buildsuites/suite_tcl86/suite.tcl +++ b/src/buildsuites/suite_tcl86/suite.tcl @@ -310,7 +310,7 @@ proc git_source {name url ref dir} { #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 build_tk86 build_mk4tcl86 critcl_zig.config 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 build_mk4tcl86 build_kithead86 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 } diff --git a/src/buildsuites/suite_tcl86/tools/kithead_smoke.tcl b/src/buildsuites/suite_tcl86/tools/kithead_smoke.tcl new file mode 100644 index 00000000..6fb4a58d --- /dev/null +++ b/src/buildsuites/suite_tcl86/tools/kithead_smoke.tcl @@ -0,0 +1,24 @@ +#kithead_smoke.tcl (G-101): asserts the mk4 kit-head resolved its OWN attached +#vfs rather than any on-disk tree - run AS the head's script argument. The +#suite invokes it with a scrubbed environment; the head sits in out/bin BESIDE +#a real ../lib/tcl8.6, so the in-exe assertions below are what prove the boot +#read the container (boot.tcl overrides tclInit entirely - disk never wins). + +proc fail {msg} {puts stderr "kithead_smoke FAIL: $msg"; flush stderr; exit 1} + +if {![info exists ::tcl::kitpath]} {fail "no ::tcl::kitpath - preinit did not run"} +set exe [file normalize [info nameofexecutable]] +set lib [file normalize [info library]] +if {$lib ne [file join $exe lib tcl$::tcl_version]} { + fail "info library is $lib - not inside the executable ($exe)" +} +if {![file isfile [file join $lib init.tcl]]} {fail "init.tcl not visible inside the mounted library"} +if {![file isdirectory $exe]} {fail "executable not self-mounted as a directory"} +if {[catch {package require vfs} v]} {fail "package require vfs (static, via in-kit index): $v"} +if {[catch {package require Mk4tcl} mv]} {fail "package require Mk4tcl (static): $mv"} +#a package resolved from the in-kit tm tree +if {[catch {package require platform} pv]} {fail "package require platform (in-kit tm tree): $pv"} +#autoloading through the in-kit library (init.tcl auto_load machinery) +if {[catch {parray tcl_platform} perr]} {fail "auto_load parray via in-kit library: $perr"} +puts "kithead_smoke OK: [info patchlevel] library-in-exe vfs:$v Mk4tcl:$mv platform:$pv auto_path:[join $auto_path { }]" +exit 0 diff --git a/src/buildsuites/suite_tcl86/tools/mk4kit_attach.tcl b/src/buildsuites/suite_tcl86/tools/mk4kit_attach.tcl new file mode 100644 index 00000000..8774a7e0 --- /dev/null +++ b/src/buildsuites/suite_tcl86/tools/mk4kit_attach.tcl @@ -0,0 +1,116 @@ +#mk4kit_attach.tcl (G-101): attach the kit-head runtime vfs to a raw head exe. +#Runs under the suite-built tclsh86t against the installed prefix (vfs::mk4 and +#Mk4tcl resolve from /lib) - the setupvfs.tcl role from the tclkit +#lineage reshaped: no bootstrapping kit shell is needed because the suite shell +#plus its installed batteries already carry the whole mk4 write stack. +# +#args: -exe -prefix -boot -out +# +#Runtime vfs written into the copied exe: +# boot.tcl the tracked boot script (lf-normalized) +# lib/tcl8.6/... the installed script library tree (encoding/ included; +# the dde/reg package dirs ride along - their dll loads +# go through mk4vfs's temp-copy mechanism) +# lib/tcl8/... the tm module tree +# lib/vfs1.4.2/... tclvfs SCRIPTS only (dll excluded - vfs is STATIC in +# the head; the pkgIndex 'vfs' entry is rewritten to +# 'load {} vfs' per the setupvfs .static convention) +# +#Text files (.tcl/.txt/.msg/.test) are rewritten with lf endings - crucial for +#boot-time reads before the vfs is in place (setupvfs.tcl's recorded rule); +#everything else is byte-copied. mtimes are preserved. + +proc fail {msg} {puts stderr "mk4kit_attach FAIL: $msg"; flush stderr; exit 1} + +set opts [dict create] +foreach {k v} $argv { dict set opts $k $v } +foreach k {-exe -prefix -boot -out} { + if {![dict exists $opts $k]} {fail "missing required argument $k"} +} +set exe [file normalize [dict get $opts -exe]] +set prefix [file normalize [dict get $opts -prefix]] +set boot [file normalize [dict get $opts -boot]] +set out [file normalize [dict get $opts -out]] +foreach {what path} [list "raw head exe" $exe "install prefix" $prefix "boot script" $boot] { + if {![file exists $path]} {fail "$what not found: $path"} +} + +if {[catch {package require vfs::mk4} err]} {fail "package require vfs::mk4 (prefix batteries): $err"} + +proc copyfile {src dest} { + file mkdir [file dirname $dest] + switch -- [file extension $src] { + .tcl - .txt - .msg - .test { + set fin [open $src r] + set fout [open $dest w] + fconfigure $fout -translation lf + fcopy $fin $fout + close $fin + close $fout + } + default { file copy $src $dest } + } + catch {file mtime $dest [file mtime $src]} +} + +#copy a tree; excludeglobs match against the path RELATIVE to srcdir +proc copytree {srcdir destdir {excludeglobs {}}} { + set srcdir [file normalize $srcdir] + set pending [list {}] + while {[llength $pending]} { + set rel [lindex $pending 0] + set pending [lrange $pending 1 end] + set here [expr {$rel eq "" ? $srcdir : [file join $srcdir $rel]}] + foreach f [glob -nocomplain -tails -dir $here -type f *] { + set frel [expr {$rel eq "" ? $f : [file join $rel $f]}] + set skip 0 + foreach g $excludeglobs { + if {[string match $g $frel]} {set skip 1; break} + } + if {$skip} continue + copyfile [file join $srcdir $frel] [file join $destdir $frel] + } + foreach d [glob -nocomplain -tails -dir $here -type d *] { + lappend pending [expr {$rel eq "" ? $d : [file join $rel $d]}] + } + } +} + +file delete -force $out +file copy $exe $out +::vfs::mk4::Mount $out $out + +copyfile $boot [file join $out boot.tcl] +copytree [file join $prefix lib tcl8.6] [file join $out lib tcl8.6] +copytree [file join $prefix lib tcl8] [file join $out lib tcl8] + +#tclvfs scripts: dll out (static in the head), pkgIndex rewritten below +set vfsdirs [glob -nocomplain -dir [file join $prefix lib] -type d vfs1*] +if {[llength $vfsdirs] != 1} {fail "expected exactly one /lib/vfs1* dir, got: $vfsdirs"} +set vfsdir [lindex $vfsdirs 0] +set vfstail [file tail $vfsdir] +copytree $vfsdir [file join $out lib $vfstail] {*.dll pkgIndex.tcl} + +#static-adjusted pkgIndex (setupvfs .static convention): the 'vfs' entry loads +#the static package instead of sourcing vfs.tcl (which would load the dll); +#every other entry is kept as installed +set f [open [file join $vfsdir pkgIndex.tcl] r] +set idx [read $f] +close $f +set rewritten [regsub {package ifneeded vfs ([0-9.]+) \[list source \[file join \$dir vfs\.tcl\]\]} $idx {package ifneeded vfs \1 {load {} vfs}} idx2] +if {!$rewritten} {fail "pkgIndex.tcl: the 'package ifneeded vfs ... vfs.tcl' entry was not found to rewrite"} +set f [open [file join $out lib $vfstail pkgIndex.tcl] w] +fconfigure $f -translation lf +puts -nonewline $f $idx2 +close $f + +vfs::unmount $out + +#verify: reopen raw and confirm boot.tcl at the root (the shape the head's +#pre-init script looks for) +mk::file open chk $out -readonly +set n [mk::select chk.dirs!0.files name boot.tcl] +mk::file close chk +if {[llength $n] != 1} {fail "attached image lacks a root boot.tcl"} +puts "mk4kit_attach OK: $out" +exit 0