Browse Source

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
master
Julian Noble 1 week ago
parent
commit
79b36ff121
  1. 152
      goals/G-101-tcl86-kit-container-strategy.md
  2. 172
      src/buildsuites/suite_tcl86/build86.zig
  3. 87
      src/buildsuites/suite_tcl86/build_kithead86/boot.tcl
  4. 123
      src/buildsuites/suite_tcl86/build_kithead86/kithead.c
  5. 16
      src/buildsuites/suite_tcl86/build_mk4tcl86/build_mk4tcl86.zig
  6. 2
      src/buildsuites/suite_tcl86/suite.tcl
  7. 24
      src/buildsuites/suite_tcl86/tools/kithead_smoke.tcl
  8. 116
      src/buildsuites/suite_tcl86/tools/mk4kit_attach.tcl

152
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<patchlevel>-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` == `<exe>/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` = `<that exe>/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)

172
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<patchlevel>-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 <sys/stat.h>
\\#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",

87
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
}

123
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 <tcl.h>
#include <string.h>
/* 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;
}

16
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

2
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
}

24
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

116
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 <prefix>/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 <raw head exe> -prefix <install prefix> -boot <boot.tcl> -out <exe>
#
#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 <prefix>/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
Loading…
Cancel
Save