Browse Source
Builds a Tcl 8.6.18 windows runtime from core-8-6-branch sources with the
pinned zig 0.16.0 toolchain only (no MS toolchain), forked from suite_tcl90
(duplication-with-a-note posture). Products (stock threaded-release naming -
rules.vc keeps the 't' SUFX on 8.6):
bin/tcl86t.dll + tcl86t.lib shared core (static zlib compiled in; no zlib1.dll)
bin/tclsh86ts.exe static shell (kit-class; dde/registry static)
bin/tclsh86t.exe dynamic shell against the dll
lib/libtclstub86.a stub lib for extensions
lib/tcl8.6/... script library tree (dde/reg dlls inside)
lib/tcl8/{8.5,8.6}/... tm modules; lib/thread2.8.13, lib/vfs1.4.2
Companions: thread 2.8.13 (thread2813t.dll, TEA installed shape from the
checkout's own pkgIndex.tcl.in) and tclvfs 1.4.2 (vfs142t.dll) build and load;
registry 1.3.5 / dde 1.4.6. No zipfs in 8.6 - exe+dll+on-disk lib tree only,
resolved hermetically (exe/dll-relative, TCLLIBPATH/TCL_LIBRARY unset in all
child runs).
Recipe: build86.zig + build_common.zig + build_zlib86/build_tclthread86/
build_tclvfs86 helpers; tools/ and src/main.zig copied verbatim (suite-agnostic).
Two load-bearing 8.6 findings recorded in the README DERIVATION and recipe
comments: MP_FIXED_CUTOFFS (the in-core libtommath ships no bn_cutoffs.c) and
TCL_BROKEN_MAINARGS + console subsystem for the gcc-class main() entry.
Test gates (suite.tcl test -> zig build test-gate/test-libraries) parse all.tcl
totals (exit codes untrusted) and gate against tracked dispositioned baselines.
First census + two-consecutive-run deterministic PASS (machine SuperBee):
core 46525 run / 2 failed (filename-16.12/16.13 admin-share self-globbing)
thread 142 run / 0 failed (empty baseline)
tclvfs 51 run / 2 failed (vfsUrl-1.2/2.1 external ftp.tcl.tk dependency)
Goal flipped to achieved 2026-07-26 and archived; reference sweep updated G-005,
G-100 (now unblocked), G-101 and ARCHITECTURE.md. No project-version bump
(arm's-length build tooling, not shipped shell behaviour).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
26 changed files with 2166 additions and 71 deletions
@ -0,0 +1,7 @@
|
||||
//build.zig - default-entry shim so plain 'zig build <step>' works from this |
||||
//directory (the bootstrap-flow invocations in the README). The recipe itself |
||||
//lives in build86.zig - a deliberately VERSIONED name (the staged flows invoke |
||||
//it explicitly via --build-file, and a future suite may carry more than one |
||||
//recipe file). build.zig.zon beside this file serves both entry points. |
||||
const recipe = @import("build86.zig"); |
||||
pub const build = recipe.build; |
||||
@ -1,20 +1,905 @@
|
||||
//build86.zig - suite_tcl86 zig recipe (G-099) - DERIVATION IN PROGRESS. |
||||
//build86.zig - suite_tcl86 zig recipe (G-099): Tcl core-8-6-branch windows |
||||
//runtime from the pinned zig toolchain only, plus the core binary companions |
||||
//punkshell's Tcl 8 targets need (thread 2.8, tclvfs trunk), gated against the |
||||
//8.6 core testsuite. |
||||
// |
||||
//This is a deliberate pending-state skeleton: 'suite.tcl build' stages the |
||||
//core-8-6-branch / thread-2-8-branch / tclvfs-trunk sources (useful on its own - |
||||
//the checkout carries win/makefile.vc, the derivation source of truth) and then |
||||
//fails HERE with a clear message instead of pretending to build. |
||||
//Forked from suite_tcl90/build905.zig (the shape reference; duplication-with-a- |
||||
//note is the recorded posture) and DERIVED from the staged 8.6.18 |
||||
//win/makefile.vc + rules.vc - the object inventory, per-object flag classes |
||||
//(appcflags/pkgcflags/stubscflags), naming (SUFX rules) and install layout all |
||||
//mirror that authority. The full derivation record lives in README.md |
||||
//DERIVATION; key differences from the 9.0.5 recipe: |
||||
// - NO zipfs in 8.6: no szip self-contained runtime, no zip staging steps. |
||||
// The runtime is exe + dll + on-disk lib tree; hermetic resolution is |
||||
// exe/dll-relative (../lib/tcl8.6 from bin/, via tclWinInit.c). |
||||
// - zlib: 8.6's makefile.vc links a PREBUILT zdll.lib for shared builds and |
||||
// compiles the 11-file ZLIBOBJS list only for static ones. This recipe |
||||
// compiles that list into a static lib linked into BOTH the dll and the |
||||
// static shell - no zlib1.dll is shipped (deviation recorded in README). |
||||
// - entry points: gcc-class 8.6 builds define TCL_BROKEN_MAINARGS (win/ |
||||
// tcl.m4:671 extra_cflags - carried globally here for parity): the CRT |
||||
// entry is plain main() (console subsystem) and the real command line is |
||||
// re-parsed from GetCommandLineW by tclAppInit.c's setargv. Without the |
||||
// define tclAppInit provides only _tmain/wmain and mingw's fallback main |
||||
// demands WinMain. No -municode / mingw_unicode_entry_point (unlike the |
||||
// 9.0.5 recipe, whose sources dropped TCL_BROKEN_MAINARGS). |
||||
// - tclMain.c is compiled twice BY UPSTREAM DESIGN (COREOBJS carries both |
||||
// tclMain.obj and tclMainW.obj): the UNICODE copy provides Tcl_MainExW, |
||||
// the plain copy Tcl_MainEx + the once-only support functions (8.6 guards |
||||
// them with #ifndef UNICODE - no TCL_ASCII_MAIN needed). |
||||
// - stub lib is 3 objects (tclStubLib, tclTomMathStubLib, tclOOStubLib - no |
||||
// tclStubCall/tclStubLibTbl/tclWinPanic in 8.6); named libtclstub86. |
||||
// - dde/registry: stubs-consumer dlls tcldde14.dll (1.4) / tclreg13.dll |
||||
// (1.3) installed into the script library's dde/ and reg/ package dirs; |
||||
// the STATIC shell instead compiles tclWinDde/tclWinReg in and registers |
||||
// them via TCL_USE_STATIC_PACKAGES (makefile.vc static-build parity). |
||||
// - tm modules install under lib/tcl8/{8.5,8.6}/ with versions parsed from |
||||
// the library pkgIndex files (makefile.vc nmakehlp -V parity). |
||||
// - MP_64BIT / MP_PREC / MP_FIXED_CUTOFFS are NOT defined (8.6's makefile.vc |
||||
// defines none of them; the in-tree libtommath self-configures - all TUs |
||||
// share one flag set so the mp_digit ABI stays consistent). |
||||
// |
||||
//Derivation record (what this recipe must express, from 8.6's win/makefile.vc): |
||||
//see README.md section DERIVATION. The 9.0.5 recipe (suite_tcl90/build905.zig + |
||||
//build_common.zig + build_zlib/build_libtommath/build_tclvfs/build_tclthread |
||||
//helper modules) is the shape reference; the G-099 Context records the known |
||||
//8.6 differences to verify rather than assume (bundled zlib/libtommath |
||||
//arrangements, no tclUuid.h, makefile.vc-derived manifest/rc generation, |
||||
//TCL_BROKEN_MAINARGS-era entry points, no zipfs). |
||||
//Products (stock threaded-release naming - rules.vc keeps the 't' SUFX on 8.6): |
||||
// bin/tcl86t.dll + tcl86t.lib shared core (+import lib for extensions) |
||||
// bin/tclsh86t.exe dynamic shell linked against the dll |
||||
// bin/tclsh86ts.exe static shell (kit-class; dde/registry static) |
||||
// lib/libtclstub86.a stub lib for stubs-consumer extension builds |
||||
// lib/tcl8.6/... script library tree (dde/reg dlls inside) |
||||
// lib/tcl8/8.x/... tm modules; lib/thread2.8.13, lib/vfs1.4.2 |
||||
// |
||||
//Windows-only for the G-099 arc: cross-target parameterization is G-105's - |
||||
//the single-target shape here deliberately avoids per-target tree copies so a |
||||
//target dimension can be added without restructuring. |
||||
const std = @import("std"); |
||||
const builtin = @import("builtin"); |
||||
const common = @import("build_common.zig"); |
||||
const build_zlib86 = @import("build_zlib86/build_zlib86.zig").build_zlib86; |
||||
const build_tclthread86 = @import("build_tclthread86/build_tclthread86.zig").build_tclthread86; |
||||
const tclvfs86 = @import("build_tclvfs86/build_tclvfs86.zig"); |
||||
|
||||
const tcl_dot_version = "8.6"; |
||||
const tcl_nodot_version = "86"; |
||||
const tcl_source_folder = "../tcl86"; |
||||
|
||||
//stock makefile.vc/rules.vc naming for a threaded release build (SUFX 't' |
||||
//kept while TCL_THREADS && TCL_VERSION <= 86; static adds 's') |
||||
const tcl_dll_name = "tcl" ++ tcl_nodot_version ++ "t"; |
||||
const tcl_dll_file = tcl_dll_name ++ ".dll"; |
||||
const tclsh_dynamic = "tclsh" ++ tcl_nodot_version ++ "t"; |
||||
const tclsh_static = "tclsh" ++ tcl_nodot_version ++ "ts"; |
||||
const tcl_stub_lib_file = "libtclstub" ++ tcl_nodot_version ++ ".a"; |
||||
const dde_dll_file = "tcldde14.dll"; //DDEDOTVERSION 1.4, SUFX 't' stripped for package dlls |
||||
const reg_dll_file = "tclreg13.dll"; //REGDOTVERSION 1.3 |
||||
|
||||
comptime { |
||||
//The suite pin (G-102 convention: the recipe records the zig version it is |
||||
//written against). The recipe is 0.16-API; punk-getzig materializes the |
||||
//pinned toolchain (see README Pinned zig). |
||||
const required_zig = "0.16.0"; |
||||
const current_zig = builtin.zig_version; |
||||
const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable; |
||||
if (current_zig.order(min_zig) == .lt) { |
||||
const error_message = |
||||
\\This recipe is written against zig {} (see README Pinned zig) and |
||||
\\does not build with older zigs. bin/punk-getzig fetches the pinned |
||||
\\toolchain; https://ziglang.org/download/ hosts releases. |
||||
\\ |
||||
; |
||||
@compileError(std.fmt.comptimePrint(error_message, .{min_zig})); |
||||
} |
||||
} |
||||
|
||||
pub fn build(b: *std.Build) !void { |
||||
//G-102 invocation modes (suite_tcl90 parity). STAGED mode: build root is the |
||||
//staged recipe copy (_build/<suite>/build) with source trees beside it |
||||
//(../tcl86 etc). BOOTSTRAP mode: build root is the TRACKED suite dir - only |
||||
//staging steps register ('zig build stage|bootstrap', the no-tclsh entry). |
||||
if (!common.pathExists(b, tcl_source_folder)) { |
||||
if (common.pathExists(b, "sources.config")) { |
||||
try bootstrapMode(b); |
||||
return; |
||||
} |
||||
@panic("suite recipe: no source trees found beside the recipe (" ++ tcl_source_folder ++ |
||||
" missing). Use 'zig build stage' (or bootstrap) from the tracked suite dir for the " ++ |
||||
"pinned zon flow, or 'tclsh suite.tcl build' for the fossil dev flow."); |
||||
} |
||||
|
||||
const target = b.standardTargetOptions(.{}); |
||||
const optimize = b.standardOptimizeOption(.{}); |
||||
if (target.result.os.tag != .windows) { |
||||
@panic("suite_tcl86 recipe is windows-only for the G-099 arc (a Tcl 8.6 WINDOWS runtime); cross-target parameterization is G-105's goal"); |
||||
} |
||||
|
||||
const prefix = try std.fmt.allocPrint(b.allocator, "{s}", .{b.install_path}); |
||||
const bindir = b.pathJoin(&.{ prefix, "bin" }); |
||||
const libdir = b.pathJoin(&.{ prefix, "lib" }); |
||||
const includedir = b.pathJoin(&.{ prefix, "include" }); |
||||
const mandir = b.pathJoin(&.{ prefix, "man" }); |
||||
const tcl_library = b.pathJoin(&.{ prefix, "lib", "tcl" ++ tcl_dot_version }); |
||||
const script_install_dir_rel = "lib/tcl" ++ tcl_dot_version; |
||||
const module_install_dir_rel = "lib/tcl8"; //tm root: tm.tcl scans lib/tcl8/8.4..8.6 |
||||
std.debug.print("prefix {s}\n", .{prefix}); |
||||
std.debug.print("tcl_library {s}\n", .{tcl_library}); |
||||
|
||||
//-- version facts from the staged tree (never hardcoded) |
||||
const tclh_content = common.readSourceFile(b, tcl_source_folder ++ "/generic/tcl.h"); |
||||
const tcl_h_version = common.parseDefineString(tclh_content, "TCL_VERSION") orelse @panic("TCL_VERSION not found in tcl.h"); |
||||
const tcl_h_patchlevel = common.parseDefineString(tclh_content, "TCL_PATCH_LEVEL") orelse @panic("TCL_PATCH_LEVEL not found in tcl.h"); |
||||
if (!std.mem.eql(u8, tcl_h_version, tcl_dot_version)) { |
||||
std.debug.panic("staged tree is Tcl {s} but this recipe expresses Tcl " ++ tcl_dot_version ++ " (sources.config tcl ref / dir mismatch?)", .{tcl_h_version}); |
||||
} |
||||
const tcl_win_version = common.winResourceVersion(b, tcl_h_version, tcl_h_patchlevel); |
||||
std.debug.print("tcl.h: TCL_PATCH_LEVEL {s} (TCL_WIN_VERSION {s})\n", .{ tcl_h_patchlevel, tcl_win_version }); |
||||
|
||||
//-- static zlib (see header: compiled into dll AND static shell) |
||||
const zlib_lib_compile = try build_zlib86(tcl_source_folder ++ "/compat/zlib", b, target, optimize); |
||||
|
||||
//-- generated configure-products into the build cache (G-102 convention: |
||||
//source trees are never written; delivery via overlay include dirs / overlay |
||||
//rc copies). |
||||
//tclUuid.h: 8.6.18's makefile.vc concatenates win/tclUuid.h.in + the |
||||
//checkout's manifest.uuid - wrapfiletofile reproduces exactly that (the .in |
||||
//is the one '#define TCL_VERSION_UUID \' line). tclEvent.c #includes it. |
||||
const wrap_exe = b.addExecutable(.{ |
||||
.name = "wrapfiletofile", |
||||
.root_module = b.createModule(.{ |
||||
.root_source_file = b.path("tools/wrapfiletofile.zig"), |
||||
.target = b.graph.host, |
||||
}), |
||||
}); |
||||
const wrap_run = b.addRunArtifact(wrap_exe); |
||||
wrap_run.addArgs(&.{ |
||||
"-prefix", |
||||
"#define TCL_VERSION_UUID \\", |
||||
"-prefixnl", |
||||
"1", |
||||
"-input", |
||||
}); |
||||
wrap_run.addFileArg(b.path(tcl_source_folder ++ "/manifest.uuid")); |
||||
wrap_run.addArg("-output"); |
||||
const tcluuid_file1 = wrap_run.addOutputFileArg("tclUuid.h"); |
||||
//include-dir overlay FIRST at each consumer so a stale tree-written copy can |
||||
//never shadow the generated one; also carries the generation-step dependency. |
||||
const tcluuid_include_dir = tcluuid_file1.dirname(); |
||||
|
||||
//tclsh.exe.manifest (tclsh.rc references it rc-file-relative, so the rc is |
||||
//compiled from an overlay COPY beside the generated manifest). |
||||
const tclsh_manifest_content = common.replaceAll(b, common.replaceAll(b, common.readSourceFile(b, tcl_source_folder ++ "/win/tclsh.exe.manifest.in"), "@TCL_WIN_VERSION@", tcl_win_version), "@MACHINE@", "AMD64"); |
||||
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"); |
||||
|
||||
//-- flag classes, mirroring rules.vc composition for a threaded release |
||||
//build. OPTDEFINES equivalents translated for zig cc (clang/mingw-class): |
||||
//HAVE_NO_SEH (no __try), HAVE_WSPIAPI_H (guard exists in tclWinPort.h), |
||||
//header HAVEs clang satisfies; NO ZIPFS/MP defines (see header). |
||||
var ac_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer ac_flags.deinit(); |
||||
try ac_flags.appendSlice(&.{ |
||||
"-pipe", |
||||
"-finput-charset=UTF-8", |
||||
"-DPACKAGE_NAME=\"tcl\"", |
||||
"-DPACKAGE_TARNAME=\"tcl\"", |
||||
"-DPACKAGE_VERSION=\"" ++ tcl_dot_version ++ "\"", |
||||
"-DPACKAGE_STRING=\"tcl " ++ tcl_dot_version ++ "\"", |
||||
"-DPACKAGE_BUGREPORT=\"\"", |
||||
"-DPACKAGE_URL=\"\"", |
||||
"-DSTDC_HEADERS=1", |
||||
"-DHAVE_STDINT_H=1", |
||||
"-DHAVE_INTTYPES_H=1", |
||||
"-DHAVE_STDBOOL_H=1", |
||||
"-DHAVE_STDIO_H=1", |
||||
//8.6-era threading defines (rules.vc: TCL_THREADS always 1 on windows, |
||||
//USE_THREAD_ALLOC rides it while TCL_VERSION < 87) |
||||
"-DTCL_THREADS=1", |
||||
"-DUSE_THREAD_ALLOC=1", |
||||
"-DNDEBUG=1", |
||||
"-DTCL_CFG_OPTIMIZED=1", |
||||
"-DTCL_CFG_DO64BIT=1", |
||||
"-DHAVE_NO_SEH=1", |
||||
"-DHAVE_WSPIAPI_H=1", |
||||
"-DHAVE_ZLIB=1", |
||||
"-DMODULE_SCOPE=extern", |
||||
//makefile.vc PRJ_DEFINES (rides every flag class via rules.vc): the |
||||
//in-core libtommath uses FIXED cutoffs - TOMMATHOBJS carries no |
||||
//bn_cutoffs.c, so without MP_FIXED_CUTOFFS the cutoff variables are |
||||
//undefined at link. (inline=__inline, _CRT_*_NO_DEPRECATE and |
||||
//z_off_t=__int64 there are MSVC-isms not translated for clang/mingw.) |
||||
"-DMP_PREC=4", |
||||
"-DMP_FIXED_CUTOFFS", |
||||
//gcc-class entry handling (win/tcl.m4:671 extra_cflags): tclAppInit.c |
||||
//provides plain main() + setargv re-parse only under this define - see |
||||
//the entry-points note in the file header |
||||
"-DTCL_BROKEN_MAINARGS", |
||||
}); |
||||
|
||||
var config_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer config_flags.deinit(); |
||||
try config_flags.appendSlice(&.{ |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_RUNTIME_LIBDIR=\"{s}\"", .{libdir}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_RUNTIME_BINDIR=\"{s}\"", .{bindir}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_RUNTIME_INCDIR=\"{s}\"", .{includedir}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_RUNTIME_SCRDIR=\"{s}\"", .{tcl_library}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_RUNTIME_DOCDIR=\"{s}\"", .{mandir}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_INSTALL_LIBDIR=\"{s}\"", .{libdir}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_INSTALL_BINDIR=\"{s}\"", .{bindir}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_INSTALL_INCDIR=\"{s}\"", .{includedir}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_INSTALL_SCRDIR=\"{s}\"", .{tcl_library}), |
||||
try std.fmt.allocPrint(b.allocator, "-DCFG_INSTALL_DOCDIR=\"{s}\"", .{mandir}), |
||||
//consumed by tclPkgConfig.c only when !STATIC_BUILD - safe globally |
||||
"-DCFG_RUNTIME_DLLFILE=\"" ++ tcl_dll_file ++ "\"", |
||||
}); |
||||
|
||||
var warning_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer warning_flags.deinit(); |
||||
try warning_flags.appendSlice(&.{ |
||||
"-Wall", |
||||
"-Wextra", |
||||
"-Wshadow", |
||||
"-Wundef", |
||||
"-Wwrite-strings", |
||||
"-Wpointer-arith", |
||||
"-fextended-identifiers", |
||||
}); |
||||
|
||||
var c_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer c_flags.deinit(); |
||||
try c_flags.appendSlice(&.{ |
||||
"-O3", |
||||
"-fomit-frame-pointer", |
||||
"-finput-charset=UTF-8", |
||||
//required on windows (suite_tcl90 finding: 'linsert {} 0 a a' class |
||||
//crashes without both) - clang UBSan traps must stay fully off |
||||
"-fno-sanitize=undefined", |
||||
"-fno-sanitize-trap=undefined", |
||||
}); |
||||
|
||||
//app-class flags (rules.vc appcflags: everything EXCEPT BUILD_tcl) |
||||
var app_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer app_flags.deinit(); |
||||
try app_flags.appendSlice(c_flags.items); |
||||
try app_flags.appendSlice(warning_flags.items); |
||||
try app_flags.appendSlice(config_flags.items); |
||||
try app_flags.appendSlice(ac_flags.items); |
||||
|
||||
//pkg-class flags (rules.vc pkgcflags: + BUILD_tcl) - core objects, tommath |
||||
var pkg_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer pkg_flags.deinit(); |
||||
try pkg_flags.appendSlice(app_flags.items); |
||||
try pkg_flags.appendSlice(&.{"-DBUILD_tcl"}); |
||||
|
||||
var pkg_static_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer pkg_static_flags.deinit(); |
||||
try pkg_static_flags.appendSlice(pkg_flags.items); |
||||
try pkg_static_flags.appendSlice(&.{"-DSTATIC_BUILD"}); |
||||
|
||||
//stubs-class flags (rules.vc stubscflags: STATIC_BUILD always, no BUILD_tcl, |
||||
//no USE_TCL_STUBS while DOING_TCL) |
||||
var stub_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer stub_flags.deinit(); |
||||
try stub_flags.appendSlice(app_flags.items); |
||||
try stub_flags.appendSlice(&.{ "-DSTATIC_BUILD", "-fno-lto" }); |
||||
|
||||
//tclMainW copies (makefile.vc: tclMain.c + /DUNICODE /D_UNICODE) |
||||
var mainw_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer mainw_flags.deinit(); |
||||
try mainw_flags.appendSlice(pkg_flags.items); |
||||
try mainw_flags.appendSlice(&.{ "-DUNICODE", "-D_UNICODE" }); |
||||
var mainw_static_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer mainw_static_flags.deinit(); |
||||
try mainw_static_flags.appendSlice(pkg_static_flags.items); |
||||
try mainw_static_flags.appendSlice(&.{ "-DUNICODE", "-D_UNICODE" }); |
||||
|
||||
//tclAppInit copies (makefile.vc: appcflags + UNICODE + TCL_USE_STATIC_PACKAGES) |
||||
var appinit_dyn_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer appinit_dyn_flags.deinit(); |
||||
try appinit_dyn_flags.appendSlice(app_flags.items); |
||||
try appinit_dyn_flags.appendSlice(&.{ "-DUNICODE", "-D_UNICODE", "-DTCL_USE_STATIC_PACKAGES=0" }); |
||||
var appinit_static_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer appinit_static_flags.deinit(); |
||||
try appinit_static_flags.appendSlice(app_flags.items); |
||||
try appinit_static_flags.appendSlice(&.{ "-DSTATIC_BUILD", "-DUNICODE", "-D_UNICODE", "-DTCL_USE_STATIC_PACKAGES=1" }); |
||||
|
||||
//stubs-consumer extension objects (makefile.vc: appcflags + USE_TCL_STUBS; |
||||
//static-shell copies add STATIC_BUILD per OPTDEFINES) |
||||
var stubext_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer stubext_flags.deinit(); |
||||
try stubext_flags.appendSlice(app_flags.items); |
||||
try stubext_flags.appendSlice(&.{"-DUSE_TCL_STUBS=1"}); |
||||
var stubext_static_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer stubext_static_flags.deinit(); |
||||
try stubext_static_flags.appendSlice(app_flags.items); |
||||
try stubext_static_flags.appendSlice(&.{ "-DSTATIC_BUILD", "-DUSE_TCL_STUBS=1" }); |
||||
|
||||
//-- source inventory (win/makefile.vc COREOBJS/PLATFORMOBJS/TOMMATHOBJS, |
||||
//8.6.18 staged tree). tclMain.c appears ONCE here - the per-consumer loops |
||||
//compile it with mainw flags (the tclMainW.obj copy) and each consumer adds |
||||
//the plain ansi copy explicitly (both objs are in stock COREOBJS). |
||||
var generic_sources = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer generic_sources.deinit(); |
||||
const generic_names = [_][]const u8{ |
||||
"regcomp", "regerror", "regexec", "regfree", |
||||
"tclAlloc", "tclAssembly", "tclAsync", "tclBasic", |
||||
"tclBinary", "tclCkalloc", "tclClock", "tclCmdAH", |
||||
"tclCmdIL", "tclCmdMZ", "tclCompCmds", "tclCompCmdsGR", |
||||
"tclCompCmdsSZ", "tclCompExpr", "tclCompile", "tclConfig", |
||||
"tclDate", "tclDictObj", "tclDisassemble", "tclEncoding", |
||||
"tclEnsemble", "tclEnv", "tclEvent", "tclExecute", |
||||
"tclFCmd", "tclFileName", "tclGet", "tclHash", |
||||
"tclHistory", "tclIndexObj", "tclInterp", "tclIO", |
||||
"tclIOCmd", "tclIOGT", "tclIOSock", "tclIOUtil", |
||||
"tclIORChan", "tclIORTrans", "tclLink", "tclListObj", |
||||
"tclLiteral", "tclLoad", "tclMain", "tclNamesp", |
||||
"tclNotify", "tclOO", "tclOOBasic", "tclOOCall", |
||||
"tclOODefineCmds", "tclOOInfo", "tclOOMethod", "tclOOStubInit", |
||||
"tclObj", "tclOptimize", "tclPanic", "tclParse", |
||||
"tclPathObj", "tclPipe", "tclPkg", "tclPkgConfig", |
||||
"tclPosixStr", "tclPreserve", "tclProc", "tclRegexp", |
||||
"tclResolve", "tclResult", "tclScan", "tclStringObj", |
||||
"tclStrToD", "tclStubInit", "tclThread", "tclThreadAlloc", |
||||
"tclThreadJoin", "tclThreadStorage", "tclTimer", "tclTomMathInterface", |
||||
"tclTrace", "tclUtf", "tclUtil", "tclVar", |
||||
"tclZlib", |
||||
}; |
||||
inline for (generic_names) |nm| { |
||||
try generic_sources.append(tcl_source_folder ++ "/generic/" ++ nm ++ ".c"); |
||||
} |
||||
|
||||
var win_sources = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer win_sources.deinit(); |
||||
const win_names = [_][]const u8{ |
||||
"tclWin32Dll", "tclWinChan", "tclWinConsole", "tclWinError", |
||||
"tclWinFCmd", "tclWinFile", "tclWinInit", "tclWinLoad", |
||||
"tclWinNotify", "tclWinPipe", "tclWinSerial", "tclWinSock", |
||||
"tclWinThrd", "tclWinTime", |
||||
}; |
||||
inline for (win_names) |nm| { |
||||
try win_sources.append(tcl_source_folder ++ "/win/" ++ nm ++ ".c"); |
||||
} |
||||
|
||||
var tommath_sources = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer tommath_sources.deinit(); |
||||
const tommath_names = [_][]const u8{ |
||||
"bn_mp_add", "bn_mp_add_d", "bn_mp_and", "bn_mp_clamp", |
||||
"bn_mp_clear", "bn_mp_clear_multi", "bn_mp_cmp", "bn_mp_cmp_d", |
||||
"bn_mp_cmp_mag", "bn_mp_cnt_lsb", "bn_mp_copy", "bn_mp_count_bits", |
||||
"bn_mp_div", "bn_mp_div_d", "bn_mp_div_2", "bn_mp_div_2d", |
||||
"bn_s_mp_div_3", "bn_mp_exch", "bn_mp_expt_n", "bn_mp_grow", |
||||
"bn_mp_init", "bn_mp_init_copy", "bn_mp_init_multi", "bn_mp_init_set", |
||||
"bn_mp_init_size", "bn_mp_lshd", "bn_mp_mod", "bn_mp_mod_2d", |
||||
"bn_mp_mul", "bn_mp_mul_2", "bn_mp_mul_2d", "bn_mp_mul_d", |
||||
"bn_mp_neg", "bn_mp_or", "bn_mp_pack", "bn_mp_pack_count", |
||||
"bn_mp_radix_size", "bn_mp_radix_smap", "bn_mp_read_radix", "bn_mp_rshd", |
||||
"bn_mp_set", "bn_mp_shrink", "bn_mp_sqr", "bn_mp_sqrt", |
||||
"bn_mp_sub", "bn_mp_sub_d", "bn_mp_signed_rsh", "bn_mp_to_ubin", |
||||
"bn_mp_to_radix", "bn_mp_ubin_size", "bn_mp_unpack", "bn_mp_xor", |
||||
"bn_mp_zero", "bn_s_mp_add", "bn_s_mp_balance_mul", "bn_s_mp_karatsuba_mul", |
||||
"bn_s_mp_karatsuba_sqr", "bn_s_mp_mul_digs", "bn_s_mp_mul_digs_fast", "bn_s_mp_reverse", |
||||
"bn_s_mp_sqr", "bn_s_mp_sqr_fast", "bn_s_mp_sub", "bn_s_mp_toom_sqr", |
||||
"bn_s_mp_toom_mul", |
||||
}; |
||||
inline for (tommath_names) |nm| { |
||||
try tommath_sources.append(tcl_source_folder ++ "/libtommath/" ++ nm ++ ".c"); |
||||
} |
||||
|
||||
var core_sources = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer core_sources.deinit(); |
||||
try core_sources.appendSlice(generic_sources.items); |
||||
try core_sources.appendSlice(win_sources.items); |
||||
try core_sources.appendSlice(tommath_sources.items); |
||||
|
||||
const core_include = struct { |
||||
fn apply(m: *std.Build.Module, bb: *std.Build, uuid_dir: std.Build.LazyPath) void { |
||||
m.addIncludePath(uuid_dir); //overlay first (G-102: stale tree copies can never shadow) |
||||
m.addIncludePath(bb.path(tcl_source_folder ++ "/generic")); |
||||
m.addIncludePath(bb.path(tcl_source_folder ++ "/win")); |
||||
m.addIncludePath(bb.path(tcl_source_folder ++ "/libtommath")); |
||||
m.addIncludePath(bb.path(tcl_source_folder ++ "/compat/zlib")); |
||||
} |
||||
}.apply; |
||||
const win_syslibs = struct { |
||||
fn apply(m: *std.Build.Module) void { |
||||
//rules.vc baselibs |
||||
m.linkSystemLibrary("netapi32", .{}); |
||||
m.linkSystemLibrary("kernel32", .{}); |
||||
m.linkSystemLibrary("user32", .{}); |
||||
m.linkSystemLibrary("advapi32", .{}); |
||||
m.linkSystemLibrary("userenv", .{}); |
||||
m.linkSystemLibrary("ws2_32", .{}); |
||||
} |
||||
}.apply; |
||||
|
||||
// ================================================================ |
||||
//stub lib: libtclstub86 (TCLSTUBOBJS = tclStubLib, tclTomMathStubLib, |
||||
//tclOOStubLib - the 8.6 set) |
||||
const finalstublib = b.addLibrary(.{ |
||||
.linkage = .static, |
||||
.name = "libtclstub" ++ tcl_nodot_version, |
||||
.root_module = b.createModule(.{ |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
core_include(finalstublib.root_module, b, tcluuid_include_dir); |
||||
inline for (.{ "tclStubLib", "tclTomMathStubLib", "tclOOStubLib" }) |nm| { |
||||
finalstublib.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/generic/" ++ nm ++ ".c"), |
||||
.flags = stub_flags.items, |
||||
}); |
||||
} |
||||
finalstublib.root_module.link_libc = true; |
||||
|
||||
//ar'd copy under the mingw-conventional archive name -> lib/libtclstub86.a |
||||
//(the extension-builder consumable; the s flag is ranlib-equivalent) |
||||
const wf_stub = b.addWriteFiles(); |
||||
const lz_stublib_file = wf_stub.addCopyFile(finalstublib.getEmittedBin(), b.fmt("buildarchive/{s}", .{finalstublib.out_filename})); |
||||
const create_stublib_archive = b.addSystemCommand(&.{ b.graph.zig_exe, "ar", "crs", "--" }); |
||||
const stublib_archive_file = create_stublib_archive.addOutputFileArg("buildarchive/" ++ tcl_stub_lib_file); |
||||
create_stublib_archive.addFileArg(lz_stublib_file); |
||||
const installed_stublib_archive = b.addInstallFileWithDir(stublib_archive_file, .prefix, "lib/" ++ tcl_stub_lib_file); |
||||
installed_stublib_archive.step.dependOn(&create_stublib_archive.step); |
||||
b.getInstallStep().dependOn(&installed_stublib_archive.step); |
||||
|
||||
// ================================================================ |
||||
//tcl86t.dll - the shared core (zlib compiled in statically; see header) |
||||
const tcl_lib_shared = b.addLibrary(.{ |
||||
.linkage = .dynamic, |
||||
.name = tcl_dll_name, |
||||
.root_module = b.createModule(.{ |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
core_include(tcl_lib_shared.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_flags.items else pkg_flags.items; |
||||
tcl_lib_shared.root_module.addCSourceFile(.{ .file = b.path(src), .flags = flags }); |
||||
} |
||||
//the plain (ansi) tclMain copy - Tcl_MainEx + the #ifndef UNICODE support fns |
||||
tcl_lib_shared.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/generic/tclMain.c"), |
||||
.flags = pkg_flags.items, |
||||
}); |
||||
tcl_lib_shared.root_module.linkLibrary(zlib_lib_compile); |
||||
win_syslibs(tcl_lib_shared.root_module); |
||||
tcl_lib_shared.root_module.link_libc = true; |
||||
tcl_lib_shared.root_module.addWin32ResourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/win/tcl.rc"), |
||||
.include_paths = &.{ |
||||
b.path(tcl_source_folder ++ "/generic"), |
||||
b.path(tcl_source_folder ++ "/win"), |
||||
}, |
||||
}); |
||||
b.installArtifact(tcl_lib_shared); //bin/tcl86t.dll (+ implib -> lib/) |
||||
|
||||
// ================================================================ |
||||
//tclsh86ts.exe - the static shell (kit-class). STATIC_BUILD everywhere, |
||||
//tclWinReg/tclWinDde compiled in and registered via TCL_USE_STATIC_PACKAGES |
||||
//(makefile.vc static-build PLATFORMOBJS parity), stub objs linked (the nmake |
||||
//TCLSH link includes TCLSTUBLIB), entry = plain main (TCL_BROKEN_MAINARGS). |
||||
const tclsh_static_exe = b.addExecutable(.{ |
||||
.name = tclsh_static, |
||||
.root_module = b.createModule(.{ |
||||
.root_source_file = b.path("src/main.zig"), |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
tclsh_static_exe.stack_size = 2300000; //makefile.vc CONEXECMD -stack:2300000 |
||||
//console subsystem, C-provided main(): without this zig infers a windows- |
||||
//subsystem entry for the zig-root exe and the link demands WinMain |
||||
tclsh_static_exe.subsystem = .Console; |
||||
core_include(tclsh_static_exe.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; |
||||
tclsh_static_exe.root_module.addCSourceFile(.{ .file = b.path(src), .flags = flags }); |
||||
} |
||||
tclsh_static_exe.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/generic/tclMain.c"), |
||||
.flags = pkg_static_flags.items, |
||||
}); |
||||
//static-build PLATFORMOBJS additions (registered as static packages) |
||||
tclsh_static_exe.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/win/tclWinReg.c"), |
||||
.flags = stubext_static_flags.items, |
||||
}); |
||||
tclsh_static_exe.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/win/tclWinDde.c"), |
||||
.flags = stubext_static_flags.items, |
||||
}); |
||||
tclsh_static_exe.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/win/tclAppInit.c"), |
||||
.flags = appinit_static_flags.items, |
||||
}); |
||||
inline for (.{ "tclStubLib", "tclTomMathStubLib", "tclOOStubLib" }) |nm| { |
||||
tclsh_static_exe.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/generic/" ++ nm ++ ".c"), |
||||
.flags = stub_flags.items, |
||||
}); |
||||
} |
||||
tclsh_static_exe.root_module.linkLibrary(zlib_lib_compile); |
||||
win_syslibs(tclsh_static_exe.root_module); |
||||
tclsh_static_exe.root_module.link_libc = true; |
||||
tclsh_static_exe.root_module.addWin32ResourceFile(.{ |
||||
.file = tclshrc_overlay, |
||||
.include_paths = &.{ |
||||
b.path(tcl_source_folder ++ "/generic"), |
||||
b.path(tcl_source_folder ++ "/win"), |
||||
}, |
||||
}); |
||||
const install_tclsh_static = b.addInstallArtifact(tclsh_static_exe, .{}); |
||||
b.getInstallStep().dependOn(&install_tclsh_static.step); |
||||
|
||||
// ================================================================ |
||||
//tclsh86t.exe - the dynamic shell against tcl86t.dll (stock pair; proves |
||||
//the dll deliverable works) |
||||
const tclsh_dyn_exe = b.addExecutable(.{ |
||||
.name = tclsh_dynamic, |
||||
.root_module = b.createModule(.{ |
||||
.root_source_file = b.path("src/main.zig"), |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
tclsh_dyn_exe.stack_size = 2300000; |
||||
tclsh_dyn_exe.subsystem = .Console; //see the static shell's subsystem note |
||||
core_include(tclsh_dyn_exe.root_module, b, tcluuid_include_dir); |
||||
tclsh_dyn_exe.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/win/tclAppInit.c"), |
||||
.flags = appinit_dyn_flags.items, |
||||
}); |
||||
tclsh_dyn_exe.root_module.linkLibrary(tcl_lib_shared); |
||||
win_syslibs(tclsh_dyn_exe.root_module); |
||||
tclsh_dyn_exe.root_module.link_libc = true; |
||||
tclsh_dyn_exe.root_module.addWin32ResourceFile(.{ |
||||
.file = tclshrc_overlay, |
||||
.include_paths = &.{ |
||||
b.path(tcl_source_folder ++ "/generic"), |
||||
b.path(tcl_source_folder ++ "/win"), |
||||
}, |
||||
}); |
||||
const install_tclsh_dyn = b.addInstallArtifact(tclsh_dyn_exe, .{}); |
||||
b.getInstallStep().dependOn(&install_tclsh_dyn.step); |
||||
|
||||
// ================================================================ |
||||
//dde/registry loadable packages (stubs consumers; installed into the script |
||||
//library's dde/ and reg/ dirs where library pkgIndexes expect them) |
||||
const dde_dll = b.addLibrary(.{ |
||||
.linkage = .dynamic, |
||||
.name = std.fs.path.stem(dde_dll_file), |
||||
.root_module = b.createModule(.{ |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
core_include(dde_dll.root_module, b, tcluuid_include_dir); |
||||
dde_dll.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/win/tclWinDde.c"), |
||||
.flags = stubext_flags.items, |
||||
}); |
||||
dde_dll.root_module.linkLibrary(finalstublib); |
||||
win_syslibs(dde_dll.root_module); |
||||
dde_dll.root_module.link_libc = true; |
||||
const install_dde_dll = b.addInstallArtifact(dde_dll, .{ |
||||
.dest_dir = .{ .override = .{ .custom = "./" ++ script_install_dir_rel ++ "/dde" } }, |
||||
}); |
||||
|
||||
const reg_dll = b.addLibrary(.{ |
||||
.linkage = .dynamic, |
||||
.name = std.fs.path.stem(reg_dll_file), |
||||
.root_module = b.createModule(.{ |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
core_include(reg_dll.root_module, b, tcluuid_include_dir); |
||||
reg_dll.root_module.addCSourceFile(.{ |
||||
.file = b.path(tcl_source_folder ++ "/win/tclWinReg.c"), |
||||
.flags = stubext_flags.items, |
||||
}); |
||||
reg_dll.root_module.linkLibrary(finalstublib); |
||||
win_syslibs(reg_dll.root_module); |
||||
reg_dll.root_module.link_libc = true; |
||||
const install_reg_dll = b.addInstallArtifact(reg_dll, .{ |
||||
.dest_dir = .{ .override = .{ .custom = "./" ++ script_install_dir_rel ++ "/reg" } }, |
||||
}); |
||||
|
||||
// ================================================================ |
||||
//thread 2.8 + tclvfs (both hook their installs onto the default install |
||||
//step inside the helpers) |
||||
const thread_build = try build_tclthread86(tcl_source_folder, "../tclthread", b, target, optimize, finalstublib); |
||||
const build_tclthread_step = b.step("build-tclthread", "build the thread 2.8 loadable extension"); |
||||
build_tclthread_step.dependOn(&thread_build.lib.step); |
||||
|
||||
const tclvfs_compile = try tclvfs86.build_tclvfs86(tcl_source_folder, "../tclvfs", b, target, optimize, finalstublib); |
||||
const build_tclvfs_step = b.step("build-tclvfs", "build the tclvfs loadable extension"); |
||||
build_tclvfs_step.dependOn(&tclvfs_compile.step); |
||||
try tclvfs86.install_tclvfs_package("../tclvfs", b, tclvfs_compile); |
||||
|
||||
// ================================================================ |
||||
//install-libraries: the whole script library tree -> lib/tcl8.6 (each |
||||
//subdir package keeps its own pkgIndex; encoding/tzdata/msgs ride inside), |
||||
//dde/reg dlls into their package dirs, tm modules under lib/tcl8/<v>/ with |
||||
//versions parsed from the library pkgIndex files (makefile.vc nmakehlp -V |
||||
//parity). NOTE the whole-tree copy keeps http/msgcat/tcltest/platform as |
||||
//directory packages TOO (stock nmake installs those four as .tm only) - |
||||
//harmless duplication (same versions; the tm loader wins), simpler install. |
||||
const install_libraries = b.step("install-libraries", "install the tcl script library tree, dde/reg package dlls and tm modules"); |
||||
const install_library_tree = b.addInstallDirectory(.{ |
||||
.source_dir = b.path(tcl_source_folder ++ "/library"), |
||||
.install_dir = .prefix, |
||||
.install_subdir = script_install_dir_rel, |
||||
}); |
||||
install_libraries.dependOn(&install_library_tree.step); |
||||
install_libraries.dependOn(&install_dde_dll.step); |
||||
install_libraries.dependOn(&install_reg_dll.step); |
||||
|
||||
const ver_http = common.pkgIfneededVersion(b, tcl_source_folder ++ "/library/http/pkgIndex.tcl", "http"); |
||||
const ver_msgcat = common.pkgIfneededVersion(b, tcl_source_folder ++ "/library/msgcat/pkgIndex.tcl", "msgcat"); |
||||
const ver_tcltest = common.pkgIfneededVersion(b, tcl_source_folder ++ "/library/tcltest/pkgIndex.tcl", "tcltest"); |
||||
const ver_platform = common.pkgIfneededVersion(b, tcl_source_folder ++ "/library/platform/pkgIndex.tcl", "platform"); |
||||
const ver_pshell = common.pkgIfneededVersion(b, tcl_source_folder ++ "/library/platform/pkgIndex.tcl", "platform::shell"); |
||||
std.debug.print("tm versions: http {s}, msgcat {s}, tcltest {s}, platform {s}, platform::shell {s}\n", .{ ver_http, ver_msgcat, ver_tcltest, ver_platform, ver_pshell }); |
||||
const tm_installs = [_]struct { src: []const u8, subdir: []const u8, tmbase: []const u8, ver: []const u8 }{ |
||||
.{ .src = "/library/http/http.tcl", .subdir = "8.6", .tmbase = "http", .ver = ver_http }, |
||||
.{ .src = "/library/msgcat/msgcat.tcl", .subdir = "8.5", .tmbase = "msgcat", .ver = ver_msgcat }, |
||||
.{ .src = "/library/tcltest/tcltest.tcl", .subdir = "8.5", .tmbase = "tcltest", .ver = ver_tcltest }, |
||||
.{ .src = "/library/platform/platform.tcl", .subdir = "8.6", .tmbase = "platform", .ver = ver_platform }, |
||||
.{ .src = "/library/platform/shell.tcl", .subdir = "8.6", .tmbase = "platform/shell", .ver = ver_pshell }, |
||||
}; |
||||
for (tm_installs) |tmi| { |
||||
const tm_inst = b.addInstallFileWithDir(b.path(b.fmt("{s}{s}", .{ tcl_source_folder, tmi.src })), .prefix, b.pathJoin(&.{ module_install_dir_rel, tmi.subdir, b.fmt("{s}-{s}.tm", .{ tmi.tmbase, tmi.ver }) })); |
||||
install_libraries.dependOn(&tm_inst.step); |
||||
} |
||||
|
||||
// ================== post-build phases (built-shell runs) ================== |
||||
//Native-host only: these execute the built artifacts. All runs use the |
||||
//INSTALLED shells beside the installed lib tree with a scrubbed environment |
||||
//and NO TCL_LIBRARY - every run doubles as the hermetic-resolution proof the |
||||
//G-099 acceptance requires (exe/dll-relative ../lib/tcl8.6 discovery). |
||||
if (target.result.os.tag == builtin.os.tag) { |
||||
const installed_static = b.pathJoin(&.{ b.install_path, "bin", tclsh_static ++ ".exe" }); |
||||
const installed_dyn = b.pathJoin(&.{ b.install_path, "bin", tclsh_dynamic ++ ".exe" }); |
||||
const testreports_dir = common.replaceAll(b, b.pathJoin(&.{ b.install_path, "testreports" }), "\\", "/"); |
||||
|
||||
//-- smoke: both shells report the expected patchlevel and resolve the |
||||
//installed library (tzdata + autoload prove init.tcl + the tree) |
||||
const smoke_step = b.step("smoke", "smoke-test the installed shells (patchlevel + installed-library resolution + package loads)"); |
||||
const shell_runs = [_]struct { exe: []const u8, label: []const u8 }{ |
||||
.{ .exe = installed_static, .label = "static" }, |
||||
.{ .exe = installed_dyn, .label = "dynamic" }, |
||||
}; |
||||
for (shell_runs) |sr| { |
||||
const smoke = b.addSystemCommand(&.{sr.exe}); |
||||
common.scrubTclEnv(smoke); |
||||
smoke.has_side_effects = true; |
||||
smoke.setName(b.fmt("smoke {s}", .{sr.label})); |
||||
smoke.addFileArg(b.path("tools/suite_smoke.tcl")); |
||||
smoke.addArgs(&.{ "-mode", "installed", "-expect", tcl_h_patchlevel }); |
||||
smoke.step.dependOn(b.getInstallStep()); |
||||
smoke.step.dependOn(install_libraries); |
||||
smoke_step.dependOn(&smoke.step); |
||||
//package-resolution smoke: the built companions + the library-dir |
||||
//packages load in each shell (Thread/vfs via lib/, registry/dde via |
||||
//the script library's package dirs - static registrations in the |
||||
//static shell, the built dlls in the dynamic one) |
||||
const pkgs = b.addSystemCommand(&.{sr.exe}); |
||||
common.scrubTclEnv(pkgs); |
||||
pkgs.has_side_effects = true; |
||||
pkgs.setName(b.fmt("pkg_smoke {s}", .{sr.label})); |
||||
pkgs.addFileArg(b.path("tools/pkg_smoke.tcl")); |
||||
pkgs.addArgs(&.{ "Thread", "vfs", "registry", "dde" }); |
||||
pkgs.step.dependOn(b.getInstallStep()); |
||||
pkgs.step.dependOn(install_libraries); |
||||
smoke_step.dependOn(&pkgs.step); |
||||
} |
||||
|
||||
//-- test-gate: the 8.6 core testsuite under the installed static shell, |
||||
//judged on PARSED totals (all.tcl exit codes lie) vs the tracked |
||||
//dispositioned baseline. First census: run with -Dtestmode=record (the |
||||
//baseline file deliberately does not exist until the census is |
||||
//dispositioned - G-099). |
||||
const gate_step = b.step("test-gate", "tcl 8.6 core testsuite under the installed static 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 testmode_opt = b.option([]const u8, "testmode", "core test-gate mode: gate|record (record = census run, no baseline needed)") orelse "gate"; |
||||
const gate_run = b.addSystemCommand(&.{installed_static}); |
||||
common.scrubTclEnv(gate_run); |
||||
gate_run.has_side_effects = true; |
||||
gate_run.setName("core test-gate"); |
||||
gate_run.addFileArg(b.path("tools/test_gate.tcl")); |
||||
gate_run.addArgs(&.{ "-library", "tclcore", "-mode", testmode_opt }); |
||||
gate_run.addArg("-testsdir"); |
||||
gate_run.addArg(common.replaceAll(b, b.pathFromRoot(tcl_source_folder ++ "/tests"), "\\", "/")); |
||||
if (std.mem.eql(u8, testmode_opt, "gate")) { |
||||
gate_run.addArg("-baseline"); |
||||
if (common.pathExists(b, "expected_test_failures.txt")) { |
||||
gate_run.addFileArg(b.path("expected_test_failures.txt")); |
||||
} else { |
||||
//missing baseline: pass the conventional path so the engine's |
||||
//message names the file to create after the record-mode census |
||||
gate_run.addArg(common.replaceAll(b, b.pathFromRoot("expected_test_failures.txt"), "\\", "/")); |
||||
} |
||||
} |
||||
gate_run.addArg("-logfile"); |
||||
gate_run.addArg(common.replaceAll(b, b.pathFromRoot("../tcltest.log"), "\\", "/")); |
||||
gate_run.addArg("-summaryfile"); |
||||
gate_run.addArg(b.fmt("{s}/tclcore.summary", .{testreports_dir})); |
||||
if (testargs_opt.len != 0) { |
||||
gate_run.addArgs(&.{ "-testargs", testargs_opt }); |
||||
} |
||||
gate_run.step.dependOn(b.getInstallStep()); |
||||
gate_run.step.dependOn(install_libraries); |
||||
gate_step.dependOn(&gate_run.step); |
||||
|
||||
//-- per-library testsuites (suite_tcl90 G-107 pattern, thread+tclvfs |
||||
//arm): policy tiers gate|record|skip resolved at invocation. |
||||
// -Dtestpolicy-<lib>=gate|record|skip -Dtestargs-<lib>="..." |
||||
// -Dtestnotfiles-<lib>="a.test b.test" |
||||
//Baselines expected_test_failures_thread.txt / _tclvfs.txt are created |
||||
//from dispositioned record-mode censuses (same first-census flow as the |
||||
//core gate). |
||||
const LibSuite = struct { |
||||
name: []const u8, |
||||
testsdir: []const u8, |
||||
driver: []const u8, |
||||
default_policy: []const u8, |
||||
baseline: []const u8, |
||||
default_notfiles: []const u8, |
||||
}; |
||||
const lib_suites = [_]LibSuite{ |
||||
.{ .name = "thread", .testsdir = "../tclthread/tests", .driver = "all.tcl", .default_policy = "gate", .baseline = "expected_test_failures_thread.txt", .default_notfiles = "" }, |
||||
.{ .name = "tclvfs", .testsdir = "../tclvfs/tests", .driver = "all.tcl", .default_policy = "gate", .baseline = "expected_test_failures_tclvfs.txt", .default_notfiles = "" }, |
||||
}; |
||||
const libtests_step = b.step("test-libraries", "library testsuites (thread, tclvfs) under the installed static shell per policy tier"); |
||||
for (lib_suites) |ls| { |
||||
const policy = b.option([]const u8, b.fmt("testpolicy-{s}", .{ls.name}), b.fmt("{s} testsuite policy: gate|record|skip (default {s})", .{ ls.name, ls.default_policy })) orelse ls.default_policy; |
||||
const lib_testargs = b.option([]const u8, b.fmt("testargs-{s}", .{ls.name}), b.fmt("extra tcltest/driver args for the {s} testsuite", .{ls.name})) orelse ""; |
||||
const lib_notfiles = b.option([]const u8, b.fmt("testnotfiles-{s}", .{ls.name}), b.fmt("test-file glob patterns excluded from the {s} run (default \"{s}\")", .{ ls.name, ls.default_notfiles })) orelse ls.default_notfiles; |
||||
const step = b.step(b.fmt("test-{s}", .{ls.name}), b.fmt("{s} testsuite under the installed static shell (policy: {s})", .{ ls.name, policy })); |
||||
libtests_step.dependOn(step); |
||||
if (std.mem.eql(u8, policy, "skip")) continue; |
||||
if (!std.mem.eql(u8, policy, "gate") and !std.mem.eql(u8, policy, "record")) { |
||||
std.debug.panic("-Dtestpolicy-{s}: unknown policy '{s}' (expected gate|record|skip)", .{ ls.name, policy }); |
||||
} |
||||
const run = b.addSystemCommand(&.{installed_static}); |
||||
common.scrubTclEnv(run); |
||||
run.has_side_effects = true; |
||||
run.setName(b.fmt("test {s}", .{ls.name})); |
||||
run.addFileArg(b.path("tools/test_gate.tcl")); |
||||
run.addArgs(&.{ "-library", ls.name, "-mode", policy }); |
||||
run.addArg("-testsdir"); |
||||
run.addArg(common.replaceAll(b, b.pathFromRoot(ls.testsdir), "\\", "/")); |
||||
run.addArgs(&.{ "-driver", ls.driver }); |
||||
if (std.mem.eql(u8, policy, "gate")) { |
||||
run.addArg("-baseline"); |
||||
if (common.pathExists(b, ls.baseline)) { |
||||
run.addFileArg(b.path(ls.baseline)); |
||||
} else { |
||||
run.addArg(common.replaceAll(b, b.pathFromRoot(ls.baseline), "\\", "/")); |
||||
} |
||||
} |
||||
run.addArg("-logfile"); |
||||
run.addArg(b.fmt("{s}/{s}.log", .{ testreports_dir, ls.name })); |
||||
run.addArg("-summaryfile"); |
||||
run.addArg(b.fmt("{s}/{s}.summary", .{ testreports_dir, ls.name })); |
||||
if (lib_testargs.len != 0) { |
||||
run.addArgs(&.{ "-testargs", lib_testargs }); |
||||
} |
||||
if (lib_notfiles.len != 0) { |
||||
run.addArgs(&.{ "-notfiles", lib_notfiles }); |
||||
} |
||||
run.step.dependOn(b.getInstallStep()); |
||||
run.step.dependOn(install_libraries); |
||||
step.dependOn(&run.step); |
||||
} |
||||
} |
||||
} |
||||
|
||||
//G-102 bootstrap mode (suite_tcl90 parity): registered when this recipe is |
||||
//invoked from the TRACKED suite directory. The stage layout matches suite.tcl's |
||||
//fossil staging so both flows share the staged invocation; the stage dir |
||||
//derives from THIS suite folder's name (copy-and-tweak isolation). |
||||
fn bootstrapMode(b: *std.Build) !void { |
||||
const optimize = b.option(std.builtin.OptimizeMode, "optimize", "optimize mode forwarded to the staged build (default ReleaseFast)") orelse .ReleaseFast; |
||||
const suite_dirname = std.fs.path.basename(b.pathFromRoot("")); |
||||
const stage_rel = b.pathJoin(&.{ "..", "_build", suite_dirname }); |
||||
|
||||
const stage_tool = b.addExecutable(.{ |
||||
.name = "stagetree", |
||||
.root_module = b.createModule(.{ |
||||
.root_source_file = b.path("tools/stagetree.zig"), |
||||
.target = b.graph.host, |
||||
}), |
||||
}); |
||||
|
||||
const stage_step = b.step("stage", "materialize pinned sources (build.zig.zon) + recipe copy into ../_build/<suite> (existing fossil/git checkouts left untouched)"); |
||||
|
||||
//dependency name (build.zig.zon) -> stage dir (the recipe-coupled dir names |
||||
//documented in sources.config) |
||||
const source_dirs = [_][2][]const u8{ |
||||
.{ "tcl", "tcl86" }, |
||||
.{ "tclthread", "tclthread" }, |
||||
.{ "tclvfs", "tclvfs" }, |
||||
}; |
||||
var deps_pending = false; |
||||
for (source_dirs) |sd| { |
||||
const dep_name = sd[0]; |
||||
const destdir = sd[1]; |
||||
const dest_from_root = b.pathJoin(&.{ stage_rel, destdir }); |
||||
//an existing fossil/git checkout is owned by the suite.tcl dev flow: skip |
||||
//the whole dependency so nothing is fetched for it (stagetree guards |
||||
//again at make time). |
||||
if (common.pathExists(b, b.pathJoin(&.{ dest_from_root, ".fslckout" })) or |
||||
common.pathExists(b, b.pathJoin(&.{ dest_from_root, "_FOSSIL_" })) or |
||||
common.pathExists(b, b.pathJoin(&.{ dest_from_root, ".git" }))) |
||||
{ |
||||
continue; |
||||
} |
||||
if (b.lazyDependency(dep_name, .{})) |dep| { |
||||
const run = b.addRunArtifact(stage_tool); |
||||
run.setName(b.fmt("stage {s}", .{destdir})); |
||||
run.has_side_effects = true; |
||||
//the dependency's content-addressed cache path doubles as the pin |
||||
//key: a manifest pin bump invalidates the materialized copy |
||||
const dep_root = dep.path("").getPath(b); |
||||
run.addArgs(&.{ "-key", dep_root, "-src", dep_root, "-dest", b.pathFromRoot(dest_from_root) }); |
||||
stage_step.dependOn(&run.step); |
||||
} else { |
||||
deps_pending = true; |
||||
} |
||||
} |
||||
if (deps_pending) return; //zig fetches the lazy deps, then re-runs configure |
||||
|
||||
//recipe copy -> <stage>/build (the same item list suite.tcl stages) |
||||
const recipe_items = [_][]const u8{ |
||||
"build86.zig", |
||||
"build_common.zig", |
||||
"build_zlib86", |
||||
"build_tclthread86", |
||||
"build_tclvfs86", |
||||
"expected_test_failures.txt", |
||||
"expected_test_failures_thread.txt", |
||||
"expected_test_failures_tclvfs.txt", |
||||
"src", |
||||
"tools", |
||||
}; |
||||
for (recipe_items) |item| { |
||||
const run = b.addRunArtifact(stage_tool); |
||||
run.setName(b.fmt("stage recipe {s}", .{item})); |
||||
run.has_side_effects = true; |
||||
run.addArgs(&.{ "-key", "-", "-src", b.pathFromRoot(item), "-dest", b.pathFromRoot(b.pathJoin(&.{ stage_rel, "build", item })) }); |
||||
stage_step.dependOn(&run.step); |
||||
} |
||||
|
||||
pub fn build(b: *std.Build) void { |
||||
_ = b; |
||||
@compileError("suite_tcl86 recipe derivation in progress (G-099) - sources are staged; see suite_tcl86/README.md DERIVATION for the makefile.vc-derived plan and goals/G-099-suite-tcl86-buildsuite.md for progress"); |
||||
//bootstrap: stage, then drive the staged recipe (per-zig-version cache dir - |
||||
//object caches must never be shared across zig versions; forward-slash paths |
||||
//throughout: the prefix flows into -D macros as C string literals) |
||||
const steps_opt = b.option([]const []const u8, "steps", "steps for the staged build (repeat the flag; default: install install-libraries smoke)") orelse @as([]const []const u8, &.{ "install", "install-libraries", "smoke" }); |
||||
var cachever: []const u8 = builtin.zig_version_string; |
||||
cachever = common.replaceAll(b, cachever, "+", "_"); |
||||
cachever = common.replaceAll(b, cachever, "/", "_"); |
||||
cachever = common.replaceAll(b, cachever, ":", "_"); |
||||
const nested = b.addSystemCommand(&.{ |
||||
b.graph.zig_exe, |
||||
"build", |
||||
"--build-file", |
||||
common.replaceAll(b, b.pathFromRoot(b.pathJoin(&.{ stage_rel, "build", "build86.zig" })), "\\", "/"), |
||||
"--cache-dir", |
||||
common.replaceAll(b, b.pathFromRoot(b.pathJoin(&.{ stage_rel, "build", b.fmt(".zig-cache-{s}", .{cachever}) })), "\\", "/"), |
||||
"--prefix", |
||||
common.replaceAll(b, b.pathFromRoot(b.pathJoin(&.{ stage_rel, "out" })), "\\", "/"), |
||||
b.fmt("-Doptimize={s}", .{@tagName(optimize)}), |
||||
}); |
||||
for (steps_opt) |s| nested.addArg(s); |
||||
nested.setName("staged build (nested zig build)"); |
||||
nested.has_side_effects = true; |
||||
//cwd = staged build dir (suite.tcl parity) |
||||
nested.setCwd(.{ .cwd_relative = b.pathFromRoot(b.pathJoin(&.{ stage_rel, "build" })) }); |
||||
//hermetic child shells (parity with suite.tcl) |
||||
for ([_][]const u8{ "TCLLIBPATH", "TCL_LIBRARY", "TK_LIBRARY" }) |ev| { |
||||
nested.removeEnvironmentVariable(ev); |
||||
} |
||||
nested.step.dependOn(stage_step); |
||||
const bootstrap_step = b.step("bootstrap", "stage + run the staged recipe end-to-end (no pre-existing tclsh required)"); |
||||
bootstrap_step.dependOn(&nested.step); |
||||
} |
||||
|
||||
@ -0,0 +1,141 @@
|
||||
//build_common.zig - shared configure-time helpers for the suite recipe files. |
||||
//Forked from suite_tcl90/build_common.zig (G-099 duplication-with-a-note posture) |
||||
//plus the 8.6-only pkgIfneededVersion parser (tm module versions derive from the |
||||
//library pkgIndex files, mirroring makefile.vc's nmakehlp -V derivation). |
||||
//These run while the build graph is being CONSTRUCTED (not as build steps). Reads |
||||
//are of staged/fetched source trees; generated content is never written into a |
||||
//source tree - it goes through b.addWriteFiles() overlays into the build cache and |
||||
//is consumed via overlay include dirs / overlay rc copies. |
||||
const std = @import("std"); |
||||
|
||||
pub fn pathExists(b: *std.Build, path_from_root: []const u8) bool { |
||||
const abs = b.pathFromRoot(path_from_root); |
||||
std.Io.Dir.cwd().access(b.graph.io, abs, .{}) catch return false; |
||||
return true; |
||||
} |
||||
|
||||
pub fn readSourceFile(b: *std.Build, path_from_root: []const u8) []u8 { |
||||
const abs = b.pathFromRoot(path_from_root); |
||||
return std.Io.Dir.cwd().readFileAlloc(b.graph.io, abs, b.allocator, .limited(8 * 1024 * 1024)) catch |err| { |
||||
std.debug.panic("suite recipe: cannot read {s}: {s}", .{ abs, @errorName(err) }); |
||||
}; |
||||
} |
||||
|
||||
//value of a '#define <name> "<value>"' line (e.g TCL_PATCH_LEVEL from tcl.h) |
||||
pub fn parseDefineString(content: []const u8, name: []const u8) ?[]const u8 { |
||||
var it = std.mem.splitScalar(u8, content, '\n'); |
||||
while (it.next()) |line| { |
||||
const trimmed = std.mem.trim(u8, line, " \t\r"); |
||||
if (!std.mem.startsWith(u8, trimmed, "#")) continue; |
||||
const after_hash = std.mem.trimStart(u8, trimmed[1..], " \t"); |
||||
if (!std.mem.startsWith(u8, after_hash, "define")) continue; |
||||
const after_def = std.mem.trimStart(u8, after_hash[6..], " \t"); |
||||
if (!std.mem.startsWith(u8, after_def, name)) continue; |
||||
const after_name = after_def[name.len..]; |
||||
if (after_name.len == 0 or (after_name[0] != ' ' and after_name[0] != '\t')) continue; |
||||
const q1 = std.mem.indexOfScalar(u8, after_name, '"') orelse continue; |
||||
const rest = after_name[q1 + 1 ..]; |
||||
const q2 = std.mem.indexOfScalar(u8, rest, '"') orelse continue; |
||||
return rest[0..q2]; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
pub fn replaceAll(b: *std.Build, input: []const u8, needle: []const u8, replacement: []const u8) []u8 { |
||||
return std.mem.replaceOwned(u8, b.allocator, input, needle, replacement) catch @panic("OOM"); |
||||
} |
||||
|
||||
//hermetic child shells: 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). The G-099 acceptance requires |
||||
//TCLLIBPATH/TCL_LIBRARY unset in ALL suite child invocations - the 8.6 recipe |
||||
//never sets TCL_LIBRARY (built-shell runs execute the INSTALLED shell beside its |
||||
//installed lib tree, proving hermetic exe-relative resolution). |
||||
pub fn scrubTclEnv(run: *std.Build.Step.Run) void { |
||||
for ([_][]const u8{ "TCLLIBPATH", "TCL_LIBRARY", "TK_LIBRARY" }) |ev| { |
||||
run.removeEnvironmentVariable(ev); |
||||
} |
||||
} |
||||
|
||||
//checkout uuid from a tree's manifest.uuid when the repo materializes it (a |
||||
//per-repo fossil 'manifest' setting; suite.tcl forces it on for staged |
||||
//checkouts), else "unrecorded" so provenance emission stays total. |
||||
pub fn manifestUuid(b: *std.Build, tree_from_root: []const u8) []const u8 { |
||||
const abs = b.pathFromRoot(b.fmt("{s}/manifest.uuid", .{tree_from_root})); |
||||
const data = std.Io.Dir.cwd().readFileAlloc(b.graph.io, abs, b.allocator, .limited(4096)) catch return "unrecorded"; |
||||
return std.mem.trim(u8, data, " \t\r\n"); |
||||
} |
||||
|
||||
//package version from a TEA tree's configure.ac 'AC_INIT([name],[version])' |
||||
//line (thread, tclvfs, ...). Version-derived artifact names must follow the |
||||
//checkout - hardcoded versions stamp mismatched sources (suite_tcl90's G-107 |
||||
//finding: a thread dll self-reporting the wrong version could never satisfy the |
||||
//testsuite's exact-version require). |
||||
pub fn acInitVersion(b: *std.Build, tree_from_root: []const u8) []const u8 { |
||||
const data = readSourceFile(b, b.fmt("{s}/configure.ac", .{tree_from_root})); |
||||
var it = std.mem.splitScalar(u8, data, '\n'); |
||||
while (it.next()) |line| { |
||||
const trimmed = std.mem.trim(u8, line, " \t\r"); |
||||
if (!std.mem.startsWith(u8, trimmed, "AC_INIT")) continue; |
||||
//the second [...] group is the version |
||||
var rest = trimmed; |
||||
var group: usize = 0; |
||||
while (std.mem.indexOfScalar(u8, rest, '[')) |open| { |
||||
const after = rest[open + 1 ..]; |
||||
const close = std.mem.indexOfScalar(u8, after, ']') orelse break; |
||||
group += 1; |
||||
if (group == 2) return b.dupe(after[0..close]); |
||||
rest = after[close + 1 ..]; |
||||
} |
||||
} |
||||
std.debug.panic("no AC_INIT version in {s}/configure.ac", .{tree_from_root}); |
||||
} |
||||
|
||||
pub fn stripChars(b: *std.Build, input: []const u8, drop: []const u8) []u8 { |
||||
var out = std.array_list.Managed(u8).init(b.allocator); |
||||
for (input) |ch| { |
||||
if (std.mem.indexOfScalar(u8, drop, ch) != null) continue; |
||||
out.append(ch) catch @panic("OOM"); |
||||
} |
||||
return out.items; |
||||
} |
||||
|
||||
//8.6 tm module versions: 'package ifneeded <name> <version> ...' from a library |
||||
//pkgIndex.tcl (makefile.vc derives PKG_HTTP_VER etc the same way via nmakehlp -V). |
||||
//Exact token match on the package name distinguishes 'platform' from |
||||
//'platform::shell'. |
||||
pub fn pkgIfneededVersion(b: *std.Build, pkgindex_from_root: []const u8, pkgname: []const u8) []const u8 { |
||||
const data = readSourceFile(b, pkgindex_from_root); |
||||
var lines = std.mem.splitScalar(u8, data, '\n'); |
||||
while (lines.next()) |line| { |
||||
var toks = std.mem.tokenizeAny(u8, line, " \t\r"); |
||||
const t0 = toks.next() orelse continue; |
||||
if (!std.mem.eql(u8, t0, "package")) continue; |
||||
const t1 = toks.next() orelse continue; |
||||
if (!std.mem.eql(u8, t1, "ifneeded")) continue; |
||||
const t2 = toks.next() orelse continue; |
||||
if (!std.mem.eql(u8, t2, pkgname)) continue; |
||||
const ver = toks.next() orelse continue; |
||||
return b.dupe(ver); |
||||
} |
||||
std.debug.panic("no 'package ifneeded {s}' in {s}", .{ pkgname, pkgindex_from_root }); |
||||
} |
||||
|
||||
//TCL_WIN_VERSION as the makefiles derive it: |
||||
//<dotversion>.<releaselevel>.<patchlevel with a/b/. stripped>, releaselevel |
||||
//0=alpha 1=beta 2=final (e.g 8.6 + 8.6.18 -> 8.6.2.8618) |
||||
pub fn winResourceVersion(b: *std.Build, dotversion: []const u8, patchlevel: []const u8) []u8 { |
||||
const rlevel: u8 = if (std.mem.indexOfScalar(u8, patchlevel, 'a') != null) |
||||
'0' |
||||
else if (std.mem.indexOfScalar(u8, patchlevel, 'b') != null) |
||||
'1' |
||||
else |
||||
'2'; |
||||
var stripped = std.array_list.Managed(u8).init(b.allocator); |
||||
for (patchlevel) |ch| { |
||||
if (ch == 'a' or ch == 'b' or ch == '.') continue; |
||||
stripped.append(ch) catch @panic("OOM"); |
||||
} |
||||
return std.fmt.allocPrint(b.allocator, "{s}.{c}.{s}", .{ dotversion, rlevel, stripped.items }) catch @panic("OOM"); |
||||
} |
||||
@ -0,0 +1,137 @@
|
||||
const std = @import("std"); |
||||
const common = @import("../build_common.zig"); |
||||
|
||||
//build_tclthread86 - thread 2.8 (thread-2-8-branch) loadable extension for the |
||||
//8.6 suite (G-099). Forked from suite_tcl90/build_tclthread (duplication-with-a- |
||||
//note posture) with the 2.8/8.6 differences: |
||||
// - dll name follows the nmake TEA convention for a tcl86 host: thread<VER>t |
||||
// (rules.vc keeps the 't' suffix while TCL_THREADS && TCL_VERSION <= 86) - |
||||
// e.g thread2813t.dll. |
||||
// - pkgIndex.tcl is GENERATED FROM THE CHECKOUT'S OWN pkgIndex.tcl.in |
||||
// (@PACKAGE_VERSION@/@PKG_LIB_FILE@/@PACKAGE_NAME@ substitution): 2.8's |
||||
// index carries the capital-'Thread' ifneeded plus the Ttrace package |
||||
// wiring, and loads the dll $dir-relative (TEA installed shape - the dll |
||||
// lives INSIDE lib/thread<version>/, not in bin/ as the 9.0 fork's |
||||
// hand-written index arranged). |
||||
// - lib/ttrace.tcl installs into the package dir (the generated index's |
||||
// '[file join $dir ttrace.tcl]' fallback branch). |
||||
// - no thread.rc in the 2-8 branch: no version resource step. |
||||
// - flags: TCL_THREADS=1 (8.6-era extension define); the 9.0 fork's |
||||
// ZIPFS_BUILD / TCL_WIDE_INT_TYPE defines dropped (9-isms; tcl.h derives |
||||
// wide ints itself under __GNUC__). |
||||
//Version-derived names follow the checkout's AC_INIT (suite_tcl90 G-107 |
||||
//lesson: hardcoded versions stamp mismatched sources). |
||||
|
||||
pub const TclThreadBuild = struct { |
||||
lib: *std.Build.Step.Compile, |
||||
pkgidx: std.Build.LazyPath, |
||||
version: []const u8, //e.g "2.8.13" (from the checkout's AC_INIT) |
||||
dll_file: []const u8, //e.g "thread2813t.dll" |
||||
pkgsub: []const u8, //e.g "lib/thread2.8.13" (prefix-relative package dir) |
||||
}; |
||||
|
||||
pub fn build_tclthread86(comptime tcldir: []const u8, comptime subdir: []const u8, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, stublib: *std.Build.Step.Compile) !TclThreadBuild { |
||||
const thread_version = common.acInitVersion(b, subdir); |
||||
const thread_vernodots = common.stripChars(b, thread_version, "."); |
||||
const pkg_lib_name = b.fmt("thread{s}t", .{thread_vernodots}); |
||||
const pkg_lib_file = b.fmt("{s}.dll", .{pkg_lib_name}); |
||||
const pkgsub = b.fmt("lib/thread{s}", .{thread_version}); |
||||
|
||||
//threadUuid.h (threadCmd.c #includes it; upstream makefiles generate it into |
||||
//their build dir) - generated into the cache, delivered via an include-dir |
||||
//overlay that also carries the dependency on the generation step. |
||||
const wrapfilecmd = b.addExecutable(.{ |
||||
.name = "wrapfiletofile", |
||||
.root_module = b.createModule(.{ |
||||
.root_source_file = b.path("tools/wrapfiletofile.zig"), |
||||
.target = b.graph.host, |
||||
}), |
||||
}); |
||||
const uuidcmd_step = b.addRunArtifact(wrapfilecmd); |
||||
uuidcmd_step.addArgs(&.{ |
||||
"-prefix", |
||||
"#define THREAD_VERSION_UUID \\", |
||||
"-prefixnl", |
||||
"1", |
||||
"-input", |
||||
}); |
||||
uuidcmd_step.addFileArg(b.path(subdir ++ "/manifest.uuid")); |
||||
uuidcmd_step.addArg("-output"); |
||||
const threaduuid_file = uuidcmd_step.addOutputFileArg("threadUuid.h"); |
||||
|
||||
const lib = b.addLibrary(.{ |
||||
.linkage = .dynamic, |
||||
.name = pkg_lib_name, |
||||
.root_module = b.createModule(.{ |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
var flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer flags.deinit(); |
||||
try flags.appendSlice(&.{ "-Wall", "-O3", "-fomit-frame-pointer" }); |
||||
try flags.appendSlice(&.{ |
||||
"-DPACKAGE_NAME=\"thread\"", |
||||
"-DPACKAGE_TARNAME=\"thread\"", |
||||
b.fmt("-DPACKAGE_VERSION=\"{s}\"", .{thread_version}), |
||||
b.fmt("-DPACKAGE_STRING=\"thread {s}\"", .{thread_version}), |
||||
"-DPACKAGE_BUGREPORT=\"\"", |
||||
"-DPACKAGE_URL=\"\"", |
||||
"-DTCL_CFG_OPTIMIZED=1", |
||||
"-DUSE_TCL_STUBS=1", |
||||
"-DTCL_THREADS=1", |
||||
"-DHAVE_INTTYPES_H=1", |
||||
"-static-libgcc", |
||||
}); |
||||
if (target.result.os.tag == .windows) { |
||||
try flags.appendSlice(&.{ |
||||
"-DBUILD_thread=/**/", |
||||
}); |
||||
} |
||||
|
||||
//thread-2-8-branch win/makefile.vc PRJ_OBJS (same 10-file set as trunk) |
||||
var tclthread_sources = std.array_list.Managed([]const u8).init(b.allocator); |
||||
try tclthread_sources.append(subdir ++ "/generic/threadNs.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/threadCmd.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/threadSvCmd.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/threadSpCmd.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/threadPoolCmd.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/psGdbm.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/psLmdb.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/threadSvListCmd.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/threadSvKeylistCmd.c"); |
||||
try tclthread_sources.append(subdir ++ "/generic/tclXkeylist.c"); |
||||
|
||||
lib.root_module.addIncludePath(b.path(subdir)); |
||||
lib.root_module.addIncludePath(b.path(subdir ++ "/generic")); |
||||
lib.root_module.addIncludePath(threaduuid_file.dirname()); |
||||
lib.root_module.addIncludePath(b.path(tcldir ++ "/generic")); |
||||
lib.root_module.addCSourceFiles(.{ |
||||
.files = tclthread_sources.items, |
||||
.flags = flags.items, |
||||
}); |
||||
|
||||
lib.root_module.linkLibrary(stublib); |
||||
lib.root_module.link_libc = true; |
||||
|
||||
//installed shape: lib/thread<version>/{<dll>, pkgIndex.tcl, ttrace.tcl} |
||||
const install_dll = b.addInstallArtifact(lib, .{ |
||||
.dest_dir = .{ .override = .{ .custom = pkgsub } }, |
||||
}); |
||||
b.getInstallStep().dependOn(&install_dll.step); |
||||
|
||||
//pkgIndex.tcl from the checkout's own template (capital-'Thread' ifneeded + |
||||
//Ttrace wiring ride along) |
||||
var pkgidx_content: []const u8 = common.readSourceFile(b, subdir ++ "/pkgIndex.tcl.in"); |
||||
pkgidx_content = common.replaceAll(b, pkgidx_content, "@PACKAGE_VERSION@", thread_version); |
||||
pkgidx_content = common.replaceAll(b, pkgidx_content, "@PKG_LIB_FILE@", pkg_lib_file); |
||||
pkgidx_content = common.replaceAll(b, pkgidx_content, "@PACKAGE_NAME@", "thread"); |
||||
const wf_pkgidx = b.addWriteFiles(); |
||||
const pkgidx = wf_pkgidx.add("pkgIndex.tcl", pkgidx_content); |
||||
const pkgidx_install = b.addInstallFileWithDir(pkgidx, .prefix, b.fmt("{s}/pkgIndex.tcl", .{pkgsub})); |
||||
b.getInstallStep().dependOn(&pkgidx_install.step); |
||||
const ttrace_install = b.addInstallFileWithDir(b.path(subdir ++ "/lib/ttrace.tcl"), .prefix, b.fmt("{s}/ttrace.tcl", .{pkgsub})); |
||||
b.getInstallStep().dependOn(&ttrace_install.step); |
||||
|
||||
return .{ .lib = lib, .pkgidx = pkgidx, .version = thread_version, .dll_file = pkg_lib_file, .pkgsub = pkgsub }; |
||||
} |
||||
@ -0,0 +1,161 @@
|
||||
const std = @import("std"); |
||||
const common = @import("../build_common.zig"); |
||||
|
||||
//build_tclvfs86 - tclvfs (TRUNK, 1.4.2 era) loadable extension for the 8.6 |
||||
//suite (G-099). Forked from suite_tcl90/build_tclvfs/build_tclvfs_shared.zig |
||||
//(duplication-with-a-note posture) with the 8.6 differences: |
||||
// - the dll is BUILT UNDER THE TCL8 TEA-NMAKE NAME (vfs<nodots>t.dll, e.g |
||||
// vfs142t.dll - rules.vc PRJLIBNAME8 with PROJECT=vfs, SUFX 't' kept on a |
||||
// tcl86 host): the configured library/vfs.tcl.in loads @PKG_LIB_FILE8@ |
||||
// with NO explicit init symbol, so Tcl's load-time package-name guessing |
||||
// derives the _Init name from the file's alpha prefix - 'vfs...' guesses |
||||
// Vfs_Init (correct); a 'tclvfs...' spelling guesses Tclvfs_Init and |
||||
// fails. @PKG_LIB_FILE9@ is still substituted so the script is complete. |
||||
// - the 9.0 fork's ZIPFS_BUILD / TCL_WIDE_INT_TYPE defines dropped (9-isms). |
||||
// - trunk vfs.c uses the same lowercase BUILD_vfs export guard as forTcl9 |
||||
// (verified in the staged tree), so the guard define carries over. |
||||
//The include-order shim carries over: under zig's mingw headers '__stat64' is |
||||
//a MACRO for _stat64 (_mingw_stat64.h, pulled in by <sys/stat.h>); tcl.h's |
||||
//'typedef struct __stat64 Tcl_StatBuf' parsed BEFORE that include binds a raw |
||||
//forever-incomplete __stat64 tag (8.6 tcl.h uses the same typedef under |
||||
//_WIN64). Compiling a generated TU that includes <sys/stat.h> first makes the |
||||
//macro visible when tcl.h parses. |
||||
|
||||
pub const TclvfsInfo = struct { |
||||
dotversion: []const u8, //e.g "1.4.2" |
||||
nodots: []const u8, //e.g "142" |
||||
dll_file: []const u8, //the tcl8-named dll this suite builds, e.g "vfs142t.dll" |
||||
dll9_file: []const u8, //tcl9 spelling for template substitution completeness |
||||
major: []const u8, |
||||
minor: []const u8, |
||||
}; |
||||
|
||||
pub fn versionInfo(b: *std.Build, comptime tree_from_root: []const u8) TclvfsInfo { |
||||
const dotversion = common.acInitVersion(b, tree_from_root); |
||||
const nodots = common.stripChars(b, dotversion, "."); |
||||
var parts = std.mem.splitScalar(u8, dotversion, '.'); |
||||
const major = parts.next() orelse "0"; |
||||
const minor = parts.next() orelse "0"; |
||||
return .{ |
||||
.dotversion = dotversion, |
||||
.nodots = nodots, |
||||
.dll_file = b.fmt("vfs{s}t.dll", .{nodots}), |
||||
.dll9_file = b.fmt("tcl9vfs{s}.dll", .{nodots}), |
||||
.major = b.dupe(major), |
||||
.minor = b.dupe(minor), |
||||
}; |
||||
} |
||||
|
||||
//configure-products (vfs.tcl, pkgIndex.tcl) generated from their .in templates |
||||
//at configure time. |
||||
fn configuredContent(b: *std.Build, comptime subdir: []const u8, comptime template: []const u8, info: TclvfsInfo) []const u8 { |
||||
var t: []const u8 = common.readSourceFile(b, subdir ++ "/" ++ template); |
||||
t = common.replaceAll(b, t, "@PACKAGE_VERSION@", info.dotversion); |
||||
t = common.replaceAll(b, t, "@PKG_LIB_FILE9@", info.dll9_file); |
||||
t = common.replaceAll(b, t, "@PKG_LIB_FILE8@", info.dll_file); |
||||
return t; |
||||
} |
||||
|
||||
//install the tclvfs script package (lib/vfs<ver>) into the prefix - the two |
||||
//generated configure-products, the library scripts + template dir copied, and |
||||
//the dll alongside (vfs.tcl loads it from its own dir via ::vfs::self). |
||||
pub fn install_tclvfs_package(comptime subdir: []const u8, b: *std.Build, tclvfs_compile: *std.Build.Step.Compile) !void { |
||||
const info = versionInfo(b, subdir); |
||||
const pkgsub = b.fmt("lib/vfs{s}", .{info.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 = b.fmt("{s}/template", .{pkgsub}), |
||||
}); |
||||
b.getInstallStep().dependOn(&install_template.step); |
||||
const wf = b.addWriteFiles(); |
||||
inline for (.{ |
||||
.{ "library/vfs.tcl.in", "vfs.tcl" }, |
||||
.{ "pkgIndex.tcl.in", "pkgIndex.tcl" }, |
||||
}) |pair| { |
||||
const gen = wf.add(pair[1], configuredContent(b, subdir, pair[0], info)); |
||||
const inst = b.addInstallFileWithDir(gen, .prefix, b.fmt("{s}/{s}", .{ pkgsub, pair[1] })); |
||||
b.getInstallStep().dependOn(&inst.step); |
||||
} |
||||
const dll_inst = b.addInstallFileWithDir(tclvfs_compile.getEmittedBin(), .prefix, b.fmt("{s}/{s}", .{ pkgsub, info.dll_file })); |
||||
b.getInstallStep().dependOn(&dll_inst.step); |
||||
} |
||||
|
||||
pub fn build_tclvfs86(comptime tcldir: []const u8, comptime subdir: []const u8, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, stublib: *std.Build.Step.Compile) !*std.Build.Step.Compile { |
||||
const info = versionInfo(b, subdir); |
||||
const lib = b.addLibrary(.{ |
||||
.linkage = .dynamic, |
||||
.name = b.fmt("vfs{s}t", .{info.nodots}), |
||||
.root_module = b.createModule(.{ |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
var flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer flags.deinit(); |
||||
try flags.appendSlice(&.{ "-Wall", "-O3", "-fomit-frame-pointer" }); |
||||
try flags.appendSlice(&.{ |
||||
"-DPACKAGE_NAME=\"vfs\"", |
||||
"-DPACKAGE_TARNAME=\"vfs\"", |
||||
b.fmt("-DPACKAGE_VERSION=\"{s}\"", .{info.dotversion}), |
||||
b.fmt("-DPACKAGE_STRING=\"vfs {s}\"", .{info.dotversion}), |
||||
"-DPACKAGE_BUGREPORT=\"\"", |
||||
"-DPACKAGE_URL=\"\"", |
||||
//vfs.c guards <sys/stat.h> paths behind HAVE_SYS_STAT_H and needs the |
||||
//COMPLETE Tcl_StatBuf - see the include-order shim below. |
||||
"-DHAVE_SYS_STAT_H=1", |
||||
"-DTCL_CFG_OPTIMIZED=1", |
||||
"-DUSE_TCL_STUBS=1", |
||||
}); |
||||
if (target.result.os.tag == .windows) { |
||||
try flags.appendSlice(&.{ |
||||
"-DBUILD_vfs=/**/", |
||||
}); |
||||
} |
||||
|
||||
lib.root_module.addIncludePath(b.path(subdir)); |
||||
lib.root_module.addIncludePath(b.path(subdir ++ "/generic")); |
||||
lib.root_module.addIncludePath(b.path(tcldir ++ "/generic")); |
||||
if (target.result.os.tag == .windows) { |
||||
lib.root_module.addIncludePath(b.path(tcldir ++ "/win")); |
||||
} else { |
||||
lib.root_module.addIncludePath(b.path(tcldir ++ "/unix")); |
||||
} |
||||
//Include-order shim (overlay TU - source trees never written): see the |
||||
//header comment for the __stat64 macro-vs-tag story. |
||||
const wf_shim = b.addWriteFiles(); |
||||
const vfs_shim = wf_shim.add("vfs_statorder_shim.c", |
||||
\\/* generated include-order shim - see build_tclvfs86.zig */ |
||||
\\#include <sys/stat.h> |
||||
\\#include "vfs.c" |
||||
\\ |
||||
); |
||||
lib.root_module.addCSourceFile(.{ |
||||
.file = vfs_shim, |
||||
.flags = flags.items, |
||||
}); |
||||
|
||||
lib.root_module.linkLibrary(stublib); |
||||
lib.root_module.link_libc = true; |
||||
lib.root_module.addWin32ResourceFile(.{ |
||||
.file = b.path(subdir ++ "/win/tclvfs.rc"), |
||||
.flags = &.{ |
||||
b.fmt("-DPACKAGE_MAJOR={s}", .{info.major}), |
||||
b.fmt("-DPACKAGE_MINOR={s}", .{info.minor}), |
||||
b.fmt("-DPACKAGE_VERSION={s}", .{info.dotversion}), |
||||
b.fmt("-DDOTVERSION={s}", .{info.dotversion}), |
||||
b.fmt("-DCOMMAVERSION={s}", .{info.dotversion}), |
||||
b.fmt("-DVERSION={s}", .{info.nodots}), |
||||
}, |
||||
}); //ignored for non-windows targets |
||||
|
||||
return lib; |
||||
} |
||||
@ -0,0 +1,50 @@
|
||||
const std = @import("std"); |
||||
|
||||
//build_zlib86 - static zlib for the 8.6 core (G-099). 8.6's win/makefile.vc does |
||||
//NOT compile zlib for shared builds - it links a PREBUILT compat/zlib/win64/ |
||||
//zdll.lib and ships zlib1.dll beside the core dll; only STATIC nmake builds |
||||
//compile zlib sources in (the 11-file ZLIBOBJS list). The zig-only recipe |
||||
//compiles that same source list into a static lib linked into BOTH the tcl86 |
||||
//dll and the static shell, eliminating the shipped zlib1.dll (deviation from |
||||
//stock shared-layout recorded in README.md DERIVATION). |
||||
// |
||||
//Leaner than suite_tcl90's build_zlib (no zlib1.dll product, no minigzip/example |
||||
//test exes, no directory iteration - the exact makefile.vc ZLIBOBJS list). |
||||
pub fn build_zlib86(comptime subdir: []const u8, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*std.Build.Step.Compile { |
||||
const statlib = b.addLibrary(.{ |
||||
.linkage = .static, |
||||
.name = "z86", |
||||
.root_module = b.createModule(.{ |
||||
.target = target, |
||||
.optimize = optimize, |
||||
}), |
||||
}); |
||||
var zlib_flags = std.array_list.Managed([]const u8).init(b.allocator); |
||||
defer zlib_flags.deinit(); |
||||
//flag set proven by suite_tcl90's zlib compile under zig cc |
||||
try zlib_flags.appendSlice(&.{ "-Wall", "-Wno-error=implicit-function-declaration", "-O3", "-fPIC", "-fno-omit-frame-pointer", "-fno-sanitize=undefined" }); |
||||
|
||||
//makefile.vc ZLIBOBJS (the static-build compile list) |
||||
const zlib_sources = [_][]const u8{ |
||||
subdir ++ "/adler32.c", |
||||
subdir ++ "/compress.c", |
||||
subdir ++ "/crc32.c", |
||||
subdir ++ "/deflate.c", |
||||
subdir ++ "/infback.c", |
||||
subdir ++ "/inffast.c", |
||||
subdir ++ "/inflate.c", |
||||
subdir ++ "/inftrees.c", |
||||
subdir ++ "/trees.c", |
||||
subdir ++ "/uncompr.c", |
||||
subdir ++ "/zutil.c", |
||||
}; |
||||
var sources = std.array_list.Managed([]const u8).init(b.allocator); |
||||
try sources.appendSlice(&zlib_sources); |
||||
statlib.root_module.link_libc = true; |
||||
statlib.root_module.addIncludePath(b.path(subdir)); |
||||
statlib.root_module.addCSourceFiles(.{ |
||||
.files = sources.items, |
||||
.flags = zlib_flags.items, |
||||
}); |
||||
return statlib; |
||||
} |
||||
@ -0,0 +1,23 @@
|
||||
# suite_tcl86 expected Tcl-core-test failures (G-099 test gate baseline). |
||||
# One test name per line; comments carry the disposition reason. 'suite.tcl test' |
||||
# (zig build test-gate, mode gate) fails on any failed test NOT listed here, and |
||||
# notes listed tests that no longer fail (candidates for removal). |
||||
# |
||||
# Baseline established 2026-07-26 against tcl core-8-6-branch 1d1d5cbd91eb |
||||
# (8.6.18), zig 0.16.0 suite build (tclsh86ts.exe static shell), machine SuperBee |
||||
# (windows 11). Full-run totals at capture: 46525 run / 39227 passed / 7296 |
||||
# skipped (constraint-driven: serverNeeded 1996, testregexp 1099, testchannel |
||||
# 859, ... the test* internal-command constraints are off - tcltest built without |
||||
# the tcltest C package) / 2 failed, both dispositioned below. |
||||
# |
||||
# The suite_tcl90 (9.0.5) fCmd-31.x/32.x 'file home' machine-trait failures do |
||||
# NOT appear here: 'file home' is a Tcl 8.7/9.0 command, absent in 8.6. |
||||
|
||||
# -- machine trait: windows admin-share self-globbing. The test globs |
||||
# //<hostname>/c/*Test expecting exactly one match (globTest). On this machine |
||||
# several *Test/*test dirs exist at C:\ (buildtest, globTest, notes_test, |
||||
# tcltest, test, _test), so the case-insensitive glob returns all of them. Fails |
||||
# on this machine's directory layout regardless of the runtime (suite_tcl90's |
||||
# 9.0.5 baseline dispositions the same two names for the same reason). |
||||
filename-16.12 |
||||
filename-16.13 |
||||
@ -0,0 +1,26 @@
|
||||
# suite_tcl86 expected tclvfs-testsuite failures (G-099 library test gate baseline). |
||||
# One test name per line; comments carry the disposition reason. The test-tclvfs |
||||
# step (policy gate) fails on any failed test NOT listed here, and notes listed |
||||
# tests that no longer fail (candidates for removal). |
||||
# |
||||
# Baseline established 2026-07-26 against tclvfs trunk f082c47f9b2e (vfs 1.4.2 |
||||
# era; the live dev-flow trunk tip is ahead of the build.zig.zon pin 9954261983 |
||||
# - both are trunk/1.4.2, the pin is the no-tclsh flow's provenance record), |
||||
# suite-built tclsh86ts.exe 8.6.18, machine SuperBee (windows 11). Census totals |
||||
# at capture: 51 run / 41 passed / 8 skipped (constraint-driven: ftp 3, |
||||
# bug1533748 2, zipcat 2, knownBug 1) / 2 failed, both dispositioned below. |
||||
# |
||||
# suite_tcl90 moved tclvfs to the forTcl9 branch (1.5.0) where these two |
||||
# network-reaching vfsUrl entries SKIP; on trunk (1.4.2) they run and fail |
||||
# against the retired external FTP host - the same 1.4.2-era entries that |
||||
# suite_tcl90's tclvfs baseline note records from its 2026-07-21 capture. |
||||
|
||||
# -- external-service dependency: both tests reach a public FTP server |
||||
# (ftp://ftp.tcl.tk) the Tcl project retired years ago, so they fail on any |
||||
# modern machine/network regardless of the build (vfsUrl-1.2 asserts |
||||
# 'file exists ftp://ftp.tcl.tk' == 1; vfsUrl-2.1 copies a file from it). Not a |
||||
# suite defect - the tclvfs URL handlers themselves are exercised by the passing |
||||
# vfsUrl mount/parse cases. (The file's other ftp cases are gated behind the |
||||
# 'ftp' constraint and skip; these two are not constraint-gated.) |
||||
vfsUrl-1.2 |
||||
vfsUrl-2.1 |
||||
@ -0,0 +1,13 @@
|
||||
# suite_tcl86 expected thread-testsuite failures (G-099 library test gate baseline). |
||||
# One test name per line; comments carry the disposition reason. The test-thread |
||||
# step (policy gate) fails on any failed test NOT listed here, and notes listed |
||||
# tests that no longer fail (candidates for removal). |
||||
# |
||||
# Baseline established 2026-07-26 against thread thread-2-8-branch e14fa771ae2e |
||||
# (thread 2.8.13), suite-built tclsh86ts.exe 8.6.18, machine SuperBee (windows |
||||
# 11). Census totals at capture: 142 run / 126 passed / 16 skipped / 0 failed - |
||||
# EMPTY baseline; skips are constraint-driven (chanTransfer windows, have_gdbm/ |
||||
# have_lmdb backends not built into the suite's psGdbm/psLmdb compile - same |
||||
# constraint profile as suite_tcl90's thread 3.0.7 census). |
||||
# |
||||
# (no expected failures) |
||||
@ -0,0 +1,4 @@
|
||||
const std = @import("std"); |
||||
|
||||
pub const _start = void; |
||||
pub const WinMainCRTStartup = void; |
||||
@ -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 <pkg> additionally asserts the package's |
||||
#critcl accelerator engaged (::<pkg>::accel(critcl) == 1, tcllibc proof). |
||||
#Formerly suite.tcl inline smokes. |
||||
# |
||||
#args: ?-gui 0|1? ?-accel <pkg>? 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 |
||||
@ -0,0 +1,173 @@
|
||||
//stagetree - materialize a source tree (or single file) into the build stage (G-102). |
||||
// |
||||
//Used by the suite recipe's bootstrap mode to copy zon-fetched dependency trees and |
||||
//the tracked recipe files into the _build/<suite> stage layout that the staged build |
||||
//invocation (and the fossil dev flow) share. Invariant guarded here: an existing |
||||
//fossil/git CHECKOUT at the destination is never touched - the fossil dev flow owns |
||||
//those directories. |
||||
// |
||||
// stagetree -key <pin|-> -src <path> -dest <path> |
||||
// |
||||
// -key <pin> pinned-tree mode: if <dest>/.stage-materialized already contains |
||||
// exactly <pin>, the destination is up to date and nothing is done. |
||||
// Otherwise the destination tree is DELETED, the source tree copied, |
||||
// and the marker written. Pass the dependency's content-addressed |
||||
// cache path as the pin - a pin bump changes it, invalidating the copy. |
||||
// -key - always mode (recipe files): delete destination, copy, no marker. |
||||
// |
||||
//Informational prints go to stdout only: zig 0.16's build_runner reports any step |
||||
//with nonempty stderr under a misleading 'failed command:' block even on success. |
||||
const std = @import("std"); |
||||
|
||||
const usage = |
||||
\\Usage: ./stagetree -key <pin|-> -src <path> -dest <path> |
||||
; |
||||
|
||||
const marker_name = ".stage-materialized"; |
||||
|
||||
pub fn main(init: std.process.Init.Minimal) !void { |
||||
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
||||
defer arena_state.deinit(); |
||||
const arena = arena_state.allocator(); |
||||
|
||||
var threaded: std.Io.Threaded = .init(arena, .{ |
||||
.environ = init.environ, |
||||
.argv0 = .init(init.args), |
||||
}); |
||||
defer threaded.deinit(); |
||||
const io = threaded.io(); |
||||
|
||||
const args = try init.args.toSlice(arena); |
||||
var opt_key: ?[]const u8 = null; |
||||
var opt_src: ?[]const u8 = null; |
||||
var opt_dest: ?[]const u8 = null; |
||||
{ |
||||
var i: usize = 1; |
||||
while (i < args.len) : (i += 1) { |
||||
const arg = args[i]; |
||||
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "-help", arg)) { |
||||
std.debug.print("{s}\n", .{usage}); |
||||
return std.process.cleanExit(io); |
||||
} else if (std.mem.eql(u8, "-key", arg)) { |
||||
i += 1; |
||||
if (i >= args.len) fatal("expected arg after '{s}'", .{arg}); |
||||
opt_key = args[i]; |
||||
} else if (std.mem.eql(u8, "-src", arg)) { |
||||
i += 1; |
||||
if (i >= args.len) fatal("expected arg after '{s}'", .{arg}); |
||||
opt_src = args[i]; |
||||
} else if (std.mem.eql(u8, "-dest", arg)) { |
||||
i += 1; |
||||
if (i >= args.len) fatal("expected arg after '{s}'", .{arg}); |
||||
opt_dest = args[i]; |
||||
} else { |
||||
fatal("unknown argument '{s}'\n{s}", .{ arg, usage }); |
||||
} |
||||
} |
||||
} |
||||
const key = opt_key orelse fatal("missing -key", .{}); |
||||
const src = opt_src orelse fatal("missing -src", .{}); |
||||
const dest = opt_dest orelse fatal("missing -dest", .{}); |
||||
const cwd = std.Io.Dir.cwd(); |
||||
|
||||
//never touch a fossil/git checkout at the destination |
||||
inline for (.{ ".fslckout", "_FOSSIL_", ".git" }) |vcsmark| { |
||||
const p = try std.fs.path.join(arena, &.{ dest, vcsmark }); |
||||
if (pathExists(cwd, io, p)) { |
||||
try say(io, arena, "stagetree: {s} is a VCS checkout ({s} present) - left untouched\n", .{ dest, vcsmark }); |
||||
return std.process.cleanExit(io); |
||||
} |
||||
} |
||||
|
||||
const src_stat = cwd.statFile(io, src, .{}) catch |err| { |
||||
fatal("cannot stat -src '{s}': {s}", .{ src, @errorName(err) }); |
||||
}; |
||||
|
||||
const marker_path = try std.fs.path.join(arena, &.{ dest, marker_name }); |
||||
const pinned = !std.mem.eql(u8, key, "-"); |
||||
if (pinned and src_stat.kind == .directory) { |
||||
if (cwd.readFileAlloc(io, marker_path, arena, .limited(64 * 1024))) |existing| { |
||||
if (std.mem.eql(u8, std.mem.trim(u8, existing, " \r\n"), key)) { |
||||
try say(io, arena, "stagetree: {s} up to date (pin match)\n", .{dest}); |
||||
return std.process.cleanExit(io); |
||||
} |
||||
} else |_| {} |
||||
} |
||||
|
||||
if (src_stat.kind == .file) { |
||||
//single recipe file: parent dir must exist or be creatable; plain replace |
||||
if (std.fs.path.dirname(dest)) |destparent| { |
||||
cwd.createDirPath(io, destparent) catch |err| { |
||||
fatal("cannot create '{s}': {s}", .{ destparent, @errorName(err) }); |
||||
}; |
||||
} |
||||
cwd.deleteFile(io, dest) catch |err| switch (err) { |
||||
error.FileNotFound => {}, |
||||
else => fatal("cannot remove stale '{s}': {s}", .{ dest, @errorName(err) }), |
||||
}; |
||||
cwd.copyFile(src, cwd, dest, io, .{}) catch |err| { |
||||
fatal("copy '{s}' -> '{s}' failed: {s}", .{ src, dest, @errorName(err) }); |
||||
}; |
||||
try say(io, arena, "stagetree: staged file {s}\n", .{dest}); |
||||
return std.process.cleanExit(io); |
||||
} |
||||
|
||||
//directory tree: delete stale destination entirely (orphan files from a previous |
||||
//pin must not survive), then copy |
||||
cwd.deleteTree(io, dest) catch |err| { |
||||
fatal("cannot remove stale tree '{s}': {s}", .{ dest, @errorName(err) }); |
||||
}; |
||||
cwd.createDirPath(io, dest) catch |err| { |
||||
fatal("cannot create '{s}': {s}", .{ dest, @errorName(err) }); |
||||
}; |
||||
|
||||
var src_dir = cwd.openDir(io, src, .{ .iterate = true }) catch |err| { |
||||
fatal("cannot open -src dir '{s}': {s}", .{ src, @errorName(err) }); |
||||
}; |
||||
defer src_dir.close(io); |
||||
var dest_dir = cwd.openDir(io, dest, .{}) catch |err| { |
||||
fatal("cannot open -dest dir '{s}': {s}", .{ dest, @errorName(err) }); |
||||
}; |
||||
defer dest_dir.close(io); |
||||
|
||||
var nfiles: usize = 0; |
||||
var walker = try src_dir.walk(arena); |
||||
defer walker.deinit(); |
||||
while (try walker.next(io)) |entry| { |
||||
switch (entry.kind) { |
||||
.directory => { |
||||
dest_dir.createDirPath(io, entry.path) catch |err| { |
||||
fatal("mkdir '{s}' in '{s}': {s}", .{ entry.path, dest, @errorName(err) }); |
||||
}; |
||||
}, |
||||
.file => { |
||||
src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| { |
||||
fatal("copy '{s}' into '{s}' failed: {s}", .{ entry.path, dest, @errorName(err) }); |
||||
}; |
||||
nfiles += 1; |
||||
}, |
||||
else => {}, |
||||
} |
||||
} |
||||
if (pinned) { |
||||
try cwd.writeFile(io, .{ .sub_path = marker_path, .data = key }); |
||||
} |
||||
try say(io, arena, "stagetree: staged {d} files -> {s}\n", .{ nfiles, dest }); |
||||
return std.process.cleanExit(io); |
||||
} |
||||
|
||||
fn say(io: std.Io, arena: std.mem.Allocator, comptime fmt: []const u8, args: anytype) !void { |
||||
const msg = try std.fmt.allocPrint(arena, fmt, args); |
||||
var f = std.Io.File.stdout(); |
||||
try f.writeStreamingAll(io, msg); |
||||
} |
||||
|
||||
fn pathExists(dir: std.Io.Dir, io: std.Io, p: []const u8) bool { |
||||
dir.access(io, p, .{}) catch return false; |
||||
return true; |
||||
} |
||||
|
||||
fn fatal(comptime format: []const u8, args: anytype) noreturn { |
||||
std.debug.print(format, args); |
||||
std.process.exit(1); |
||||
} |
||||
@ -0,0 +1,26 @@
|
||||
#suite_smoke.tcl (G-099; forked from suite_tcl90's): smoke assertions run under |
||||
#a suite-built shell itself (the shell being smoked executes this script). |
||||
# |
||||
#args: -mode installed -expect <patchlevel> |
||||
# installed: interpreter reports the expected patchlevel AND resolves the |
||||
# INSTALLED on-disk library hermetically (the caller scrubs |
||||
# TCLLIBPATH/TCL_LIBRARY): reaching this script at all proves |
||||
# init.tcl resolved exe/dll-relative; tzdata reachable (clock |
||||
# format with a zone name) and auto_load present prove the tree. |
||||
# (8.6 has no zipfs - the 9.0 suite's 'zip' attached-library mode |
||||
# does not apply; the on-disk tree IS the deliverable.) |
||||
|
||||
proc fail {msg} {puts stderr "suite_smoke FAIL: $msg"; flush stderr; exit 1} |
||||
|
||||
array set opt {-mode installed -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 <patchlevel>"} |
||||
set pl [info patchlevel] |
||||
if {$pl ne $opt(-expect)} {fail "patchlevel mismatch: got '$pl' want '$opt(-expect)' ([info nameofexecutable])"} |
||||
if {[catch {clock format 0 -gmt 0 -format %Z} tz]} {fail "tzdata not reachable from installed library: $tz"} |
||||
if {[llength [info procs auto_load]] != 1} {fail "auto_load missing - installed library init incomplete"} |
||||
puts "suite_smoke OK: $pl (library [info library], tz=$tz) [info nameofexecutable]" |
||||
exit 0 |
||||
@ -0,0 +1,218 @@
|
||||
#test_gate.tcl (G-102 core gate; generalized for the G-107 library test runs): |
||||
#run a tcltest-driven testsuite under THIS interpreter's own shell |
||||
#([info nameofexecutable] - the suite-built tclsh when driven as a |
||||
#'zig build test-*' step) and act on the PARSED totals: tcltest drivers' exit |
||||
#codes do NOT reflect failures, so results are judged from the captured log. |
||||
# |
||||
#Modes (-mode, default gate): |
||||
# gate every failed test must appear in the tracked dispositioned baseline |
||||
# or the gate fails listing the unexplained names (the core test-gate |
||||
# mechanism, G-098); notes baseline entries that no longer fail. |
||||
# record the run must COMPLETE (totals parsed) but failures do not fail the |
||||
# step - results are recorded as evidence (G-107 library policy tier |
||||
# for suites without a dispositioned baseline yet). |
||||
#EVERY mode: a run with no parseable aggregate totals line FAILS the step - |
||||
#infrastructure breakage must never masquerade as recorded results. |
||||
# |
||||
#Driver: <testsdir>/all.tcl by default. tcllib-family trees are driven via |
||||
#their own support/devel/all.tcl (-driver), which runs every module's test |
||||
#files in per-file child interps and aggregates counts into the same standard |
||||
#totals line ('all.tcl: Total N Passed N Skipped N Failed N'). That driver |
||||
#marks a test file that errors out with '@+'/'@|' errorInfo blocks - counted |
||||
#here as errored_files evidence (recorded in the summary, not a gate criterion; |
||||
#the gate criterion stays totals-vs-baseline, core-gate semantics). The |
||||
#runAllTests-vintage drivers (tcl core, tk) report 'Test files exiting with |
||||
#errors:' - captured as errorexit_files evidence the same way. |
||||
# |
||||
#Evidence summary (-summaryfile): a line-record file for artifact-metadata |
||||
#consumers (G-103). Each non-comment line is a Tcl list: '<key> <value>' |
||||
#(value may itself be a list). Shape documented in |
||||
#goals/G-107-buildsuite-library-tests.md. |
||||
# |
||||
#args: -testsdir <dir> -logfile <file> ?-mode gate|record? |
||||
# ?-baseline <file>? (required in gate mode) |
||||
# ?-driver <file>? (default all.tcl; relative paths resolve against -testsdir) |
||||
# ?-summaryfile <file>? ?-library <name>? ?-testargs {tcltest args}? |
||||
# ?-notfiles {file-tail-glob ...}? |
||||
# |
||||
#-notfiles is a Tcl list of test-file glob patterns excluded from the run |
||||
#(driver -notfile). It exists because -testargs cannot carry a multi-file |
||||
#exclusion through the option chain (-notfile takes one list value; a flat |
||||
#-testargs re-split on whitespace would break it). Use it to disposition |
||||
#test FILES that cannot run in this context - e.g. Tk's native-dialog suites |
||||
#(winDialog.test, winMsgbox.test) whose message-injection automation does not |
||||
#drive the real OS dialogs under the batch/suite-built shell, so each of their |
||||
#~90 cases blocks waiting on a human. Recorded in the evidence summary. |
||||
|
||||
proc fail {msg} {puts stderr "test_gate ERROR: $msg"; flush stderr; exit 1} |
||||
|
||||
array set opt {-testsdir {} -baseline {} -logfile {} -testargs {} -mode gate -driver all.tcl -summaryfile {} -library {} -notfiles {}} |
||||
foreach {k v} $argv { |
||||
if {![info exists opt($k)]} {fail "unknown option '$k' (expected -testsdir -baseline -logfile -mode -driver -summaryfile -library -testargs -notfiles)"} |
||||
set opt($k) $v |
||||
} |
||||
foreach req {-testsdir -logfile} { |
||||
if {$opt($req) eq ""} {fail "missing required option $req"} |
||||
} |
||||
if {$opt(-mode) ni {gate record}} {fail "unknown -mode '$opt(-mode)' (expected gate|record)"} |
||||
if {![file isdirectory $opt(-testsdir)]} {fail "no tests dir at $opt(-testsdir) - build first"} |
||||
if {$opt(-mode) eq "gate"} { |
||||
if {$opt(-baseline) eq ""} {fail "-mode gate requires -baseline"} |
||||
if {![file exists $opt(-baseline)]} {fail "no baseline file at $opt(-baseline) - for a first census run the step in record mode (-Dtestpolicy-<lib>=record), disposition the recorded failures into the baseline, then gate"} |
||||
} |
||||
|
||||
set library $opt(-library) |
||||
if {$library eq ""} {set library [file tail [file normalize $opt(-testsdir)]]} |
||||
set exe [info nameofexecutable] |
||||
set logfile [file normalize $opt(-logfile)] |
||||
file mkdir [file dirname $logfile] |
||||
set driver $opt(-driver) |
||||
if {[file pathtype $driver] ne "absolute"} {set driver [file join $opt(-testsdir) $driver]} |
||||
set driver [file normalize $driver] |
||||
if {![file exists $driver]} {fail "no driver script at $driver"} |
||||
|
||||
#-- evidence summary (written on every outcome once the run has been attempted) |
||||
set summary [dict create] |
||||
proc sumset {key value} {global summary; dict set summary $key $value} |
||||
proc write_summary {} { |
||||
global opt summary |
||||
if {$opt(-summaryfile) eq ""} return |
||||
set sf [file normalize $opt(-summaryfile)] |
||||
file mkdir [file dirname $sf] |
||||
set f [open $sf w] |
||||
fconfigure $f -translation lf |
||||
puts $f "# library-test evidence summary (G-107; consumed by G-103 artifact metadata)" |
||||
puts $f "# line records: <key> <value> - parse each non-comment line as a Tcl list." |
||||
puts $f "# shape: goals/G-107-buildsuite-library-tests.md (punkshell repo)" |
||||
dict for {k v} $summary {puts $f [list $k $v]} |
||||
close $f |
||||
} |
||||
proc finish {result failreason msg} { |
||||
#single exit point after the run: record outcome, write summary, report |
||||
global opt logfile |
||||
sumset result $result |
||||
if {$failreason ne ""} {sumset failreason $failreason} |
||||
write_summary |
||||
if {$opt(-summaryfile) ne ""} { |
||||
puts "test_gate: evidence: [file normalize $opt(-summaryfile)] (log: $logfile)" |
||||
} |
||||
if {$result eq "pass"} { |
||||
puts "test_gate: $msg" |
||||
exit 0 |
||||
} |
||||
puts stderr "test_gate $msg" |
||||
flush stderr |
||||
exit 1 |
||||
} |
||||
|
||||
sumset library $library |
||||
sumset mode $opt(-mode) |
||||
sumset shell $exe |
||||
sumset driver $driver |
||||
sumset testsdir [file normalize $opt(-testsdir)] |
||||
sumset testargs $opt(-testargs) |
||||
sumset notfiles $opt(-notfiles) |
||||
sumset baseline [expr {$opt(-baseline) ne "" ? [file normalize $opt(-baseline)] : ""}] |
||||
sumset logfile $logfile |
||||
|
||||
#-notfiles is passed as ONE driver arg (a Tcl list value tcltest reads as a |
||||
#list of -notfile patterns) - it must NOT be whitespace-split like -testargs. |
||||
set exclargs {} |
||||
if {[llength $opt(-notfiles)]} {lappend exclargs -notfile $opt(-notfiles)} |
||||
puts "test_gate: $library testsuite under $exe [expr {[llength $opt(-testargs)] ? $opt(-testargs) : "(full suite)"}] (mode $opt(-mode))[expr {[llength $opt(-notfiles)] ? " excluding files: $opt(-notfiles)" : ""}] -> $logfile" |
||||
flush stdout |
||||
set savedpwd [pwd] |
||||
cd $opt(-testsdir) |
||||
catch {exec $exe $driver {*}$opt(-testargs) {*}$exclargs > $logfile 2>@1} |
||||
cd $savedpwd |
||||
|
||||
set f [open $logfile r]; set testout [read $f]; close $f |
||||
|
||||
#aggregate totals: the master driver's final cleanupTests/runAllTests line. |
||||
#Take the LAST match - per-file summary lines carry the file's own name, but a |
||||
#driver arrangement that reuses 'all.tcl' for a nested stage must not shadow |
||||
#the final aggregate. |
||||
set quads [regexp -all -inline {all\.tcl:\s+Total\s+(\d+)\s+Passed\s+(\d+)\s+Skipped\s+(\d+)\s+Failed\s+(\d+)} $testout] |
||||
if {![llength $quads]} { |
||||
finish fail no-totals "FAIL ($library): no all.tcl totals line found in $logfile - run did not complete" |
||||
} |
||||
lassign [lrange $quads end-3 end] ntotal npassed nskipped nfailed |
||||
puts "test_gate: totals: $ntotal run, $npassed passed, $nskipped skipped, $nfailed failed" |
||||
sumset total $ntotal |
||||
sumset passed $npassed |
||||
sumset skipped $nskipped |
||||
sumset failed $nfailed |
||||
|
||||
set failednames {} |
||||
foreach {- tname} [regexp -all -inline -line {^==== (\S+) FAILED} $testout] { |
||||
if {$tname ni $failednames} {lappend failednames $tname} |
||||
} |
||||
sumset failednames $failednames |
||||
|
||||
#file-level completeness evidence (recorded, not gate criteria - the gate |
||||
#criterion is totals-vs-baseline, matching the core gate): |
||||
#tcllib-family driver: a test file that errors mid-source is caught and marked |
||||
#with an '@+' line followed by '@|' errorInfo lines. |
||||
set errored_count 0 |
||||
foreach line [split $testout \n] { |
||||
if {$line eq "@+"} {incr errored_count} |
||||
} |
||||
sumset errored_files $errored_count |
||||
#runAllTests-vintage drivers (tcl core, tk): 'Test files exiting with errors:' |
||||
#section lists files whose child run errored. |
||||
set errorexit_files {} |
||||
if {[regexp -indices {Test files exiting with errors:} $testout headidx]} { |
||||
set tailtext [string range $testout [lindex $headidx 1]+1 end] |
||||
foreach line [split $tailtext \n] { |
||||
set line [string trim $line] |
||||
if {$line eq ""} continue |
||||
if {[regexp {^(\S+\.test)$} $line -> fname]} { |
||||
lappend errorexit_files $fname |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
} |
||||
sumset errorexit_files $errorexit_files |
||||
if {$errored_count || [llength $errorexit_files]} { |
||||
puts "test_gate: note: file-level errors recorded: errored_files=$errored_count errorexit_files=[llength $errorexit_files] ([join $errorexit_files { }])" |
||||
} |
||||
|
||||
if {$opt(-mode) eq "record"} { |
||||
finish pass "" "RECORDED ($library): $ntotal run, $nfailed failed ([llength $failednames] distinct names) - evidence artifacts written" |
||||
} |
||||
|
||||
#-- gate mode: diff failed names against the dispositioned baseline |
||||
#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} |
||||
} |
||||
sumset unexplained $unexplained |
||||
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]} { |
||||
puts "test_gate: note: [llength $stale] baseline entries did not fail this run (candidates for removal): $stale" |
||||
} |
||||
} |
||||
sumset stalebaseline $stale |
||||
if {[llength $unexplained]} { |
||||
puts stderr "test_gate GATE FAIL ($library): [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))" |
||||
flush stderr |
||||
finish fail unexplained "GATE FAIL ($library): [llength $unexplained] unexplained failure(s) - see above" |
||||
} |
||||
finish pass "" "GATE PASS ($library): [llength $failednames] failures, all baselined" |
||||
@ -0,0 +1,92 @@
|
||||
const std = @import("std"); |
||||
|
||||
const usage = |
||||
\\Usage: ./wrapfiletofile [options] |
||||
\\ |
||||
\\Options: |
||||
\\ -prefix STRING |
||||
\\ -prefixnl BOOLEAN |
||||
\\ -input INPUTFILENAME |
||||
\\ -outut OUTPUTFILENAME |
||||
; |
||||
|
||||
pub fn main(init: std.process.Init.Minimal) !void { |
||||
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
||||
defer arena_state.deinit(); |
||||
const arena = arena_state.allocator(); |
||||
|
||||
var threaded: std.Io.Threaded = .init(arena, .{ |
||||
.environ = init.environ, |
||||
.argv0 = .init(init.args), |
||||
}); |
||||
defer threaded.deinit(); |
||||
const io = threaded.io(); |
||||
|
||||
const args = try init.args.toSlice(arena); |
||||
|
||||
var opt_input_file_path: ?[]const u8 = null; |
||||
var opt_output_file_path: ?[]const u8 = null; |
||||
var opt_prefix: ?[]const u8 = null; |
||||
var opt_prefixnl: bool = true; |
||||
|
||||
{ |
||||
var i: usize = 1; |
||||
while (i < args.len) : (i += 1) { |
||||
const arg = args[i]; |
||||
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "-help", arg)) { |
||||
std.debug.print("{s}\n", .{usage}); |
||||
return std.process.cleanExit(io); |
||||
} else if (std.mem.eql(u8, "-prefix", arg)) { |
||||
i += 1; |
||||
if (i > args.len) fatal("expected arg after '{s}'", .{arg}); |
||||
opt_prefix = args[i]; |
||||
} else if (std.mem.eql(u8, "-input", arg)) { |
||||
i += 1; |
||||
if (i > args.len) fatal("expected arg after '{s}'", .{arg}); |
||||
if (opt_input_file_path != null) fatal("duplicated {s} argument", .{arg}); |
||||
opt_input_file_path = args[i]; |
||||
} else if (std.mem.eql(u8, "-output", arg)) { |
||||
i += 1; |
||||
if (i > args.len) fatal("expected arg after '{s}'", .{arg}); |
||||
if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg}); |
||||
opt_output_file_path = args[i]; |
||||
} else if (std.mem.eql(u8, "-prefixnl", arg)) { |
||||
i += 1; |
||||
if (i > args.len) fatal("expected arg after '{s}'", .{arg}); |
||||
if (std.mem.eql(u8, "1", args[i])) { |
||||
opt_prefixnl = true; |
||||
} else { |
||||
opt_prefixnl = false; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
const input_file_path = opt_input_file_path orelse fatal("missing -input file", .{}); |
||||
const output_file_path = opt_output_file_path orelse fatal("missing -output file", .{}); |
||||
const prefix = opt_prefix orelse fatal("missing -prefix string", .{}); |
||||
|
||||
const input_data = std.Io.Dir.cwd().readFileAlloc(io, input_file_path, arena, .unlimited) catch |err| { |
||||
fatal("Unable to read '{s}': {s}", .{ input_file_path, @errorName(err) }); |
||||
}; |
||||
defer arena.free(input_data); |
||||
//no informational print here: std.debug.print writes to stderr, and zig 0.16's |
||||
//build_runner reports any step with nonempty stderr under a misleading |
||||
//"failed command:" block even on success. Quiet on success, loud on error. |
||||
|
||||
var output_file = std.Io.Dir.cwd().createFile(io, output_file_path, .{}) catch |err| { |
||||
fatal("Unable to open '{s}': {s}", .{ output_file_path, @errorName(err) }); |
||||
}; |
||||
defer output_file.close(io); |
||||
try output_file.writeStreamingAll(io, prefix); |
||||
if (opt_prefixnl) { |
||||
try output_file.writeStreamingAll(io, "\n"); |
||||
} |
||||
try output_file.writeStreamingAll(io, input_data); |
||||
return std.process.cleanExit(io); |
||||
} |
||||
|
||||
fn fatal(comptime format: []const u8, args: anytype) noreturn { |
||||
std.debug.print(format, args); |
||||
std.process.exit(1); |
||||
} |
||||
Loading…
Reference in new issue