diff --git a/.gitignore b/.gitignore index df3dfbdf..e217e799 100644 --- a/.gitignore +++ b/.gitignore @@ -81,7 +81,9 @@ _FOSSIL_ todo.txt zig-cache/ +.zig-cache/ zig-out/ +zig-pkg/ /release/ /debug/ /build/ diff --git a/src/buildsuites/suite_tcl90/README.md b/src/buildsuites/suite_tcl90/README.md index ff0d8a22..2cbbca92 100644 --- a/src/buildsuites/suite_tcl90/README.md +++ b/src/buildsuites/suite_tcl90/README.md @@ -1,24 +1,51 @@ -# suite_tcl90 - zig build of the Tcl 9.0 windows runtime (G-096) +# suite_tcl90 - zig build of the Tcl 9.0 windows runtime (G-096, G-102) The first real, tracked buildsuite: reproducibly builds `tclsh90s.exe` (static) and -`tclsh90szip.exe` (self-contained zipfs) plus the thread/vfs extension dlls from -a fresh Tcl core-9-0-branch checkout, using zig only (no MS toolchain, per the project +`tclsh90szip.exe` (self-contained zipfs) plus the TCLSH_PIPEREPL variants and the +thread/vfs/tk extension dlls, tklib/tcllib installs and critcl-built tcllibc, from +Tcl core-9-0-branch sources, using zig only (no MS toolchain, per the project toolchain policy). (vqtcl/vlerq was removed 2026-07-20 - an old-tclkit experiment; future tclkit investigation has TEMP_REFERENCE/metakit and TEMP_REFERENCE/KitCreator as reference material.) Everything transient lives under `src/buildsuites/_build/suite_tcl90/` (VCS-ignored via -the repo's existing `_build` globs): staged recipe, source checkouts, zig cache, and the -`out/` install prefix. The tracked content here is the recipe and its driver. +the repo's existing `_build` globs): staged recipe, source trees, zig cache, and the +`out/` install prefix. The tracked content here is the recipe and its drivers. -## Usage +## Two flows, one pipeline (G-102) + +The PIPELINE - configure-product generation (overlays, source trees never written), +compile, package installs, smokes, test gate - lives entirely in the zig recipe as +build steps. Two ways to arrange sources and drive it: + +**No-tclsh bootstrap flow** (pinned sources; nothing but the pinned zig required): + + zig build stage # fetch build.zig.zon-pinned sources + stage recipe -> ../_build/suite_tcl90 + zig build bootstrap # stage + run the staged pipeline end-to-end + +run from THIS directory with the pinned zig. Sources come from `build.zig.zon` - +content-hashed per-checkin tarball pins ('refresh' = bump a pin; `zig fetch +--save= ` edits the manifest). Steps for the nested staged build: +`-Dsteps=...` (repeat the flag), default install install-libraries make-zipfs smoke +tklib tcllib tcllibc. The test gate: `zig build bootstrap -Dsteps=test-gate` (or +invoke the staged build file directly, as suite.tcl does). zig extracts fetched +packages into `zig-pkg/` beside the manifest and `.zig-cache/` for the outer +invocation - both VCS-ignored. + +**Fossil dev flow** (live checkouts; a plain tclsh drives fetch+staging): tclsh suite.tcl build ?options? + tclsh suite.tcl test ?-testargs {...}? tclsh suite.tcl clean +`suite.tcl` arranges LIVE fossil/git checkouts per `sources.config` (branch work, +`-refresh`, `-tclbranch` overrides) into the same stage layout, then delegates to +the same staged zig steps. An existing fossil/git checkout in the stage is never +touched by the bootstrap staging (guarded twice), so the flows can be mixed. + See `suite.tcl`'s header for options (`-zig`, `-optimize`, `-tclbranch`, `-refresh`, `-steps`). Zig resolution: `-zig` > `$env(PUNK_ZIG)` > the pinned -toolchain at `bin/tools/zig-x86_64-windows-0.14.1/zig.exe` > `zig` on PATH. +toolchain at `bin/tools/zig-x86_64-windows-0.16.0/zig.exe` > `zig` on PATH. ## Pinned zig @@ -51,12 +78,17 @@ producing stale/duplicate artifacts at link time). cache-install artifact). - `src/main.zig` - stub root module that disables zig's own start symbols so the mingw CRT provides the exe entry. -- `tools/` - `wrapfiletofile.zig` (uuid-header generation used by prepare-source), - `zipfs_mkzip.tcl` / `zipfs_mkimg.tcl` (make-zipfs drivers). +- `tools/` - `wrapfiletofile.zig` (uuid-header overlay generation), + `stagetree.zig` (bootstrap-mode source/recipe materializer), + `zipfs_mkzip.tcl` / `zipfs_mkimg.tcl` (make-zipfs drivers), and the + suite-built-shell step scripts `test_gate.tcl` / `suite_smoke.tcl` / + `pkg_smoke.tcl` (G-102). - `patches/` - recovered 2024 experiment patches, including TCLSH_PIPEREPL - (see `patches/README.md`). NOT applied by the default build - stock sources. -- `suite.tcl` - the driver: stage recipe -> arrange sources -> generate the - configure-produced files a fresh checkout lacks -> build -> smoke-test. + (see `patches/README.md`) - compiled into the SEPARATE `tclsh90spr`/ + `tclsh90sprzip` products; the stock shells stay unpatched. +- `build.zig.zon` - pinned source manifest for the bootstrap flow. +- `suite.tcl` - the fossil dev driver: stage recipe -> arrange live checkouts -> + delegate to the staged recipe's steps. ## Sources @@ -94,16 +126,23 @@ changes additionally touch `build905.zig` and the driver's install steps (checkout dir names, version-derived tm/dll/manifest names) - see the depth-limit note in sources.config. -## Fresh-checkout preparations the driver performs - -Normally products of `configure`/`nmake` that a bare checkout lacks (G-076 findings): - -- `win/tclUuid.h` - generated from `manifest.uuid`. -- `win/tclsh.exe.manifest` - generated from its `.in` (`TCL_WIN_VERSION` per the tree's - own formula: `TCL_VERSION . releaselevel . patchlevel-stripped-of-'ab.'`). -- `thread/win/thread.rc` - gains `#include "../../tcl905/generic/tcl.h"` (the zig rc - compile does not receive the makefile's include paths; historically an uncommitted - tweak in the 2024 checkout, now a scripted idempotent edit of the staged copy). +## Fresh-checkout preparations (configure-products) + +Normally products of `configure`/`nmake` that a bare checkout lacks (G-076 findings). +Since G-102 these are generated INSIDE the zig recipe as build-cache OVERLAYS - +source trees are never written (overlay include dirs added first so stale tree +copies can never shadow; rc files that reference generated resources compile from +overlay copies where rc-file-relative lookup would otherwise win): + +- `tclUuid.h` / `tkUuid.h` / `threadUuid.h` - from each tree's `manifest.uuid` + (wrapfiletofile Run steps; consumed via overlay include dirs). +- `tclsh.exe.manifest` / `wish.exe.manifest` - from their `.in` templates + (`*_WIN_VERSION` per the trees' own formula: + `VERSION . releaselevel . patchlevel-stripped-of-'ab.'`). +- `thread.rc` - overlay copy gains `#include "tcl.h"` resolved via rc include + paths (the zig rc compile does not receive the makefile's include paths; + historically an uncommitted checkout tweak, later a scripted edit, now an + overlay - checkouts stay pristine). ## Lineage diff --git a/src/buildsuites/suite_tcl90/build905.zig b/src/buildsuites/suite_tcl90/build905.zig index 77e56481..1ed63792 100644 --- a/src/buildsuites/suite_tcl90/build905.zig +++ b/src/buildsuites/suite_tcl90/build905.zig @@ -255,6 +255,12 @@ pub fn build(b: *std.Build) !void { const wf_tclshrc = b.addWriteFiles(); _ = wf_tclshrc.add("tclsh.exe.manifest", tclsh_manifest_content); const tclshrc_overlay = wf_tclshrc.addCopyFile(b.path(tcl_source_folder ++ "/win/tclsh.rc"), "tclsh.rc"); + + //Runs of the built (non-zip) shell execute the CACHED artifact, which has no + //../lib/tcl9.0 beside it - point such runs at the source tree's own script + //library so init.tcl resolves (the zip shell's runs must NOT get this: its + //attached library is the thing under test). + const tcl_library_src = common.replaceAll(b, b.pathFromRoot(tcl_source_folder ++ "/library"), "\\", "/"); // ================================================ var ac_flags = std.array_list.Managed([]const u8).init(b.allocator); @@ -1759,6 +1765,11 @@ pub fn build(b: *std.Build) !void { const build_tclvfs_step = b.step("build-tclvfs", "build tclvfs shared library"); build_tclvfs_step.dependOn(&tclvfs_compile.step); tclsh_exe.step.dependOn(&tclvfs_compile.step); // review + if (target.result.os.tag == .windows) { + //G-102: vfs script package (lib/vfs1.4.2) installed by the recipe - the repl + //code interp wants vfs/vfs::zip (modpod mounting). Formerly suite.tcl post-build. + try tclvfs_shared.install_tclvfs_package("../tclvfs", b, tclvfs_compile); + } //////////////////// //vqtcl/vlerq removed from the suite (user 2026-07-20): it was an attempt at old @@ -2071,6 +2082,8 @@ pub fn build(b: *std.Build) !void { } else { mkzip_run = b.addSystemCommand(&.{"tclsh90"}); } + common.scrubTclEnv(mkzip_run); + mkzip_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); mkzip_run.addFileArg(b.path("tools/zipfs_mkzip.tcl")); mkzip_run.addArg("-outfile"); const out_mkzip = mkzip_run.addOutputFileArg("mkzipfs.zip"); @@ -2097,6 +2110,8 @@ pub fn build(b: *std.Build) !void { //require an existing tclsh90 - with zipfs support on the path for x-builds mkimg_run = b.addSystemCommand(&.{"tclsh90"}); } + common.scrubTclEnv(mkimg_run); + mkimg_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); mkimg_run.addFileArg(b.path("tools/zipfs_mkimg.tcl")); mkimg_run.addArg("-outfile"); var out_mkimg: std.Build.LazyPath = undefined; @@ -2136,6 +2151,8 @@ pub fn build(b: *std.Build) !void { //require an existing tclsh90 - with zipfs support on the path for x-builds mkimg_pr_run = b.addSystemCommand(&.{"tclsh90"}); } + common.scrubTclEnv(mkimg_pr_run); + mkimg_pr_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); mkimg_pr_run.addFileArg(b.path("tools/zipfs_mkimg.tcl")); mkimg_pr_run.addArg("-outfile"); var out_mkimg_pr: std.Build.LazyPath = undefined; @@ -2178,6 +2195,159 @@ pub fn build(b: *std.Build) !void { //install without zipfs //b.getInstallStep().dependOn(install_libraries); + // ================== G-102 post-tclsh phases ================== + //Tcl phases executed by the FRESHLY BUILT suite shell as build steps - Tcl stays + //the tool exactly where a tclsh is guaranteed to exist because the suite just + //built it. Formerly suite.tcl's post-build blocks; suite.tcl now drives these + //via step names. Native-host builds only (the runs execute the built artifacts). + if (target.result.os.tag == builtin.os.tag) { + //-- smoke: shells respond with the expected patchlevel; the zip shell proves + //the attached library (tzdata + autoload) + const smoke_step = b.step("smoke", "smoke-test the built shells (patchlevel; zip: attached tzdata/autoload)"); + const smoke_static = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(smoke_static); + smoke_static.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + smoke_static.has_side_effects = true; + smoke_static.addFileArg(b.path("tools/suite_smoke.tcl")); + smoke_static.addArgs(&.{ "-mode", "static", "-expect", tcl_h_patchlevel }); + smoke_static.step.dependOn(b.getInstallStep()); + smoke_step.dependOn(&smoke_static.step); + const zipexe_name = if (target.result.os.tag == .windows) tclsh_static ++ "zip.exe" else tclsh_static ++ "zip"; + const smoke_zip = b.addSystemCommand(&.{b.pathJoin(&.{ b.install_path, "bin", zipexe_name })}); + common.scrubTclEnv(smoke_zip); + smoke_zip.has_side_effects = true; + smoke_zip.addFileArg(b.path("tools/suite_smoke.tcl")); + smoke_zip.addArgs(&.{ "-mode", "zip", "-expect", tcl_h_patchlevel }); + smoke_zip.step.dependOn(&install_mkimg.step); + smoke_step.dependOn(&smoke_zip.step); + + //-- tklib: pure-tcl but its installer is NOT a straight copy (module + //exclusions + aggregating pkgIndex generation). Batch mode, packages only; + //the -pkg-path is a cached output dir installed into the prefix. + const tklib_ver = common.installerPackageVersion(b, "../tklib"); + const tklib_step = b.step("tklib", "install tklib via its own installer under the suite-built shell (+Tk smoke)"); + const tklib_run = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(tklib_run); + tklib_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + //cached (non-side-effect) Runs spawn with stdout IGNORED - on windows the + //child then has no stdout handle and tcl errors on any puts ('can not find + //channel named "stdout"', critcl's logging). Capturing gives it a real pipe. + _ = tklib_run.captureStdOut(.{}); + tklib_run.setCwd(b.path("../tklib")); + tklib_run.addFileArg(b.path("../tklib/installer.tcl")); + tklib_run.addArgs(&.{ "-no-gui", "-no-wait", "-no-apps", "-no-html", "-no-nroff", "-no-examples", "-pkg-path" }); + const tklib_out = tklib_run.addOutputDirectoryArg("tklib-pkg"); + const tklib_install = b.addInstallDirectory(.{ + .source_dir = tklib_out, + .install_dir = .prefix, + .install_subdir = b.fmt("lib/tklib{s}", .{tklib_ver}), + }); + const tklib_smoke = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(tklib_smoke); + tklib_smoke.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + tklib_smoke.has_side_effects = true; + tklib_smoke.addFileArg(b.path("tools/pkg_smoke.tcl")); + tklib_smoke.addArgs(&.{ "-gui", "1", "tooltip" }); + tklib_smoke.step.dependOn(&tklib_install.step); + tklib_smoke.step.dependOn(b.getInstallStep()); //Tk dll + pkgIndex + tcl library + tklib_step.dependOn(&tklib_smoke.step); + + //-- tcllib: same installer family (critcl DSL stubbed to no-ops - this is the + //PURE-TCL side; the critcl-built tcllibc accelerators are the next step) + const tcllib_ver = common.installerPackageVersion(b, "../tcllib"); + const tcllib_step = b.step("tcllib", "install tcllib (pure-tcl side) via its own installer under the suite-built shell (+smoke)"); + const tcllib_run = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(tcllib_run); + tcllib_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + //cached (non-side-effect) Runs spawn with stdout IGNORED - on windows the + //child then has no stdout handle and tcl errors on any puts ('can not find + //channel named "stdout"', critcl's logging). Capturing gives it a real pipe. + _ = tcllib_run.captureStdOut(.{}); + tcllib_run.setCwd(b.path("../tcllib")); + tcllib_run.addFileArg(b.path("../tcllib/installer.tcl")); + tcllib_run.addArgs(&.{ "-no-gui", "-no-wait", "-no-apps", "-no-html", "-no-nroff", "-no-examples", "-pkg-path" }); + const tcllib_out = tcllib_run.addOutputDirectoryArg("tcllib-pkg"); + const tcllib_install = b.addInstallDirectory(.{ + .source_dir = tcllib_out, + .install_dir = .prefix, + .install_subdir = b.fmt("lib/tcllib{s}", .{tcllib_ver}), + }); + const tcllib_smoke = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(tcllib_smoke); + tcllib_smoke.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + tcllib_smoke.has_side_effects = true; + tcllib_smoke.addFileArg(b.path("tools/pkg_smoke.tcl")); + tcllib_smoke.addArgs(&.{ "md5", "sha1", "cmdline" }); + tcllib_smoke.step.dependOn(&tcllib_install.step); + tcllib_smoke.step.dependOn(b.getInstallStep()); + tcllib_step.dependOn(&tcllib_smoke.step); + + //-- tcllibc: tcllib's critcl accelerators, critcl driving zig cc (the + //tracked critcl_zig.config; zig resolved from PATH with our own dir + //prepended). sak.tcl's critcl target expects a tclkitsh on windows - this + //replicates its underlying invocation directly, as suite.tcl did. + const tcllibc_step = b.step("tcllibc", "build+install tcllibc (critcl accelerators, zig cc) under the suite-built shell (+accel smoke)"); + const critcl_run = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(critcl_run); + critcl_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + //cached (non-side-effect) Runs spawn with stdout IGNORED - on windows the + //child then has no stdout handle and tcl errors on any puts ('can not find + //channel named "stdout"', critcl's logging). Capturing gives it a real pipe. + _ = critcl_run.captureStdOut(.{}); + critcl_run.setCwd(b.path("../tcllib")); + critcl_run.addFileArg(b.path("../critcl/main.tcl")); + critcl_run.addArg("-config"); + critcl_run.addFileArg(b.path("critcl_zig.config")); + critcl_run.addArg("-cache"); + _ = critcl_run.addOutputDirectoryArg("critcl-cache"); + critcl_run.addArg("-force"); + critcl_run.addArg("-libdir"); + const critcl_libout = critcl_run.addOutputDirectoryArg("critcl-lib"); + critcl_run.addArgs(&.{ "-pkg", "tcllibc" }); + for (common.tcllibCritclFiles(b, "../tcllib")) |cf| { + critcl_run.addFileArg(b.path(b.pathJoin(&.{ "../tcllib/modules", cf }))); + } + const zigdir = std.fs.path.dirname(b.graph.zig_exe) orelse "."; + const inherited_path = b.graph.environ_map.get("PATH") orelse ""; + critcl_run.setEnvironmentVariable("PATH", b.fmt("{s}{c}{s}", .{ zigdir, std.fs.path.delimiter, inherited_path })); + const tcllibc_install = b.addInstallDirectory(.{ + .source_dir = critcl_libout, + .install_dir = .prefix, + .install_subdir = "lib", + }); + const tcllibc_smoke = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(tcllibc_smoke); + tcllibc_smoke.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + tcllibc_smoke.has_side_effects = true; + tcllibc_smoke.addFileArg(b.path("tools/pkg_smoke.tcl")); + tcllibc_smoke.addArgs(&.{ "-accel", "md5", "tcllibc" }); + tcllibc_smoke.step.dependOn(&tcllibc_install.step); + tcllibc_smoke.step.dependOn(&tcllib_install.step); //accel host packages come from tcllib + tcllibc_smoke.step.dependOn(b.getInstallStep()); + tcllibc_step.dependOn(&tcllibc_smoke.step); + + //-- test-gate: the core testsuite under the built shell, gated on parsed + //totals vs the tracked dispositioned baseline (all.tcl's exit code lies) + const gate_step = b.step("test-gate", "tcl core testsuite under the built shell, gated on parsed totals vs expected_test_failures.txt"); + const testargs_opt = b.option([]const u8, "testargs", "extra tcltest args for test-gate (e.g -file http.test)") orelse ""; + const gate_run = b.addRunArtifact(tclsh_exe); + common.scrubTclEnv(gate_run); + gate_run.setEnvironmentVariable("TCL_LIBRARY", tcl_library_src); + gate_run.has_side_effects = true; + gate_run.addFileArg(b.path("tools/test_gate.tcl")); + gate_run.addArg("-testsdir"); + gate_run.addArg(common.replaceAll(b, b.pathFromRoot(tcl_source_folder ++ "/tests"), "\\", "/")); + gate_run.addArg("-baseline"); + gate_run.addFileArg(b.path("expected_test_failures.txt")); + gate_run.addArg("-logfile"); + gate_run.addArg(common.replaceAll(b, b.pathFromRoot("../tcltest.log"), "\\", "/")); + if (testargs_opt.len != 0) { + gate_run.addArgs(&.{ "-testargs", testargs_opt }); + } + gate_run.step.dependOn(b.getInstallStep()); + gate_step.dependOn(&gate_run.step); + } + // ================== } //G-102 bootstrap mode: registered when this recipe is invoked from the TRACKED suite @@ -2252,6 +2422,8 @@ fn bootstrapMode(b: *std.Build) !void { "build_tclvfs", "build_tclthread", "build_tk", + "critcl_zig.config", + "expected_test_failures.txt", "patches", "src", "tools", @@ -2268,7 +2440,7 @@ fn bootstrapMode(b: *std.Build) !void { //suite.tcl performs after its fossil staging. Per-zig-version cache dir (object //caches must never be shared across zig versions - user-confirmed hazard), same //name suite.tcl derives so both flows share the staged cache. - const steps_opt = b.option([]const []const u8, "steps", "steps for the staged build (repeat the flag; default: install install-libraries make-zipfs)") orelse @as([]const []const u8, &.{ "install", "install-libraries", "make-zipfs" }); + const steps_opt = b.option([]const []const u8, "steps", "steps for the staged build (repeat the flag; default: install install-libraries make-zipfs smoke tklib tcllib tcllibc - the full pipeline short of test-gate)") orelse @as([]const []const u8, &.{ "install", "install-libraries", "make-zipfs", "smoke", "tklib", "tcllib", "tcllibc" }); var cachever: []const u8 = builtin.zig_version_string; cachever = common.replaceAll(b, cachever, "+", "_"); cachever = common.replaceAll(b, cachever, "/", "_"); diff --git a/src/buildsuites/suite_tcl90/build_common.zig b/src/buildsuites/suite_tcl90/build_common.zig index 04b7711f..18be5cc3 100644 --- a/src/buildsuites/suite_tcl90/build_common.zig +++ b/src/buildsuites/suite_tcl90/build_common.zig @@ -42,6 +42,60 @@ pub fn replaceAll(b: *std.Build, input: []const u8, needle: []const u8, replacem return std.mem.replaceOwned(u8, b.allocator, input, needle, replacement) catch @panic("OOM"); } +//hermetic child shells (G-102): steps that run the suite-built tclsh must not see +//the user's machine-level tcl environment (TCLLIBPATH pushing machine package dirs +//misdirects installer default paths and risks false-positive smokes; TCL_LIBRARY/ +//TK_LIBRARY misdirect init script resolution). suite.tcl scrubbed these for its +//whole process; zig-driven runs scrub per step. +pub fn scrubTclEnv(run: *std.Build.Step.Run) void { + for ([_][]const u8{ "TCLLIBPATH", "TCL_LIBRARY", "TK_LIBRARY" }) |ev| { + run.removeEnvironmentVariable(ev); + } +} + +//tcllib-family installer trees carry 'package_version ' in +//support/installation/version.tcl +pub fn installerPackageVersion(b: *std.Build, tree_from_root: []const u8) []const u8 { + const data = readSourceFile(b, b.fmt("{s}/support/installation/version.tcl", .{tree_from_root})); + var lines = std.mem.splitScalar(u8, data, '\n'); + while (lines.next()) |line| { + var toks = std.mem.tokenizeAny(u8, line, " \t\r"); + const first = toks.next() orelse continue; + if (!std.mem.eql(u8, first, "package_version")) continue; + const ver = toks.next() orelse continue; + return b.dupe(ver); + } + std.debug.panic("no package_version in {s}/support/installation/version.tcl", .{tree_from_root}); +} + +//tcllib's critcl module files from its version.tcl declarations: the critcl_main +//module's files first, then every 'critcl' module's files (sak.tcl semantics - +//the same derivation suite.tcl's tcllibc block used). Returned paths are relative +//to the tcllib tree's modules/ dir. Tcl brace-list tokens are unwrapped by brace +//stripping (the declared file names contain no spaces). +pub fn tcllibCritclFiles(b: *std.Build, tcllib_from_root: []const u8) []const []const u8 { + const data = readSourceFile(b, b.fmt("{s}/support/installation/version.tcl", .{tcllib_from_root})); + var mains = std.array_list.Managed([]const u8).init(b.allocator); + var mods = std.array_list.Managed([]const u8).init(b.allocator); + var lines = std.mem.splitScalar(u8, data, '\n'); + while (lines.next()) |line| { + var toks = std.mem.tokenizeAny(u8, line, " \t\r"); + const first = toks.next() orelse continue; + const is_main = std.mem.eql(u8, first, "critcl_main"); + if (!is_main and !std.mem.eql(u8, first, "critcl")) continue; + _ = toks.next() orelse continue; //module name + while (toks.next()) |tok| { + var f = tok; + f = std.mem.trim(u8, f, "{}"); + if (f.len == 0) continue; + (if (is_main) &mains else &mods).append(b.dupe(f)) catch @panic("OOM"); + } + } + if (mains.items.len == 0) @panic("no critcl_main declaration found in tcllib version.tcl"); + mains.appendSlice(mods.items) catch @panic("OOM"); + return mains.items; +} + //TCL_WIN_VERSION / TK_WIN_VERSION as the makefiles derive them: //.., releaselevel //0=alpha 1=beta 2=final (e.g 9.0 + 9.0.5 -> 9.0.2.905) diff --git a/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig b/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig index e4b3d5d3..1ae2ea82 100644 --- a/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig +++ b/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig @@ -3,7 +3,8 @@ const fs = std.fs; const builtin = @import("builtin"); const common = @import("../build_common.zig"); -const mkvar_thread_zip_file = "libthread3.0.1.zip"; +const mkvar_thread_version = "3.0.1"; +const mkvar_thread_zip_file = "libthread" ++ mkvar_thread_version ++ ".zip"; const mkvar_thread_vfs_root = "libthread.vfs"; const mkvar_thread_vfs_path = mkvar_thread_vfs_root ++ "/thread_library"; @@ -165,5 +166,14 @@ pub fn build_tclthread(comptime tcldir: []const u8, comptime subdir: []const u8, }); //ignored for non-windows targets b.installArtifact(lib); + //G-102: pkgIndex.tcl so 'package require Thread' resolves against the prefix + //(punkshell runtests -jobs and codethread need it) - formerly a suite.tcl + //post-build block. + const wf_pkgidx = b.addWriteFiles(); + const pkgidx = wf_pkgidx.add("pkgIndex.tcl", "package ifneeded Thread " ++ mkvar_thread_version ++ + " [list load [file normalize [file join $dir .. .. bin " ++ mkvar_pkg_lib_file ++ "]] Thread]\n"); + const pkgidx_install = b.addInstallFileWithDir(pkgidx, .prefix, "lib/thread" ++ mkvar_thread_version ++ "/pkgIndex.tcl"); + b.getInstallStep().dependOn(&pkgidx_install.step); + return lib; } diff --git a/src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_shared.zig b/src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_shared.zig index 13248199..2fd799d5 100644 --- a/src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_shared.zig +++ b/src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_shared.zig @@ -1,9 +1,48 @@ const std = @import("std"); const fs = std.fs; +const common = @import("../build_common.zig"); const tclvfs_dotversion = "1.4.2"; const tclvfs_nodotversion = "142"; +//G-102: install the tclvfs script package (lib/vfs) into the prefix - the two +//configure-products (vfs.tcl, pkgIndex.tcl) generated from their .in templates at +//configure time, the library scripts + template dir copied, and the dll alongside +//(vfs.tcl loads it from its own dir via ::vfs::self). Formerly a suite.tcl +//post-build block. Windows dll naming - the cross-target story is G-105's. +pub fn install_tclvfs_package(comptime subdir: []const u8, b: *std.Build, tclvfs_compile: *std.Build.Step.Compile) !void { + const pkgsub = "lib/vfs" ++ tclvfs_dotversion; + const install_scripts = b.addInstallDirectory(.{ + .source_dir = b.path(subdir ++ "/library"), + .install_dir = .prefix, + .install_subdir = pkgsub, + .include_extensions = &.{".tcl"}, + }); + b.getInstallStep().dependOn(&install_scripts.step); + //template dir wholesale (carries non-.tcl support files such as tclIndex) + const install_template = b.addInstallDirectory(.{ + .source_dir = b.path(subdir ++ "/library/template"), + .install_dir = .prefix, + .install_subdir = pkgsub ++ "/template", + }); + b.getInstallStep().dependOn(&install_template.step); + const wf = b.addWriteFiles(); + inline for (.{ + .{ "library/vfs.tcl.in", "vfs.tcl" }, + .{ "pkgIndex.tcl.in", "pkgIndex.tcl" }, + }) |pair| { + var t: []const u8 = common.readSourceFile(b, subdir ++ "/" ++ pair[0]); + t = common.replaceAll(b, t, "@PACKAGE_VERSION@", tclvfs_dotversion); + t = common.replaceAll(b, t, "@PKG_LIB_FILE9@", "tcl9vfs" ++ tclvfs_nodotversion ++ ".dll"); + t = common.replaceAll(b, t, "@PKG_LIB_FILE8@", "tclvfs" ++ tclvfs_nodotversion ++ ".dll"); + const gen = wf.add(pair[1], t); + const inst = b.addInstallFileWithDir(gen, .prefix, pkgsub ++ "/" ++ pair[1]); + b.getInstallStep().dependOn(&inst.step); + } + const dll_inst = b.addInstallFileWithDir(tclvfs_compile.getEmittedBin(), .prefix, pkgsub ++ "/tcl9vfs" ++ tclvfs_nodotversion ++ ".dll"); + b.getInstallStep().dependOn(&dll_inst.step); +} + pub fn vfsadd_tclvfs_files(comptime tcldir: []const u8, comptime subdir: []const u8, b: *std.Build, vfswrite: *std.Build.Step.WriteFile) !*std.Build.Step.WriteFile { //_ = tcldir; //const version = "1.4.2"; diff --git a/src/buildsuites/suite_tcl90/build_tk/build_tk.zig b/src/buildsuites/suite_tcl90/build_tk/build_tk.zig index f5913d03..4c573a62 100644 --- a/src/buildsuites/suite_tcl90/build_tk/build_tk.zig +++ b/src/buildsuites/suite_tcl90/build_tk/build_tk.zig @@ -180,8 +180,7 @@ pub fn build_tk(comptime tcldir: []const u8, comptime subdir: []const u8, b: *st b.installArtifact(lib); - //tk script library -> /lib/tk9.0 (pkgIndex.tcl is generated by the suite - //driver post-build, per the makefile.vc install-binaries convention) + //tk script library -> /lib/tk9.0 const install_tklib = b.addInstallDirectory(.{ .source_dir = b.path(subdir ++ "/library"), .install_dir = .prefix, @@ -189,5 +188,17 @@ pub fn build_tk(comptime tcldir: []const u8, comptime subdir: []const u8, b: *st }); b.getInstallStep().dependOn(&install_tklib.step); + //G-102: pkgIndex.tcl per the makefile.vc install-binaries convention - formerly + //a suite.tcl post-build block. + const wf_pkgidx = b.addWriteFiles(); + const pkgidx = wf_pkgidx.add("pkgIndex.tcl", b.fmt( + \\if {{[catch {{package present Tcl 9.0-}}]}} return + \\package ifneeded tk {s} [list load [file normalize [file join $dir .. .. bin tcl9tk90.dll]]] + \\package ifneeded Tk {s} [list load [file normalize [file join $dir .. .. bin tcl9tk90.dll]]] + \\ + , .{ tk_h_patchlevel, tk_h_patchlevel })); + const pkgidx_install = b.addInstallFileWithDir(pkgidx, .prefix, "lib/tk9.0/pkgIndex.tcl"); + b.getInstallStep().dependOn(&pkgidx_install.step); + return lib; } diff --git a/src/buildsuites/suite_tcl90/sources.config b/src/buildsuites/suite_tcl90/sources.config index cfdd22f0..bfe4fa50 100644 --- a/src/buildsuites/suite_tcl90/sources.config +++ b/src/buildsuites/suite_tcl90/sources.config @@ -1,5 +1,13 @@ -# sources.config - suite_tcl90 source-pull declarations. THIS is the tweak surface -# for retargeting the suite at different repositories/branches/tags. +# sources.config - suite_tcl90 source-pull declarations for the FOSSIL DEV FLOW +# (live checkouts driven by suite.tcl). This is the tweak surface for retargeting +# that flow at different repositories/branches/tags. +# +# G-102: the no-tclsh bootstrap flow ('zig build stage|bootstrap') has its own +# equivalent surface, build.zig.zon - content-hashed PER-CHECKIN pins rather than +# live refs. The dependency names there match the source names here, and both +# flows materialize into the same recipe-coupled stage dirs. When retargeting, +# tweak whichever flow you drive (or both, to keep them aligned): refs here may +# float (branches); zon pins are exact by design. # # Read by suite.tcl as plain line records - the file is parsed, never executed. # Format (whitespace separated, one record per line, # comments and blanks ignored): diff --git a/src/buildsuites/suite_tcl90/suite.tcl b/src/buildsuites/suite_tcl90/suite.tcl index a709347a..7e9a8453 100644 --- a/src/buildsuites/suite_tcl90/suite.tcl +++ b/src/buildsuites/suite_tcl90/suite.tcl @@ -1,13 +1,19 @@ #!/usr/bin/env tclsh -#suite_tcl90 driver (G-096): stage the tracked zig recipe into ../_build/suite_tcl90, -#arrange sources, generate the configure-produced files fresh checkouts lack, build, -#and smoke-test the result. +#suite_tcl90 dev driver (G-096, thinned under G-102): stage the tracked zig recipe +#into ../_build/suite_tcl90, arrange sources from LIVE fossil/git checkouts, and +#delegate the build/test pipeline to the staged zig recipe's steps. +# +#This is the FOSSIL DEV FLOW - convenient live-branch work against checkouts. The +#pipeline itself (configure-product generation, compile, package installs, smokes, +#test gate) lives entirely in the zig recipe; the equivalent no-tclsh flow is +#'zig build stage|bootstrap' from this directory, fetching the PINNED sources +#declared in build.zig.zon instead of checkouts (see README). # #Usage: tclsh suite.tcl build ?options? # tclsh suite.tcl test ?-testargs {tcltest args}? (e.g -testargs {-file "assocd.test util.test"}) # tclsh suite.tcl clean #Options: -# -zig zig executable (default: $env(PUNK_ZIG), else 'zig' on PATH) +# -zig zig executable (default: $env(PUNK_ZIG), else pinned bin/tools zig, else 'zig' on PATH) # -optimize Debug|ReleaseSafe|ReleaseFast|ReleaseSmall (default ReleaseFast) # -tclbranch override the 'tcl' ref from sources.config for this run # (default: the ref declared in sources.config) @@ -15,7 +21,8 @@ # -repofolder fossil clone store (default ~/.punkshell/fossils; env PUNK_FOSSIL_STORE) # -seedfossils read-only seed collection for missing clones (default ~/.fossils; "" disables) # -all <0|1> with 'clean': reserved (the clone store lives outside the stage) -# -steps zig build steps (default {install install-libraries make-zipfs}) +# -steps zig build steps (default {install install-libraries make-zipfs +# smoke tklib tcllib tcllibc} - the full pipeline short of test-gate) # #The build area lives entirely under src/buildsuites/_build (VCS-ignored via the #existing _build globs). Fossil clones live in the PUNKSHELL-OWNED machine-level @@ -47,6 +54,7 @@ set suiteroot [file dirname [file normalize [info script]]] #stage derived from the suite FOLDER name so a copied tree (copy-and-tweak workflow, #see sources.config) stages into its own _build/ - copies stay isolated. set stage [file normalize [file join $suiteroot .. _build [file tail $suiteroot]]] +set builddir [file join $stage build] #-- options --------------------------------------------------------------- set action [lindex $argv 0] @@ -58,7 +66,7 @@ array set opt { -optimize ReleaseFast -tclbranch {} -refresh 0 - -steps {install install-libraries make-zipfs} + -steps {install install-libraries make-zipfs smoke tklib tcllib tcllibc} -testargs {} -repofolder {} -all 0 @@ -104,6 +112,35 @@ if {$opt(-repofolder) ne ""} { set fossildir [file join [file normalize $homedir] .punkshell fossils] } +#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). +#The zig recipe additionally scrubs these per built-shell Run step (G-102). +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 run_zig_steps {steps {extraopts {}}} { + #drive the STAGED recipe (suite.tcl parity contract with 'zig build bootstrap': + #cwd = staged build dir, per-zig-version cache, forward-slash prefix) + global opt builddir stage + if {![file exists [file join $builddir build905.zig]]} { + fail "no staged recipe at $builddir - run 'suite.tcl build' first" + } + set zigv [exec $opt(-zig) version] + log "zig: $opt(-zig) ($zigv)" + #Per-zig-version local cache: object caches must never be shared across zig + #versions (observed producing stale/duplicate artifacts in the link). + set cachedir ".zig-cache-[string map {+ _ / _ : _} $zigv]" + set savedpwd [pwd] + cd $builddir + run $opt(-zig) build --build-file build905.zig --cache-dir $cachedir --prefix [file join $stage out] -Doptimize=$opt(-optimize) {*}$extraopts {*}$steps + cd $savedpwd +} + if {$action eq "clean"} { log "removing $stage (the clone store at $fossildir is untouched)" file delete -force $stage @@ -111,66 +148,19 @@ if {$action eq "clean"} { } if {$action eq "test"} { - #Exercise the Tcl core test suite on the suite-built static shell and GATE on the - #parsed totals (all.tcl's exit code does NOT reflect failures): every failed test - #must appear in the tracked expected-failure baseline - #(suite_tcl90/expected_test_failures.txt) or the action fails listing the - #unexplained names. Full output is written to /tcltest.log. - set exe [file join $stage out bin tclsh90s.exe] - set testsdir [file join $stage tcl905 tests] - if {![file exists $exe]} {fail "no built shell at $exe - run 'suite.tcl build' first"} - if {![file isdirectory $testsdir]} {fail "no tcl tests dir at $testsdir - run 'suite.tcl build' first"} - set logfile [file join $stage tcltest.log] - log "tcl testsuite on $exe [expr {[llength $opt(-testargs)] ? $opt(-testargs) : "(full suite)"}] -> $logfile" - set savedpwd [pwd] - cd $testsdir - catch {exec $exe all.tcl {*}$opt(-testargs) > $logfile 2>@1} - cd $savedpwd - set f [open $logfile r]; set testout [read $f]; close $f - if {![regexp {all\.tcl:\s+Total\s+(\d+)\s+Passed\s+(\d+)\s+Skipped\s+(\d+)\s+Failed\s+(\d+)} $testout -> ntotal npassed nskipped nfailed]} { - fail "no all.tcl totals line found in $logfile - run did not complete" + #Delegates to the recipe's test-gate step (tools/test_gate.tcl under the + #suite-built shell): core testsuite gated on parsed totals vs the tracked + #expected_test_failures.txt baseline. Full output: /tcltest.log. + set extraopts {} + if {[llength $opt(-testargs)]} { + lappend extraopts -Dtestargs=$opt(-testargs) } - log "totals: $ntotal run, $npassed passed, $nskipped skipped, $nfailed failed" - set failednames {} - foreach {- tname} [regexp -all -inline -line {^==== (\S+) FAILED} $testout] { - if {$tname ni $failednames} {lappend failednames $tname} - } - #baseline: one test name per line; '#' comments carry the disposition reasons - set baseline {} - set basefile [file join $suiteroot expected_test_failures.txt] - if {[file exists $basefile]} { - set f [open $basefile r] - foreach line [split [read $f] \n] { - set line [string trim $line] - if {$line eq "" || [string index $line 0] eq "#"} continue - lappend baseline [lindex $line 0] - } - close $f - } - set unexplained {} - foreach tname $failednames { - if {$tname ni $baseline} {lappend unexplained $tname} - } - set stale {} - if {![llength $opt(-testargs)]} { - #only meaningful for full runs: baseline entries that no longer fail - foreach tname $baseline { - if {$tname ni $failednames} {lappend stale $tname} - } - } - if {[llength $stale]} { - log "note: [llength $stale] baseline entries did not fail this run (candidates for removal): $stale" - } - if {[llength $unexplained]} { - puts stderr "suite_tcl90 TEST GATE FAIL: [llength $unexplained] failed test(s) not in the expected-failure baseline:" - foreach tname $unexplained {puts stderr " $tname"} - puts stderr "(full output: $logfile; baseline: $basefile)" - exit 1 - } - log "TEST GATE PASS ([llength $failednames] failures, all baselined)" + run_zig_steps {test-gate} $extraopts exit 0 } +#-- action: build ---------------------------------------------------------- + #-- fossil environment ---------------------------------------------------- #Checkout registrations go to a stage-local config db, not the user's global one #(agent-memory: fossil scratchpad registry pollution). Clones are still shared. @@ -178,16 +168,6 @@ 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 @@ -226,22 +206,35 @@ proc fossil_source {name url branch dir} { } } +proc git_source {name url ref dir} { + #clone (or leave) a git checkout pinned at ref (critcl - the recipe's tcllibc + #step consumes it from the stage) + global opt + if {![file isdirectory [file join $dir .git]]} { + log "cloning $name (git) -> $dir" + run git clone --quiet $url $dir + } elseif {$opt(-refresh)} { + run git -C $dir fetch --quiet + } + run git -C $dir checkout --quiet $ref +} + #-- stage the recipe ------------------------------------------------------ #Per-item sync (not a wholesale delete of the build dir): zig cache dirs live in the -#build dir and are kept - they are per-zig-version (see --cache-dir below), and windows -#can hold transient locks on recently-used cache trees. -set builddir [file join $stage build] +#build dir and are kept - they are per-zig-version (see run_zig_steps), and windows +#can hold transient locks on recently-used cache trees. Item list mirrors the zig +#bootstrap mode's recipe_items (build905.zig bootstrapMode). log "staging recipe -> $builddir" file mkdir $builddir -foreach item {build905.zig build_common.zig build_libtommath build_zlib build_tclvfs build_tclthread build_tk patches src tools} { +foreach item {build905.zig build_common.zig build_libtommath build_zlib build_tclvfs build_tclthread build_tk critcl_zig.config expected_test_failures.txt patches src tools} { file delete -force [file join $builddir $item] file copy [file join $suiteroot $item] $builddir } #-- sources --------------------------------------------------------------- -#Declared in sources.config (the copy-and-tweak surface - see its header for the -#format, the copy workflow, and the recipe-coupling depth limit). Parsed as line -#records - never executed. +#Declared in sources.config (the copy-and-tweak surface for the LIVE-checkout dev +#flow - see its header; build.zig.zon is the equivalent PINNED surface for the +#no-tclsh flow). Parsed as line records - never executed. proc load_sources_config {path} { set sources [dict create] set f [open $path r]; set data [read $f]; close $f @@ -268,13 +261,15 @@ foreach required {tcl tclthread tclvfs tk tklib tcllib critcl} { } } -foreach name {tcl tclthread tclvfs tk tklib tcllib} { - set s [dict get $sources $name] +dict for {name s} $sources { set ref [dict get $s ref] if {$name eq "tcl" && $opt(-tclbranch) ne ""} { set ref $opt(-tclbranch) ;#per-run CLI override of the declared tcl ref } - fossil_source $name [dict get $s url] $ref [file join $stage [dict get $s dir]] + switch -- [dict get $s kind] { + fossil {fossil_source $name [dict get $s url] $ref [file join $stage [dict get $s dir]]} + git {git_source $name [dict get $s url] $ref [file join $stage [dict get $s dir]]} + } } #zlib: the recipe compiles the tcl tree's own vendored copy in place @@ -282,190 +277,16 @@ foreach name {tcl tclthread tclvfs tk tklib tcllib} { #vqtcl/vlerq: removed from the suite (user 2026-07-20) - old-tclkit experiment with no #confirmed public upstream. Future tclkit work: TEMP_REFERENCE/metakit + KitCreator. -#-- version facts for smokes/logging --------------------------------------- -#G-102: the configure-products a fresh checkout lacks (tclUuid.h, tclsh.exe.manifest, -#the thread.rc tcl.h include, tkUuid.h, wish.exe.manifest) are now generated INSIDE -#the zig recipe as build-cache overlays - source checkouts are never written. What -#remains here is read-only: version facts for the smoke expectations and the log. -set tcldir [file join $stage tcl905] -set f [open [file join $tcldir manifest.uuid] r]; set uuid [read $f]; close $f -set f [open [file join $tcldir generic tcl.h] r]; set tclh [read $f]; close $f +#-- version facts for the final log ---------------------------------------- +#G-102: configure-product generation, package installs, smokes and the test gate all +#live in the zig recipe now (build-cache overlays + steps under the suite-built +#shell) - source checkouts are never written. Read-only facts for the log only. +set f [open [file join $stage tcl905 manifest.uuid] r]; set uuid [read $f]; close $f +set f [open [file join $stage tcl905 generic tcl.h] r]; set tclh [read $f]; close $f if {![regexp {#\s*define\s+TCL_PATCH_LEVEL\s+"([^"]+)"} $tclh -> patchlevel]} {fail "TCL_PATCH_LEVEL not found in tcl.h"} -set tkdir [file join $stage tk9] -set f [open [file join $tkdir generic tk.h] r]; set tkh [read $f]; close $f -if {![regexp {#\s*define\s+TK_VERSION\s+"([^"]+)"} $tkh -> tkversion]} {fail "TK_VERSION not found in tk.h"} -if {![regexp {#\s*define\s+TK_PATCH_LEVEL\s+"([^"]+)"} $tkh -> tkpatchlevel]} {fail "TK_PATCH_LEVEL not found in tk.h"} -log "sources: tcl $patchlevel (checkout [string range $uuid 0 11]...), tk $tkpatchlevel" - -#-- build ----------------------------------------------------------------- -set zigv [exec $opt(-zig) version] -log "zig: $opt(-zig) ($zigv)" -#Per-zig-version local cache: object caches must never be shared across zig versions -#(observed producing stale/duplicate artifacts in the link; user-confirmed hazard). -set cachedir ".zig-cache-[string map {+ _ / _ : _} $zigv]" -set savedpwd [pwd] -cd $builddir -run $opt(-zig) build --build-file build905.zig --cache-dir $cachedir --prefix [file join $stage out] -Doptimize=$opt(-optimize) {*}$opt(-steps) -cd $savedpwd - -#-- thread pkgIndex (G-098: the recipe installs tcl9thread.dll but no index; -#punkshell runtests/-jobs and codethread need 'package require Thread' to resolve) -- -set threaddlls [glob -nocomplain [file join $stage out bin tcl9thread*.dll]] -if {[llength $threaddlls]} { - set tdll [file tail [lindex $threaddlls 0]] - if {[regexp {tcl9thread(\d)(\d)(\d)\.dll} $tdll -> tj tn tp]} { - set threadver "$tj.$tn.$tp" - set tpkgdir [file join $stage out lib thread$threadver] - file mkdir $tpkgdir - set f [open [file join $tpkgdir pkgIndex.tcl] w] - fconfigure $f -translation lf - puts $f "package ifneeded Thread $threadver \[list load \[file normalize \[file join \$dir .. .. bin $tdll\]\] Thread\]" - close $f - log "thread pkgIndex.tcl written ($tpkgdir -> $tdll)" - } -} - -#-- vfs package install (G-098): the recipe builds tcl9vfs142.dll only; punkshell's -#repl code interp wants vfs/vfs::zip (modpod mounting). Install the tclvfs script -#package with its two configure-products generated (vfs.tcl, pkgIndex.tcl) and the -#dll alongside (vfs.tcl loads from its own dir via ::vfs::self). -set vfssrc [file join $stage tclvfs] -if {[file isdirectory $vfssrc]} { - set f [open [file join $vfssrc configure.ac] r]; set cac [read $f]; close $f - if {![regexp {AC_INIT\(\[vfs\],\[([^\]]+)\]} $cac -> vfsver]} {fail "vfs version not found in tclvfs configure.ac"} - set vpkgdir [file join $stage out lib vfs$vfsver] - if {![file isdirectory $vpkgdir]} { - log "installing tclvfs $vfsver package -> $vpkgdir" - file mkdir $vpkgdir - foreach tf [glob -directory [file join $vfssrc library] *.tcl] { - file copy -force $tf $vpkgdir - } - file copy -force [file join $vfssrc library template] $vpkgdir - set map [list @PACKAGE_VERSION@ $vfsver @PKG_LIB_FILE9@ tcl9vfs142.dll @PKG_LIB_FILE8@ tclvfs142.dll] - foreach {intmpl outname} [list [file join $vfssrc library vfs.tcl.in] vfs.tcl [file join $vfssrc pkgIndex.tcl.in] pkgIndex.tcl] { - set f [open $intmpl r]; set t [read $f]; close $f - set f [open [file join $vpkgdir $outname] w] - fconfigure $f -translation lf - puts -nonewline $f [string map $map $t] - close $f - } - file copy -force [file join $stage out bin tcl9vfs142.dll] $vpkgdir - } -} - -#-- tk pkgIndex (makefile.vc install-binaries convention) ------------------ -set tkpkgdir [file join $stage out lib tk$tkversion] -if {[file isdirectory $tkpkgdir]} { - set f [open [file join $tkpkgdir pkgIndex.tcl] w] - fconfigure $f -translation lf - puts $f "if {\[catch {package present Tcl 9.0-}\]} return" - puts $f "package ifneeded tk $tkpatchlevel \[list load \[file normalize \[file join \$dir .. .. bin tcl9tk90.dll\]\]\]" - puts $f "package ifneeded Tk $tkpatchlevel \[list load \[file normalize \[file join \$dir .. .. bin tcl9tk90.dll\]\]\]" - close $f - log "tk pkgIndex.tcl written ($tkpkgdir)" -} +log "sources: tcl $patchlevel (checkout [string range $uuid 0 11]...)" -#-- smoke ----------------------------------------------------------------- -set exe [file join $stage out bin tclsh90s.exe] -set zexe [file join $stage out bin tclsh90szip.exe] -foreach {label path script} [list \ - static $exe {puts [info patchlevel]} \ - zip $zexe {puts "[info patchlevel] tz=[clock format 0 -gmt 0 -format %Z] auto=[llength [info procs auto_load]]"} \ -] { - if {![file exists $path]} {fail "$label shell missing: $path"} - set out [exec $path << "$script; exit"] - log "$label shell: $out" - if {![string match "$patchlevel*" $out]} {fail "$label shell patchlevel mismatch: got '$out' want '$patchlevel'"} -} -#-- tklib install (G-098) -------------------------------------------------- -#tklib is pure-tcl but its installer is NOT a straight copy: module exclusions and -#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*]]]} { - 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 -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*]] -if {![llength $tkliblanded]} {fail "tklib install did not land under [file join $stage out lib]"} -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 [dict get $sources critcl dir]] -set critclpin [dict get $sources critcl ref] -if {![file isdirectory [file join $critcldir .git]]} { - log "cloning critcl (git) -> $critcldir" - run git clone --quiet [dict get $sources critcl url] $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" +#-- build (staged recipe steps) ------------------------------------------- +run_zig_steps $opt(-steps) -log "PASS - built $patchlevel (checkout [string range $uuid 0 11]..., zig $zigv) -> $stage/out" +log "PASS - built $patchlevel (checkout [string range $uuid 0 11]...) -> $stage/out" diff --git a/src/buildsuites/suite_tcl90/tools/pkg_smoke.tcl b/src/buildsuites/suite_tcl90/tools/pkg_smoke.tcl new file mode 100644 index 00000000..05f9475e --- /dev/null +++ b/src/buildsuites/suite_tcl90/tools/pkg_smoke.tcl @@ -0,0 +1,42 @@ +#pkg_smoke.tcl (G-102): package-resolution smoke run under a suite-built shell. +#Requires each named package against the shell's own installed prefix, printing +#name+version; -gui 1 wraps the requires in a withdrawn Tk root (for tklib +#packages that need a display); -accel additionally asserts the package's +#critcl accelerator engaged (::::accel(critcl) == 1, tcllibc proof). +#Formerly suite.tcl inline smokes. +# +#args: ?-gui 0|1? ?-accel ? package ?package ...? + +proc fail {msg} {puts stderr "pkg_smoke FAIL: $msg"; flush stderr; exit 1} + +set gui 0 +set accel {} +set pkgs {} +for {set i 0} {$i < [llength $argv]} {incr i} { + set a [lindex $argv $i] + switch -- $a { + -gui {incr i; set gui [lindex $argv $i]} + -accel {incr i; set accel [lindex $argv $i]} + default {lappend pkgs $a} + } +} +if {![llength $pkgs] && $accel eq ""} {fail "no packages given"} +if {$gui} { + if {[catch {package require Tk; wm withdraw .} err]} {fail "Tk load failed: $err"} +} +set report {} +foreach pkg $pkgs { + if {[catch {package require $pkg} ver]} {fail "package require $pkg failed: $ver"} + lappend report "$pkg:$ver" +} +if {$accel ne ""} { + if {[catch {package require $accel} err]} {fail "package require $accel (accel host) failed: $err"} + upvar #0 ::${accel}::accel accelarr + if {![info exists accelarr(critcl)] || !$accelarr(critcl)} { + fail "critcl accelerator not engaged for $accel (tcllibc not loading?)" + } + lappend report "${accel}-critcl-accel:1" +} +if {$gui} {catch {destroy .}} +puts "pkg_smoke OK: [join $report { }]" +exit 0 diff --git a/src/buildsuites/suite_tcl90/tools/suite_smoke.tcl b/src/buildsuites/suite_tcl90/tools/suite_smoke.tcl new file mode 100644 index 00000000..b02c9b6a --- /dev/null +++ b/src/buildsuites/suite_tcl90/tools/suite_smoke.tcl @@ -0,0 +1,26 @@ +#suite_smoke.tcl (G-102): smoke assertions run under a suite-built shell itself +#(the shell being smoked executes this script). Formerly suite.tcl inline smokes. +# +#args: -mode static|zip -expect +# static: interpreter reports the expected patchlevel +# zip: additionally proves the ATTACHED library works - tzdata reachable +# (clock format with a named zone) and auto_load present + +proc fail {msg} {puts stderr "suite_smoke FAIL: $msg"; flush stderr; exit 1} + +array set opt {-mode static -expect {}} +foreach {k v} $argv { + if {![info exists opt($k)]} {fail "unknown option '$k'"} + set opt($k) $v +} +if {$opt(-expect) eq ""} {fail "missing -expect "} +set pl [info patchlevel] +if {$pl ne $opt(-expect)} {fail "patchlevel mismatch: got '$pl' want '$opt(-expect)' ([info nameofexecutable])"} +if {$opt(-mode) eq "zip"} { + if {[catch {clock format 0 -gmt 0 -format %Z} tz]} {fail "tzdata not reachable from attached library: $tz"} + if {[llength [info procs auto_load]] != 1} {fail "auto_load missing - attached library init incomplete"} + puts "suite_smoke OK: $pl (zip: tz=$tz auto=1) [info nameofexecutable]" +} else { + puts "suite_smoke OK: $pl [info nameofexecutable]" +} +exit 0 diff --git a/src/buildsuites/suite_tcl90/tools/test_gate.tcl b/src/buildsuites/suite_tcl90/tools/test_gate.tcl new file mode 100644 index 00000000..18e5cad1 --- /dev/null +++ b/src/buildsuites/suite_tcl90/tools/test_gate.tcl @@ -0,0 +1,71 @@ +#test_gate.tcl (G-102): run the Tcl core testsuite under THIS interpreter's own +#shell ([info nameofexecutable] - the suite-built tclsh when driven as the +#'zig build test-gate' step) and GATE on the parsed totals: all.tcl's exit code +#does NOT reflect failures, so every failed test must appear in the tracked +#dispositioned baseline or the gate fails listing the unexplained names. +#Formerly suite.tcl's 'test' action; suite.tcl now delegates here. +# +#args: -testsdir -baseline -logfile ?-testargs {tcltest args}? + +proc fail {msg} {puts stderr "test_gate ERROR: $msg"; flush stderr; exit 1} + +array set opt {-testsdir {} -baseline {} -logfile {} -testargs {}} +foreach {k v} $argv { + if {![info exists opt($k)]} {fail "unknown option '$k' (expected -testsdir -baseline -logfile -testargs)"} + set opt($k) $v +} +foreach req {-testsdir -baseline -logfile} { + if {$opt($req) eq ""} {fail "missing required option $req"} +} +if {![file isdirectory $opt(-testsdir)]} {fail "no tests dir at $opt(-testsdir) - build first"} +if {![file exists $opt(-baseline)]} {fail "no baseline file at $opt(-baseline)"} + +set exe [info nameofexecutable] +set logfile [file normalize $opt(-logfile)] +puts "test_gate: tcl testsuite under $exe [expr {[llength $opt(-testargs)] ? $opt(-testargs) : "(full suite)"}] -> $logfile" +flush stdout +set savedpwd [pwd] +cd $opt(-testsdir) +catch {exec $exe all.tcl {*}$opt(-testargs) > $logfile 2>@1} +cd $savedpwd + +set f [open $logfile r]; set testout [read $f]; close $f +if {![regexp {all\.tcl:\s+Total\s+(\d+)\s+Passed\s+(\d+)\s+Skipped\s+(\d+)\s+Failed\s+(\d+)} $testout -> ntotal npassed nskipped nfailed]} { + fail "no all.tcl totals line found in $logfile - run did not complete" +} +puts "test_gate: totals: $ntotal run, $npassed passed, $nskipped skipped, $nfailed failed" +set failednames {} +foreach {- tname} [regexp -all -inline -line {^==== (\S+) FAILED} $testout] { + if {$tname ni $failednames} {lappend failednames $tname} +} +#baseline: one test name per line; '#' comments carry the disposition reasons +set baseline {} +set f [open $opt(-baseline) r] +foreach line [split [read $f] \n] { + set line [string trim $line] + if {$line eq "" || [string index $line 0] eq "#"} continue + lappend baseline [lindex $line 0] +} +close $f +set unexplained {} +foreach tname $failednames { + if {$tname ni $baseline} {lappend unexplained $tname} +} +if {![llength $opt(-testargs)]} { + #only meaningful for full runs: baseline entries that no longer fail + set stale {} + foreach tname $baseline { + if {$tname ni $failednames} {lappend stale $tname} + } + if {[llength $stale]} { + puts "test_gate: note: [llength $stale] baseline entries did not fail this run (candidates for removal): $stale" + } +} +if {[llength $unexplained]} { + puts stderr "test_gate GATE FAIL: [llength $unexplained] failed test(s) not in the expected-failure baseline:" + foreach tname $unexplained {puts stderr " $tname"} + puts stderr "(full output: $logfile; baseline: $opt(-baseline))" + exit 1 +} +puts "test_gate: GATE PASS ([llength $failednames] failures, all baselined)" +exit 0