You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

905 lines
48 KiB

//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.
//
//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).
//
//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);
}
//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);
}