Browse Source

make.tcl: restricted kitname choices + single memoized mapvfs read

User-directed pair completing the declaration-authoritative arc:

1. bake/bakelist kitnames are -choicerestricted 1: the configured kit
names + @group selectors (computed into the declaration when the mapping
parses at definition time) now GATE at dispatch - an unknown kit is a
punk::args choice error before any build (the no-build guarantee moves
to the gate), unambiguous prefixes resolve to canonical names, and the
dry-run help mirrors the verdict ('help bake punk91 -confirm 0' is now
a pointed choice error naming '-confirm' rather than a value-swallow).
The handlers' own validation remains the backstop where declarations
cannot gate: PUNKBOOT_PLAIN degraded mode, and a definition-time
mapping-read failure (the choices clause is omitted entirely - no gate).

2. All kit-mapping consumers share ONE memoized parse per invocation:
new punkboot::lib::mapvfs_model front door (locate+parse, keyed by
resolved mapfile/rtbase/sourcefolder/target - a PUNK_MAPVFS_CONFIG
override keys its own entry) consumed by the definition-time choices
(now passing the real bin/runtime rtbase - mapvfs_parse is pure, so the
models are interchangeable) and the five handler sites (selective bake,
main bake, bakelist, vfslibs %platform% derivation, check smoke
listing). Success is cached; a throwing parse deliberately is not, so
each caller diagnoses fresh.

maketclbakelist.test: unknown-name pins updated to the dispatch choice
error + PUNKBOOT_PLAIN backstop variants (runner gains envoverrides);
maketclhelp.test: bake dry-run case now pins the choice-gate rejection
and 'help bake punk91' acceptance. Full punkexe subtree 114 tests, 0
fail. punkshell 0.41.0.

Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
Julian Noble 1 week ago
parent
commit
13dc78191b
  1. 20
      CHANGELOG.md
  2. 2
      punkproject.toml
  3. 4
      src/AGENTS.md
  4. 56
      src/make.tcl
  5. 113
      src/tests/shell/testsuites/punkexe/maketclbakelist.test
  6. 35
      src/tests/shell/testsuites/punkexe/maketclhelp.test

20
CHANGELOG.md

@ -5,6 +5,26 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.41.0] - 2026-08-02
- make.tcl bake/bakelist kitnames are declaration-authoritative (user-directed,
completing the G-143 arc): the configured kit names + @group selectors are now
RESTRICTED punk::args choices, so an unknown kit is rejected at dispatch with
the choice error (before any build - the no-build guarantee moves to the
gate), unambiguous prefixes resolve to canonical names, and 'make.tcl help
bake <line>' dry-run mirrors the verdict (the out-of-order 'bake punk91
-confirm 0' is now a pointed choice error naming '-confirm' instead of a
silent value-swallow). The handlers' own validation remains the backstop
under PUNKBOOT_PLAIN degraded mode and when the definition-time mapping read
fails (the choices clause is omitted entirely in that case - no gate).
- make.tcl kit-mapping reads consolidated to a single memoized parse per
invocation (punkboot::lib::mapvfs_model): the definition-time kitname
choices and every handler (selective bake, main bake, bakelist, vfslibs
%platform% derivation, check's smoke listing) consume the same model
(mapvfs_parse is pure, so cached and fresh models are interchangeable;
success is cached, a throwing parse diagnoses fresh per caller; a
PUNK_MAPVFS_CONFIG override keys its own cache entry).
## [0.40.3] - 2026-08-02
- make.tcl help dry-run purified (G-143 follow-up, user-directed): the 0.40.2

2
punkproject.toml

@ -1,6 +1,6 @@
[project]
name = "punkshell"
version = "0.40.3"
version = "0.41.0"
license = "BSD-2-Clause"
url = "https://www.gitea1.intx.com.au/jn/punkshell"
#packager: declared identity for published artifacts (declarative, not proof -

4
src/AGENTS.md

@ -69,13 +69,13 @@ Recovery after a wrong path guess:
- Provenance warnings (dirty-src gate + vendorupdate source-project check) print with a plain column-0 `PROVENANCE-WARNING:` token (grep for it in captured build output) and are recapped at the end of the run via make.tcl's wrapped `::exit`. Interactive terminal runs get a 3-second ctrl-c grace countdown before a dirty build proceeds; piped/agent runs skip it. `tclsh src/make.tcl check` reports the current src provenance status and what the build commands would do.
- Use `tclsh src/make.tcl vfscommonupdate` to rebuild `_vfscommon.vfs`. The REPLACE confirmation prompts on an interactive terminal; unattended/agent runs must pass `-confirm 0` (with the default `-confirm 1` and a piped/closed stdin, make.tcl aborts fast with guidance instead of reading stdin - do not pipe `y`).
- `tclsh src/make.tcl workflow` prints an embedded ASCII data-flow overview of the build/release workflow (release-ready command sequence, source→outputs folder flow, kit assembly, keyed notes). The text lives in `::punkboot::workflow_text` in `src/make.tcl` - embedded rather than a data file so it travels with the make.tcl copies seeded into generated projects. **Update contract (agents):** whenever build data flow changes - a make.tcl subcommand added/removed/repurposed, a source or output folder added or rerouted, a propagation target added, a gate (staleness/provenance/confirm) or deploy behaviour changed - update the workflow text in the same change-set. Keep it plain ASCII, max line width 100, and preserve the RELEASE SEQUENCE / DIAGRAM / KEY-NOTES / MAINTENANCE structure; verify by running `tclsh src/make.tcl workflow` (and once under `PUNKBOOT_PLAIN=1`). Adding/changing a subcommand also means: SUMMARIES + HELPTEXTS + its braced `punk::args::define` block (+ SUBGROUPS) in `::punkboot::argdoc`, `known_commands`, the plain `punkboot_gethelp` text, and a project-version bump (make.tcl interface is product surface per root AGENTS.md). Layout make.tcl copies pick the change up via the thin-layout sync step in any `make.tcl modules`/`libs`/`packages`/`bakehouse` run - do not hand-sync them.
- make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved): `tclsh src/make.tcl help ?subcommand? ?arg ...?` or `<subcommand> ?arg ...? -help` shows tabled usage; invalid arguments produce punk::args usage errors. tool and buildsuite are multi-form definitions (one @form per action with a literal action leader), so whole-subcommand help renders one synopsis line per action, and help accepts the subcommand's own COMMAND LINE after the subject and DRY-RUNS it through that subcommand's declaration: `make.tcl help tool build -test 0 punkzip` (equivalently that command line with `-help` appended) parses via punk::args form auto-selection and renders the build form's usage, while a line the subcommand would reject (unknown action word, option-first line, unknown flag in option position) gets the same pointed punk::args noformmatch diagnosis dispatch gives - synopsis plus per-form reasons - on stderr with exit 1, never a plain-help fallback. There is deliberately no bare-action carve-out: an action word alone is dry-run too, so a form with required values reports them via the diagnosis (`help buildsuite build` exits 1 naming the missing suitename - the error table carries the form's argument rows, so it IS the form documentation). An accepted line is confirmed with a one-line received-args report after the table (`dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip`); punk::args' positional model consumes flag-like words at/after the first value position as VALUES, and the report makes that visible (`kitname = punk91 -confirm 0`). Declaration-level passthroughs (shell args, buildsuite driver args) always parse clean; numeric form indexes are deliberately not an interface. tool DISPATCH parses through its definition: unknown actions/flags are punk::args usage errors (exit 1), and the declared positional model puts options before the tool names (`make.tcl tool build -test 0 <name> ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions.
- make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved): `tclsh src/make.tcl help ?subcommand? ?arg ...?` or `<subcommand> ?arg ...? -help` shows tabled usage; invalid arguments produce punk::args usage errors. tool and buildsuite are multi-form definitions (one @form per action with a literal action leader), so whole-subcommand help renders one synopsis line per action, and help accepts the subcommand's own COMMAND LINE after the subject and DRY-RUNS it through that subcommand's declaration: `make.tcl help tool build -test 0 punkzip` (equivalently that command line with `-help` appended) parses via punk::args form auto-selection and renders the build form's usage, while a line the subcommand would reject (unknown action word, option-first line, unknown flag in option position) gets the same pointed punk::args noformmatch diagnosis dispatch gives - synopsis plus per-form reasons - on stderr with exit 1, never a plain-help fallback. There is deliberately no bare-action carve-out: an action word alone is dry-run too, so a form with required values reports them via the diagnosis (`help buildsuite build` exits 1 naming the missing suitename - the error table carries the form's argument rows, so it IS the form documentation). An accepted line is confirmed with a one-line received-args report after the table (`dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip`); punk::args' positional model consumes flag-like words at/after the first value position as VALUES where the value arg is unconstrained (tool's toolname), and the report makes such swallows visible - value args with restricted choices (bake/bakelist kitnames as of 2026-08-02) instead reject the flag-like word at the choice gate, exactly as dispatch does. Declaration-level passthroughs (shell args, buildsuite driver args) always parse clean; numeric form indexes are deliberately not an interface. tool DISPATCH parses through its definition: unknown actions/flags are punk::args usage errors (exit 1), and the declared positional model puts options before the tool names (`make.tcl tool build -test 0 <name> ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions.
- Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, declared per-kit `*.vfs/lib_tcl<N>/<pkg>` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk/<layout>/src/{make.tcl,build.tcl}` + bootsupport manifest copies + the inert `gitignore.in` payload copies refreshed from the root `.gitignore` - G-012), and the module-shipped layout payload under `src/modules/punk/mix/#modpod-templates-*/templates/project_layouts/` (G-087: synced from `src/project_layouts` - never hand-edit the modpod copy). (Root `modules/`, `lib/`, `modules_tcl<N>/`, `lib_tcl<N>/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl<N>` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns.
- **Boot-precondition gate (G-125).** A bake refuses any kit whose merged `.vfs` supplies no tcl library: the kit is listed under `FAILED KITS` with a reason naming the cause, and NOTHING is written - no `src/_build/<kit>`, no deploy, and the previously deployed `bin/<kit>` is left byte-identical. This is deliberately a failure rather than a warning: the deploy step deletes the old kit before copying the new one, so the pre-gate behaviour replaced a working shell with an artifact that died at startup with `application-specific initialization failed: Cannot find a usable init.tcl`. The check is structural and non-executing (so it covers cross-target kits too): `init.tcl` in `tcl_library/`, `lib/tcl<major>.<minor>/` or `tcl<major>.<minor>/` - the third for runtimes whose archive mounts at the executable's own path rather than `//zipfs:/app`, so `[info library]` is `<exe>/tcl8.6` (the androwish/undroidwish zipfs backport for 8.6 does this) - plus one companion file a real Tcl library carries beside it (`tm.tcl`, `package.tcl`, `auto.tcl`, `clock.tcl`, `history.tcl`, `word.tcl`) or an `encoding/` directory - without that second test the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries would answer for a tcl library it cannot provide. It reads the MERGED tree rather than asking whether extraction ran, so a `.vfs` that legitimately supplies its own tcl library (`src/vfs/punk8_statictwapi.vfs`, `src/vfs/punk9test.vfs`) builds and deploys unchanged. **Remedy when a kit is refused:** the usual cause is that the runtime's own payload could not be extracted - read the recapped `BUILD-WARNING` naming what was tried, then either fix extraction (a driving tclsh with no zipfs needs punk::zip >= 0.2.0 in bootsupport - see the extraction bullet above), point the kit at a runtime that carries a payload, or have the kit's `.vfs` supply a tcl library itself. The predicate is `punkboot::utils::vfs_boot_library_report` called through a guarded require, exactly as the dirty-src provenance check is: a stale or missing bootsupport snapshot degrades the gate to a `NOTE` rather than failing every kit. `tclsh src/make.tcl check` reports whether the gate is `ACTIVE` or `UNAVAILABLE`.
- **Payload/target consistency checks (G-133), both ADVISORY** - warnings are recapped `BUILD-WARNING`s and the kit still builds and deploys (unlike the G-125 gate). (a) **Binary-arch scan**, at the same post-merge seam as the gate: every binary library (`*.dll`/`*.so`/`*.dylib`) in the merged tree is classified by header bytes (`punkboot::utils::binary_arch_classify` - PE Machine field, ELF e_machine, Mach-O incl. universal; unclassifiable content is honestly `unknown` and never warned about) against the kit's target platform. Structural and non-executing, so it runs for cross-target kits too (measured ~30-140ms per kit, avg ~93ms across the 12 assembled trees). Libraries under a platform-discriminated subdir - canonical `<os>-<cpu>` platform-dir names, the universal `macosx` folder, or a recognised vendor spelling (`win-x64` etc; the lists are namespace variables in `punkboot::utils`) - are exempt: multi-arch payloads are legitimate there. First real sweep (2026-07-27) found a true latent instance: `zint.dll` 2.13.0 in `src/vfs/punk8win.vfs/lib_tcl8/` is 32-bit (i386) and can never load in the x64 tcl8 kits (punk86/punkbi/punksys) that carry it - those bakes warn until the payload is fixed. (b) **Smoke-require probe**: a kit declaring packages in `src/runtime/mapvfs.toml` (`smokerequire` key - see `src/runtime/AGENTS.md`) has each one plain-`package require`d INSIDE the freshly built artifact via its tclsh subcommand, which exits cleanly even on runtimes whose full repl teardown is fragile. This is the only check that observes actual package RESOLUTION - the punkluck86 2026-07-27 incident class (a higher-versioned wrong-arch Thread shadowing the runtime's working copy on plain require) is invisible to structural checks because the working file exists too. Failures are recapped naming kit, package and the actual error; cross-target kits skip with a stated reason; undeclared kits run nothing new; the probe only runs when the kit actually rebuilds. **What the checks do NOT guarantee:** statically linked packages, pure-tcl packages whose binary dependency lives elsewhere, and version-preference outcomes are visible only to the smoke probe - and only for the declared package set; the arch scan proves nothing about loadability beyond architecture, and an `unknown` classification is silence, not a pass. Both degrade to a NOTE when the bootsupport `punkboot::utils` snapshot is stale (same guarded require as the G-125 gate); `tclsh src/make.tcl check` reports the scan's ACTIVE/UNAVAILABLE state and the currently declared smoke-require kits.
- **Kit offset-style pin (G-134), ADVISORY** - at the same seam family, every ASSEMBLED kit image is probed with `punk::zip::archive_info` (via `punkboot::utils::kit_offsetstyle_report`, same guarded-require degradation to a NOTE; `make.tcl check` reports ACTIVE/UNAVAILABLE): a FILE-relative attached zip payload earns a recapped `BUILD-WARNING` naming the kit, because the pipeline emits ARCHIVE-relative by construction and the G-128 stamper refuses file-relative by default - a warning here means an assembly path regressed the output convention. **What the pin does NOT cover:** `plain` and `none` results (a bare zip, or no attached zip at all - the metakit kit shape, and any unreadable input) are silence, never warnings - the pin asserts the offset CONVENTION of an attached zip, not the presence of one; and it asserts only what make.tcl's own assembly paths EMIT - `punk::zip::mkzip -offsettype file` (and modpod's equivalent) remain available for deliberate manual creation of file-relative artifacts, and file-relative INPUT artifacts remain tolerated by the G-124 reader and normalized by any re-bake.
- Kit bakes consume the punk-runtime WORKING COPIES under `bin/runtime/<platform>/`: `make.tcl bake`/`bakehouse` emit a `BUILD-WARNING:` (recapped at end of run) when a wrapped runtime's beside-toml revision is older than an `-r<N>` artifact present in the same folder - the forgot-to-switch-back guard for deliberate `punk-runtime use <old-rN>` testing excursions. Heed it before trusting freshly baked family kits; `bin/punk-runtime.cmd use <artifact-r<N>>` (`.exe` suffix optional) re-materializes the working copy.
- `tclsh src/make.tcl bakelist ?kitname|@group ...?` (G-121/G-024) reports the kit outputs configured in `src/runtime/mapvfs.toml`: kit name, kit type, runtime (with presence in the runtime store), vfs folder, and the deployed state of `bin/<kit>` vs the `src/_build` build product (`current|stale|absent|nobuild`), with anomalies (`runtime=missing`, `vfs=missing`, `rtrev=r<cur><r<max>` materialization staleness), the cross-target marker `target=<platform>`, groups (`group=<name>`), full-bake exclusion (`default=no`) and scheme roles (`scheme=versioned|dev|release`) in a trailing notes column. A nonexistent store tier is flagged loudly per tier actually referenced (header `(FOLDER MISSING)` for the default tier + stderr derivation; bake's no-runtimes exit self-diagnoses the same way). Name/@group arguments filter the report and add a per-kit detail block (resolved store tier, target and provenance of the target, config entry, group, paths/sizes/mtimes). `make.tcl bake ?kitname|@group ...?` bakes and deploys only the named kits - other kits' `_build`/`bin` artifacts and punkcheck records are untouched and the vfslibs phase narrows to the named kits' vfs folders; an unknown name errors before any build, listing the configured names and groups; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Bare `bake` processes all configured kits except `bake_default=false` entries; a `versioned`-scheme release output (plain `punk<gen>` name) is created when absent and otherwise skipped - a normal bake never overwrites it (the explicit release step is G-023's). Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format (G-024: toml reader canonical, deprecated `mapvfs.config` line reader retained as fallback; `PUNK_MAPVFS_CONFIG` env points at an alternate config for characterization). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclbakelist.test`.
- `tclsh src/make.tcl bakelist ?kitname|@group ...?` (G-121/G-024) reports the kit outputs configured in `src/runtime/mapvfs.toml`: kit name, kit type, runtime (with presence in the runtime store), vfs folder, and the deployed state of `bin/<kit>` vs the `src/_build` build product (`current|stale|absent|nobuild`), with anomalies (`runtime=missing`, `vfs=missing`, `rtrev=r<cur><r<max>` materialization staleness), the cross-target marker `target=<platform>`, groups (`group=<name>`), full-bake exclusion (`default=no`) and scheme roles (`scheme=versioned|dev|release`) in a trailing notes column. A nonexistent store tier is flagged loudly per tier actually referenced (header `(FOLDER MISSING)` for the default tier + stderr derivation; bake's no-runtimes exit self-diagnoses the same way). Name/@group arguments filter the report and add a per-kit detail block (resolved store tier, target and provenance of the target, config entry, group, paths/sizes/mtimes). `make.tcl bake ?kitname|@group ...?` bakes and deploys only the named kits - other kits' `_build`/`bin` artifacts and punkcheck records are untouched and the vfslibs phase narrows to the named kits' vfs folders; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Kitname arguments are DECLARED restricted choices (2026-08-02, declaration-authoritative per the G-143 arc): an unknown name is rejected at dispatch with the punk::args choice error (before any build; the choices list the configured names + `@groups`), unambiguous prefixes resolve to canonical names, and the dry-run help mirrors the verdict. The handlers' own validation remains the backstop where the declarations cannot gate: `PUNKBOOT_PLAIN=1` degraded mode, and a definition-time mapping-read failure (the choices clause is then omitted). Bare `bake` processes all configured kits except `bake_default=false` entries; a `versioned`-scheme release output (plain `punk<gen>` name) is created when absent and otherwise skipped - a normal bake never overwrites it (the explicit release step is G-023's). Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format (G-024: toml reader canonical, deprecated `mapvfs.config` line reader retained as fallback; `PUNK_MAPVFS_CONFIG` env points at an alternate config for characterization), and all mapping consumers - the definition-time choices and every handler - share one memoized read per invocation (`mapvfs_model`; success cached, a throwing parse diagnoses fresh per caller). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclbakelist.test`.
- **Host vs target platform (G-122).** Everything a bake EMITS is keyed by the artifact's TARGET platform, never by the driving tclsh's personality: the `bin/runtime/<tier>` store the runtime is read from, `.exe` suffixing of runtime files and kit outputs, the presence checks behind `runtime=missing`, and whether the pre-deploy process sweep uses `tasklist`/`taskkill` or `ps`/`kill`. Host semantics - copy commands, path handling, filesystem case rules, prompts - stay keyed to the host. The default target is the host's own platform canon, EXCEPT a cygwin-family host (an msys2/cygwin-runtime tclsh, which reports `tcl_platform(platform)` `unix` on windows and canonizes as `msys-x86_64`/`cygwin-x86_64`), which targets `win32-x86_64`: such a host now drives the identical kit set, names and store addressing as a native tclsh. `tclsh src/make.tcl check` prints the derivation on one `platform (G-122): host=... target=... store=... exe-suffix=... process-tooling=...` line. A mapvfs entry may declare its own target platform (`target` key - see `src/runtime/AGENTS.md`), which is then read from that platform's tier, named with that platform's executable convention, and skipped by the process sweep (its processes are not visible to this host). Traps this closes, all field-verified 2026-07-26: msys `ps` cannot see a natively-launched kit (only `tasklist` can), and msys2 rewrites arguments that look like absolute posix paths when spawning a native windows program - so `taskkill /PID <n>` arrived as `taskkill C:/<msysroot>/PID <n>`; native-windows command lines therefore go through `::punkboot::exec_nativeargs` (sets `MSYS2_ARG_CONV_EXCL=*`, inert elsewhere). Also note a cygwin-family tclsh is a POSIX Tcl: name the script the way that shell spells paths (`tclsh /c/repo/.../src/make.tcl`, not `C:/repo/...`, which it resolves relative to the cwd) - make.tcl diagnoses the windows-spelling case. Piped characterization: `src/tests/shell/testsuites/punkexe/maketclplatform.test` (the cygwin-host tests self-gate on finding and probing a real msys/cygwin tclsh; `PUNK_MSYS_TCLSH` names one explicitly).
- **Target-keyed kit OUTPUT locations (G-127) - the output half of the G-122 split.** A kit for the host's default target builds to `src/_build/<kit>` and deploys to `bin/<kit>` exactly as always; any other target's kit builds under `src/_build/kits/<platform>/` and deploys under `bin/kits/<platform>/` (per-tier `.punkcheck` ledger; the merged `.vfs` image, resource sidecar, arch scan, offset probe, smoke probe and process-sweep guards all follow the per-kit location). Same-named kits for different targets coexist - the artifact path is a pure function of (name, target) and `.exe` suffixing follows the target - so separate selective bakes can never overwrite each other's cross-target artifacts; only a same-name same-TARGET duplicate keeps the `<name>_<runtime>` disambiguation. A kit NAME matching several targets' entries selects all of them (bakelist and selective bake). Per-.vfs payload declarations gain the same axis: a `[payload.*]` source containing `%platform%` materializes once per CONSUMING kit target into `_targets/<platform>/` staging inside the `.vfs` folder (VCS-ignored), and each kit's merged image receives only its own target's subtree upstream of the arch scan and G-125 gate (`src/vfs/README.md` "Per-platform entries"). Characterization: `src/tests/shell/testsuites/punkexe/maketclkitlocations.test`; output-tier contract for consumers: `bin/AGENTS.md` "Cross-target kit outputs".
- Zip-type kit assembly does not require zipfs in the driving tcl (G-122): with `tcl::zipfs::mkimg` present it is used as before, otherwise the image is assembled by concatenation (raw-runtime split + `punk::zip::mkzip` + append, the same `::punkboot::assemble_zipcat_image` helper the `zipcat` kit type uses). Both mount identically - tcl zipfs reads the archive with archive-start-relative offsets.

56
src/make.tcl

@ -775,6 +775,29 @@ proc ::punkboot::lib::mapvfs_parse {mapfile rtbase sourcefolder default_target}
return [mapvfs_parse_config $mapfile $rtbase $sourcefolder $default_target]
}
# Memoized front door: locate + parse once per distinct (mapfile, rtbase,
# sourcefolder, target) tuple within a make.tcl invocation (2026-08-02
# consolidation) - the define-time kitname choices and every handler consume
# the SAME model. mapvfs_parse is pure (no output/exits), so cached and fresh
# models are interchangeable; config edits during a single run are outside the
# contract. A THROWING locate/parse is deliberately not cached: each caller's
# attempt diagnoses fresh (identical text for a persistent problem), and the
# choices computation catches its attempt while the handlers report theirs.
# The mapfile is part of the key, so a PUNK_MAPVFS_CONFIG override keys
# distinctly from the tree's own config.
namespace eval ::punkboot::lib {
variable mapvfs_model_cache [dict create]
}
proc ::punkboot::lib::mapvfs_model {rt_sourcefolder rtbase sourcefolder default_target} {
variable mapvfs_model_cache
set mapfile [mapvfs_locate $rt_sourcefolder]
set key [list $mapfile $rtbase $sourcefolder $default_target]
if {![dict exists $mapvfs_model_cache $key]} {
dict set mapvfs_model_cache $key [mapvfs_parse $mapfile $rtbase $sourcefolder $default_target]
}
return [dict get $mapvfs_model_cache $key]
}
# G-024 canonical reader: mapvfs.toml. Strict per-entry validation - a malformed
# or unresolvable declaration lands in configerrors naming its table (the bake
# path aborts on configerrors; missing-on-disk runtime/vfs items land in
@ -3391,27 +3414,34 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
already verifies against the server sha1sums."
}
#bake and bakelist take optional trailing kit names (G-121 selective bake).
#Discoverability nicety: the configured kit names are offered as choices when
#the mapping parses at definition time - informational only (-choicerestricted
#0), so the handlers' own validation (which lists the configured names, and
#still works when the mapping cannot be parsed here) stays authoritative.
#The configured kit names + @group selectors become RESTRICTED choices when
#the mapping parses at definition time (-choicerestricted 1, 2026-08-02 -
#declaration-authoritative per the G-143 arc): dispatch rejects an unknown
#kit with the punk::args choice error (the same configured-names
#information the handler error carries), unambiguous prefixes resolve to
#canonical names, and the dry-run help mirrors the verdict. The read is
#the same memoized model the handlers consume (mapvfs_model - one parse
#per invocation). When the mapping cannot be parsed here the choices
#clause is omitted entirely (no gate) and the handlers' own validation
#remains the backstop - as it also is in PUNKBOOT_PLAIN degraded mode,
#where these definitions don't exist.
#KITNAME_CHOICEPART persists as a namespace variable because the kitname
#records below interpolate it at definition RESOLVE time (lazy - first
#parse/usage/get_spec of the bake/bakelist ids).
variable KITNAME_CHOICES [list]
catch {
set _mapmodel [punkboot::lib::mapvfs_parse [punkboot::lib::mapvfs_locate [file join $::punkboot::scriptfolder runtime]] "" $::punkboot::scriptfolder $::punkboot::target_platform]
foreach _rec [punkboot::lib::mapvfs_kit_outputs $_mapmodel "" $::punkboot::scriptfolder] {
set _mapmodel [punkboot::lib::mapvfs_model [file join $::punkboot::scriptfolder runtime] [file dirname $::punkboot::scriptfolder]/bin/runtime $::punkboot::scriptfolder $::punkboot::target_platform]
foreach _rec [punkboot::lib::mapvfs_kit_outputs $_mapmodel [file dirname $::punkboot::scriptfolder]/bin/runtime $::punkboot::scriptfolder] {
lappend KITNAME_CHOICES [dict get $_rec kitname]
}
#G-024 group selectors join the informational choices (@<group>)
#G-024 group selectors join the choices (@<group>)
foreach _g [dict keys [dict get $_mapmodel groups]] {
lappend KITNAME_CHOICES @$_g
}
}
variable KITNAME_CHOICEPART ""
if {[llength $KITNAME_CHOICES]} {
set KITNAME_CHOICEPART " -choices [list $KITNAME_CHOICES] -choicerestricted 0"
set KITNAME_CHOICEPART " -choices [list $KITNAME_CHOICES] -choicerestricted 1"
}
#per-subcommand definitions: braced file-style blocks with -& record
#continuations (G-045). -summary values and -help bodies are pulled from
@ -4147,7 +4177,7 @@ set ::punkboot::bake_selected_vfs [list] ;#vfs folder tails the selected
set ::punkboot::bake_selected_runtimes [list] ;#unsuffixed runtime names the selected kits wrap
if {$::punkboot::command eq "bake" && [llength $::punkboot::opt_kitnames]} {
set _rtbase $binfolder/runtime
set _mapmodel [punkboot::lib::mapvfs_parse [punkboot::lib::mapvfs_locate $sourcefolder/runtime] $_rtbase $sourcefolder $::punkboot::target_platform]
set _mapmodel [punkboot::lib::mapvfs_model $sourcefolder/runtime $_rtbase $sourcefolder $::punkboot::target_platform]
if {[llength [dict get $_mapmodel configerrors]]} {
foreach _errline [dict get $_mapmodel configerrors] {
puts stderr $_errline
@ -4715,7 +4745,7 @@ if {$::punkboot::command eq "check"} {
}
set _smoke_declared [list]
if {[file exists [punkboot::lib::mapvfs_locate $sourcefolder/runtime]]} {
set _smokemodel [punkboot::lib::mapvfs_parse [punkboot::lib::mapvfs_locate $sourcefolder/runtime] [file dirname $sourcefolder]/bin/runtime $sourcefolder $::punkboot::target_platform]
set _smokemodel [punkboot::lib::mapvfs_model $sourcefolder/runtime [file dirname $sourcefolder]/bin/runtime $sourcefolder $::punkboot::target_platform]
foreach _rec [punkboot::lib::mapvfs_kit_outputs $_smokemodel [file dirname $sourcefolder]/bin/runtime $sourcefolder] {
if {[llength [dict get $_rec smokerequire]]} {
lappend _smoke_declared "[dict get $_rec kitname] ([join [dict get $_rec smokerequire] {, }])"
@ -4932,7 +4962,7 @@ if {$::punkboot::command eq "bakelist"} {
set rtfolder $rtbase/$rt_os_arch
set buildfolder $sourcefolder/_build
set mapfile [punkboot::lib::mapvfs_locate $sourcefolder/runtime]
set mapmodel [punkboot::lib::mapvfs_parse $mapfile $rtbase $sourcefolder $rt_default_target]
set mapmodel [punkboot::lib::mapvfs_model $sourcefolder/runtime $rtbase $sourcefolder $rt_default_target]
foreach errline [dict get $mapmodel configerrors] {
puts stderr $errline
}
@ -6542,7 +6572,7 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} {
#only its own target's payload.
set vfspayload_kit_targets [dict create]
if {[llength $payload_files]} {
set _pmapmodel [punkboot::lib::mapvfs_parse [punkboot::lib::mapvfs_locate $sourcefolder/runtime] $binfolder/runtime $sourcefolder $::punkboot::target_platform]
set _pmapmodel [punkboot::lib::mapvfs_model $sourcefolder/runtime $binfolder/runtime $sourcefolder $::punkboot::target_platform]
if {[llength [dict get $_pmapmodel configerrors]]} {
puts stderr "WARNING: VFSPAYLOAD: kit-mapping config errors - per-platform (%platform%) payload derivation unavailable this run (the mapping's own gates report the errors)"
} else {
@ -7139,7 +7169,7 @@ proc ::punkboot::runtime_materialization_warning {rtfolder runtime_fullname} {
#store tiers this bake must look in - an entry declaring a target platform other
#than the host default addresses its own bin/runtime/<tier>.
set mapfile [punkboot::lib::mapvfs_locate $rt_sourcefolder]
set mapmodel [punkboot::lib::mapvfs_parse $mapfile $rtbase $sourcefolder $rt_default_target]
set mapmodel [punkboot::lib::mapvfs_model $rt_sourcefolder $rtbase $sourcefolder $rt_default_target]
foreach warnline [dict get $mapmodel warnings] {
puts stderr $warnline
}

113
src/tests/shell/testsuites/punkexe/maketclbakelist.test

@ -9,9 +9,12 @@ package require tcltest
# with a deployed state from the documented vocabulary
# - 'bakelist <kitname>' filters to the named entry and appends the per-kit detail
# block; unfiltered rows do not appear
# - 'bakelist <unknown>' exits 1 naming the unknown and listing the configured names
# - 'bake <unknown>' exits 1 BEFORE any build ("nothing built"), listing the
# configured names - the no-build guarantee of selective bake
# - 'bakelist <unknown>' / 'bake <unknown>' exit 1 at DISPATCH with the punk::args
# choice error (kitnames are -choicerestricted 1 as of 2026-08-02 - declaration-
# authoritative; the choices list carries the configured names + @groups), and
# the bake case never reaches the kit machinery - the no-build guarantee holds
# at the gate. Under PUNKBOOT_PLAIN=1 (no definitions) the handlers' historic
# validation remains the backstop, naming the unknown and the configured names.
#The row pins are characterization of the current src/runtime/mapvfs.toml kit
#matrix (punk91 = tclsfe-x64 + punk9wintk903.vfs, zip) - a deliberate config change
#legitimately updates them.
@ -60,9 +63,11 @@ namespace eval ::testspace {
#Run <punkexe> script src/make.tcl <subcommand...> with output captured through a
#pipe (stdin half-closed for immediate EOF - make.tcl must never wait on stdin for
#these subcommands). Returns dict: timedout 0|1, exitcode <int|"">, output
#<combined stdout+stderr>.
proc maketcl_run {cmdargs} {
#these subcommands). envoverrides is a dict: name value pairs applied to ::env for
#the child (a value of "" means unset); saved values are restored after the run
#(added 2026-08-02 for the PUNKBOOT_PLAIN degraded-mode pins). Returns dict:
#timedout 0|1, exitcode <int|"">, output <combined stdout+stderr>.
proc maketcl_run {cmdargs {envoverrides {}}} {
variable runstate
variable maketcl_run_timeout_ms
variable punkexe
@ -71,29 +76,53 @@ namespace eval ::testspace {
set runstate(output) ""
set runstate(done) ""
set chan [open |[list $punkexe script $maketcl {*}$cmdargs 2>@1] r+]
chan configure $chan -blocking 0 -translation binary
catch {chan close $chan write} ;#no stdin for the child - immediate EOF
set timerid [after $maketcl_run_timeout_ms [list set [namespace current]::runstate(done) timeout]]
chan event $chan readable [list [namespace current]::maketcl_run_read $chan]
while {$runstate(done) eq ""} {
vwait [namespace current]::runstate(done)
set saved [dict create]
dict for {evar eval_} $envoverrides {
if {[info exists ::env($evar)]} {
dict set saved $evar [list 1 $::env($evar)]
} else {
dict set saved $evar [list 0 {}]
}
if {$eval_ eq ""} {
unset -nocomplain ::env($evar)
} else {
set ::env($evar) $eval_
}
}
after cancel $timerid
set timedout [expr {$runstate(done) eq "timeout"}]
set exitcode ""
if {$timedout} {
catch {exec {*}[auto_execok taskkill] /F /T /PID [lindex [pid $chan] 0]}
catch {chan close $chan}
} else {
chan configure $chan -blocking 1
if {[catch {chan close $chan} errdata errdict]} {
set exitcode [lindex [dict get $errdict -errorcode] end]
try {
set chan [open |[list $punkexe script $maketcl {*}$cmdargs 2>@1] r+]
chan configure $chan -blocking 0 -translation binary
catch {chan close $chan write} ;#no stdin for the child - immediate EOF
set timerid [after $maketcl_run_timeout_ms [list set [namespace current]::runstate(done) timeout]]
chan event $chan readable [list [namespace current]::maketcl_run_read $chan]
while {$runstate(done) eq ""} {
vwait [namespace current]::runstate(done)
}
after cancel $timerid
set timedout [expr {$runstate(done) eq "timeout"}]
set exitcode ""
if {$timedout} {
catch {exec {*}[auto_execok taskkill] /F /T /PID [lindex [pid $chan] 0]}
catch {chan close $chan}
} else {
set exitcode 0
chan configure $chan -blocking 1
if {[catch {chan close $chan} errdata errdict]} {
set exitcode [lindex [dict get $errdict -errorcode] end]
} else {
set exitcode 0
}
}
return [dict create timedout $timedout exitcode $exitcode output $runstate(output)]
} finally {
dict for {evar restore} $saved {
lassign $restore existed oldval
if {$existed} {
set ::env($evar) $oldval
} else {
unset -nocomplain ::env($evar)
}
}
}
return [dict create timedout $timedout exitcode $exitcode output $runstate(output)]
}
proc esc_count {text} {
@ -125,29 +154,43 @@ namespace eval ::testspace {
set result
} -result {timedout 0 exitcode 0 punk91row 1 otherrows 0 detailblock 1 detailpaths 1}
#added 2026-07-26 (agent, G-121)
test maketcl_bakelist_unknown_name {bakelist with an unknown kit name exits 1 naming it and listing the configured names} -constraints {punkexeavailable} -body {
#added 2026-07-26 (agent, G-121); updated 2026-08-02 (agent) - kitnames are now
#-choicerestricted 1 (declaration-authoritative): dispatch rejects the unknown
#with the punk::args choice error before the handler runs; PUNKBOOT_PLAIN=1
#keeps the historic handler surface as the degraded-mode backstop
test maketcl_bakelist_unknown_name {bakelist with an unknown kit name exits 1 at dispatch with the choice error listing configured kits; plain mode keeps the handler backstop} -constraints {punkexeavailable} -body {
set r [maketcl_run {bakelist nosuchkitxyz}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result named [regexp {bakelist: unknown kit name\(s\): nosuchkitxyz} $out]
lappend result confignames [regexp {configured kit outputs: .*punk91} $out]
lappend result received [regexp {Received: 'nosuchkitxyz'} $out]
lappend result confignames [regexp {punk91} $out]
set r2 [maketcl_run {bakelist nosuchkitxyz} {PUNKBOOT_PLAIN 1}]
set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result named2 [regexp {bakelist: unknown kit name\(s\): nosuchkitxyz} $out2]
lappend result confignames2 [regexp {configured kit outputs: .*punk91} $out2]
set result
} -result {timedout 0 exitcode 1 named 1 confignames 1}
} -result {timedout 0 exitcode 1 received 1 confignames 1 timedout2 0 exitcode2 1 named2 1 confignames2 1}
#added 2026-07-26 (agent, G-121)
test maketcl_bake_unknown_name_no_build {bake with an unknown kit name exits 1 before any build, listing the configured names} -constraints {punkexeavailable} -body {
#added 2026-07-26 (agent, G-121); updated 2026-08-02 (agent) - the no-build
#guarantee now holds at the dispatch gate (choice error; the kit machinery is
#never reached); PUNKBOOT_PLAIN=1 keeps the handler's historic pre-build check
test maketcl_bake_unknown_name_no_build {bake with an unknown kit name exits 1 before any build - at dispatch (choice error), and via the handler check under PUNKBOOT_PLAIN} -constraints {punkexeavailable} -body {
set r [maketcl_run {bake nosuchkitxyz}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result nothingbuilt [regexp {make\.tcl bake: unknown kit name\(s\): nosuchkitxyz - nothing built} $out]
lappend result confignames [regexp {configured kit outputs: .*punk91} $out]
lappend result received [regexp {Received: 'nosuchkitxyz'} $out]
#the run must never reach the kit machinery (no vfs scan / kit processing output)
lappend result nomachinery [expr {![regexp {processing targetkit:} $out]}]
set r2 [maketcl_run {bake nosuchkitxyz} {PUNKBOOT_PLAIN 1}]
set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result nothingbuilt2 [regexp {make\.tcl bake: unknown kit name\(s\): nosuchkitxyz - nothing built} $out2]
lappend result nomachinery2 [expr {![regexp {processing targetkit:} $out2]}]
set result
} -result {timedout 0 exitcode 1 nothingbuilt 1 confignames 1 nomachinery 1}
} -result {timedout 0 exitcode 1 received 1 nomachinery 1 timedout2 0 exitcode2 1 nothingbuilt2 1 nomachinery2 1}
#added 2026-07-31 (agent, G-024)
test maketcl_bakelist_group_filter {bakelist @verify-ix86 selects the group's rows and no others} -constraints {punkexeavailable} -body {

35
src/tests/shell/testsuites/punkexe/maketclhelp.test

@ -20,17 +20,19 @@ package require tcltest
# - 'help <subject> <cmdline...>': help DRY-RUNS the subject's own command line
# through its declaration - 'help tool build -test 0 punkzip' renders the build
# form (punk::args form auto-selection; flag-like words at/after the first
# value position parse as values, so subtly-wrong-but-accepted lines still
# render usage: 'help bake punk91 -confirm 0' renders bake's whole usage),
# while a line the subcommand would reject ('help tool -test 0 build',
# value position parse as values where the value arg is unconstrained, e.g
# tool's toolname - the received-args report reveals such swallows), while a
# line the subcommand would reject ('help tool -test 0 build',
# 'help tool 2') exits 1 with the same punk::args noformmatch diagnosis
# dispatch gives (its table carries the all-forms synopsis, so the error is
# itself usage documentation); no bare-action carve-out: 'help buildsuite
# build' (suitename required) exits 1 with that same diagnosis, whose table
# still carries the build form's argument rows; accepted lines are confirmed
# with a received-args report ('dry-run: line accepted (form build) - action =
# build | ...'), which also reveals flag-like words consumed as values
# ('kitname = punk91 -confirm 0')
# build | ...'); bake kitnames are -choicerestricted (2026-08-02), so the
# out-of-order 'help bake punk91 -confirm 0' is REJECTED at the choice gate
# exactly as dispatch would reject it (Received: '-confirm'), while
# 'help bake punk91' is accepted with 'kitname = punk91' reported
# - 'help tool frobnicate' / 'help buildsuite frobnicate': exit 1 with the
# punk::args noformmatch diagnosis naming the candidate forms
# - 'tool build -help': the <subcommand> <action> -help route renders the form
@ -179,13 +181,15 @@ namespace eval ::testspace {
#added 2026-08-01 (agent, G-143 refinement); updated 2026-08-02 (agent) - help
#dry-runs the subject's own command line through its declaration: accepted
#lines render the matched form's usage plus a received-args report (flag-like
#words at/after the first value position parse as VALUES per the punk::args
#positional model, so 'bake punk91 -confirm 0' is accepted and the report
#reveals the swallow); rejected lines (option-first, numeric form index,
#unknown flags in option position) exit 1 with the noformmatch diagnosis,
#whose table carries the all-forms synopsis - the error is itself usage
#words at/after the first value position parse as VALUES where the value arg
#is unconstrained - tool's toolname - and the report reveals the swallow);
#rejected lines (option-first, numeric form index, unknown flags in option
#position, and - since kitnames became -choicerestricted 2026-08-02 - the
#out-of-order 'bake punk91 -confirm 0', whose '-confirm' fails the kitname
#choice gate exactly as dispatch rejects it) exit 1 with the punk::args
#diagnosis, whose table carries the synopsis - the error is itself usage
#documentation
test maketcl_help_cmdline_selection {'make.tcl help tool build -test 0 punkzip' renders the build form + received-args report; accepted single-form lines render whole usage with the value-swallow visible; rejected lines exit 1 with the self-documenting noformmatch diagnosis} -constraints {tclshavailable} -body {
test maketcl_help_cmdline_selection {'make.tcl help tool build -test 0 punkzip' renders the build form + received-args report; 'help bake punk91' accepted/reported; rejected lines (incl. the kitname choice gate) exit 1 with the self-documenting diagnosis} -constraints {tclshavailable} -body {
set r [maketcl_run {help tool build -test 0 punkzip}]
set out [dict get $r output]
set result [list]
@ -196,8 +200,11 @@ namespace eval ::testspace {
set r2 [maketcl_run {help bake punk91 -confirm 0}]
set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result bakeusage2 [regexp {make\.tcl bake \[-k\]} $out2]
lappend result swallow2 [regexp {kitname = punk91 -confirm 0} $out2]
lappend result gate2 [regexp {Received: '-confirm'} $out2]
set r2b [maketcl_run {help bake punk91}]
set out2b [dict get $r2b output]
lappend result timedout2b [dict get $r2b timedout] exitcode2b [dict get $r2b exitcode]
lappend result accepted2b [regexp {kitname = punk91} $out2b]
set r3 [maketcl_run {help tool -test 0 build}]
set out3 [dict get $r3 output]
lappend result timedout3 [dict get $r3 timedout] exitcode3 [dict get $r3 exitcode]
@ -208,7 +215,7 @@ namespace eval ::testspace {
lappend result timedout4 [dict get $r4 timedout] exitcode4 [dict get $r4 exitcode]
lappend result noform4 [regexp {No form of the command matches the supplied arguments} $out4]
set result
} -result {timedout 0 exitcode 0 synbuild 1 accepted 1 landed 1 timedout2 0 exitcode2 0 bakeusage2 1 swallow2 1 timedout3 0 exitcode3 1 noform3 1 synindiag3 1 timedout4 0 exitcode4 1 noform4 1}
} -result {timedout 0 exitcode 0 synbuild 1 accepted 1 landed 1 timedout2 0 exitcode2 1 gate2 1 timedout2b 0 exitcode2b 0 accepted2b 1 timedout3 0 exitcode3 1 noform3 1 synindiag3 1 timedout4 0 exitcode4 1 noform4 1}
#added 2026-08-01 (agent, G-143)
test maketcl_help_buildsuite_multiform {piped 'make.tcl help buildsuite': exit 0, one synopsis line per action} -constraints {tclshavailable} -body {

Loading…
Cancel
Save