Browse Source

G-096: suite_tcl90 - tracked zig recipe + driver build Tcl 9.0.5 end-to-end; piperepl patches recovered

First real buildsuite: build905.zig (verbatim G-076-validated recipe) +
helper modules + tools, driven by suite.tcl which stages the recipe into
src/buildsuites/_build/suite_tcl90, arranges sources (tcl fossil
core-9-0-branch --nested with FOSSIL_HOME isolation; thread/tclvfs trunk;
zlib from the tcl tree's compat/zlib; vqtcl from a local dir), generates the
configure-products fresh checkouts lack (tclUuid.h, tclsh.exe.manifest,
thread.rc include), builds and smoke-tests. Verified PASS: static + zip
shells report 9.0.5 (checkout 1a9c3b9d96), Thread 3.0.1 builds from current
trunk (new generated-include fix in build_tclthread) and loads. The 2024
TCLSH_PIPEREPL/tclZipfs/tclCmdIL patches are now tracked under
suite_tcl90/patches with a semantics README (not applied by default builds).

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 weeks ago
parent
commit
120f03085d
  1. 56
      goals/G-096-zig-buildsuite-piperepl.md
  2. 77
      src/buildsuites/suite_tcl90/README.md
  3. 2009
      src/buildsuites/suite_tcl90/build905.zig
  4. 190
      src/buildsuites/suite_tcl90/build_libtommath/build_libtommath.zig
  5. 152
      src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig
  6. 81
      src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_shared.zig
  7. 76
      src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_static.zig
  8. 80
      src/buildsuites/suite_tcl90/build_vqtcl/build_vqtcl_shared.zig
  9. 161
      src/buildsuites/suite_tcl90/build_zlib/build_zlib.zig
  10. 41
      src/buildsuites/suite_tcl90/patches/README.md
  11. 13
      src/buildsuites/suite_tcl90/patches/tclCmdIL.patch
  12. 233
      src/buildsuites/suite_tcl90/patches/tclMain.c.piperepl.patch
  13. 1090
      src/buildsuites/suite_tcl90/patches/tclMain_piperepl.c
  14. 93
      src/buildsuites/suite_tcl90/patches/tclZipfs.patch
  15. 4
      src/buildsuites/suite_tcl90/src/main.zig
  16. 178
      src/buildsuites/suite_tcl90/suite.tcl
  17. 85
      src/buildsuites/suite_tcl90/tools/wrapfiletofile.zig
  18. 55
      src/buildsuites/suite_tcl90/tools/zipfs_mkimg.tcl
  19. 65
      src/buildsuites/suite_tcl90/tools/zipfs_mkzip.tcl

56
goals/G-096-zig-buildsuite-piperepl.md

@ -1,6 +1,6 @@
# G-096 Tracked zig tcl buildsuite (suite_tcl90) with recovered TCLSH_PIPEREPL kit-repl capability
Status: proposed
Status: active
Scope: src/buildsuites/ (suite_tcl90 new; samplesuite1 sketch reconciliation; VCS ignore flip), src/buildsuites/_build/ (untracked build/source area via existing _build globs), .gitignore + .fossil-settings/ignore-glob, src/scriptapps/bin/ (getzig scriptset rename), bin/ (regenerated punk-getzig wrapper twins, old names retired, AGENTS.md naming policy), bin/tools/ (untracked zig toolchains, existing), TEMP_REFERENCE/2024zig (read-only recovery origin)
Goal: src/buildsuites gains its first real, VCS-tracked suite (suite_tcl90) that reproducibly builds the Tcl 9.0 windows runtime from clean sources using only project-managed tooling - a punk-getzig wrapper fetching a pinned zig into untracked bin/tools, with all transient checkouts and outputs under untracked src/buildsuites/_build - and the 2024 TCLSH_PIPEREPL tclMain patch is recovered into the suite in tracked form and rebased onto current Tcl 9 sources, so a suite-built runtime can return to a standard interactive repl after a kit-style main.tcl completes with unconsumed piped input buffered rather than eval'd, while stock behaviour is preserved whenever the env gate is unset.
Acceptance: from a clean checkout plus a punk-getzig-fetched pinned zig, the suite builds tclsh90s/tclsh90szip from a fresh core-9-0-branch checkout under src/buildsuites/_build with recipe-generated tclUuid.h and tclsh.exe.manifest (no hand-placed files) and the result reports the expected patchlevel; the recovered patch set (piperepl plus the companion tclZipfs/tclCmdIL patches and a notes doc) is tracked in the suite; a piperepl-patched build with TCLSH_PIPEREPL unset behaves identically to the unpatched build on a tty/piped/script-arg matrix, and with TCLSH_PIPEREPL=1 a script setting ::tclsh(dorepl) 1 lands in a live interactive console repl after piped-input eof with the unconsumed input available in ::tclsh(inputbuffer); src/buildsuites is tracked in both VCSes with only _build content ignored and the samplesuite1 vendorbuild-prefix residue reconciled (vendorbuild concept retired); bin/AGENTS.md documents the utility naming policy (punk- prefix for punkshell-own utilities; exceptions: external-tool wrappers such as dtplite/sdx/kettle, and getpunk as the intended future cross-platform entry point) and the getzig scriptset and its bin wrapper twins (getzig.cmd/getzig.ps1) are renamed to punk-getzig with the old names removed and references updated.
@ -100,3 +100,57 @@ Acceptance: from a clean checkout plus a punk-getzig-fetched pinned zig, the sui
- The G-076 kill-procedure runtime (zig-out905 under c:/buildtcl/2024zig) predates this
suite; once suite_tcl90 reproduces it, the ad-hoc c:/buildtcl/2024zig builds become
historical reference only.
## Progress
### 2026-07-20 increment 1: activated; buildsuites tracked; suite_tcl90 builds 9.0.5 end-to-end
- Goal set active (user 2026-07-20). VCS tracking flip: the blanket /src/buildsuites
ignores removed from .gitignore and .fossil-settings/ignore-glob; the retired
vendorbuilds ignores removed with them; src/buildsuites/_build confirmed ignored via
the existing **/_build/ (git) and */_build (fossil) globs.
- samplesuite1 sketch reconciled: marked SKETCH ONLY with the toolchain-policy note;
buildprefix moved from <PROJECTDIR>/vendorbuild to src/buildsuites/_build/samplesuite1.
- suite_tcl90 created (tracked): build905.zig verbatim from the G-076-validated recipe,
helper build modules, src/main.zig stub, tools (wrapfiletofile.zig,
zipfs_mkzip/mkimg.tcl), patches/ (piperepl pair + tclZipfs + tclCmdIL + README with
the piperepl semantics), suite README, and the suite.tcl driver.
- Driver design: the tracked recipe is STAGED into _build/suite_tcl90/build with
sibling sources arranged around it (tcl905 = fossil core-9-0-branch; tclthread and
tclvfs fossil trunk; zlib copied from the tcl checkout's own compat/zlib 1.3.2;
vqtcl from a local dir - no public upstream confirmed), so the recipe needs no path
surgery and cache/outputs land under _build naturally. Fossil checkouts open --nested
(the stage is inside the punkshell checkout) with FOSSIL_HOME=<stage> so the global
fossil config db records nothing; clones reuse ~/.fossils (tcl, tclthread, tclvfs
all already present).
- Fresh-checkout generation lives in the driver (G-076 findings productized):
win/tclUuid.h from manifest.uuid; win/tclsh.exe.manifest from its .in
(TCL_WIN_VERSION per the tree's own formula, parsed from tcl.h - note the defines
are indented '# define'); thread.rc gains the tcl.h include for its version
resource (scripted idempotent edit of the staged checkout, replacing the historical
uncommitted tweak in the 2024 tclthread checkout).
- Recipe fixes made in the suite copies: the four tcl-dependent helper modules'
hardcoded ../tcl90/generic include (author's "todo - fix") now use their tcldir
param; build_tclthread additionally puts the generated threadUuid.h's output dir on
the include path - current thread trunk #includes threadUuid.h (upstream makefiles
generate it into their build dir; the recipe's copy-to-source went to win/, which is
not an include dir). build_libtommath.zig keeps its hardcoded path but is not called
by the recipe.
- End-to-end run PASS (native tclsh 9.0.3 driving; zig 0.14.0-dev.2074 at C:/zig):
fetch -> stage -> generate -> build -> smoke. Static and zip shells both report
9.0.5 (checkout 1a9c3b9d96 = core-9-0-branch tip carrying the f10d91c2 console
fix); zip shell tzdata working; tcl9thread301.dll builds from CURRENT thread trunk
(3.0.1) and loads. vfs/vlerq dlls also built.
Remaining for acceptance:
- punk-getzig rename (scriptset + both bin wrapper twins, old names removed,
references updated) and the naming-policy section in bin/AGENTS.md.
- Pin an exact zig version retrievable via punk-getzig and have the suite consume it
(driver currently takes -zig / PUNK_ZIG / PATH; validated with 0.14.0-dev.2074).
- Rebase the piperepl patch onto current core-9-0-branch sources; build a
piperepl-enabled variant; behaviour matrix (TCLSH_PIPEREPL unset == stock on
tty/piped/script-arg forms; =1 with a script setting ::tclsh(dorepl) 1 -> live
interactive console repl after piped eof with unconsumed input in
::tclsh(inputbuffer)).
- Final clean-checkout re-verification of the whole flow once the above land.

77
src/buildsuites/suite_tcl90/README.md

@ -0,0 +1,77 @@
# suite_tcl90 - zig build of the Tcl 9.0 windows runtime (G-096)
The first real, tracked buildsuite: reproducibly builds `tclsh90s.exe` (static) and
`tclsh90szip.exe` (self-contained zipfs) plus the thread/vfs/vlerq extension dlls from
a fresh Tcl core-9-0-branch checkout, using zig only (no MS toolchain, per the project
toolchain policy).
Everything transient lives under `src/buildsuites/_build/suite_tcl90/` (VCS-ignored via
the repo's existing `_build` globs): staged recipe, source checkouts, zig cache, and the
`out/` install prefix. The tracked content here is the recipe and its driver.
## Usage
tclsh suite.tcl build ?options?
tclsh suite.tcl clean
See `suite.tcl`'s header for options (`-zig`, `-optimize`, `-tclbranch`, `-refresh`,
`-vqtcl-source`, `-steps`). The zig executable defaults to `$env(PUNK_ZIG)` or `zig` on
PATH; the recipe's known-good zig is 0.14.0-dev.2074 (see Lineage) - a punk-getzig
fetched pinned zig is the intended source (G-096 acceptance; wrapper rename pending).
## Layout
- `build905.zig` - the zig recipe (verbatim lineage copy - see Lineage). Expects its
build root's SIBLING dirs to hold sources (`../tcl905`, `../zlib`, `../tclthread`,
`../tclvfs`, `../vqtcl`), which is why `suite.tcl` stages it into
`_build/suite_tcl90/build/` rather than building in place.
- `build_libtommath/ build_zlib/ build_tclvfs/ build_vqtcl/ build_tclthread/` - helper
build modules. The four tcl-dependent helpers take the tcl source dir as their
`tcldir` param (a hardcoded `../tcl90/generic` include, marked "todo - fix" upstream,
was fixed when these copies were made). `build_libtommath.zig` still carries the
hardcoded path but is not called by the current recipe (tcl's in-tree tommath is
compiled into the core; the `libtommath.dll` seen in old output dirs was a stale
cache-install artifact).
- `src/main.zig` - stub root module that disables zig's own start symbols so the mingw
CRT provides the exe entry.
- `tools/` - `wrapfiletofile.zig` (uuid-header generation used by prepare-source),
`zipfs_mkzip.tcl` / `zipfs_mkimg.tcl` (make-zipfs drivers).
- `patches/` - recovered 2024 experiment patches, including TCLSH_PIPEREPL
(see `patches/README.md`). NOT applied by the default build - stock sources.
- `suite.tcl` - the driver: stage recipe -> arrange sources -> generate the
configure-produced files a fresh checkout lacks -> build -> smoke-test.
## Sources
| dir (sibling of build root) | origin | selection |
|---|---|---|
| `tcl905` | fossil `https://core.tcl-lang.org/tcl` (clone cached `~/.fossils/tcl.fossil`) | `-tclbranch`, default `core-9-0-branch` |
| `zlib` | the tcl checkout's own `compat/zlib` (version-matched, includes contrib/minizip) | copied by suite.tcl |
| `tclthread` | fossil `https://core.tcl-lang.org/thread` | trunk |
| `tclvfs` | fossil `https://core.tcl-lang.org/tclvfs` | trunk |
| `vqtcl` | local dir only (vlerq 4.1 by jcw; no public upstream confirmed) | `-vqtcl-source` |
Checkouts under `_build` use `FOSSIL_HOME=<stage>` so the user's global fossil config
db records nothing (stage-local `_fossil` db, deleted with `clean`).
## Fresh-checkout preparations the driver performs
Normally products of `configure`/`nmake` that a bare checkout lacks (G-076 findings):
- `win/tclUuid.h` - generated from `manifest.uuid`.
- `win/tclsh.exe.manifest` - generated from its `.in` (`TCL_WIN_VERSION` per the tree's
own formula: `TCL_VERSION . releaselevel . patchlevel-stripped-of-'ab.'`).
- `thread/win/thread.rc` - gains `#include "../../tcl905/generic/tcl.h"` (the zig rc
compile does not receive the makefile's include paths; historically an uncommitted
tweak in the 2024 checkout, now a scripted idempotent edit of the staged copy).
## Lineage
`build905.zig` descends from the validated ad-hoc builds in `c:/buildtcl/2024zig/build_tcl90`
(reference snapshot: `TEMP_REFERENCE/2024zig`): `build90.zig` (tcl 9.0.1 era, last
built 2024-11-27, zig 0.14.0-dev.2074) plus the 2026-07-20 G-076 deltas - source folder
`../tcl905`, tclUuid.h copy-to-source targets derived from `tcl_source_folder`, and
`mingw_unicode_entry_point = true` (9.0.5 sources dropped `TCL_BROKEN_MAINARGS`;
tclAppInit's `_tmain` is `wmain` under `-DUNICODE`). Full provenance:
`goals/archive/G-076-tcl9-deadconsole-fix-adoption.md` (2026-07-20 entries). Goal
contract for this suite: `goals/G-096-zig-buildsuite-piperepl.md`.

2009
src/buildsuites/suite_tcl90/build905.zig

File diff suppressed because it is too large Load Diff

190
src/buildsuites/suite_tcl90/build_libtommath/build_libtommath.zig

@ -0,0 +1,190 @@
const std = @import("std");
const fs = std.fs;
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
//pub fn build(b: *std.Build) !void {}
pub fn build_libtommath(comptime subdir: []const u8, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*std.Build.Step.Compile {
const lib = b.addStaticLibrary(.{
.name = "tommath",
.target = target,
.optimize = optimize,
});
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
//try flags.appendSlice(&.{ "-Wall", "-O2" });
try flags.appendSlice(&.{ "-Wall", "-O2", "-Wextra", "-Werror=return-type" });
//libtom relies on dead code elimination - see s_mp_rand_platform.c
//The -O2 seems to fix the issue where dead code elimination is not performed (on windows anyway)
var sources = std.ArrayList([]const u8).init(b.allocator);
{
var dir = try fs.cwd().openDir(subdir, .{ .iterate = true });
defer dir.close();
var it = dir.iterate();
const allowed_exts = [_][]const u8{
".c",
};
while (try it.next()) |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 sources.append(b.pathJoin(&.{
subdir,
entry.name,
}));
}
}
//const ext = std.fs.path.extension(entry.basename);
}
}
//std.debug.print("sources: {any}",.{sources.items});
lib.linkLibC();
if (target.result.os.tag == .windows) {
lib.linkSystemLibrary("advapi32"); //crypt
}
//lib.addIncludePath(.{ .path = subdir });
lib.addIncludePath(b.path(subdir));
lib.addIncludePath(b.path("../tcl90/generic")); //todo - fix
lib.addCSourceFiles(.{
.files = sources.items,
.flags = flags.items,
});
// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
b.installArtifact(lib);
const exe = b.addExecutable(.{
.name = "test_libtommath",
.target = target,
.optimize = optimize,
});
exe.step.dependOn(&lib.step);
exe.linkLibC();
if (target.result.os.tag == .windows) {
//exe.linkSystemLibrary("netapi32");
//exe.linkSystemLibrary("kernel32");
//exe.linkSystemLibrary("user32");
exe.linkSystemLibrary("advapi32"); //crypt
//exe.linkSystemLibrary("userenv");
//exe.linkSystemLibrary("ws2_32");
}
//exe.addIncludePath(.{ .path = subdir });
exe.addIncludePath(b.path(subdir));
//exe.addIncludePath(.{ .path = subdir ++ "/demo" });
exe.addIncludePath(b.path(subdir ++ "/demo"));
exe.addCSourceFile(.{
//.file = .{ .path = subdir ++ "/demo/test.c" },
.file = b.path(subdir ++ "/demo/test.c"),
.flags = flags.items,
});
exe.addCSourceFile(.{
//.file = .{ .path = subdir ++ "/demo/shared.c" },
.file = b.path(subdir ++ "/demo/shared.c"),
.flags = flags.items,
});
exe.linkLibrary(lib);
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);
if (target.result.os.tag == .windows) {
try flags.appendSlice(&.{
"-Wl,--out-implib=libtommath.dll.a",
});
const dll = b.addSharedLibrary(.{
.name = "libtommath",
.target = target,
.optimize = optimize,
});
dll.linkLibC();
dll.linkSystemLibrary("advapi32"); //crypt
//dll.addIncludePath(.{ .path = subdir });
dll.addIncludePath(b.path(subdir));
dll.addCSourceFiles(.{
.files = sources.items,
.flags = flags.items,
});
b.installArtifact(dll);
} else {
const shlib_tom = b.addSharedLibrary(.{
.name = "tommath",
.target = target,
.optimize = optimize,
});
shlib_tom.linkLibC();
//shlib_tom.addIncludePath(.{ .path = subdir });
shlib_tom.addIncludePath(b.path(subdir));
shlib_tom.addCSourceFiles(.{
.files = sources.items,
.flags = flags.items,
});
b.installArtifact(shlib_tom);
}
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const prepare = b.addSystemCommand(&.{
"echo",
"--system command prepare--",
});
run_cmd.step.dependOn(&prepare.step);
b.getInstallStep().dependOn(&prepare.step);
return lib;
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
//const lib_unit_tests = b.addTest(.{
// //.root_source_file = .{ .path = "src/root.zig" },
// .target = target,
// .optimize = optimize,
//});
//const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
//const exe_unit_tests = b.addTest(.{
// .root_source_file = .{ .path = "src/main.zig" },
// .target = target,
// .optimize = optimize,
//});
//const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
//const test_step = b.step("test", "Run unit tests");
//test_step.dependOn(&run_lib_unit_tests.step);
//test_step.dependOn(&run_exe_unit_tests.step);
}

152
src/buildsuites/suite_tcl90/build_tclthread/build_tclthread.zig

@ -0,0 +1,152 @@
const std = @import("std");
const fs = std.fs;
const builtin = @import("builtin");
const mkvar_thread_zip_file = "libthread3.0.1.zip";
const mkvar_thread_vfs_root = "libthread.vfs";
const mkvar_thread_vfs_path = mkvar_thread_vfs_root ++ "/thread_library";
const mkvar_pkg_lib_file = "tcl9thread301.dll";
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
//pub fn build(b: *std.Build) !void {}
pub fn build_tclthread(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 wrapfilecmd = b.addExecutable(.{
.name = "wrapfiletofile",
.root_source_file = b.path("tools/wrapfiletofile.zig"),
.target = b.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");
//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 THREAD_VERSION_UUID \\",
// "-prefixnl",
// "1",
// "-input",
//});
////uuid_cmd.addFileArg(.{ .path = b.pathJoin(&.{ b.pathFromRoot(""), "..", "tcl" ++ tcl_version, "manifest.uuid" }) });
//uuid_cmd.addFileArg(b.path(subdir ++ "/manifest.Uuid"));
//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);
const lib = b.addSharedLibrary(.{
.name = "tcl9thread301",
.target = target,
.optimize = optimize,
});
//lib.step.dependOn(&wf_uuidh.step);
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
//try flags.appendSlice(&.{ "-Wall", "-O2", "-fomit-frame-pointer", "-DNDEBUG" });
try flags.appendSlice(&.{ "-Wall", "-O3", "-fomit-frame-pointer" });
try flags.appendSlice(&.{
"-DPACKAGE_NAME=\"thread\"",
"-DPACKAGE_TARNAME=\"thread\"",
"-DPACKAGE_VERSION=\"3.0.1\"",
"-DPACKAGE_STRING=\"thread 3.0.1\"",
"-DPACKAGE_BUGREPORT=\"\"",
"-DPACKAGE_URL=\"\"",
"-DTCL_CFG_OPTIMIZED=1",
"-DUSE_TCL_STUBS=1",
"-DZIPFS_BUILD=1",
"-DTCL_WIDE_INT_TYPE=long long",
//"-DHAVE_STDIO_H=1",
//"-DHAVE_STDLIB_H=1",
//"-DHAVE_STRING_H=1",
"-DHAVE_INTTYPES_H=1",
//"-DHAVE_STDINT_H=1",
//"-DHAVE_STRINGS_H=1",
//"-DHAVE_SYS_STAT_H=1",
//"-DHAVE_SYS_TYPES_H=1",
//"-DHAVE_FILE_OFFSET_BITS=64",
//"-DHAVE_STRUCT_STAT64=1",
//"-DHAVE_LSEEK64=1",
"-static-libgcc",
});
//BUILD_<somelibrary> needs to be defined for windows
if (target.result.os.tag == .windows) {
try flags.appendSlice(&.{
"-DBUILD_thread=/**/",
});
}
var tclthread_sources = std.ArrayList([]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");
//if (target.result.os.tag == .windows) {
// lib.linkSystemLibrary("advapi32"); //crypt
//}
lib.addIncludePath(b.path(subdir));
lib.addIncludePath(b.path(subdir ++ "/generic"));
//generated threadUuid.h: current thread trunk #includes it (upstream makefiles
//generate it into their build dir); using the generator's output dir as an include
//path also gives the compile its dependency on the generation step.
lib.addIncludePath(threaduuid_file.dirname());
lib.addIncludePath(b.path(tcldir ++ "/generic")); //todo - fix
lib.addCSourceFiles(.{
.files = tclthread_sources.items,
.flags = flags.items,
});
lib.linkLibrary(stublib);
lib.linkLibC();
lib.addWin32ResourceFile(.{
.file = b.path(subdir ++ "/win/thread.rc"),
.flags = &.{
"-DPACKAGE_MAJOR=3",
"-DPACKAGE_MINOR=0",
"-DPACKAGE_VERSION=3.0b2",
},
}); //ignored for non-windows targets
b.installArtifact(lib);
return lib;
}

81
src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_shared.zig

@ -0,0 +1,81 @@
const std = @import("std");
const fs = std.fs;
const tclvfs_dotversion = "1.4.2";
const tclvfs_nodotversion = "142";
pub fn vfsadd_tclvfs_files(comptime tcldir: []const u8, comptime subdir: []const u8, b: *std.Build, vfswrite: *std.Build.Step.WriteFile) !*std.Build.Step.WriteFile {
//_ = tcldir;
//const version = "1.4.2";
//recursive copy
//_ = make_vfs_root.addCopyDirectory(b.path(tcl_source_folder ++ "/../tclvfs/library"), "base/lib/vfs1.4.2", .{});
_ = vfswrite.addCopyDirectory(b.path(tcldir ++ "/" ++ subdir ++ "/library"), b.pathJoin(&.{ "base", "lib", "vfs" ++ tclvfs_dotversion }), .{});
return vfswrite;
}
pub fn build_tclvfs(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 {
//review - libname Vfs vs vfs???
const lib = b.addSharedLibrary(.{
.name = "tcl9vfs142",
.target = target,
.optimize = optimize,
});
//_ = stublib;
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
//try flags.appendSlice(&.{ "-Wall", "-O3" });
try flags.appendSlice(&.{ "-Wall", "-O3", "-fomit-frame-pointer" });
try flags.appendSlice(&.{
"-DPACKAGE_NAME=\"vfs\"",
"-DPACKAGE_TARNAME=\"vfs\"",
"-DPACKAGE_VERSION=\"" ++ tclvfs_dotversion ++ "\"",
"-DPACKAGE_STRING=\"vfs " ++ tclvfs_dotversion ++ "\"",
"-DPACKAGE_BUGREPORT=\"\"",
"-DPACKAGE_URL=\"\"",
"-DBUILD_VFS=1",
"-DTCL_CFG_OPTIMIZED=1",
"-DUSE_TCL_STUBS=1",
"-DZIPFS_BUILD=1",
"-DTCL_WIDE_INT_TYPE=long long",
});
if (target.result.os.tag == .windows) {
try flags.appendSlice(&.{
"-static-libgcc",
});
}
//-DBUILD_VFS only on windows? value?
var sources = std.ArrayList([]const u8).init(b.allocator);
try sources.append(subdir ++ "/generic/vfs.c");
lib.addIncludePath(b.path(subdir));
lib.addIncludePath(b.path(subdir ++ "/generic"));
lib.addIncludePath(b.path(tcldir ++ "/generic"));
lib.addIncludePath(b.path(tcldir ++ "/generic"));
if (target.result.os.tag == .windows) {
lib.addIncludePath(b.path(tcldir ++ "/win"));
} else {
lib.addIncludePath(b.path(tcldir ++ "/unix"));
}
lib.addCSourceFiles(.{
.files = sources.items,
.flags = flags.items,
});
lib.linkLibrary(stublib);
lib.linkLibC();
lib.addWin32ResourceFile(.{
.file = b.path(subdir ++ "/win/tclvfs.rc"),
.flags = &.{
"-DPACKAGE_MAJOR=1",
"-DPACKAGE_MINOR=4",
"-DPACKAGE_VERSION=" ++ tclvfs_dotversion,
"-DDOTVERSION=" ++ tclvfs_dotversion,
"-DCOMMAVERSION=" ++ tclvfs_dotversion,
"-DVERSION=" ++ tclvfs_nodotversion,
},
}); //ignored for non-windows targets
// COMMAVERSION??? VERSION???
b.installArtifact(lib);
return lib; //static library
}

76
src/buildsuites/suite_tcl90/build_tclvfs/build_tclvfs_static.zig

@ -0,0 +1,76 @@
const std = @import("std");
const fs = std.fs;
const tclvfs_dotversion = "1.4.2";
const tclvfs_nodotversion = "142";
pub fn vfsadd_tclvfs_files(comptime tcldir: []const u8, comptime subdir: []const u8, b: *std.Build, vfswrite: *std.Build.Step.WriteFile) !*std.Build.Step.WriteFile {
//_ = tcldir;
//const version = "1.4.2";
//recursive copy
//_ = make_vfs_root.addCopyDirectory(b.path(tcl_source_folder ++ "/../tclvfs/library"), "base/lib/vfs1.4.2", .{});
_ = vfswrite.addCopyDirectory(b.path(tcldir ++ "/" ++ subdir ++ "/library"), b.pathJoin(&.{ "base", "lib", "vfs" ++ tclvfs_dotversion }), .{});
return vfswrite;
}
pub fn build_tclvfs(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 {
//review - libname Vfs vs vfs???
const slib = b.addStaticLibrary(.{
.name = "Vfs",
.target = target,
.optimize = optimize,
});
_ = stublib;
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
//try flags.appendSlice(&.{ "-Wall", "-O3" });
try flags.appendSlice(&.{ "-Wall", "-O2", "-fomit-frame-pointer" });
try flags.appendSlice(&.{
"-DPACKAGE_NAME=\"vfs\"",
"-DPACKAGE_TARNAME=\"vfs\"",
"-DPACKAGE_VERSION=\"" ++ tclvfs_dotversion ++ "\"",
"-DPACKAGE_STRING=\"vfs " ++ tclvfs_dotversion ++ "\"",
"-DPACKAGE_BUGREPORT=\"\"",
"-DPACKAGE_URL=\"\"",
"-DBUILD_VFS=1",
"-DTCL_CFG_OPTIMIZED=1",
"-DUSE_TCL_STUBS=0",
"-DZIPFS_BUILD=1",
"-DTCL_WIDE_INT_TYPE=long long",
"-DSTATIC_BUILD",
// ...
//"-static-libgcc",
});
//-DBUILD_VFS only on windows? value?
var sources = std.ArrayList([]const u8).init(b.allocator);
try sources.append(subdir ++ "/generic/vfs.c");
slib.addIncludePath(b.path(subdir));
slib.addIncludePath(b.path(subdir ++ "/generic"));
slib.addIncludePath(b.path(tcldir ++ "/generic"));
slib.addIncludePath(b.path(tcldir ++ "/generic"));
slib.addIncludePath(b.path(tcldir ++ "/win"));
slib.addCSourceFiles(.{
.files = sources.items,
.flags = flags.items,
});
//slib.linkLibrary(stublib);
slib.linkLibC();
slib.addWin32ResourceFile(.{
.file = b.path(subdir ++ "/win/tclvfs.rc"),
.flags = &.{
"-DPACKAGE_MAJOR=1",
"-DPACKAGE_MINOR=4",
"-DPACKAGE_VERSION=" ++ tclvfs_dotversion,
"-DDOTVERSION=" ++ tclvfs_dotversion,
"-DCOMMAVERSION=" ++ tclvfs_dotversion,
"-DVERSION=" ++ tclvfs_nodotversion,
},
}); //ignored for non-windows targets
// COMMAVERSION??? VERSION???
//b.installArtifact(slib);
return slib; //static library
}

80
src/buildsuites/suite_tcl90/build_vqtcl/build_vqtcl_shared.zig

@ -0,0 +1,80 @@
const std = @import("std");
const fs = std.fs;
const vqtcl_dotversion = "4.1";
const vqtcl_nodotversion = "41";
pub fn vfsadd_vqtcl_files(comptime tcldir: []const u8, comptime subdir: []const u8, b: *std.Build, vfswrite: *std.Build.Step.WriteFile) !*std.Build.Step.WriteFile {
//_ = tcldir;
//recursive copy
//_ = make_vfs_root.addCopyDirectory(b.path(tcl_source_folder ++ "/../tclvfs/library"), "base/lib/vfs1.4.2", .{});
_ = vfswrite.addCopyDirectory(b.path(tcldir ++ "/" ++ subdir ++ "/library"), b.pathJoin(&.{ "base", "lib", "vqtcl" ++ vqtcl_dotversion }), .{});
return vfswrite;
}
pub fn build_vqtcl(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 lib = b.addSharedLibrary(.{
.name = "tcl9vlerq" ++ vqtcl_nodotversion,
.target = target,
.optimize = optimize,
});
//_ = stublib;
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
//try flags.appendSlice(&.{ "-Wall", "-O3" });
//try flags.appendSlice(&.{ "-Wall", "-O2", "-fomit-frame-pointer" });
try flags.appendSlice(&.{ "-O3", "-fomit-frame-pointer" });
try flags.appendSlice(&.{
"-DPACKAGE_NAME=\"vfs\"",
"-DPACKAGE_TARNAME=\"vfs\"",
"-DPACKAGE_VERSION=\"" ++ vqtcl_dotversion ++ "\"",
"-DPACKAGE_STRING=\"vfs " ++ vqtcl_dotversion ++ "\"",
"-DPACKAGE_BUGREPORT=\"\"",
"-DPACKAGE_URL=\"\"",
"-DTCL_CFG_OPTIMIZED=1",
"-DUSE_TCL_STUBS=1",
"-DZIPFS_BUILD=1",
"-DTCL_WIDE_INT_TYPE=long long",
});
// "-DTCL_MEM_DEBUG=1", //define for all or none of modules that are linked together
// "-DTCL_MAJOR_VERSION=9",
//"-static-libgcc", //argument unused - review
var sources = std.ArrayList([]const u8).init(b.allocator);
try sources.append(subdir ++ "/generic/vlerq.c");
lib.addIncludePath(b.path(subdir));
lib.addIncludePath(b.path(subdir ++ "/generic"));
lib.addIncludePath(b.path(subdir ++ "/win")); //header for MSVC only - possibly not needed here - unless zig build can use it?
lib.addIncludePath(b.path(tcldir ++ "/generic"));
lib.addIncludePath(b.path(tcldir ++ "/generic"));
if (target.result.os.tag == .windows) {
lib.addIncludePath(b.path(tcldir ++ "/win"));
} else {
lib.addIncludePath(b.path(tcldir ++ "/unix"));
}
lib.addCSourceFiles(.{
.files = sources.items,
.flags = flags.items,
});
lib.linkLibrary(stublib);
lib.linkLibC();
// no resource file - todo - make one?
//lib.addWin32ResourceFile(.{
// .file = b.path(subdir ++ "/win/tclvfs.rc"),
// .flags = &.{
// "-DPACKAGE_MAJOR=1",
// "-DPACKAGE_MINOR=4",
// "-DPACKAGE_VERSION=" ++ vqtcl_dotversion,
// "-DDOTVERSION=" ++ vqtcl_dotversion,
// "-DCOMMAVERSION=" ++ vqtcl_dotversion,
// "-DVERSION=" ++ vqtcl_nodotversion,
// },
//}); //ignored for non-windows targets
// COMMAVERSION??? VERSION???
b.installArtifact(lib);
return lib; //static library
}

161
src/buildsuites/suite_tcl90/build_zlib/build_zlib.zig

@ -0,0 +1,161 @@
const std = @import("std");
const fs = std.fs;
pub fn build_zlib(comptime subdir: []const u8, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*std.Build.Step.Compile {
var statlibname: []const u8 = undefined;
if (target.result.os.tag == .windows) {
statlibname = "libz";
} else {
statlibname = "z";
}
const statlib = b.addStaticLibrary(.{
.name = statlibname,
.target = target,
.optimize = optimize,
});
var zlib_flags = std.ArrayList([]const u8).init(b.allocator);
defer zlib_flags.deinit();
//try zlib_flags.appendSlice(&.{ "-Wall", "-static-libgcc", "-Wno-error=implicit-function-declaration", "-O3" }); //DL_info undeclared
//try zlib_flags.appendSlice(&.{ "-Wall", "-static-libgcc", "-O3" }); //DL_info undeclared
//try zlib_flags.appendSlice(&.{ "-Wall", "-Wno-error=implicit-function-declaration", "-O3", "-fPIC" }); //seems to work with linux-musl
//try zlib_flags.appendSlice(&.{ "-Wall", "-Wno-error=implicit-function-declaration", "-O3", "-fPIC", "-fno-omit-frame-pointer", "-fsanitize=undefined" });
//2024 - seems to work
try zlib_flags.appendSlice(&.{ "-Wall", "-Wno-error=implicit-function-declaration", "-O3", "-fPIC", "-fno-omit-frame-pointer", "-fno-sanitize=undefined" });
//2024 trial
//try zlib_flags.appendSlice(&.{ "-Wall", "-Wno-error=implicit-function-declaration", "-O2", "-fPIC", "-fomit-frame-pointer", "-fno-sanitize=undefined" });
//try zlib_flags.appendSlice(&.{ "-Wall", "-Wno-error=implicit-function-declaration", "-fPIC", "-fomit-frame-pointer", "-fno-sanitize=undefined" });
//try zlib_flags.appendSlice(&.{ "-Wall", "-O3" });
//try zlib_flags.appendSlice(&.{"-O"}); //C99 and later do not support implicit function declarations
//try zlib_flags.appendSlice(&.{ "-O3", "-Wall", "-Wwrite-strings", "-Wpointer-arith", "-Wconversion", "-Wstrict-prototypes", "-Wmissing-prototypes" });
var zlib_sources = std.ArrayList([]const u8).init(b.allocator);
{
var dir = try fs.cwd().openDir(subdir, .{ .iterate = true });
defer dir.close();
//var walker = try dir.walk(b.allocator);
//defer walker.deinit();
var it = dir.iterate();
const allowed_exts = [_][]const u8{
".c",
};
while (try it.next()) |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 zlib_sources.append(b.pathJoin(&.{
subdir,
entry.name,
}));
}
}
}
}
statlib.linkLibC();
statlib.addIncludePath(b.path(subdir));
statlib.addIncludePath(b.path(subdir ++ "/contrib/minzip"));
statlib.addCSourceFiles(.{
.files = zlib_sources.items,
.flags = zlib_flags.items,
});
b.installArtifact(statlib);
var dylib_flags = std.ArrayList([]const u8).init(b.allocator);
try dylib_flags.appendSlice(zlib_flags.items);
//trial
//try dylib_flags.appendSlice(&.{
// "-DZLIB_DLL",
//});
//"-Wl,-with-default-msvcrt=ucrtbase", //no effect - seems to be unnecessary anyway - ldd does show msvcrt as well as ucrtbase, but shims seem to all use ucrtbase.dll
//"-Wl,-out-implib,libz.dll.a", //seems to have no effect. zig always creates a .lib
//"-DZLIB_WINAPI",
//"-Wl,-exclude-libs=ALL",
std.debug.print("build_zlib.zig flags {s}\n", .{dylib_flags.items});
const dylib = b.addSharedLibrary(.{
.name = "zlib1",
.target = target,
.optimize = optimize,
});
dylib.linkLibC();
dylib.addIncludePath(b.path(subdir));
dylib.addIncludePath(b.path(subdir ++ "/contrib/minizip"));
dylib.addCSourceFiles(.{
.files = zlib_sources.items,
.flags = dylib_flags.items,
});
//dylib.addWin32ResourceFile(.{ .file = .{ .path = "zlib/win32/zlib1.rc" } }); //ignored for non-windows targets
dylib.addWin32ResourceFile(.{ .file = b.path(subdir ++ "/win32/zlib1.rc") }); //ignored for non-windows targets
b.installArtifact(dylib);
const mgz_exe = b.addExecutable(.{
.name = "minigzip",
.target = target,
.optimize = optimize,
});
mgz_exe.step.dependOn(&statlib.step);
mgz_exe.addIncludePath(b.path(subdir));
mgz_exe.addCSourceFile(.{
.file = b.path(subdir ++ "/test/minigzip.c"),
.flags = zlib_flags.items,
});
mgz_exe.linkLibC();
mgz_exe.linkLibrary(statlib);
b.installArtifact(mgz_exe);
const eg_exe = b.addExecutable(.{
.name = "zlib_example",
.target = target,
.optimize = optimize,
});
eg_exe.step.dependOn(&statlib.step);
eg_exe.addIncludePath(b.path(subdir));
eg_exe.addCSourceFile(.{
.file = b.path(subdir ++ "/test/example.c"),
.flags = zlib_flags.items,
});
eg_exe.linkLibC();
eg_exe.linkLibrary(statlib);
b.installArtifact(eg_exe);
const egd_exe = b.addExecutable(.{
.name = "zlib_example_d",
.target = target,
.optimize = optimize,
});
egd_exe.step.dependOn(&statlib.step);
egd_exe.addIncludePath(b.path(subdir));
egd_exe.addCSourceFile(.{
.file = b.path(subdir ++ "/test/example.c"),
.flags = dylib_flags.items,
});
egd_exe.linkLibrary(dylib);
egd_exe.root_module.addRPathSpecial("$ORIGIN/../lib");
egd_exe.linkLibC();
b.installArtifact(egd_exe);
const mgzd_exe = b.addExecutable(.{
.name = "minigzip_d",
.target = target,
.optimize = optimize,
});
mgzd_exe.step.dependOn(&statlib.step);
mgzd_exe.addIncludePath(b.path(subdir));
mgzd_exe.addCSourceFile(.{
.file = b.path(subdir ++ "/test/minigzip.c"),
.flags = dylib_flags.items,
});
mgzd_exe.root_module.addRPathSpecial("$ORIGIN/../lib");
mgzd_exe.linkLibrary(dylib);
b.installArtifact(mgzd_exe);
return statlib;
}

41
src/buildsuites/suite_tcl90/patches/README.md

@ -0,0 +1,41 @@
# Recovered 2024 Tcl source patches (G-096)
Origin: `c:/buildtcl/2024zig/build_tcl90/patch/` (reference snapshot
`TEMP_REFERENCE/2024zig/build_tcl90/patch/`), written against ~Tcl 9.0b4-era sources
(Aug 2024; tclZipfs/tclCmdIL Nov 2024). Recovered into tracked form 2026-07-20 - these
files previously existed only in those two VCS-ignored on-disk copies.
NONE of these are applied by the suite's default build: `suite.tcl build` compiles
stock upstream sources. Rebasing/applying is per-patch opt-in work.
## tclMain.c.piperepl.patch / tclMain_piperepl.c (the TCLSH_PIPEREPL capability)
`tclMain_piperepl.c` is the full patched file; the `.patch` is its diff against the
era's stock `tclMain.c`.
Purpose (the G-096 goal capability): allow returning to a standard C-level tcl repl
after launching from a kit with its own `main.tcl` - not possible with unmodified
sources. Gated by the `TCLSH_PIPEREPL` env var (unset/`0` = stock behaviour). When
enabled:
- Publishes `::tclsh(istty)` (unlinked fact) plus linked `::tclsh(dorepl)` and
`::tclsh(evalinput)` before the startup script runs; a script-arg launch defaults
`dorepl` to 0 (stock semantics) but the script may set `::tclsh(dorepl) 1` to drop
into the interactive repl after it completes.
- Piped (non-tty) input the script did not consume is accumulated into
`::tclsh(inputbuffer)` rather than evaluated as commands, unless the script opts in
with `::tclsh(evalinput) 1` - protects arbitrary piped data from accidental eval.
- On stdin eof with `dorepl` set, stdin is reopened from the console (`CONIN$` on
windows, `/dev/tty` elsewhere) and installed as the std channel, yielding a live
standard repl after a pipeline ends.
Status: NOT yet rebased onto current Tcl 9 sources. Note the punkshell-level analogue
for the punk repl is G-038 (piped-to-interactive session continuity) - this patch is
the plain-tclsh/kit-runtime counterpart.
## tclZipfs.patch, tclCmdIL.patch
Companion experiment patches from the same era (zipfs behaviour and tclCmdIL tweak),
recovered for durability; unexamined in detail. See the fossil-era notes in
`TEMP_REFERENCE/2024zig/build_tcl90/patch/` (doc/, prev/, test_zipfs_patch/) for their
surrounding experiment context.

13
src/buildsuites/suite_tcl90/patches/tclCmdIL.patch

@ -0,0 +1,13 @@
--- tclCmdIL_orig.c Sat Oct 05 06:09:52 2024
+++ tclCmdIL.c Mon Nov 18 13:59:01 2024
@@ -3926,6 +3926,10 @@
itemPtr = SelectObjFromSublist(listv[i+groupOffset],
&sortInfo);
Tcl_ListObjAppendElement(interp, listPtr, itemPtr);
+ } else if (returnSubindices && (sortInfo.indexc == 0) && (groupSize > 1)) {
+ Tcl_BounceRefCount(itemPtr);
+ itemPtr = listv[i + groupOffset];
+ Tcl_ListObjAppendElement(interp, listPtr, itemPtr);
} else if (groupSize > 1) {
Tcl_ListObjReplace(interp, listPtr, LIST_MAX, 0,
groupSize, &listv[i]);

233
src/buildsuites/suite_tcl90/patches/tclMain.c.piperepl.patch

@ -0,0 +1,233 @@
--- tclMain.c Fri Aug 16 04:10:58 2024
+++ tclMain_piperepl.c Mon Aug 19 01:24:03 2024
@@ -105,6 +105,22 @@
* commands. */
} InteractiveState;
+typedef struct {
+ int istty; /* Non-zero means standard input is a
+ * terminal-like device
+ * Unlike InteractiveState.tty this is
+ * unaffected by whether a script path
+ * was specified at argv0 */
+ int dorepl; /* whether to run the default repl
+ * linked to ::tclsh(dorepl)
+ * so script file or stdin script
+ * can influence */
+ int reopened; /* if 1, we are processing from console - not piped input*/
+ int evalinput; /* default 0 - whether to evaluate piped input as commands
+ * This is input the script (if any) didn't consume
+ */
+ Tcl_Obj *inputbuffer;
+} PipeRepl;
/*
* Forward declarations for functions defined later in this file.
*/
@@ -291,6 +307,10 @@
Tcl_MainLoopProc *mainLoopProc;
Tcl_Channel chan;
InteractiveState is;
+ PipeRepl piperepl;
+ Tcl_Obj *tclInteractive; //JMN
+ int ttyAtStart; // Whether we initially had piped data or not
+ int inputReopen; // flag for reopening stdin
TclpSetInitialEncodings();
if (argc > 0) {
@@ -359,6 +379,37 @@
*/
is.tty = isatty(0);
+ ttyAtStart = is.tty; //JMN
+ int hasPipeRepl; //flag for whether piperepl and ::tclsh() behaviour enabled
+ {
+ const char *s;
+ Tcl_DString ds;
+ s = TclGetEnv("TCLSH_PIPEREPL", &ds);
+ hasPipeRepl = ((s != NULL) && strcmp(s, "0"));
+ if (s != NULL) {
+ Tcl_DStringFree(&ds);
+ }
+ }
+ piperepl.reopened = 0;
+ piperepl.dorepl = 0;
+ piperepl.evalinput = 0;
+ if (hasPipeRepl) {
+ TclNewObj(piperepl.inputbuffer);
+ piperepl.istty = ttyAtStart;
+ piperepl.dorepl = ttyAtStart;
+ //unlinked - setting from script should have no effect
+ Tcl_SetVar2Ex(interp, "tclsh", "istty",
+ Tcl_NewBooleanObj(ttyAtStart), TCL_GLOBAL_ONLY);
+ //will be linked so script/stdin can influence
+ // if path (script) supplied, but no stdin
+ // - will be set 0 to match default 'tclsh <script>' behaviour
+ // when ::tclsh(dorepl) not set in the script
+ Tcl_SetVar2Ex(interp, "tclsh", "dorepl",
+ Tcl_NewBooleanObj(ttyAtStart), TCL_GLOBAL_ONLY);
+ Tcl_SetVar2Ex(interp, "tclsh", "evalinput",
+ Tcl_NewBooleanObj(piperepl.evalinput), TCL_GLOBAL_ONLY);
+ }
+
Tcl_SetVar2Ex(interp, "tcl_interactive", NULL,
Tcl_NewBooleanObj(!path && is.tty), TCL_GLOBAL_ONLY);
@@ -397,9 +448,18 @@
* Invoke the script specified on the command line, if any. Must fetch it
* again, as the appInitProc might have reset it.
*/
-
+ if (hasPipeRepl) {
+ //REVIEW - ok to use Tcl_LinkVar on individual array elements? (we don't want to Tcl_LinkArray the whole thing)
+ Tcl_LinkVar(interp, "tclsh(dorepl)", &piperepl.dorepl, TCL_LINK_BOOLEAN);
+ Tcl_LinkVar(interp, "tclsh(evalinput)", &piperepl.evalinput, TCL_LINK_BOOLEAN);
+ Tcl_LinkVar(interp, "tcl_interactive", &is.tty, TCL_LINK_BOOLEAN); // link before script only when TCLSH_PIPEREPL true
+ }
path = Tcl_GetStartupScript(&encodingName);
if (path != NULL) {
+ if (hasPipeRepl) {
+ piperepl.dorepl = 0; //default to standard behaviour of no repl when script arg present - script can override
+ // stdin can only override dorepl, tcl_interactive if evalinput set to true in script
+ }
Tcl_ResetResult(interp);
code = Tcl_FSEvalFileEx(interp, path, encodingName);
if (code != TCL_OK) {
@@ -419,7 +479,35 @@
}
exitCode = 1;
}
- goto done;
+ if (hasPipeRepl) {
+ if (!(piperepl.dorepl || piperepl.evalinput)) {
+ //tty or not - we need some reason to progress
+ goto done;
+ }
+ //evalinput may be specified when tty - but in that case there can be no pipeline input
+ //This is for usecase to allow pipeline to set dorepl without modifiying script
+ if (piperepl.istty && !piperepl.dorepl) {
+ //Launch without pipeline -
+ goto done;
+ }
+ } else {
+ goto done;
+ }
+ }
+
+
+ if (hasPipeRepl) {
+ if (path == NULL) {
+ piperepl.evalinput = 1; //duplicate standard tclsh behaviour and pass through to repl for eval
+ }
+ piperepl.inputbuffer = Tcl_NewStringObj("", -1);
+ Tcl_IncrRefCount(piperepl.inputbuffer);
+ if (piperepl.istty) {
+ inputReopen = 0; //no reopen required - eof is from console?
+ } else {
+ //assume there was some data piped?
+ inputReopen = 1;
+ }
}
/*
@@ -444,7 +532,9 @@
* Get a new value for tty if anyone writes to ::tcl_interactive
*/
- Tcl_LinkVar(interp, "tcl_interactive", &is.tty, TCL_LINK_BOOLEAN);
+ if (!hasPipeRepl) {
+ Tcl_LinkVar(interp, "tcl_interactive", &is.tty, TCL_LINK_BOOLEAN);
+ }
is.input = Tcl_GetStdChannel(TCL_STDIN);
while ((is.input != NULL) && !Tcl_InterpDeleted(interp)) {
mainLoopProc = TclGetMainLoop();
@@ -464,12 +554,34 @@
break;
}
}
- if (Tcl_IsShared(is.commandPtr)) {
- Tcl_DecrRefCount(is.commandPtr);
- is.commandPtr = Tcl_DuplicateObj(is.commandPtr);
- Tcl_IncrRefCount(is.commandPtr);
+ if (hasPipeRepl && !piperepl.istty && !piperepl.reopened && !piperepl.evalinput) {
+ /* still handling from pipe
+ * This is data the script (if any) didn't read
+ * We should only evaluate as commands if we were explicitly instructed to (::tclsh(evalinput) == 1)
+ * Otherwise a script which processes arbitrary data (but set ::tclsh(dorepl) to 1 to drop into a shell)
+ * may cause the arbitrary data to be evaled unexpectedly if it failed to process all the input */
+ if (Tcl_IsShared(piperepl.inputbuffer)) {
+ Tcl_DecrRefCount(piperepl.inputbuffer);
+ piperepl.inputbuffer = Tcl_DuplicateObj(piperepl.inputbuffer);
+ Tcl_IncrRefCount(piperepl.inputbuffer);
+ }
+ length = Tcl_GetsObj(is.input,piperepl.inputbuffer);
+ if (length >= 0) {
+ if (Tcl_IsShared(piperepl.inputbuffer)) {
+ Tcl_DecrRefCount(piperepl.inputbuffer);
+ piperepl.inputbuffer = Tcl_DuplicateObj(piperepl.inputbuffer);
+ Tcl_IncrRefCount(piperepl.inputbuffer);
+ }
+ Tcl_AppendToObj(piperepl.inputbuffer, "\n", 1);
+ }
+ } else {
+ if (Tcl_IsShared(is.commandPtr)) {
+ Tcl_DecrRefCount(is.commandPtr);
+ is.commandPtr = Tcl_DuplicateObj(is.commandPtr);
+ Tcl_IncrRefCount(is.commandPtr);
+ }
+ length = Tcl_GetsObj(is.input, is.commandPtr);
}
- length = Tcl_GetsObj(is.input, is.commandPtr);
if (length < 0) {
if (Tcl_InputBlocked(is.input)) {
/*
@@ -487,9 +599,48 @@
* Either EOF, or an error on stdin; we're done
*/
- break;
+ if (piperepl.dorepl) {
+ chan = Tcl_GetStdChannel(TCL_STDOUT);
+ inputReopen--;
+ if (inputReopen >= 0) {
+ Tcl_WriteChars(chan, "? reopen stdin from console \n", -1);
+ if (is.input != NULL) {
+ //Tcl_UnregisterChannel should close stdin without killing console?
+ if (Tcl_UnregisterChannel(interp, is.input) != TCL_OK) {
+ Tcl_WriteChars(chan, "?after eof/error on stdin, unregister stdin failed\n", -1);
+ break;
+ }
+ }
+ #ifndef _WIN32
+ is.input = Tcl_OpenFileChannel(interp,"/dev/tty", "r", 0644);
+ #else
+ is.input = Tcl_OpenFileChannel(interp, "CONIN$", "r", 0644); //windows specific
+ #endif
+ //is.input = Tcl_GetStdChannel(TCL_STDIN);
+ if (is.input == NULL) {
+ Tcl_WriteChars(chan, "?after eof/error on stdin, reopen stdin from console failed\n", -1);
+ break;
+ } else {
+ piperepl.reopened = 1;
+ Tcl_SetVar2Ex(interp, "tclsh", "inputbuffer", piperepl.inputbuffer, TCL_GLOBAL_ONLY);
+ Tcl_SetChannelOption(interp, is.input, "-blocking", "1");
+ Tcl_SetChannelOption(interp, is.input, "-buffering", "line");
+ Tcl_SetStdChannel(is.input,TCL_STDIN);
+ Tcl_RegisterChannel(interp, is.input);
+ continue;
+ }
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (hasPipeRepl && !piperepl.istty && !piperepl.reopened && !piperepl.evalinput) {
+ /* still handling input from pipe and we are not configured to eval the input
+ * keep accumulating in piperepl.inputbuffer*/
+ continue;
}
-
/*
* Add the newline removed by Tcl_GetsObj back to the string. Have
* to add it back before testing completeness, because it can make

1090
src/buildsuites/suite_tcl90/patches/tclMain_piperepl.c

File diff suppressed because it is too large Load Diff

93
src/buildsuites/suite_tcl90/patches/tclZipfs.patch

@ -0,0 +1,93 @@
--- tclZipfs_original.c Sun Nov 24 15:41:53 2024
+++ tclZipfs.c Sun Nov 24 20:04:46 2024
@@ -336,11 +336,12 @@
static void SerializeCentralDirectoryEntry(
const unsigned char *start,
const unsigned char *end, unsigned char *buf,
- ZipEntry *z, size_t nameLength);
+ ZipEntry *z, size_t nameLength, long long dataStartOffset);
static void SerializeCentralDirectorySuffix(
const unsigned char *start,
const unsigned char *end, unsigned char *buf,
- int entryCount, long long directoryStartOffset,
+ int entryCount, long long dataStartOffset,
+ long long directoryStartOffset,
long long suffixStartOffset);
static void SerializeLocalEntryHeader(
const unsigned char *start,
@@ -3355,6 +3356,8 @@
int count, ret = TCL_ERROR;
Tcl_Size pwlen = 0, slen = 0, len, i = 0;
Tcl_Size lobjc;
+ long long dataStartOffset; /* The overall file offset of the start of the
+ * data section of the file. */
long long directoryStartOffset;
/* The overall file offset of the start of the
* central directory. */
@@ -3527,6 +3530,9 @@
}
memset(passBuf, 0, sizeof(passBuf));
Tcl_Flush(out);
+ dataStartOffset = Tcl_Tell(out);
+ } else {
+ dataStartOffset = 0;
}
/*
@@ -3578,7 +3584,7 @@
name = Tcl_DStringValue(&ds);
len = Tcl_DStringLength(&ds);
SerializeCentralDirectoryEntry(start, end, (unsigned char *) buf,
- z, len);
+ z, len, dataStartOffset);
if ((Tcl_Write(out, buf, ZIP_CENTRAL_HEADER_LEN)
!= ZIP_CENTRAL_HEADER_LEN)
|| (Tcl_Write(out, name, len) != len)) {
@@ -3598,7 +3604,7 @@
Tcl_Flush(out);
suffixStartOffset = Tcl_Tell(out);
SerializeCentralDirectorySuffix(start, end, (unsigned char *) buf,
- count, directoryStartOffset, suffixStartOffset);
+ count, dataStartOffset, directoryStartOffset, suffixStartOffset);
if (Tcl_Write(out, buf, ZIP_CENTRAL_END_LEN) != ZIP_CENTRAL_END_LEN) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"write error: %s", Tcl_PosixError(interp)));
@@ -3755,7 +3761,9 @@
const unsigned char *end, /* The end of writable memory. */
unsigned char *buf, /* Where to serialize to */
ZipEntry *z, /* The description of what to serialize. */
- size_t nameLength) /* The length of the name. */
+ size_t nameLength, /* The length of the name. */
+ long long dataStartOffset) /* The overall file offset of the start of the
+ * data section of the file. */
{
ZipWriteInt(start, end, buf + ZIP_CENTRAL_SIG_OFFS,
ZIP_CENTRAL_HEADER_SIG);
@@ -3780,7 +3788,7 @@
ZipWriteShort(start, end, buf + ZIP_CENTRAL_IATTR_OFFS, 0);
ZipWriteInt(start, end, buf + ZIP_CENTRAL_EATTR_OFFS, 0);
ZipWriteInt(start, end, buf + ZIP_CENTRAL_LOCALHDR_OFFS,
- z->offset);
+ z->offset - dataStartOffset);
}
static void
@@ -3789,6 +3797,9 @@
const unsigned char *end, /* The end of writable memory. */
unsigned char *buf, /* Where to serialize to */
int entryCount, /* The number of entries in the directory */
+ long long dataStartOffset,
+ /* The overall file offset of the start of the
+ * data file. */
long long directoryStartOffset,
/* The overall file offset of the start of the
* central directory. */
@@ -3805,7 +3816,7 @@
ZipWriteInt(start, end, buf + ZIP_CENTRAL_DIRSIZE_OFFS,
suffixStartOffset - directoryStartOffset);
ZipWriteInt(start, end, buf + ZIP_CENTRAL_DIRSTART_OFFS,
- directoryStartOffset);
+ directoryStartOffset - dataStartOffset);
ZipWriteShort(start, end, buf + ZIP_CENTRAL_COMMENTLEN_OFFS, 0);
}

4
src/buildsuites/suite_tcl90/src/main.zig

@ -0,0 +1,4 @@
const std = @import("std");
pub const _start = void;
pub const WinMainCRTStartup = void;

178
src/buildsuites/suite_tcl90/suite.tcl

@ -0,0 +1,178 @@
#!/usr/bin/env tclsh
#suite_tcl90 driver (G-096): stage the tracked zig recipe into ../_build/suite_tcl90,
#arrange sources, generate the configure-produced files fresh checkouts lack, build,
#and smoke-test the result.
#
#Usage: tclsh suite.tcl build ?options?
# tclsh suite.tcl clean
#Options:
# -zig <path> zig executable (default: $env(PUNK_ZIG), else 'zig' on PATH)
# -optimize <mode> Debug|ReleaseSafe|ReleaseFast|ReleaseSmall (default ReleaseFast)
# -tclbranch <branch> tcl branch/tag to open (default core-9-0-branch)
# -refresh <0|1> fossil update existing source checkouts (default 0)
# -vqtcl-source <dir> local vqtcl source dir (no public upstream confirmed;
# default C:/buildtcl/2024zig/vqtcl)
# -steps <list> zig build steps (default {install install-libraries make-zipfs})
#
#The build area lives entirely under src/buildsuites/_build (VCS-ignored via the
#existing _build globs). Fossil clones are cached in ~/.fossils; checkouts made here
#use FOSSIL_HOME=<stage> so the user's global fossil config db records nothing.
proc log {msg} {puts stdout "suite_tcl90: $msg"; flush stdout}
proc fail {msg} {puts stderr "suite_tcl90 ERROR: $msg"; flush stderr; exit 1}
proc run {args} {
#stream output; abort on nonzero exit
log "run: $args"
if {[catch {exec {*}$args >@stdout 2>@stderr} err opts]} {
set code [dict get $opts -errorcode]
if {[lindex $code 0] eq "CHILDSTATUS"} {
fail "command failed (exit [lindex $code 2]): $args"
}
fail "command failed: $args\n$err"
}
}
set suiteroot [file dirname [file normalize [info script]]]
set stage [file normalize [file join $suiteroot .. _build suite_tcl90]]
set fossildir [file normalize [file join $env(HOME) .fossils]]
#-- options ---------------------------------------------------------------
set action [lindex $argv 0]
if {$action ni {build clean}} {
puts stderr "usage: tclsh suite.tcl build|clean ?options? (see header)"
exit 2
}
array set opt {
-optimize ReleaseFast
-tclbranch core-9-0-branch
-refresh 0
-vqtcl-source C:/buildtcl/2024zig/vqtcl
-steps {install install-libraries make-zipfs}
}
set opt(-zig) [expr {[info exists env(PUNK_ZIG)] ? $env(PUNK_ZIG) : "zig"}]
foreach {k v} [lrange $argv 1 end] {
if {![info exists opt($k)]} {fail "unknown option '$k'"}
set opt($k) $v
}
if {$action eq "clean"} {
log "removing $stage"
file delete -force $stage
exit 0
}
#-- fossil environment ----------------------------------------------------
#Checkout registrations go to a stage-local config db, not the user's global one
#(agent-memory: fossil scratchpad registry pollution). Clones are still shared.
file mkdir $stage
set env(FOSSIL_HOME) $stage
file mkdir $fossildir
proc fossil_source {name url branch dir} {
#ensure ~/.fossils/<name>.fossil exists (clone if needed) and open it at branch in dir
global fossildir opt
set repo [file join $fossildir $name.fossil]
if {![file exists $repo]} {
log "cloning $url -> $repo"
run fossil clone $url $repo
}
if {![file exists [file join $dir _FOSSIL_]] && ![file exists [file join $dir .fslckout]]} {
file mkdir $dir
set savedpwd [pwd]
cd $dir
#--nested: the stage lives inside the punkshell checkout tree
run fossil open $repo $branch --nested
cd $savedpwd
} elseif {$opt(-refresh)} {
set savedpwd [pwd]
cd $dir
run fossil update $branch
cd $savedpwd
}
}
#-- stage the recipe ------------------------------------------------------
set builddir [file join $stage build]
log "staging recipe -> $builddir"
file delete -force $builddir
file mkdir $builddir
foreach item {build905.zig build_libtommath build_zlib build_tclvfs build_vqtcl build_tclthread src tools} {
file copy [file join $suiteroot $item] $builddir
}
#-- sources ---------------------------------------------------------------
fossil_source tcl https://core.tcl-lang.org/tcl $opt(-tclbranch) [file join $stage tcl905]
fossil_source tclthread https://core.tcl-lang.org/thread trunk [file join $stage tclthread]
fossil_source tclvfs https://core.tcl-lang.org/tclvfs trunk [file join $stage tclvfs]
#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
}
#vqtcl: local source only (vlerq 4.1 by jcw - no public upstream confirmed)
set vqdir [file join $stage vqtcl]
if {![file isdirectory $vqdir]} {
if {![file isdirectory $opt(-vqtcl-source)]} {
fail "vqtcl source not found at '$opt(-vqtcl-source)' (set -vqtcl-source)"
}
log "staging vqtcl from $opt(-vqtcl-source)"
file copy $opt(-vqtcl-source) $vqdir
}
#-- generated files a fresh tcl checkout lacks (normally configure/prepare products) --
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 <winver.h>" "#include <winver.h>\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"
}
#-- build -----------------------------------------------------------------
set zigv [exec $opt(-zig) version]
log "zig: $opt(-zig) ($zigv)"
set savedpwd [pwd]
cd $builddir
run $opt(-zig) build --build-file build905.zig --prefix [file join $stage out] -Doptimize=$opt(-optimize) {*}$opt(-steps)
cd $savedpwd
#-- smoke -----------------------------------------------------------------
set exe [file join $stage out bin tclsh90s.exe]
set zexe [file join $stage out bin tclsh90szip.exe]
foreach {label path script} [list \
static $exe {puts [info patchlevel]} \
zip $zexe {puts "[info patchlevel] tz=[clock format 0 -gmt 0 -format %Z] auto=[llength [info procs auto_load]]"} \
] {
if {![file exists $path]} {fail "$label shell missing: $path"}
set out [exec $path << "$script; exit"]
log "$label shell: $out"
if {![string match "$patchlevel*" $out]} {fail "$label shell patchlevel mismatch: got '$out' want '$patchlevel'"}
}
log "PASS - built $patchlevel (checkout [string range $uuid 0 11]..., zig $zigv) -> $stage/out"

85
src/buildsuites/suite_tcl90/tools/wrapfiletofile.zig

@ -0,0 +1,85 @@
const std = @import("std");
const usage =
\\Usage: ./wrapfiletofile [options]
\\
\\Options:
\\ -prefix STRING
\\ -prefixnl BOOLEAN
\\ -input INPUTFILENAME
\\ -outut OUTPUTFILENAME
;
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(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)) {
try std.io.getStdOut().writeAll(usage);
return std.process.cleanExit();
} 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});
//cast? review
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",.{});
var input_file = std.fs.cwd().openFile(input_file_path,.{ .mode = .read_only}) catch |err| {
fatal("Unable to open '{s}': {s}",. { input_file_path, @errorName(err) });
};
defer input_file.close();
const input_data = try input_file.readToEndAlloc(arena, std.math.maxInt(usize));
defer arena.free(input_data);
std.debug.print("inputfile - Read {d} octets.\n", .{input_data.len});
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("Unable to open '{s}': {s}", .{ output_file_path, @errorName(err)});
};
defer output_file.close();
_ = try output_file.write(prefix);
if (opt_prefixnl) {
_ = try output_file.write("\n");
}
try output_file.writeAll(input_data);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format,args);
std.process.exit(1);
}

55
src/buildsuites/suite_tcl90/tools/zipfs_mkimg.tcl

@ -0,0 +1,55 @@
#JMN 2024
#A simple wrapper to call the 'zipfs mkimg' (tcl::zipfs::mkimg) command via commandline arguments.
#(avoiding piping commands to tclsh via stdin to make calling simpler, and to provide more specific error messages)
#
set script_title "[file tail [info nameofexecutable]] [file tail [info script]]"
set usage "$script_title -outfile <target_zipfile_path> -indir <folder_to_zip> ?-strip <path_prefix_to_strip>? ?-password <plaintext>? ?-infile <explicit_prepend_file>?"
set arglist $::argv
puts stderr "====> '$arglist'"
#error 42
if {![llength $arglist]} {
puts stderr $usage
exit 1
}
if {[llength $arglist] %2 != 0} {
puts stderr "$script_title error: expected an even number of option value pairs. Got [llength $arglist] commandline arguments. '$arglist'"
puts stderr $usage
exit 2
}
set known_opts [list -outfile -indir -strip -password -infile]
dict for {k v} $arglist {
switch -exact -- $k {
-outfile - -indir - -strip - -password - -infile {}
default {
puts stderr "$script_title error: unrecognised option $k. Known_options $known_opts"
exit 3
}
}
}
set defaults [dict create\
-strip ""\
-password ""\
]
set opts [dict merge $defaults $arglist]
if {![dict exists $opts -outfile]} {
puts stderr "$script_title error: Missing -outfile <zipfilename>"
exit 4
}
if {![dict exists $opts -indir]} {
puts stderr "$script_title error: Missing -indir <folder>"
exit 5
}
set callargs [list [file normalize [dict get $opts -outfile]] [file normalize [dict get $opts -indir]] [file normalize [dict get $opts -strip]] [dict get $opts -password]]
if {[dict exists $opts -infile]} {
lappend callargs [file normalize [dict get $opts -infile]]
}
puts stderr "$script_title call 'tcl::zipfs::mkimg $callargs'"
if {[catch {
tcl::zipfs::mkimg {*}$callargs
} errM]} {
puts stderr "$script_title error: Failure during call 'tcl::zipfs::mkimg $callargs'\n $errM"
exit 6
}
exit 0

65
src/buildsuites/suite_tcl90/tools/zipfs_mkzip.tcl

@ -0,0 +1,65 @@
#JMN 2024
#A simple wrapper to call the 'zipfs mkzip' (tcl::zipfs::mkzip) command via commandline arguments.
#(avoiding piping commands to tclsh via stdin to make calling simpler, and to provide more specific error messages)
#
set script_title "[file tail [info nameofexecutable]] [file tail [info script]]"
set usage "$script_title -outfile <target_zipfile_path> -indir <folder_to_zip> ?-strip <path_prefix_to_strip>? ?-password <plaintext>?"
set arglist $::argv
#puts stderr "====> '$arglist'"
#error 42
if {![llength $arglist]} {
puts stderr $usage
exit 1
}
if {[llength $arglist] %2 != 0} {
puts stderr "$script_title error: expected an even number of option value pairs. Got [llength $arglist] commandline arguments. '$arglist'"
puts stderr $usage
exit 2
}
set known_opts [list -outfile -indir -strip -password]
dict for {k v} $arglist {
switch -exact -- $k {
-outfile - -indir - -strip - -password {}
default {
puts stderr "$script_title error: unrecognised option $k. Known_options $known_opts"
exit 3
}
}
}
set defaults [dict create\
]
set opts [dict merge $defaults $arglist]
if {![dict exists $opts -outfile]} {
puts stderr "$script_title error: Missing -outfile <zipfilename>"
exit 4
}
if {![dict exists $opts -indir]} {
puts stderr "$script_title error: Missing -indir <folder>"
exit 5
}
set callargs [list [file normalize [dict get $opts -outfile]] [file normalize [dict get $opts -indir]]]
if {[dict exists $opts -strip]} {
set opt_strip [dict get $opts -strip]
#avoid extraneous underscore(empty path segment?) in zip archive on windows.
#review - test if trailing slash needed on other platforms
#if {"windows" eq "$::tcl_platform(platform)" && $opt_strip ne "" && [lindex $opt_strip end] ne "\\"} {
# append opt_strip \\
#}
lappend callargs [file normalize $opt_strip]
}
if {[dict exists $opts -password]} {
if {![dict exists $opts -strip]} {
lappend callargs ""
}
lappend callargs [dict get $opts -password]
}
puts stderr "$script_title call 'tcl::zipfs::mkzip $callargs'"
if {[catch {
tcl::zipfs::mkzip {*}$callargs
} errM]} {
puts stderr "$script_title error: Failure during call 'tcl::zipfs::mkzip $callargs'\n $errM"
exit 6
}
exit 0
Loading…
Cancel
Save