From 84a1b225153c8a469a35766b8d4e52c8c8f767a0 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Tue, 21 Jul 2026 01:43:56 +1000 Subject: [PATCH] G-102 M1: suite_tcl90 configure-products as zig overlays; source trees read-only Generation of the configure-products a fresh checkout lacks moves from suite.tcl tree-writes into the zig recipe as build-cache overlays: - tclUuid.h / tkUuid.h / threadUuid.h: wrapfiletofile Run outputs consumed via overlay include dirs added FIRST per module (stale tree copies from pre-G-102 flows can never shadow; the LazyPath also carries the step dep). The unused addUpdateSourceFiles tree-write variants (build905 prepare-source, build_tclthread) are removed. - tclsh.exe.manifest: configure-time substitution (tcl.h parsed by the new build_common.zig helpers); tclsh.rc compiled from an overlay COPY beside the generated manifest because rc resource references resolve rc-file-relative before include paths. Both tclsh exes switch to .include_paths LazyPaths. - wish.exe.manifest: overlay dir first in tk.rc include_paths (RT_MANIFEST resolves through rc include paths - the arrangement the tk build already relied on). tk.rc itself stays tree-compiled (lives in win/rc, no stale risk). - thread.rc: compiled from an overlay copy with the tcl.h include inserted (the historical checkout edit is normalized if a mutated tree is staged); the staged tclthread checkout's thread.rc edit has been fossil-reverted. - build905.zig: redundant configure-time top-level library install loop removed (install_library_tree already covers it); dead zlib stage copy and the suite.tcl generation blocks removed (read-only version parsing kept for smokes/logging). Verified: full staged-mode rebuild PASS (all smokes incl tklib/tcllib/tcllibc); built binaries carry the overlay products (tcl::build-info reports 9.0.5+1a9c3b9d..., tclsh manifest version 9.0.2.905, tk dll manifest + tk checkout uuid, thread dll uuid; thread dll's empty FileVersion matches the pre-change binary - upstream quirk). Source checkouts stay pristine (fossil changes empty). Core test gate: 69783 run, 12 failed - 11 baselined plus http-4.14.2, a network-timing flake that passes on isolated rerun (528/519/9/0). tclsh90sprzip.exe install into out/bin is currently blocked by an open user application holding the file; the product builds identically into the zig cache and installs on next run once released. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- src/buildsuites/suite_tcl90/build905.zig | 144 ++++++------------ src/buildsuites/suite_tcl90/build_common.zig | 61 ++++++++ .../build_tclthread/build_tclthread.zig | 34 +++-- .../suite_tcl90/build_tk/build_tk.zig | 40 +++++ src/buildsuites/suite_tcl90/suite.tcl | 61 ++------ 5 files changed, 179 insertions(+), 161 deletions(-) create mode 100644 src/buildsuites/suite_tcl90/build_common.zig diff --git a/src/buildsuites/suite_tcl90/build905.zig b/src/buildsuites/suite_tcl90/build905.zig index 383aebb7..f9f0e0b2 100644 --- a/src/buildsuites/suite_tcl90/build905.zig +++ b/src/buildsuites/suite_tcl90/build905.zig @@ -1,6 +1,7 @@ const std = @import("std"); const fs = std.fs; const builtin = @import("builtin"); +const common = @import("build_common.zig"); const build_tommath = @import("build_libtommath/build_libtommath.zig").build_libtommath; const build_zlib = @import("build_zlib/build_zlib.zig").build_zlib; @@ -189,52 +190,14 @@ pub fn build(b: *std.Build) !void { //if (target.result.os.tag != .windows) { //} - //dependency on tclsh being on the system - review - //despite .cmd extension - should be callable by bash on unix, but as it's a polyglot script can have no shebang so will need to be explicit - review - - //const uuid_cmd = b.addSystemCommand(&.{ - // "wrapfiletofile.cmd", - // "-prefix", - // "#define TCL_VERSION_UUID \\", - // "-prefixnl", - // "1", - // "-input", - //}); - // ================================================ - // win/tclUuid.h & win/tclUuid.h are in the source tree but need to be created/updated - // The zig recommendation seems to be to make this a separate callable step rather than part of the standard build process. - // In this case it's required for the build, required to be in a specific source location - but shouldn't be checked in to the repo - // Ideally this should be run once on first build (and if source manifest.uuid changes) - and we shouldn't need to ask the user to specifically remember to call it. - // todo - something? - //var interp: []const []const u8 = undefined; - ////note we determine the interp based on the machine running the build - not the target - //if (builtin.os.tag == .windows) { - // interp = &.{ - // "wrapfiletofile.cmd", - // }; - //} else { - // interp = &.{ - // "bash", //explicitly use bash. Sometimes sh is linked to some other shell such as zsh, or dash (e.g debian,ubuntu) which don't understand substitution syntax like bash and ordinary posix sh do - // "wrapfiletofile.cmd", - // }; - //} - //const uuid_cmd = b.addSystemCommand(interp); - //uuid_cmd.addArgs(&.{ - // "-prefix", - // "#define TCL_VERSION_UUID \\", - // "-prefixnl", - // "1", - // "-input", - //}); - ////uuid_cmd.addFileArg(.{ .path = b.pathJoin(&.{ b.pathFromRoot(""), "..", "tcl" ++ tcl_nodot_version, "manifest.uuid" }) }); - ////uuid_cmd.addFileArg(.{ .src_path = b.path(b.pathJoin(&.{ b.pathFromRoot(""), "..", "tcl" ++ tcl_nodot_version, "manifest.uuid" })) }); - ////uuid_cmd.addFileArg(b.path(b.pathJoin(&.{ b.pathFromRoot(""), "..", "tcl" ++ tcl_nodot_version, "manifest.uuid" }))); - //uuid_cmd.addFileArg(b.path(tcl_source_folder ++ "/manifest.uuid" )); - //uuid_cmd.addArg("-output"); - //const tcluuid_file1 = uuid_cmd.addOutputFileArg("tclUuid.h"); - - const prepare_source = b.step("prepare-source", "patch and prep source"); + //Generated configure-products (G-102): created into the build cache and consumed + //via overlay include dirs / overlay rc copies. Source trees (staged fossil + //checkouts or zon-fetched trees) are never written - suite.tcl formerly generated + //these files into the checkout (and before 2026-07-21 this recipe carried an + //addUpdateSourceFiles tree-write variant behind the unused 'prepare-source' step). + // + //tclUuid.h (tclEvent.c/tclTest.c #include it; upstream configure generates it) const wrap_exe = b.addExecutable(.{ .name = "wrapfiletofile", .root_module = b.createModule(.{ @@ -242,10 +205,7 @@ pub fn build(b: *std.Build) !void { .target = b.graph.host, }), }); - //const tool_step = b.addRunArtifact(tool); const wrap_run = b.addRunArtifact(wrap_exe); - //const wrap_install = b.addInstallArtifact(wrap_exe, .{}); - wrap_run.addArgs(&.{ "-prefix", "#define TCL_VERSION_UUID \\", @@ -256,24 +216,26 @@ pub fn build(b: *std.Build) !void { wrap_run.addFileArg(b.path(tcl_source_folder ++ "/manifest.uuid")); wrap_run.addArg("-output"); const tcluuid_file1 = wrap_run.addOutputFileArg("tclUuid.h"); - - //zig prior to 0.14 - addCopyFileToSource worked with b.addWriteFiles - //zig post 0.14 - b.addUpdateSourcFiles() is required to use addCopyFileToSource - //const wf_uuidh = b.addWriteFiles(); - const wf_uuidh = b.addUpdateSourceFiles(); - - //var uuidh: std.Build.LazyPath = undefined; - if (target.result.os.tag == .windows) { - wf_uuidh.addCopyFileToSource(tcluuid_file1, b.pathJoin(&.{ tcl_source_folder, "win", "tclUuid.h" })); - } else { - wf_uuidh.addCopyFileToSource(tcluuid_file1, b.pathJoin(&.{ tcl_source_folder, "unix", "tclUuid.h" })); - } - wf_uuidh.step.dependOn(&wrap_run.step); - prepare_source.dependOn(&wf_uuidh.step); - - //const copy_uuid = b.addInstallFileWithDir(tcluuid_file1, .prefix, b.pathJoin(&.{ "lib", "tclUuid.h.copy" })); - //copy_uuid.step.dependOn(&wf_uuidh.step); - //b.getInstallStep().dependOn(©_uuid.step); + //include-dir overlay: using the generator's output dir as an include path also + //gives each consuming compile its dependency on the generation step. Added FIRST + //at each consuming module so a stale tree-written tclUuid.h (pre-G-102 flows) + //can never shadow the generated one. + const tcluuid_include_dir = tcluuid_file1.dirname(); + + //tclsh.exe.manifest (tclsh.rc references it; upstream configure substitutes the + //.in). Generated at configure time; tclsh.rc is compiled from an overlay COPY + //in the same generated dir because rc resource references resolve + //rc-file-relative BEFORE include paths - a stale manifest beside the tree's + //tclsh.rc would otherwise win over any include-path overlay. + 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"); + 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 }); + 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"); // ================================================ var ac_flags = std.array_list.Managed([]const u8).init(b.allocator); @@ -1350,6 +1312,7 @@ pub fn build(b: *std.Build) !void { }), }); + tcl_objs_static.root_module.addIncludePath(tcluuid_include_dir); //overlay first (G-102) tcl_objs_static.root_module.addIncludePath(b.path(tcl_source_folder ++ "/generic")); if (target.result.os.tag == .windows) { tcl_objs_static.root_module.addIncludePath(b.path(tcl_source_folder ++ "/win")); @@ -1524,6 +1487,7 @@ pub fn build(b: *std.Build) !void { // tcl_slib.step.dependOn(&zlib_lib_compile.step); //} + tcl_lib_shared.root_module.addIncludePath(tcluuid_include_dir); //overlay first (G-102) tcl_lib_shared.root_module.addIncludePath(b.path(tcl_source_folder ++ "/generic")); if (target.result.os.tag == .windows) { tcl_lib_shared.root_module.addIncludePath(b.path(tcl_source_folder ++ "/win")); @@ -1649,6 +1613,7 @@ pub fn build(b: *std.Build) !void { // tclsh_exe.step.dependOn(&zlib_lib_compile.step); //} + tclsh_exe.root_module.addIncludePath(tcluuid_include_dir); //overlay first (G-102) tclsh_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/generic")); if (target.result.os.tag == .windows) { tclsh_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/win")); @@ -1785,11 +1750,13 @@ pub fn build(b: *std.Build) !void { tclsh_exe.root_module.link_libc = true; //LDFLAGS_CONSOLE= -mconsole -pipe -static-libgcc -municode -Wl,--enable-auto-image-base + //rc compiled from the overlay copy (generated tclsh.exe.manifest beside it); + //tclsh.ico + tcl.h resolve via the include paths. tclsh_exe.root_module.addWin32ResourceFile(.{ - .file = b.path(tcl_source_folder ++ "/win/tclsh.rc"), - .flags = &.{ - "/I", - tcl_source_folder ++ "/generic", + .file = tclshrc_overlay, + .include_paths = &.{ + b.path(tcl_source_folder ++ "/generic"), + b.path(tcl_source_folder ++ "/win"), }, }); const install_tclsh = b.addInstallArtifact(tclsh_exe, .{}); @@ -1815,6 +1782,7 @@ pub fn build(b: *std.Build) !void { tclsh_pr_exe.mingw_unicode_entry_point = true; tclsh_pr_exe.step.dependOn(&finalstublib.step); tclsh_pr_exe.step.dependOn(&tcl_objs_static.step); + tclsh_pr_exe.root_module.addIncludePath(tcluuid_include_dir); //overlay first (G-102) tclsh_pr_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/generic")); if (target.result.os.tag == .windows) { tclsh_pr_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/win")); @@ -1901,10 +1869,10 @@ pub fn build(b: *std.Build) !void { tclsh_pr_exe.step.dependOn(&tclvfs_compile.step); // review (parity with stock shell) tclsh_pr_exe.root_module.link_libc = true; tclsh_pr_exe.root_module.addWin32ResourceFile(.{ - .file = b.path(tcl_source_folder ++ "/win/tclsh.rc"), - .flags = &.{ - "/I", - tcl_source_folder ++ "/generic", + .file = tclshrc_overlay, + .include_paths = &.{ + b.path(tcl_source_folder ++ "/generic"), + b.path(tcl_source_folder ++ "/win"), }, }); const install_tclsh_pr = b.addInstallArtifact(tclsh_pr_exe, .{}); @@ -1942,30 +1910,10 @@ pub fn build(b: *std.Build) !void { }); install_libraries.dependOn(&install_library_tree.step); - //Install library files - non recursive - var library_dir = try std.Io.Dir.cwd().openDir(b.graph.io, tcl_source_folder ++ "/library", .{ .iterate = true }); - defer library_dir.close(b.graph.io); - const allowed_exts = [_][]const u8{ - ".tcl", - "", - }; - var it = library_dir.iterate(); - //var copy_libfile: []const u8 = undefined; - //var copy_libfile_step: *std.Build.Step = undefined; - var install_libfile: *std.Build.Step.InstallFile = undefined; - while (try it.next(b.graph.io)) |entry| { - if (entry.kind == .file) { - const ext = fs.path.extension(entry.name); - const include_file = for (allowed_exts) |e| { - if (std.mem.eql(u8, ext, e)) break true; - } else false; - if (include_file) { - //try generic_sources.append(b.pathJoin(&.{"tcl" ++ tcl_nodot_version ++ "/generic", entry.name,})); - install_libfile = b.addInstallFileWithDir(b.path(b.pathJoin(&.{ tcl_source_folder ++ "/library", entry.name })), .prefix, b.pathJoin(&.{ script_install_dir_rel, entry.name })); - install_libraries.dependOn(&install_libfile.step); - } - } - } + //(G-102: the former per-file top-level library install loop - a configure-time + //directory iteration - was removed as redundant: install_library_tree above + //already installs the whole library tree, top-level files included, to the same + //lib/tcl9.0 destination.) //Makefile for tcl9.0b1 uses target folder /opt0.4 /cookiejar0.2 - but we don't really need the version as part of the folder structure //At least some distributions of tcl9 don't include the version - which seems simpler diff --git a/src/buildsuites/suite_tcl90/build_common.zig b/src/buildsuites/suite_tcl90/build_common.zig new file mode 100644 index 00000000..04b7711f --- /dev/null +++ b/src/buildsuites/suite_tcl90/build_common.zig @@ -0,0 +1,61 @@ +//build_common.zig - shared configure-time helpers for the suite recipe files (G-102). +//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 ""' 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"); +} + +//TCL_WIN_VERSION / TK_WIN_VERSION as the makefiles derive them: +//.., releaselevel +//0=alpha 1=beta 2=final (e.g 9.0 + 9.0.5 -> 9.0.2.905) +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"); +} diff --git a/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig b/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig index f2522ef7..e4b3d5d3 100644 --- a/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig +++ b/src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig @@ -1,6 +1,7 @@ const std = @import("std"); const fs = std.fs; const builtin = @import("builtin"); +const common = @import("../build_common.zig"); const mkvar_thread_zip_file = "libthread3.0.1.zip"; const mkvar_thread_vfs_root = "libthread.vfs"; @@ -57,16 +58,9 @@ pub fn build_tclthread(comptime tcldir: []const u8, comptime subdir: []const u8, //uuid_cmd.addArg("-output"); //const tcluuid_file1 = uuid_cmd.addOutputFileArg("threadUuid.h"); - //const wf_uuidh = b.addWriteFiles(); - const wf_uuidh = b.addUpdateSourceFiles(); - if (target.result.os.tag == .windows) { - wf_uuidh.addCopyFileToSource(threaduuid_file, b.pathJoin(&.{ subdir, "win", "threadUuid.h" })); - } else { - wf_uuidh.addCopyFileToSource(threaduuid_file, b.pathJoin(&.{ subdir, "unix", "threadUuid.h" })); - } - - wf_uuidh.step.dependOn(&uuidcmd_step.step); - b.getInstallStep().dependOn(&wf_uuidh.step); + //(G-102: the former addUpdateSourceFiles copy of threadUuid.h into the checkout + //was removed - the include-dir overlay below is the operative delivery and source + //trees are never written.) const lib = b.addLibrary(.{ .linkage = .dynamic, @@ -143,13 +137,31 @@ pub fn build_tclthread(comptime tcldir: []const u8, comptime subdir: []const u8, lib.root_module.linkLibrary(stublib); lib.root_module.link_libc = true; + //thread.rc overlay (G-102): the version resource needs tcl.h; the recipe + //historically carried that as an edit of the checkout's thread.rc - now the rc + //is compiled from an overlay copy with the include inserted (and the historical + //relative-path edit normalized if a previously-mutated tree is staged), with + //tcl.h resolving via the include paths. Source trees are never written. + var threadrc_content: []const u8 = common.readSourceFile(b, subdir ++ "/win/thread.rc"); + const legacy_edit = "#include \"../../tcl905/generic/tcl.h\""; + if (std.mem.indexOf(u8, threadrc_content, legacy_edit) != null) { + threadrc_content = common.replaceAll(b, threadrc_content, legacy_edit, "#include \"tcl.h\""); + } else if (std.mem.indexOf(u8, threadrc_content, "#include \"tcl.h\"") == null) { + threadrc_content = common.replaceAll(b, threadrc_content, "#include ", "#include \n#include \"tcl.h\""); + } + const wf_threadrc = b.addWriteFiles(); + const threadrc_overlay = wf_threadrc.add("thread.rc", threadrc_content); lib.root_module.addWin32ResourceFile(.{ - .file = b.path(subdir ++ "/win/thread.rc"), + .file = threadrc_overlay, .flags = &.{ "-DPACKAGE_MAJOR=3", "-DPACKAGE_MINOR=0", "-DPACKAGE_VERSION=3.0b2", }, + .include_paths = &.{ + b.path(tcldir ++ "/generic"), + b.path(subdir ++ "/win"), + }, }); //ignored for non-windows targets b.installArtifact(lib); diff --git a/src/buildsuites/suite_tcl90/build_tk/build_tk.zig b/src/buildsuites/suite_tcl90/build_tk/build_tk.zig index 719730e9..f5913d03 100644 --- a/src/buildsuites/suite_tcl90/build_tk/build_tk.zig +++ b/src/buildsuites/suite_tcl90/build_tk/build_tk.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const common = @import("../build_common.zig"); //G-098: Tk 9.0 windows loadable extension (tcl9tk90.dll) from stock sources, plus the //tk script library installed to lib/tk9.0. Object inventory derived from @@ -91,6 +92,30 @@ pub fn build_tk(comptime tcldir: []const u8, comptime subdir: []const u8, b: *st inline for (win_names) |n| try sources.append(subdir ++ "/win/" ++ n ++ ".c"); inline for (ttk_names) |n| try sources.append(subdir ++ "/generic/ttk/" ++ n ++ ".c"); + //tkUuid.h overlay (G-102): tkWindow.c #includes it (upstream makefiles generate + //it into their build dirs). Generated into the build cache and consumed via an + //include-dir overlay added FIRST, so a stale tree-written copy in win/ (pre-G-102 + //flows generated one there) can never shadow it. Source trees are never written. + 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 TK_VERSION_UUID \\", + "-prefixnl", + "1", + "-input", + }); + uuidcmd_step.addFileArg(b.path(subdir ++ "/manifest.uuid")); + uuidcmd_step.addArg("-output"); + const tkuuid_file = uuidcmd_step.addOutputFileArg("tkUuid.h"); + lib.root_module.addIncludePath(tkuuid_file.dirname()); //overlay first (G-102) + lib.root_module.addIncludePath(b.path(subdir ++ "/generic")); lib.root_module.addIncludePath(b.path(subdir ++ "/generic/ttk")); lib.root_module.addIncludePath(b.path(subdir ++ "/win")); @@ -122,9 +147,24 @@ pub fn build_tk(comptime tcldir: []const u8, comptime subdir: []const u8, b: *st .flags = mainw_flags.items, }); + //wish.exe.manifest overlay (G-102): tk.rc references it and RT_MANIFEST file + //references resolve through the rc include paths (the pre-G-102 arrangement + //already relied on that, finding a tree-written copy via subdir/win). Generated + //from the .in at configure time; the overlay dir is FIRST so a stale tree copy + //never shadows it. tk.rc itself stays tree-compiled: it lives in win/rc/ where + //no generated files were ever written, so rc-file-relative lookup cannot pick + //up stale state. + const tkh_content = common.readSourceFile(b, subdir ++ "/generic/tk.h"); + const tk_h_version = common.parseDefineString(tkh_content, "TK_VERSION") orelse @panic("TK_VERSION not found in tk.h"); + const tk_h_patchlevel = common.parseDefineString(tkh_content, "TK_PATCH_LEVEL") orelse @panic("TK_PATCH_LEVEL not found in tk.h"); + const tk_win_version = common.winResourceVersion(b, tk_h_version, tk_h_patchlevel); + const wish_manifest_content = common.replaceAll(b, common.replaceAll(b, common.readSourceFile(b, subdir ++ "/win/wish.exe.manifest.in"), "@TK_WIN_VERSION@", tk_win_version), "@MACHINE@", "AMD64"); + const wf_tkrc = b.addWriteFiles(); + _ = wf_tkrc.add("wish.exe.manifest", wish_manifest_content); lib.root_module.addWin32ResourceFile(.{ .file = b.path(subdir ++ "/win/rc/tk.rc"), .include_paths = &.{ + wf_tkrc.getDirectory(), b.path(subdir ++ "/win"), b.path(subdir ++ "/win/rc"), b.path(subdir ++ "/generic"), diff --git a/src/buildsuites/suite_tcl90/suite.tcl b/src/buildsuites/suite_tcl90/suite.tcl index af60ee84..a709347a 100644 --- a/src/buildsuites/suite_tcl90/suite.tcl +++ b/src/buildsuites/suite_tcl90/suite.tcl @@ -233,7 +233,7 @@ proc fossil_source {name url branch dir} { set builddir [file join $stage build] log "staging recipe -> $builddir" file mkdir $builddir -foreach item {build905.zig build_libtommath build_zlib build_tclvfs build_tclthread build_tk patches src tools} { +foreach item {build905.zig build_common.zig build_libtommath build_zlib build_tclvfs build_tclthread build_tk patches src tools} { file delete -force [file join $builddir $item] file copy [file join $suiteroot $item] $builddir } @@ -277,68 +277,25 @@ foreach name {tcl tclthread tclvfs tk tklib tcllib} { fossil_source $name [dict get $s url] $ref [file join $stage [dict get $s dir]] } -#zlib: use the tcl tree's own vendored copy (version-matched; includes contrib/minizip) -set zlibdir [file join $stage zlib] -if {![file isdirectory $zlibdir]} { - log "staging zlib from tcl compat/zlib" - file copy [file join $stage tcl905 compat zlib] $zlibdir -} +#zlib: the recipe compiles the tcl tree's own vendored copy in place +#(tcl905/compat/zlib, version-matched, includes contrib/minizip) - nothing to stage. #vqtcl/vlerq: removed from the suite (user 2026-07-20) - old-tclkit experiment with no #confirmed public upstream. Future tclkit work: TEMP_REFERENCE/metakit + KitCreator. -#-- generated files a fresh tcl checkout lacks (normally configure/prepare products) -- +#-- version facts for smokes/logging --------------------------------------- +#G-102: the configure-products a fresh checkout lacks (tclUuid.h, tclsh.exe.manifest, +#the thread.rc tcl.h include, tkUuid.h, wish.exe.manifest) are now generated INSIDE +#the zig recipe as build-cache overlays - source checkouts are never written. What +#remains here is read-only: version facts for the smoke expectations and the log. set tcldir [file join $stage tcl905] -#tclUuid.h: '#define TCL_VERSION_UUID \' + manifest.uuid content set f [open [file join $tcldir manifest.uuid] r]; set uuid [read $f]; close $f -set f [open [file join $tcldir win tclUuid.h] w] -fconfigure $f -translation lf -puts -nonewline $f "#define TCL_VERSION_UUID \\\n$uuid" -close $f -#tclsh.exe.manifest from .in: TCL_WIN_VERSION = TCL_VERSION . releaselevel . patchlevel-stripped set f [open [file join $tcldir generic tcl.h] r]; set tclh [read $f]; close $f -if {![regexp {#\s*define\s+TCL_VERSION\s+"([^"]+)"} $tclh -> tclversion]} {fail "TCL_VERSION not found in tcl.h"} if {![regexp {#\s*define\s+TCL_PATCH_LEVEL\s+"([^"]+)"} $tclh -> patchlevel]} {fail "TCL_PATCH_LEVEL not found in tcl.h"} -set rlevel [expr {[string match *a* $patchlevel] ? 0 : ([string match *b* $patchlevel] ? 1 : 2)}] -set winver "$tclversion.$rlevel.[string map {a {} b {} . {}} $patchlevel]" -set f [open [file join $tcldir win tclsh.exe.manifest.in] r]; set manifest [read $f]; close $f -set manifest [string map [list @TCL_WIN_VERSION@ $winver @MACHINE@ AMD64] $manifest] -set f [open [file join $tcldir win tclsh.exe.manifest] w] -fconfigure $f -translation lf -puts -nonewline $f $manifest -close $f -log "generated tclUuid.h + tclsh.exe.manifest (TCL_WIN_VERSION $winver, patchlevel $patchlevel)" - -#thread.rc: the zig rc compile does not receive the makefile's include paths; give the -#version resource script the tcl header it needs (idempotent scripted edit of the -#staged checkout - the historical recipe carried this as an uncommitted tweak). -set rcfile [file join $stage tclthread win thread.rc] -set f [open $rcfile r]; set rc [read $f]; close $f -if {![string match "*tcl905/generic/tcl.h*" $rc]} { - set rc [string map [list "#include " "#include \n#include \"../../tcl905/generic/tcl.h\""] $rc] - set f [open $rcfile w]; fconfigure $f -translation lf; puts -nonewline $f $rc; close $f - log "thread.rc: inserted tcl.h include for the version resource" -} - -#tk generated files (G-098): tkUuid.h from manifest.uuid; wish.exe.manifest from its -#.in (tk.rc depends on it) - same configure-product class as the tcl ones above. set tkdir [file join $stage tk9] -set f [open [file join $tkdir manifest.uuid] r]; set tkuuid [read $f]; close $f -set f [open [file join $tkdir win tkUuid.h] w] -fconfigure $f -translation lf -puts -nonewline $f "#define TK_VERSION_UUID \\\n$tkuuid" -close $f set f [open [file join $tkdir generic tk.h] r]; set tkh [read $f]; close $f if {![regexp {#\s*define\s+TK_VERSION\s+"([^"]+)"} $tkh -> tkversion]} {fail "TK_VERSION not found in tk.h"} if {![regexp {#\s*define\s+TK_PATCH_LEVEL\s+"([^"]+)"} $tkh -> tkpatchlevel]} {fail "TK_PATCH_LEVEL not found in tk.h"} -set tkrlevel [expr {[string match *a* $tkpatchlevel] ? 0 : ([string match *b* $tkpatchlevel] ? 1 : 2)}] -set tkwinver "$tkversion.$tkrlevel.[string map {a {} b {} . {}} $tkpatchlevel]" -set f [open [file join $tkdir win wish.exe.manifest.in] r]; set m [read $f]; close $f -set m [string map [list @TK_WIN_VERSION@ $tkwinver @MACHINE@ AMD64] $m] -set f [open [file join $tkdir win wish.exe.manifest] w] -fconfigure $f -translation lf -puts -nonewline $f $m -close $f -log "tk prepared (patchlevel $tkpatchlevel, TK_WIN_VERSION $tkwinver)" +log "sources: tcl $patchlevel (checkout [string range $uuid 0 11]...), tk $tkpatchlevel" #-- build ----------------------------------------------------------------- set zigv [exec $opt(-zig) version]