Browse Source

G-122 achieved: make.tcl bakes by target platform, not host personality (punkshell 0.25.0)

Everything a bake EMITS is now keyed by the artifact's TARGET platform rather than the
driving tclsh's personality: the bin/runtime/<tier> store a runtime is read from, .exe
suffixing of runtime files and kit outputs, presence checks, 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 platform canon EXCEPT for a cygwin-family host (an
msys2/cygwin-runtime tclsh, which reports tcl_platform(platform) unix on windows), which
targets win32-x86_64. Such a host now drives the identical kit set, names and store
addressing as a native tclsh; before, it reported every kit runtime=missing against a
nonexistent mingw64-x86_64 store.

mapvfs.config entries take an optional 4th element naming the kit's target platform, so a
runtime kept in another platform's tier is a first-class mapping entry. The linux tclkit
entry now declares linux-x86_64 and bakes to a suffixless bin/punkshell902.

Zip-type kits assemble without zipfs in the driving tcl: when tcl::zipfs::mkimg is absent
(any 8.6) the image is built by raw-runtime split + punk::zip::mkzip + concatenation - the
same helper the zipcat type uses. Both mount identically (archive-start-relative offsets).

Fixes exposed by the above: the runtime capability probe looked for a suffixless filename
and so reported find-fail for every runtime on windows; with real capabilities arriving,
the extraction try-list was found to test capability presence rather than value, which
would send a plain zipfs runtime down the kit path where a failed sdx mksplit replaces
raw_runtime with the un-split original; BUILDCOPY names now follow the runtime's own
filename; runtime map keys strip only .exe (file rootname would eat the last dotted
segment of tclsh9.0.5); and native windows command lines run through a guard suppressing
msys2's posix-path argument rewriting - without it taskkill /PID <n> reached taskkill as
taskkill C:/<msysroot>/PID <n>, so the sweep could find a running kit but never replace it.

punk::platform folds the msys2 family (mingw64/mingw32/ucrt64/clang*) onto one stable
'msys' tag and CYGWIN_NT onto 'cygwin' - msys2's uname renders that token from MSYSTEM,
so one binary otherwise canonizes differently per launching shell. New normalize_os;
cygwin-x86_64 added to the platform table; 'help platforms' documents the fold.

make.tcl check reports the derivation on one line; bakelist rows carry target=<platform>
for non-default targets and the detail block names the tier; workflow text gains key note
[K9]. New characterization suite maketclplatform.test (5 tests, two self-gating on
discovering and probing a real msys/cygwin tclsh). Full suite 1112 tests: 1093 passed,
18 skipped, 1 failed = the documented exec-14.3 baseline.

Verified: msys2 tclsh8.6 bakelist output identical to native Tcl 9; a changed punk91
baked and deployed from that host with the sweep killing a running instance msys ps
cannot see; a real cross-target linux kit baked from windows to a 24MB ELF; a zipfs-less
8.6 bake producing a bootable zip kit.

Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
Julian Noble 4 days ago
parent
commit
04d2a51b67
  1. 1
      ARCHITECTURE.md
  2. 40
      CHANGELOG.md
  3. 4
      GOALS-archive.md
  4. 4
      GOALS.md
  5. 5
      bin/AGENTS.md
  6. 2
      goals/G-013-raw-mode-default.md
  7. 104
      goals/G-122-host-target-platform-split.md
  8. 12
      goals/G-123-thirdparty-runtime-tiers.md
  9. 224
      goals/archive/G-122-host-target-platform-split.md
  10. 2
      punkproject.toml
  11. 4
      src/AGENTS.md
  12. 873
      src/make.tcl
  13. 9
      src/modules/punk-999999.0a1.0.tm
  14. 3
      src/modules/punk-buildversion.txt
  15. 79
      src/modules/punk/platform-999999.0a1.0.tm
  16. 3
      src/modules/punk/platform-buildversion.txt
  17. 5
      src/runtime/AGENTS.md
  18. 22
      src/runtime/mapvfs.config
  19. 1
      src/tests/shell/AGENTS.md
  20. 245
      src/tests/shell/testsuites/punkexe/maketclplatform.test
  21. 17
      src/vfs/_config/project_main.tcl
  22. 17
      src/vfs/_config/punk_main.tcl

1
ARCHITECTURE.md

@ -81,6 +81,7 @@ Two layers with a deliberate dependency direction (the class never depends on th
## Build, provenance and kits ## Build, provenance and kits
- **`src/make.tcl` is the single build entry.** Key subcommands: `modules`/`libs`/`packages` (repo-root build outputs), `vfscommonupdate` (regenerate `_vfscommon.vfs`), `bake` (kit assembly into `bin/`; optional kit names confine the run to those kits - G-121), `bakelist` (report the configured kit matrix with runtime/vfs presence and deployed state), `bin`, `bakehouse` (packages + bake for a clean checkout; `project`/`vfs` remain as deprecated aliases - G-112 rename, achieved), `bootsupport` (snapshot refresh), `vendorupdate`, `check`, `workflow`, `projectversion`. Dispatch and help dogfood `punk::args` (G-030, achieved) and degrade to plain scan/help when bootsupport `punk::args` is stale (`PUNKBOOT_PLAIN=1` forces the degraded mode). Source: `src/AGENTS.md`. - **`src/make.tcl` is the single build entry.** Key subcommands: `modules`/`libs`/`packages` (repo-root build outputs), `vfscommonupdate` (regenerate `_vfscommon.vfs`), `bake` (kit assembly into `bin/`; optional kit names confine the run to those kits - G-121), `bakelist` (report the configured kit matrix with runtime/vfs presence and deployed state), `bin`, `bakehouse` (packages + bake for a clean checkout; `project`/`vfs` remain as deprecated aliases - G-112 rename, achieved), `bootsupport` (snapshot refresh), `vendorupdate`, `check`, `workflow`, `projectversion`. Dispatch and help dogfood `punk::args` (G-030, achieved) and degrade to plain scan/help when bootsupport `punk::args` is stale (`PUNKBOOT_PLAIN=1` forces the degraded mode). Source: `src/AGENTS.md`.
- **Host vs target platform (G-122).** The kit surfaces separate what the driving tclsh IS from what a bake is FOR. Target-keyed: the `bin/runtime/<tier>` store a runtime is read from, `.exe` suffixing of runtimes and kit outputs, presence checks, and the pre-deploy process sweep's tooling (`tasklist`/`taskkill` vs `ps`/`kill`, skipped when the target's processes cannot exist on this host). Host-keyed: copy commands, path handling, filesystem case rules, prompts. The default target is the host canon except for cygwin-family hosts (msys2/cygwin-runtime tclsh - `tcl_platform(platform)` `unix` on windows), which target `win32-x86_64`; a `mapvfs.config` entry may declare its own target and is then addressed in that platform's tier. `make.tcl check` prints the derivation. Zip-type kits assemble without zipfs in the driving tcl (raw-runtime split + `punk::zip::mkzip` + concatenation). Sources: `src/AGENTS.md`, `src/runtime/AGENTS.md`, `src/modules/punk/platform-999999.0a1.0.tm`.
- **punkcheck** records every install/delete as events in per-folder `.punkcheck` directories - the basis for skip/copy change detection, superseded-module pruning and provenance. Single OO record lifecycle (G-094, achieved) with atomic saves and advisory event-scoped locking for concurrent writers (G-095, achieved). - **punkcheck** records every install/delete as events in per-folder `.punkcheck` directories - the basis for skip/copy change detection, superseded-module pruning and provenance. Single OO record lifecycle (G-094, achieved) with atomic saves and advisory event-scoped locking for concurrent writers (G-095, achieved).
- **Provenance gates.** Build/promotion commands warn on uncommitted `src/` changes (column-0 `PROVENANCE-WARNING:` token, `-dirty-abort` for strict mode); `vendorupdate` warns for dirty source-project checkouts. - **Provenance gates.** Build/promotion commands warn on uncommitted `src/` changes (column-0 `PROVENANCE-WARNING:` token, `-dirty-abort` for strict mode); `vendorupdate` warns for dirty source-project checkouts.
- **Buildsuites and the kit family.** `src/buildsuites/suite_tcl90/` builds Tcl/Tk/tcllib from source with a pinned zig toolchain and produces the runtime kit family (plain / punk / bi) plus artifact metadata (G-096-G-117 era: see archived goals G-096, G-098, G-102, G-103, G-107); artifacts publish to the punkbin repo that `bin/punk-runtime.cmd` fetches from. `src/buildsuites/suite_tcl86/` is the 8.6 sibling (G-099 + G-100, both achieved): a Tcl 8.6 windows runtime (static + dynamic shells, `tcl86t.dll`, on-disk lib tree - no zipfs, so no self-contained kit) with thread 2.8 + tclvfs + Tk 8.6 + tklib + tcllib(+tcllibc critcl accelerators) companions, gated against the 8.6 core/thread/tclvfs testsuites with tcllib/tklib on a record tier and an opt-in `test-tk`; punkshell's own runtests is censused on that runtime at parity with a same-day native-8.6 baseline. Suite child shells scrub `TCL<major>_<minor>_TM_PATH` as well as TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY - without it a census measures the machine's module trees. The 8.6 kit container strategy remains in-flux - see "In-flux areas". - **Buildsuites and the kit family.** `src/buildsuites/suite_tcl90/` builds Tcl/Tk/tcllib from source with a pinned zig toolchain and produces the runtime kit family (plain / punk / bi) plus artifact metadata (G-096-G-117 era: see archived goals G-096, G-098, G-102, G-103, G-107); artifacts publish to the punkbin repo that `bin/punk-runtime.cmd` fetches from. `src/buildsuites/suite_tcl86/` is the 8.6 sibling (G-099 + G-100, both achieved): a Tcl 8.6 windows runtime (static + dynamic shells, `tcl86t.dll`, on-disk lib tree - no zipfs, so no self-contained kit) with thread 2.8 + tclvfs + Tk 8.6 + tklib + tcllib(+tcllibc critcl accelerators) companions, gated against the 8.6 core/thread/tclvfs testsuites with tcllib/tklib on a record tier and an opt-in `test-tk`; punkshell's own runtests is censused on that runtime at parity with a same-day native-8.6 baseline. Suite child shells scrub `TCL<major>_<minor>_TM_PATH` as well as TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY - without it a census measures the machine's module trees. The 8.6 kit container strategy remains in-flux - see "In-flux areas".

40
CHANGELOG.md

@ -5,6 +5,46 @@ 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` Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy. "Project Versioning" section for the bump policy.
## [0.25.0] - 2026-07-26
- make.tcl host/target platform split (G-122): everything a bake EMITS is now keyed by the
artifact's TARGET platform rather than the driving tclsh's personality - the
bin/runtime/<tier> store a runtime is read from, .exe suffixing of runtime files and kit
outputs, presence checks, 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 platform canon EXCEPT for a
cygwin-family host (msys2/cygwin-runtime tclsh: reports tcl_platform(platform) unix on
windows), which targets win32-x86_64 - such a host now drives the identical kit set,
names and store addressing as a native tclsh, where before it reported every kit
runtime=missing against a nonexistent mingw64-x86_64 store.
- mapvfs.config entries take an optional 4th element naming the kit's target platform, so a
runtime kept in another platform's store tier is a first-class mapping entry: it is read
from that tier, named with that platform's executable convention, and skipped by the
process sweep. The existing linux tclkit entry now declares linux-x86_64 and bakes to a
suffixless bin/punkshell902.
- make.tcl check reports the derivation on one line: `platform (G-122): host=... target=...
store=... exe-suffix=... process-tooling=...`, with the tcl_platform values it came from.
- Zip-type kits assemble without zipfs in the driving tcl: when tcl::zipfs::mkimg is absent
(any 8.6) the image is built by raw-runtime split + punk::zip::mkzip + concatenation - the
same helper the zipcat kit type uses. Both mount identically (archive-start-relative
offsets). Note the runtime's own zip is still EXTRACTED via tcllib's zipfile::decode,
which is not vendored; without it the bake emits a recapped BUILD-WARNING that the kit
will carry no tcl_library.
- Fixes exposed by the above: the runtime capability probe looked for a suffixless filename
and so reported find-fail for every runtime on windows (its results now feed the
extraction-type choice, which additionally now tests each capability's VALUE rather than
its mere presence); BUILDCOPY names follow the runtime's own filename; and native windows
command lines run through a guard that suppresses msys2's posix-path argument rewriting -
without it `taskkill /PID <n>` reached taskkill as `taskkill C:/<msysroot>/PID <n>` and
the pre-deploy sweep could find a running kit but never replace it.
- make.tcl diagnoses being handed a windows-style script path by a cygwin-family (POSIX)
tclsh, which silently resolves it relative to the cwd, and names the path spelling to use.
- punk::platform: the msys2 family (mingw64/mingw32/ucrt64/clang*) folds onto one stable
`msys` tag and CYGWIN_NT onto `cygwin` - msys2's uname renders that token from MSYSTEM,
so one binary otherwise canonizes differently per launching shell. New
punk::platform::normalize_os; cygwin-x86_64 added to the platform table; 'help platforms'
documents the fold.
## [0.24.1] - 2026-07-26 ## [0.24.1] - 2026-07-26
- make.tcl bakelist/bake: a nonexistent per-platform runtime store folder is now flagged - make.tcl bakelist/bake: a nonexistent per-platform runtime store folder is now flagged

4
GOALS-archive.md

@ -194,3 +194,7 @@ Acceptance: from the pinned zig and fetched core-8-6-branch sources (hermetic cl
### G-100 [achieved 2026-07-26] suite_tcl86 dependency set: Tk 8.6 + tklib, tcllib with critcl accelerators, punkshell runtests on the 8.6 runtime → detail: goals/archive/G-100-suite-tcl86-tk-tcllib.md ### G-100 [achieved 2026-07-26] suite_tcl86 dependency set: Tk 8.6 + tklib, tcllib with critcl accelerators, punkshell runtests on the 8.6 runtime → detail: goals/archive/G-100-suite-tcl86-tk-tcllib.md
Scope: src/buildsuites/suite_tcl86/ (recipe extensions, driver steps); src/buildsuites/_build/ (tk/tklib/tcllib/critcl sources and outputs); bin/tools/ (pinned zig as consumed); src/tests/ (punkshell suites exercised against the suite-built 8.6 runtime) Scope: src/buildsuites/suite_tcl86/ (recipe extensions, driver steps); src/buildsuites/_build/ (tk/tklib/tcllib/critcl sources and outputs); bin/tools/ (pinned zig as consumed); src/tests/ (punkshell suites exercised against the suite-built 8.6 runtime)
Acceptance: from the pinned zig and fetched sources the suite produces a Tk 8.6 the suite tclsh loads (package require Tk with a create/destroy-window verification recorded here) and a matching tklib whose packages resolve (a representative require recorded); a tcllib install via its installer whose accelerators load (package require tcllibc) with the accelerated-vs-pure comparison recorded; punkshell runtests runs against the suite 8.6 runtime and its census is recorded here against a same-day native/kit 8.6 baseline run (same include set), with every difference dispositioned - Tk-dependent suites run unskipped, and any 8.6-runtime-only failures are root-caused or baselined with reasons. Acceptance: from the pinned zig and fetched sources the suite produces a Tk 8.6 the suite tclsh loads (package require Tk with a create/destroy-window verification recorded here) and a matching tklib whose packages resolve (a representative require recorded); a tcllib install via its installer whose accelerators load (package require tcllibc) with the accelerated-vs-pure comparison recorded; punkshell runtests runs against the suite 8.6 runtime and its census is recorded here against a same-day native/kit 8.6 baseline run (same include set), with every difference dispositioned - Tk-dependent suites run unskipped, and any 8.6-runtime-only failures are root-caused or baselined with reasons.
### G-122 [achieved 2026-07-26] make.tcl host/target platform split: bake by target, not host personality → detail: goals/archive/G-122-host-target-platform-split.md
Scope: src/make.tcl (target-platform derivation; store folder, exe suffixing, kit naming, presence checks and process-sweep tooling keyed by target; platform-canon inline copy; zipfs-less zip-type assembly fallback); src/modules/punk/platform-999999.0a1.0.tm (stable canon tags for cygwin-family hosts, help platforms doc); src/runtime/mapvfs.config (per-entry target platform via minimal compatible extension - format ownership coordinated with G-024); bin/runtime/<platform>/ store tiers (as consumed; fixture tier for tests); src/tests/shell/testsuites/punkexe/ (characterization)
Acceptance: an msys/cygwin-runtime tclsh driving bakelist/bake on windows reports - and for a changed kit, bakes and deploys - the same win32-x86_64 kit set with identical names and store addressing as a native tclsh, with the pre-deploy process sweep using windows tooling for win32 targets regardless of host personality; punk::platform and the make.tcl inline canon map MSYS_NT/MINGW64_NT/CYGWIN_NT/UCRT64-class hosts to stable documented tags (MSYSTEM-variance documented, no windows build number in the tag) and 'help platforms' lists them; a mapvfs entry declaring a non-default target platform lists in bakelist with its own tier's store presence and bakes to a correctly suffixed artifact, verified against a fixture store tier (a real third-party runtime is not required); a zipfs-less driving tcl (8.6) assembles a zip-type kit via runtime split + punk::zip::mkzip + concatenation (archive-start-relative offsets) and the artifact boots on a zipfs-capable runtime; the linuxulator expectation (a linux-personality tclsh on FreeBSD 14/15 addresses the linux-x86_64 store unchanged) is recorded here with verification deferred to the planned linux-kit-on-FreeBSD trial; native-windows behaviour stays characterization-stable (existing punkexe maketcl tests pass unchanged).

4
GOALS.md

@ -385,10 +385,6 @@ Detail: goals/G-115-declarative-vfs-composition.md
Scope: src/buildsuites/suite_tcl90/ (build_tcltls module + crypto-backend build, sources.config + build.zig.zon source records, kit-family bi payload + metadata extension, test_gate record step); src/vfs kit payloads carrying vendored tcltls binaries (current-state reference + recorded disposition only - removal stays G-004-era work); punkbin (as eventual artifact destination via the G-067 library class); consumers punk::imap4 / punk::netbox (package require tls) as verification context Scope: src/buildsuites/suite_tcl90/ (build_tcltls module + crypto-backend build, sources.config + build.zig.zon source records, kit-family bi payload + metadata extension, test_gate record step); src/vfs kit payloads carrying vendored tcltls binaries (current-state reference + recorded disposition only - removal stays G-004-era work); punkbin (as eventual artifact destination via the G-067 library class); consumers punk::imap4 / punk::netbox (package require tls) as verification context
Detail: goals/G-116-suite-built-tcltls.md Detail: goals/G-116-suite-built-tcltls.md
### G-122 [proposed] make.tcl host/target platform split: bake by target, not host personality
Scope: src/make.tcl (target-platform derivation; store folder, exe suffixing, kit naming, presence checks and process-sweep tooling keyed by target; platform-canon inline copy; zipfs-less zip-type assembly fallback); src/modules/punk/platform-999999.0a1.0.tm (stable canon tags for cygwin-family hosts, help platforms doc); src/runtime/mapvfs.config (per-entry target platform via minimal compatible extension - format ownership coordinated with G-024); bin/runtime/<platform>/ store tiers (as consumed; fixture tier for tests); src/tests/shell/testsuites/punkexe/ (characterization)
Detail: goals/G-122-host-target-platform-split.md
### G-123 [proposed] Third-party runtime tiers via artifact servers: per-target publish, list, fetch, materialize ### G-123 [proposed] Third-party runtime tiers via artifact servers: per-target publish, list, fetch, materialize
Scope: src/scriptapps/punk-runtime.ps1 + src/scriptapps/bin/punk-runtime.bash + punk-runtime_wrap.toml (named-tier operations in both payloads; bin/punk-runtime.cmd via the established re-wrap channel); punkbin artifact repo (external, c:/repo/jn/punkbin - per-tier layout, defaults, sha1sums, schema-v2 provenance sidecars incl. retroactive backfill, layout docs); src/buildsuites/suite_tcl90/ (family_artifacts.tcl schema-v2 emission for suite-built artifacts); alternative-server override + trusted-server set (PUNKBIN_URL pattern; consent surface coordinated with G-006); src/tests/shell/testsuites/binscripts/ (fixture-server characterization); bin/runtime/<tier>/ stores (as materialization targets) Scope: src/scriptapps/punk-runtime.ps1 + src/scriptapps/bin/punk-runtime.bash + punk-runtime_wrap.toml (named-tier operations in both payloads; bin/punk-runtime.cmd via the established re-wrap channel); punkbin artifact repo (external, c:/repo/jn/punkbin - per-tier layout, defaults, sha1sums, schema-v2 provenance sidecars incl. retroactive backfill, layout docs); src/buildsuites/suite_tcl90/ (family_artifacts.tcl schema-v2 emission for suite-built artifacts); alternative-server override + trusted-server set (PUNKBIN_URL pattern; consent surface coordinated with G-006); src/tests/shell/testsuites/binscripts/ (fixture-server characterization); bin/runtime/<tier>/ stores (as materialization targets)
Detail: goals/G-123-thirdparty-runtime-tiers.md Detail: goals/G-123-thirdparty-runtime-tiers.md

5
bin/AGENTS.md

@ -101,6 +101,11 @@ The first argument to a built punk shell may be a dash-delimited package mode co
`bin/runtime/<platform>/` (retrieved from the punkbin artifact repo with sha1 `bin/runtime/<platform>/` (retrieved from the punkbin artifact repo with sha1
verification against its `sha1sums.txt` - both the powershell payload on windows and the verification against its `sha1sums.txt` - both the powershell payload on windows and the
bash payload on unix verify; a fetch whose checksum fails leaves only a `.tmp`). bash payload on unix verify; a fetch whose checksum fails leaves only a `.tmp`).
The `<platform>` folders are store TIERS keyed by the platform a runtime is FOR, and the
kit build reads them the same way: `src/make.tcl` picks a tier per kit from that kit's
target platform, not from the driving tclsh's own personality (G-122 - see
`src/AGENTS.md` and `src/runtime/AGENTS.md`), so a tier holding runtimes for another
platform is a first-class thing to populate here.
Hashing is deliberately dependency-free in both payloads: the powershell payload computes Hashing is deliberately dependency-free in both payloads: the powershell payload computes
sha1 via .NET (`Get-PunkFileSha1`) rather than `Get-FileHash`, because Get-FileHash is a sha1 via .NET (`Get-PunkFileSha1`) rather than `Get-FileHash`, because Get-FileHash is a
script-defined function in Windows PowerShell 5.1 resolved through PSModulePath and script-defined function in Windows PowerShell 5.1 resolved through PSModulePath and

2
goals/G-013-raw-mode-default.md

@ -84,4 +84,4 @@ stays (see inventory item 4).
the suite runtimes and kit shape; first enable of a session costs ~0.6-0.7s server the suite runtimes and kit shape; first enable of a session costs ~0.6-0.7s server
spawn - an eager warm-up at repl init is noted there as a future nicety if raw spawn - an eager warm-up at repl init is noted there as a future nicety if raw
becomes the default). See goals/archive/G-106-powershell-consolemode-fallback.md. becomes the default). See goals/archive/G-106-powershell-consolemode-fallback.md.
- 2026-07-26 field finding (msys/cygwin-runtime tclsh: scoop msys2 tclsh8.6 driving 'src/make.tcl shell' on windows): line mode is largely functional, but RAW mode places the cursor at row 1 after each command - the raw-mode cursor bookkeeping (cursor-position query/screen-state handling) does not yet cater for the msys pty terminal class. Recorded as a raw-mode-default blocker dimension alongside the existing cursor-move issues; the make.tcl host/target platform-split goal (G-122, proposed the same day) is build-tooling only and deliberately excludes this console/repl work. - 2026-07-26 field finding (msys/cygwin-runtime tclsh: scoop msys2 tclsh8.6 driving 'src/make.tcl shell' on windows): line mode is largely functional, but RAW mode places the cursor at row 1 after each command - the raw-mode cursor bookkeeping (cursor-position query/screen-state handling) does not yet cater for the msys pty terminal class. Recorded as a raw-mode-default blocker dimension alongside the existing cursor-move issues; the make.tcl host/target platform-split goal (G-122, achieved 2026-07-26 - see goals/archive/G-122-host-target-platform-split.md) was build-tooling only and deliberately excluded this console/repl work, so the msys pty raw-mode behaviour remains entirely this goal's.

104
goals/G-122-host-target-platform-split.md

@ -1,104 +0,0 @@
# G-122 make.tcl host/target platform split: bake by target, not host personality
Status: proposed
Scope: src/make.tcl (target-platform derivation; store folder, exe suffixing, kit naming, presence checks and process-sweep tooling keyed by target; platform-canon inline copy; zipfs-less zip-type assembly fallback); src/modules/punk/platform-999999.0a1.0.tm (stable canon tags for cygwin-family hosts, help platforms doc); src/runtime/mapvfs.config (per-entry target platform via minimal compatible extension - format ownership coordinated with G-024); bin/runtime/<platform>/ store tiers (as consumed; fixture tier for tests); src/tests/shell/testsuites/punkexe/ (characterization)
Goal: make.tcl's kit surfaces (bake/bakelist/bin) operate on an explicit target platform rather than the driving tclsh's personality: any capable host (native windows tclsh, msys/cygwin-runtime tclsh, a FreeBSD host running linux binaries) addresses the same per-target runtime store, artifact naming and process handling for the kits it reports/bakes; non-native-personality runtime tiers (e.g third-party mingw64 builds served from punkbin or an alternative artifact server) are representable as first-class mapping entries with their own store tier; and zip-type kit assembly no longer requires zipfs in the driving tcl.
Acceptance: an msys/cygwin-runtime tclsh driving bakelist/bake on windows reports - and for a changed kit, bakes and deploys - the same win32-x86_64 kit set with identical names and store addressing as a native tclsh, with the pre-deploy process sweep using windows tooling for win32 targets regardless of host personality; punk::platform and the make.tcl inline canon map MSYS_NT/MINGW64_NT/CYGWIN_NT/UCRT64-class hosts to stable documented tags (MSYSTEM-variance documented, no windows build number in the tag) and 'help platforms' lists them; a mapvfs entry declaring a non-default target platform lists in bakelist with its own tier's store presence and bakes to a correctly suffixed artifact, verified against a fixture store tier (a real third-party runtime is not required); a zipfs-less driving tcl (8.6) assembles a zip-type kit via runtime split + punk::zip::mkzip + concatenation (archive-start-relative offsets) and the artifact boots on a zipfs-capable runtime; the linuxulator expectation (a linux-personality tclsh on FreeBSD 14/15 addresses the linux-x86_64 store unchanged) is recorded here with verification deferred to the planned linux-kit-on-FreeBSD trial; native-windows behaviour stays characterization-stable (existing punkexe maketcl tests pass unchanged).
## Context
Field session 2026-07-26: the scoop msys2 tclsh8.6 driving `src/make.tcl
bakelist` on windows showed every configured kit as runtime=missing (and
deployed absent) from two compounding causes: the store folder derives from the
HOST's platform canon (observed `mingw64-x86_64` under a MINGW64_NT os string -
note the tag follows the MSYSTEM environment, so the same binary can canonize
as msys/ucrt64/mingw64 in different shells), and the platform=unix branch
probes suffixless filenames - an assumption that is false for the entire
cygwin family, whose executables are `.exe` files (the msys tclsh itself is
`tclsh8.6.exe`). The interim self-diagnosis shipped in punkshell 0.24.1
(bakelist store-folder WARNING with the canon derivation + `(FOLDER MISSING)`
header mark; bake's no-runtimes exit note) tells the user what happened; this
goal makes the tooling actually cater for it.
The underlying defect is a conflation: make.tcl's scattered
`$::tcl_platform(platform) eq "windows"` tests mix three separable concerns -
host process semantics (cp vs `cmd /c copy`, `ps`/`kill` vs
tasklist/taskkill, path personality), target artifact naming (`.exe`
suffixing, kit/output names), and store addressing (`bin/runtime/<canon>`).
An msys host pulls them apart. The process sweep is the sharpest edge: msys
`ps` sees only msys-descendant processes, so a natively-launched kit exe is
invisible to the pre-deploy sweep (taskkill remains exec-able from msys).
Direction (user, 2026-07-26): even if msys never becomes a zig buildsuite
target, third-party-built runtime tiers (e.g an msys2-packaged tclsh) served
from punkbin or an alternative artifact server must be supportable as kit
runtimes - REPRESENTATION (mapping entry + store tier) belongs here;
publication/fetch is the flagged follow-on goal.
FreeBSD linuxulator parallel (user): a linux-personality tclsh running on
FreeBSD via the linux compat layer is expected to self-identify as linux and
address `bin/runtime/linux-x86_64` correctly - the planned
linux-kit-on-FreeBSD-14/15 trial doubles as verification that target-keyed
stores follow runtime personality, not host OS identity.
Zip offsets (user correction, 2026-07-26): tcl zipfs kits mount with
archive-start-relative offsets - cat-style construction (split the original
runtime first, then append a freshly written zip) is valid; `zipfs mkimg`'s
file-relative offset adjustment is one convention, not a mounting
requirement. The existing zipcat kit type is the in-repo proof, and its
machinery already has zipfs-less fallbacks.
## Approach
- One `target_platform` derivation: default from the host canon, with
cygwin-family hosts on windows defaulting to target `win32-x86_64`;
per-entry mapping override for non-default tiers (a minimal compatible
extension of the current vfsconfig entry - e.g a fourth element - whose
schema home is G-024's toml conversion; the parsed-model seam (G-121,
achieved - see goals/archive/G-121-bakelist-selective-bake.md) keeps every
consumer format-agnostic while the format moves).
- Key store folder, suffixing, kit/artifact naming, presence checks and the
process-sweep tool selection by TARGET; keep copy commands, path handling
and prompt behaviour host-driven.
- punk::platform canon: explicit MSYS_NT/MINGW64_NT/CYGWIN_NT/UCRT64 mapping
to stable documented tags with the MSYSTEM-variance caveat; the make.tcl
inline canon copy stays in sync per its existing comment contract.
- zip-type fallback for zipfs-less driving tcls: reuse the zipcat machinery
(extract_preamble split + punk::zip::mkzip + concatenation).
- Characterization against a fixture store tier so no real third-party
runtime is required; the msys-hosted acceptance run uses the dev machine's
msys tclsh where present (else recorded env-gated, per punkexe suite
conventions).
## Alternatives considered
- Per-site host patches (special-case each windows test) - rejected: the
three-concern conflation just re-accumulates with each new host class.
- Full self-hosted msys platform lineage (msys-personality kits, accelerator
builds, own test baselines) - out of scope: the third-party tier direction
covers the need without a new first-party build lineage.
- Status quo (require a native tclsh to drive make.tcl) - rejected: field
friction (the all-missing bakelist), and upcoming linux/FreeBSD hosts make
host-personality assumptions untenable regardless of msys.
## Notes
- Related: G-024 - owns the mapping format; the per-entry target platform is
drafted as a minimal compatible extension and its schema home is that
goal's toml conversion (make.tcl's own comments already pair the platform
column with the toml change).
- Related: G-105 - cross-target zig builds populate first-party non-native
store tiers; this goal is the consuming/addressing side.
- Related: G-114 - same punk::platform canon vocabulary, applied to
per-platform tm module roots.
- Related: G-101 - 8.6 kit containers ride the same mapping/store surfaces;
lister/bake stay kit-type-agnostic (per the archived G-121 decision).
- Related: G-057 - per-entry mapping extension precedent (per-vfs icon
override rides the same entry shape).
- Related: G-023 - kit naming schemes; target-keyed naming must not collide
with its version-named binaries direction.
- Follow-on: G-123 (drafted 2026-07-26, user-approved) - third-party runtime
tier publication/fetch via punkbin or alternative servers, with schema-v2
provenance labeling and server-trust-keyed consent.
- Out of scope: msys pty raw-mode console/repl behaviour - the cursor-row-1
field finding of 2026-07-26 is recorded in G-013's Notes.

12
goals/G-123-thirdparty-runtime-tiers.md

@ -75,7 +75,17 @@ see goals/archive/G-058-static-runtime-packages.md).
- Depends on: G-122 (canon tags, target-keyed stores, non-default-target - Depends on: G-122 (canon tags, target-keyed stores, non-default-target
mapping entries - the end-to-end acceptance item consumes its fixture-tier mapping entries - the end-to-end acceptance item consumes its fixture-tier
support). support). ACHIEVED 2026-07-26 - see goals/archive/G-122-host-target-platform-split.md;
the consuming side is in place, so this goal's end-to-end item is now purely
about getting a served artifact into `bin/runtime/<tier>/`. What that archived
goal leaves for whoever builds on it: `msys-x86_64` and `cygwin-x86_64` are
distinct canon tags (msys2 and cygwin are different runtime/mount worlds - one
tier does not serve the other, and MSYSTEM-derived names like mingw64/ucrt64
fold onto `msys`, so a server tier must never be named from MSYSTEM); a
mapvfs entry addresses its tier through a declared target platform (4th
element); and the zipfs-less kit path depends on tcllib's `zipfile::decode`,
which punkshell does not vendor - a served third-party runtime whose consumer
host lacks tcllib cannot have its own zip extracted into a kit.
- Related: G-006 - consent-gated binary downloads; the untrusted-server gate - Related: G-006 - consent-gated binary downloads; the untrusted-server gate
here is a concrete consumer (interim documented flag if this lands first). here is a concrete consumer (interim documented flag if this lands first).
- Related: G-067 - artifact-channel kinship (module tier vs runtime tier); - Related: G-067 - artifact-channel kinship (module tier vs runtime tier);

224
goals/archive/G-122-host-target-platform-split.md

@ -0,0 +1,224 @@
# G-122 make.tcl host/target platform split: bake by target, not host personality
Status: achieved 2026-07-26
Scope: src/make.tcl (target-platform derivation; store folder, exe suffixing, kit naming, presence checks and process-sweep tooling keyed by target; platform-canon inline copy; zipfs-less zip-type assembly fallback); src/modules/punk/platform-999999.0a1.0.tm (stable canon tags for cygwin-family hosts, help platforms doc); src/runtime/mapvfs.config (per-entry target platform via minimal compatible extension - format ownership coordinated with G-024); bin/runtime/<platform>/ store tiers (as consumed; fixture tier for tests); src/tests/shell/testsuites/punkexe/ (characterization)
Goal: make.tcl's kit surfaces (bake/bakelist/bin) operate on an explicit target platform rather than the driving tclsh's personality: any capable host (native windows tclsh, msys/cygwin-runtime tclsh, a FreeBSD host running linux binaries) addresses the same per-target runtime store, artifact naming and process handling for the kits it reports/bakes; non-native-personality runtime tiers (e.g third-party mingw64 builds served from punkbin or an alternative artifact server) are representable as first-class mapping entries with their own store tier; and zip-type kit assembly no longer requires zipfs in the driving tcl.
Acceptance: an msys/cygwin-runtime tclsh driving bakelist/bake on windows reports - and for a changed kit, bakes and deploys - the same win32-x86_64 kit set with identical names and store addressing as a native tclsh, with the pre-deploy process sweep using windows tooling for win32 targets regardless of host personality; punk::platform and the make.tcl inline canon map MSYS_NT/MINGW64_NT/CYGWIN_NT/UCRT64-class hosts to stable documented tags (MSYSTEM-variance documented, no windows build number in the tag) and 'help platforms' lists them; a mapvfs entry declaring a non-default target platform lists in bakelist with its own tier's store presence and bakes to a correctly suffixed artifact, verified against a fixture store tier (a real third-party runtime is not required); a zipfs-less driving tcl (8.6) assembles a zip-type kit via runtime split + punk::zip::mkzip + concatenation (archive-start-relative offsets) and the artifact boots on a zipfs-capable runtime; the linuxulator expectation (a linux-personality tclsh on FreeBSD 14/15 addresses the linux-x86_64 store unchanged) is recorded here with verification deferred to the planned linux-kit-on-FreeBSD trial; native-windows behaviour stays characterization-stable (existing punkexe maketcl tests pass unchanged).
## Context
Field session 2026-07-26: the scoop msys2 tclsh8.6 driving `src/make.tcl
bakelist` on windows showed every configured kit as runtime=missing (and
deployed absent) from two compounding causes: the store folder derives from the
HOST's platform canon (observed `mingw64-x86_64` under a MINGW64_NT os string -
note the tag follows the MSYSTEM environment, so the same binary can canonize
as msys/ucrt64/mingw64 in different shells), and the platform=unix branch
probes suffixless filenames - an assumption that is false for the entire
cygwin family, whose executables are `.exe` files (the msys tclsh itself is
`tclsh8.6.exe`). The interim self-diagnosis shipped in punkshell 0.24.1
(bakelist store-folder WARNING with the canon derivation + `(FOLDER MISSING)`
header mark; bake's no-runtimes exit note) tells the user what happened; this
goal makes the tooling actually cater for it.
The underlying defect is a conflation: make.tcl's scattered
`$::tcl_platform(platform) eq "windows"` tests mix three separable concerns -
host process semantics (cp vs `cmd /c copy`, `ps`/`kill` vs
tasklist/taskkill, path personality), target artifact naming (`.exe`
suffixing, kit/output names), and store addressing (`bin/runtime/<canon>`).
An msys host pulls them apart. The process sweep is the sharpest edge: msys
`ps` sees only msys-descendant processes, so a natively-launched kit exe is
invisible to the pre-deploy sweep (taskkill remains exec-able from msys).
Direction (user, 2026-07-26): even if msys never becomes a zig buildsuite
target, third-party-built runtime tiers (e.g an msys2-packaged tclsh) served
from punkbin or an alternative artifact server must be supportable as kit
runtimes - REPRESENTATION (mapping entry + store tier) belongs here;
publication/fetch is the flagged follow-on goal.
FreeBSD linuxulator parallel (user): a linux-personality tclsh running on
FreeBSD via the linux compat layer is expected to self-identify as linux and
address `bin/runtime/linux-x86_64` correctly - the planned
linux-kit-on-FreeBSD-14/15 trial doubles as verification that target-keyed
stores follow runtime personality, not host OS identity.
Zip offsets (user correction, 2026-07-26): tcl zipfs kits mount with
archive-start-relative offsets - cat-style construction (split the original
runtime first, then append a freshly written zip) is valid; `zipfs mkimg`'s
file-relative offset adjustment is one convention, not a mounting
requirement. The existing zipcat kit type is the in-repo proof, and its
machinery already has zipfs-less fallbacks.
## Approach
- One `target_platform` derivation: default from the host canon, with
cygwin-family hosts on windows defaulting to target `win32-x86_64`;
per-entry mapping override for non-default tiers (a minimal compatible
extension of the current vfsconfig entry - e.g a fourth element - whose
schema home is G-024's toml conversion; the parsed-model seam (G-121,
achieved - see goals/archive/G-121-bakelist-selective-bake.md) keeps every
consumer format-agnostic while the format moves).
- Key store folder, suffixing, kit/artifact naming, presence checks and the
process-sweep tool selection by TARGET; keep copy commands, path handling
and prompt behaviour host-driven.
- punk::platform canon: explicit MSYS_NT/MINGW64_NT/CYGWIN_NT/UCRT64 mapping
to stable documented tags with the MSYSTEM-variance caveat; the make.tcl
inline canon copy stays in sync per its existing comment contract.
- zip-type fallback for zipfs-less driving tcls: reuse the zipcat machinery
(extract_preamble split + punk::zip::mkzip + concatenation).
- Characterization against a fixture store tier so no real third-party
runtime is required; the msys-hosted acceptance run uses the dev machine's
msys tclsh where present (else recorded env-gated, per punkexe suite
conventions).
## Alternatives considered
- Per-site host patches (special-case each windows test) - rejected: the
three-concern conflation just re-accumulates with each new host class.
- Full self-hosted msys platform lineage (msys-personality kits, accelerator
builds, own test baselines) - out of scope: the third-party tier direction
covers the need without a new first-party build lineage.
- Status quo (require a native tclsh to drive make.tcl) - rejected: field
friction (the all-missing bakelist), and upcoming linux/FreeBSD hosts make
host-personality assumptions untenable regardless of msys.
## Notes
- Related: G-024 - owns the mapping format; the per-entry target platform is
drafted as a minimal compatible extension and its schema home is that
goal's toml conversion (make.tcl's own comments already pair the platform
column with the toml change).
- Related: G-105 - cross-target zig builds populate first-party non-native
store tiers; this goal is the consuming/addressing side.
- Related: G-114 - same punk::platform canon vocabulary, applied to
per-platform tm module roots.
- Related: G-101 - 8.6 kit containers ride the same mapping/store surfaces;
lister/bake stay kit-type-agnostic (per the archived G-121 decision).
- Related: G-057 - per-entry mapping extension precedent (per-vfs icon
override rides the same entry shape).
- Related: G-023 - kit naming schemes; target-keyed naming must not collide
with its version-named binaries direction.
- Follow-on: G-123 (drafted 2026-07-26, user-approved) - third-party runtime
tier publication/fetch via punkbin or alternative servers, with schema-v2
provenance labeling and server-trust-keyed consent.
- Related: G-028 - the pre-deploy process sweep is the shared surface: this
goal picks the sweep TOOLING by target (windows tooling for win32 targets
from any host personality), that goal names the process actually holding a
target the build cannot replace. A locker-report helper must make the same
host-vs-target tooling choice. (activation-freshness survey, 2026-07-26)
- Related: G-060 - the eventual vehicle for exercising non-native target
output: this goal makes cross-target kits addressable/bakeable from one
host, a guest matrix is what verifies the artifacts actually boot (and is
the natural home for the deferred linuxulator trial below).
(activation-freshness survey, 2026-07-26)
- Out of scope: msys pty raw-mode console/repl behaviour - the cursor-row-1
field finding of 2026-07-26 is recorded in G-013's Notes.
- FreeBSD linuxulator expectation (recorded per the acceptance; verification
deferred to the planned linux-kit-on-FreeBSD-14/15 trial, G-060 being the
natural harness). A linux-personality tclsh running on FreeBSD via the linux
compat layer self-identifies as linux, so `platform_punk` yields
`linux-x86_64`, `platform_target_default` returns it unchanged (the
cygwin-family exception is keyed on the os token being msys/cygwin, which a
linuxulator tclsh never reports), and the bake addresses
`bin/runtime/linux-x86_64` with suffixless artifact names and posix
(`ps`/`kill`) process tooling. Target-keyed stores follow RUNTIME
personality, not host OS identity: that is the same rule that makes an msys
host bake win32 kits, applied in the other direction. What the trial must
actually check is that `tcl_platform(os)` reports Linux (not FreeBSD) under
the compat layer - if it reported FreeBSD, the store addressed would be
`bin/runtime/freebsd-x86_64` and the expectation would need revisiting.
- Finding (2026-07-26, kept for whoever meets it next): a cygwin-family tclsh
is a POSIX Tcl - `C:/repo/.../src/make.tcl` is not an absolute path to it, so
`file normalize` prefixes the cwd and the run dies in an unrecognisable
cascade (missing bootsupport, then no project root). make.tcl now diagnoses
exactly that shape and prints the `/c/repo/...` spelling to use. This is
caller-side spelling, deliberately not rewritten by make.tcl.
- Finding (2026-07-26): the zipfs-less EXTRACTION half depends on tcllib's
`zipfile::decode`, a SYSTEM package that punkshell does not vendor. msys2's
`/usr/bin/tclsh8.6` has no tcllib, so a bake there cannot lift the runtime's
own zip (its `tcl_library`) into the kit and the artifact will not boot; the
bake now emits a recapped BUILD-WARNING saying so, and still deploys. With a
tcllib on the path the same msys tclsh splits and decodes byte-identically to
a native 8.6 host (verified: baseoffset 5267968, 1429659 zip bytes, 1784
entries from both). Candidate follow-ons flagged to the user rather than
written as goals: (a) vendor or write a pure-Tcl zip decoder so the
zipfs-less path is self-contained, (b) refuse to DEPLOY a kit whose
extraction produced no tcl_library instead of warning past it.
- Finding (2026-07-26): the `bin/punkshell902` name is now producible on
windows (cross-target bake), and several punkexe suites use it as the
second-choice `PUNK_SHELL_TEST_EXE` candidate after `bin/punk902z.exe`. On a
windows machine with no windows kit but a cross-baked linux one, that
fallback would select an ELF binary. Left as-is (pre-existing candidate-list
logic, narrow state); `PUNK_SHELL_TEST_EXE` overrides it.
## Progress
Achieved 2026-07-26 in one work unit. What landed and how each acceptance
clause was verified:
- **Target-platform derivation** (`::punkboot::lib::platform_*` in
`src/make.tcl`): `platform_target_default` (cygwin-family host -> win32),
`platform_store_tier` (macosx-* -> macosx), `platform_exe_suffix`,
`platform_process_family`, `platform_name_problem`, plus
`platform_normalize_os` mirroring the module. Globals
`::punkboot::host_platform` / `target_platform` / `host_windows` separate the
two axes; `this_platform_generic` stays the HOST key for bootsupport and
vendorlib platform dirs.
- **Mapping model keyed by target**: `mapvfs_parse` takes the store ROOT plus a
default target and resolves each entry's own tier/suffix (4th element;
conflicting or malformed targets are config errors); `mapvfs_kit_outputs`
records carry `target`/`store_tier`/`runtime_dir`. The bake path parses the
mapping BEFORE runtime discovery, since the mapping is what says which tiers
to look in; cross-target runtimes are picked up only when named.
- **msys-host acceptance** (msys2 `/usr/bin/tclsh8.6`, canon `msys-x86_64`,
`tcl_platform(platform)` unix): `bakelist` output is identical to the native
Tcl 9.0.3 run (line endings aside) - same 10 kits, same names, same
`runtimes: bin/runtime/win32-x86_64` addressing, `punk91` detail resolving
`bin/runtime/win32-x86_64/tclsfe-x64.exe (present)`, `target: win32-x86_64
(host default)`. Before the split every row read `runtime=missing` against a
nonexistent `mingw64-x86_64` store. A changed `punk91` then baked and
deployed from that host with a hidden `punk91.exe` (pid 10120) running: the
sweep chose tasklist, FOUND it - msys `ps` reports 0 matches for the same
process, measured side by side - killed it with taskkill and replaced
`bin/punk91.exe`.
- **msys2 argument mangling** (found by that run): msys2 rewrites arguments
that look like absolute posix paths when spawning a native windows program,
so `taskkill /PID 10120` arrived as `taskkill C:/<msysroot>/PID 10120` and
the kill failed the kit. Native windows command lines now go through
`::punkboot::exec_nativeargs` (`MSYS2_ARG_CONV_EXCL=*`, inert on other
hosts); the re-run killed and deployed cleanly.
- **Cross-target entry**: the linux tclkit entry declares `linux-x86_64` and is
read from `bin/runtime/linux-x86_64/` with no `.exe`. `bakelist` lists it with
a `target=linux-x86_64` note and `(present)` store state; `make.tcl bake
punkshell902` from a windows host produced a 24,200,032-byte ELF
`bin/punkshell902` (sdx wrap over the linux runtime), with the process sweep
correctly skipped as not-visible-from-this-host. A real cross-target runtime
was used - the acceptance's fixture-tier allowance was not needed.
- **Zipfs-less zip assembly**: native Tcl 8.6.17 (no zipfs) baked `punk91` via
raw-runtime split + `punk::zip::mkzip` + concatenation (5,267,968 + 44,242,534
= 49,510,502 bytes) and the artifact BOOTS - `//zipfs:/app/tcl_library`
mounted, 4 zipfs mounts, punk::lib 0.5.1 loaded. The `zipcat` kit type now
shares the same `::punkboot::assemble_zipcat_image` helper.
- **Latent defects the split exposed and fixed**: the runtime capability probe
tested a suffixless filename and so reported `find-fail` for every runtime on
windows (it now probes the target's own tier and suffix, and skips
cross-target runtimes as unrunnable); with real capabilities arriving, the
extraction try-list was found to test capability PRESENCE rather than value,
which would send a plain zipfs runtime down the kit path where a failed `sdx
mksplit` replaces `raw_runtime` with the un-split original - a silently
unbootable kit; BUILDCOPY naming now follows the runtime's own filename; and
runtime map keys strip only `.exe` (`file rootname` would eat the last dotted
segment of `tclsh9.0.5`).
- **Characterization**: new `src/tests/shell/testsuites/punkexe/maketclplatform.test`
(5 tests: the `check` derivation line, default-target detail, the cross-target
entry's tier/target/suffixless naming, and two cygwin-host tests that
self-gate on discovering and probing a real msys/cygwin tclsh - the
derivation line and full bakelist row-set parity against the native run).
Native behaviour stayed characterization-stable: full suite 1112 tests,
1093 passed, 18 skipped, 1 failed = the documented `exec-14.3` baseline; the
pre-existing `maketclbakelist`/`maketclcolour` tests pass unchanged.
- **Reported surface**: `make.tcl check` prints
`platform (G-122): host=... target=... store=... exe-suffix=... process-tooling=...`;
`bakelist` rows carry `target=<platform>` for non-default targets and the
detail block names the tier and whether the target is the host default or
declared; the workflow text gained key note [K9] and a target-keyed store
line. punkshell 0.24.1 -> 0.25.0.

2
punkproject.toml

@ -1,4 +1,4 @@
[project] [project]
name = "punkshell" name = "punkshell"
version = "0.24.1" version = "0.25.0"
license = "BSD-2-Clause" license = "BSD-2-Clause"

4
src/AGENTS.md

@ -72,7 +72,9 @@ Recovery after a wrong path guess:
- make.tcl subcommands and flags are punk::args-declared (G-030): `tclsh src/make.tcl help ?subcommand?` or `<subcommand> -help` shows tabled usage; invalid arguments produce punk::args usage errors. 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 `::punkboot::argdoc` definitions are constructed (string-built) and use the G-045 authoring mechanisms — block-form help values + `@normalize`, explicit `@form -synopsis` — 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): `tclsh src/make.tcl help ?subcommand?` or `<subcommand> -help` shows tabled usage; invalid arguments produce punk::args usage errors. 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 `::punkboot::argdoc` definitions are constructed (string-built) and use the G-045 authoring mechanisms — block-form help values + `@normalize`, explicit `@form -synopsis` — 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), 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. - 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), 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.
- 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. - 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 ...?` (G-121) reports the kit outputs configured in `src/runtime/mapvfs.config`: 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) in a trailing notes column. A nonexistent per-platform store folder is flagged loudly (header `(FOLDER MISSING)` + stderr canon derivation; bake's no-runtimes exit self-diagnoses the same way) - the classic trigger is an msys/cygwin-runtime tclsh whose platform canon (e.g `mingw64-x86_64`) is not the windows store the kits use. Name arguments filter the report and add a per-kit detail block. `make.tcl bake ?kitname ...?` 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; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Bare `bake` processes all configured kits as before. Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format, so the G-024 config-format work swaps the reader underneath. Piped characterization: `src/tests/shell/testsuites/punkexe/maketclbakelist.test`. - `tclsh src/make.tcl bakelist ?kitname ...?` (G-121) reports the kit outputs configured in `src/runtime/mapvfs.config`: 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) and the cross-target marker `target=<platform>` 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 arguments filter the report and add a per-kit detail block (resolved store tier, target and provenance of the target, paths/sizes/mtimes). `make.tcl bake ?kitname ...?` 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; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Bare `bake` processes all configured kits as before. Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format, so the G-024 config-format work swaps the reader underneath. 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 (4th element - 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).
- 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. Caveat for zipfs-less hosts: EXTRACTING the runtime's own attached zip (needed to carry its `tcl_library` into the kit) uses tcllib's `zipfile::decode`, which is a system package and is NOT vendored - a tclsh without tcllib (e.g msys2's `/usr/bin/tclsh8.6`) cannot take that path and the bake emits a recapped `BUILD-WARNING` that the kit will get no `tcl_library`. Heed that warning: the build still produces and deploys an artifact.
- Use `punk make.tcl bakehouse` or `punk902z make.tcl bakehouse` inside Punk shell when building binaries through Punk (the deprecated `project` alias still maps). Driving make.tcl from a built punk executable is supported for informational/update subcommands and for kit builds of *other* kits — the kit whose deployed executable is running the build is skipped with a warning (it cannot be replaced while running, and the pre-deploy process sweep must not kill the build itself; the sweep also excludes the build's own pid in all cases). Rebuild that kit from tclsh or a different kit. - Use `punk make.tcl bakehouse` or `punk902z make.tcl bakehouse` inside Punk shell when building binaries through Punk (the deprecated `project` alias still maps). Driving make.tcl from a built punk executable is supported for informational/update subcommands and for kit builds of *other* kits — the kit whose deployed executable is running the build is skipped with a warning (it cannot be replaced while running, and the pre-deploy process sweep must not kill the build itself; the sweep also excludes the build's own pid in all cases). Rebuild that kit from tclsh or a different kit.
- **Punk-exe-hosted make.tcl runs in a pre-loaded interp, not a virgin one** — the kit's script-mode boot has already loaded much of the punk stack (punk, punk::lib, punk::repl, punk::console, punk::du, flagfilter, struct::set ...) and set process state (app-punkscript forces `::tcl_interactive 0` for script semantics; libunknown/packagepreference are active). Consequences make.tcl must (and now does) handle explicitly: `package require` of an already-provided package is a no-op, so anything make.tcl breaks in the interp (the accelerator-reload block forgets+destroys sha1/md5/struct::* — it re-requires what was loaded), and anything computed at package-load time (punk::repl's `::tcl_interactive` probe — the shell branch recomputes it before `repl::start`), must be restored deliberately rather than relying on later loads to re-fire. Also note the copies that run in this mode are the *kit's* pre-loaded modules, not the bootsupport snapshots make.tcl's paths would otherwise prefer — silent provenance mixing when versions diverge. When adding interp-surgery or load-time-state assumptions to make.tcl, test under both `tclsh src/make.tcl ...` and `<punkexe> src/make.tcl ...`. - **Punk-exe-hosted make.tcl runs in a pre-loaded interp, not a virgin one** — the kit's script-mode boot has already loaded much of the punk stack (punk, punk::lib, punk::repl, punk::console, punk::du, flagfilter, struct::set ...) and set process state (app-punkscript forces `::tcl_interactive 0` for script semantics; libunknown/packagepreference are active). Consequences make.tcl must (and now does) handle explicitly: `package require` of an already-provided package is a no-op, so anything make.tcl breaks in the interp (the accelerator-reload block forgets+destroys sha1/md5/struct::* — it re-requires what was loaded), and anything computed at package-load time (punk::repl's `::tcl_interactive` probe — the shell branch recomputes it before `repl::start`), must be restored deliberately rather than relying on later loads to re-fire. Also note the copies that run in this mode are the *kit's* pre-loaded modules, not the bootsupport snapshots make.tcl's paths would otherwise prefer — silent provenance mixing when versions diverge. When adding interp-surgery or load-time-state assumptions to make.tcl, test under both `tclsh src/make.tcl ...` and `<punkexe> src/make.tcl ...`.
- Binary images are platform-specific; build on each target platform rather than expecting a cross-platform flag. - Binary images are platform-specific; build on each target platform rather than expecting a cross-platform flag.

873
src/make.tcl

File diff suppressed because it is too large Load Diff

9
src/modules/punk-999999.0a1.0.tm

@ -9002,7 +9002,14 @@ namespace eval punk {
runtime-artifact metadata target field. The raw Tcl runtime-artifact metadata target field. The raw Tcl
platform-package identifiers are shown for comparison platform-package identifiers are shown for comparison
(punk::platform::normalize folds their version-dependent (punk::platform::normalize folds their version-dependent
aliases - amd64/aarch64/macos - into the canonical names)." aliases - amd64/aarch64/macos - into the canonical names, and
folds the msys2 family - mingw64/mingw32/ucrt64/clang* - onto
the one 'msys' tag, since msys2's uname renders that token from
MSYSTEM rather than from the binary).
These are HOST names. What a name implies for build OUTPUT
(.exe suffixing, process tooling, which bin/runtime tier holds
a runtime) is a TARGET question src/make.tcl derives from the
name - a cygwin-family host targets win32-x86_64 by default."
@values -min 0 -max 0 @values -min 0 -max 0
} }
proc platforms {context args} { proc platforms {context args} {

3
src/modules/punk-buildversion.txt

@ -1,6 +1,7 @@
0.2.7 0.2.8
#First line must be a semantic version number #First line must be a semantic version number
#all other lines are ignored. #all other lines are ignored.
#0.2.8 - 'help platforms' help text documents the G-122 msys2-family fold (mingw64/mingw32/ucrt64/clang* -> the one 'msys' tag, since msys2's uname renders that token from MSYSTEM rather than from the binary) and states that these are HOST names: what a name implies for build OUTPUT (.exe suffixing, process tooling, bin/runtime store tier) is a TARGET question src/make.tcl derives from it, a cygwin-family host targeting win32-x86_64 by default. Rendered table unchanged (it is generated from punk::platform::platforms, which gained cygwin-x86_64).
#0.2.7 - new 'help platforms' topic (aliases: platform): canonical punkshell platform-dir names from punk::platform::platforms rendered with status/tiers/buildsuite/notes (buildsuite = zig-buildsuite runtime production, a separate axis from hosting), the running interpreter's lib-tier and runtime-tier names marked, and the raw Tcl platform-package identifiers (generic/identify) shown for comparison. Degrades cleanly when punk::platform is unavailable. All platform/platforms prefixes are mutually ambiguous so they fall through to command lookup (env/environment precedent). #0.2.7 - new 'help platforms' topic (aliases: platform): canonical punkshell platform-dir names from punk::platform::platforms rendered with status/tiers/buildsuite/notes (buildsuite = zig-buildsuite runtime production, a separate axis from hosting), the running interpreter's lib-tier and runtime-tier names marked, and the raw Tcl platform-package identifiers (generic/identify) shown for comparison. Degrades cleanly when punk::platform is unavailable. All platform/platforms prefixes are mutually ambiguous so they fall through to command lookup (env/environment precedent).
#0.2.6 - G-076: 'help tcl' renders the mitigated/mitigation buginfo axis (punk::lib 0.4.3+): a triggered check reporting mitigated keeps its severity level but displays 'warning level: <level> (mitigated)' in subdued grey (term-grey foreground) instead of the level colour, followed by an indented 'mitigated: <text>' block when mitigation text is supplied. Unmitigated warnings render unchanged. #0.2.6 - G-076: 'help tcl' renders the mitigated/mitigation buginfo axis (punk::lib 0.4.3+): a triggered check reporting mitigated keeps its severity level but displays 'warning level: <level> (mitigated)' in subdued grey (term-grey foreground) instead of the level colour, followed by an indented 'mitigated: <text>' block when mitigation text is supplied. Unmitigated warnings render unchanged.
#0.2.5 - G-045: ::punk::helptopic::define_docs converts from interim left-margin authoring to indented block-form values under the new punk::args @normalize directive (the constructed-definition normalization consumer proof): basehelp/topichelp/help_chunks-extra are braced indented blocks with a structural leading newline, the generated definitions declare @normalize, and the -unindentedfields declarations from punk 0.2.4 are dropped. Rendered 'i help'/'i help_chunks' output unchanged (verified aligned incl. the blank-line separator in help_chunks). #0.2.5 - G-045: ::punk::helptopic::define_docs converts from interim left-margin authoring to indented block-form values under the new punk::args @normalize directive (the constructed-definition normalization consumer proof): basehelp/topichelp/help_chunks-extra are braced indented blocks with a structural leading newline, the generated definitions declare @normalize, and the -unindentedfields declarations from punk 0.2.4 are dropped. Rendered 'i help'/'i help_chunks' output unchanged (verified aligned incl. the blank-line separator in help_chunks).

79
src/modules/punk/platform-999999.0a1.0.tm

@ -40,6 +40,29 @@ namespace eval punk::platform {
# the darwin 'arm64' machine name to 'arm'; Apple silicon is 64-bit only, # the darwin 'arm64' machine name to 'arm'; Apple silicon is 64-bit only,
# so the arm token on macosx always means arm64. Elsewhere 'arm' is a # so the arm token on macosx always means arm64. Elsewhere 'arm' is a
# genuine 32-bit token and is preserved.) # genuine 32-bit token and is preserved.)
# - os mingw64/mingw32/ucrt64/clang64/clangarm64 -> msys (G-122; see the
# MSYSTEM-variance note below)
#
#MSYSTEM VARIANCE (cygwin-family hosts on windows). An msys2-runtime tclsh
#reports tcl_platform(platform) 'unix' with an os string msys2's uname
#renders from the MSYSTEM environment variable, so ONE binary canonizes
#differently per launching shell - verified 2026-07-26 with msys2's
#/usr/bin/tclsh8.6: MSYSTEM=MSYS -> MSYS_NT-10.0-26200 (msys-x86_64), and
#MSYSTEM=MINGW64, =UCRT64 or unset (msys2 defaults it) -> MINGW64_NT-10.0-26200
#(mingw64-x86_64). The MSYSTEM token names the shell that launched the
#process, not the binary's ABI: a genuinely MINGW-built tclsh is a native
#windows binary reporting os 'Windows NT' and canonizes as win32-*. So every
#msys2-runtime token folds to the one stable tag 'msys'. Real cygwin keeps
#its own tag 'cygwin' - a separate runtime dll and mount world, so a store
#tier serving one does not serve the other. Windows build numbers never
#reach a tag (platform::generic cuts the os at its first _ or -; normalize_os
#re-folds a raw uname-style string anyway).
#These are HOST canon names. What a tag implies for BUILD OUTPUT - .exe
#suffixing, which process tooling manages a running artifact, which
#bin/runtime store tier holds a runtime - is a TARGET question that
#src/make.tcl derives from the tag (G-122 host/target split): cygwin-family
#hosts default their build target to win32-x86_64, since the kits they bake
#are native windows binaries.
#The special name 'macosx' (no cpu suffix) is the RUNTIME-tier convention for #The special name 'macosx' (no cpu suffix) is the RUNTIME-tier convention for
#universal (multi-arch) macOS binaries - punkbin and bin/runtime keep one #universal (multi-arch) macOS binaries - punkbin and bin/runtime keep one
#universal folder; the per-arch macosx-x86_64/macosx-arm64 names serve the #universal folder; the per-arch macosx-x86_64/macosx-arm64 names serve the
@ -83,7 +106,8 @@ namespace eval punk::platform {
macosx-arm64 {status supported tiers {lib} buildsuite none notes "Apple silicon; would follow a macosx runtime arc"} macosx-arm64 {status supported tiers {lib} buildsuite none notes "Apple silicon; would follow a macosx runtime arc"}
freebsd-x86_64 {status supported tiers {runtime lib} buildsuite candidate notes ""} freebsd-x86_64 {status supported tiers {runtime lib} buildsuite candidate notes ""}
freebsd-arm64 {status supported tiers {runtime lib} buildsuite candidate notes "no artifacts yet"} freebsd-arm64 {status supported tiers {runtime lib} buildsuite candidate notes "no artifacts yet"}
msys-x86_64 {status dormant tiers {lib} buildsuite none notes "msys/cygwin-built tclsh runtimes; utility under review"} msys-x86_64 {status dormant tiers {runtime lib} buildsuite none notes "msys2-runtime tclsh on windows (posix personality, .exe files); host canon for every MSYSTEM shell - see normalize"}
cygwin-x86_64 {status recognized tiers {runtime lib} buildsuite none notes "cygwin-runtime tclsh on windows; separate dll/mount world from msys2, so a distinct tier"}
openbsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"} openbsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"}
netbsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"} netbsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"}
dragonflybsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"} dragonflybsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"}
@ -124,6 +148,45 @@ namespace eval punk::platform {
return $platforms return $platforms
} }
namespace eval argdoc {
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::punk::platform::normalize_os
@cmd -name punk::platform::normalize_os\
-summary\
"Fold an os token to its canonical punkshell spelling."\
-help\
"Fold a single os token (the <os> half of an <os>-<cpu> platform
identifier) to its canonical punkshell spelling:
macos -> macosx
mingw32|mingw64|ucrt64|clang* -> msys
MSYS_NT-<build> -> msys
CYGWIN_NT-<build> -> cygwin
The msys2 family folds to one tag because msys2's uname renders
the os string from the MSYSTEM environment variable: the same
binary reports MSYS_NT or MINGW64_NT depending on which shell
launched it, while a genuinely MINGW-built (native windows)
tclsh reports 'Windows NT' and canonizes as win32. Raw
uname-style strings carrying a windows build number fold too -
a build number never belongs in a platform tag.
Unrecognized tokens pass through unchanged."
@values -min 1 -max 1
os -type string -help\
"os token, e.g the <os> half of platform::generic's result."
}]
}
proc normalize_os {os} {
switch -glob -- [string tolower $os] {
macos {return macosx}
msys - msys_nt-* {return msys}
mingw32 - mingw64 - mingw_nt-* - mingw32_nt-* - mingw64_nt-* {return msys}
ucrt64 - ucrt64_nt-* {return msys}
clang32 - clang64 - clangarm64 - clang32_nt-* - clang64_nt-* - clangarm64_nt-* {return msys}
cygwin_nt-* {return cygwin}
}
return $os
}
namespace eval argdoc { namespace eval argdoc {
variable PUNKARGS variable PUNKARGS
lappend PUNKARGS [list { lappend PUNKARGS [list {
@ -137,6 +200,9 @@ namespace eval punk::platform {
platform-dir name: platform-dir name:
cpu amd64 -> x86_64, aarch64 -> arm64 cpu amd64 -> x86_64, aarch64 -> arm64
os macos -> macosx (Tcl platform 1.1.x renamed modern macOS) os macos -> macosx (Tcl platform 1.1.x renamed modern macOS)
os mingw64/mingw32/ucrt64/clang* -> msys (one stable tag for
the msys2 runtime family - the reported name follows MSYSTEM,
not the binary; see punk::platform::normalize_os)
macosx cpu arm -> arm64 (platform::generic's arm* glob folds macosx cpu arm -> arm64 (platform::generic's arm* glob folds
the darwin arm64 machine name to arm; Apple silicon is the darwin arm64 machine name to arm; Apple silicon is
64-bit only - non-macosx 'arm' stays 32-bit arm) 64-bit only - non-macosx 'arm' stays 32-bit arm)
@ -150,18 +216,13 @@ namespace eval punk::platform {
proc normalize {platform} { proc normalize {platform} {
set parts [split $platform -] set parts [split $platform -]
if {[llength $parts] < 2} { if {[llength $parts] < 2} {
#single-token names (e.g the universal 'macosx') - fold macos only #single-token names (e.g the universal 'macosx') - fold the os token only
if {$platform eq "macos"} { return [normalize_os $platform]
return macosx
}
return $platform
} }
#os may itself contain a dash in principle - treat last token as cpu #os may itself contain a dash in principle - treat last token as cpu
set cpu [lindex $parts end] set cpu [lindex $parts end]
set os [join [lrange $parts 0 end-1] -] set os [join [lrange $parts 0 end-1] -]
switch -- $os { set os [normalize_os $os]
macos {set os macosx}
}
switch -- $cpu { switch -- $cpu {
amd64 {set cpu x86_64} amd64 {set cpu x86_64}
aarch64 {set cpu arm64} aarch64 {set cpu arm64}

3
src/modules/punk/platform-buildversion.txt

@ -1,4 +1,5 @@
0.1.0 0.2.0
#First line must be a semantic version number #First line must be a semantic version number
#all other lines are ignored. #all other lines are ignored.
#0.2.0 - G-122 host/target platform split groundwork: new normalize_os folds the msys2 family (msys/mingw32/mingw64/ucrt64/clang*, plus raw MSYS_NT-*/MINGW*_NT-* uname strings carrying a windows build number) onto the one stable 'msys' tag and CYGWIN_NT-* onto 'cygwin' - msys2's uname renders that token from MSYSTEM, so one binary otherwise canonizes differently per launching shell while a genuinely MINGW-built (native) tclsh reports 'Windows NT' and is win32. normalize routes both its shapes through it. platforms table gains cygwin-x86_64 and moves msys-x86_64 to tiers {runtime lib} (third-party runtimes for these are representable as kit mapping entries with their own store tier). Module comments document the MSYSTEM variance and that .exe suffixing / process tooling / store tier are TARGET questions src/make.tcl derives from these HOST names.
#0.1.0 - initial release (2026-07-22 platform-folder synchronization; G-105 groundwork): the canonical punkshell platform naming layer over Tcl's platform package - platforms (canonical records: status/tiers/buildsuite/notes; buildsuite = whether the punkshell zig buildsuites produce runtimes for the platform, deliberately a separate axis from the tiers a name organizes - punkbin-structured repos can host runtimes built by any mechanism, so runtime-tier hosting can coexist with buildsuite=none), normalize (amd64->x86_64, aarch64->arm64, macos->macosx, macosx arm->arm64), local ?-tier lib|runtime? (runtime tier collapses macosx per-arch to the universal macosx runtime-store name). Canon consumed by: punkbin platform folders, bin/runtime/<platform>/, lib_tclX/vendorlib_tclX platform dirs, G-103 artifact metadata targets, 'help platforms' topic. The boot machinery (punkboot::lib platform_generic snips in punk_main.tcl/project_main.tcl/make.tcl) carries an inline copy of the normalization mapping - keep in sync. #0.1.0 - initial release (2026-07-22 platform-folder synchronization; G-105 groundwork): the canonical punkshell platform naming layer over Tcl's platform package - platforms (canonical records: status/tiers/buildsuite/notes; buildsuite = whether the punkshell zig buildsuites produce runtimes for the platform, deliberately a separate axis from the tiers a name organizes - punkbin-structured repos can host runtimes built by any mechanism, so runtime-tier hosting can coexist with buildsuite=none), normalize (amd64->x86_64, aarch64->arm64, macos->macosx, macosx arm->arm64), local ?-tier lib|runtime? (runtime tier collapses macosx per-arch to the universal macosx runtime-store name). Canon consumed by: punkbin platform folders, bin/runtime/<platform>/, lib_tclX/vendorlib_tclX platform dirs, G-103 artifact metadata targets, 'help platforms' topic. The boot machinery (punkboot::lib platform_generic snips in punk_main.tcl/project_main.tcl/make.tcl) carries an inline copy of the normalization mapping - keep in sync.

5
src/runtime/AGENTS.md

@ -12,16 +12,19 @@ Houses the `mapvfs.config` that maps VFS payloads to platform runtimes, plus the
## Local Contracts ## Local Contracts
- `mapvfs.config` defines which `src/vfs/*.vfs` folders combine with which runtime binaries (stored under `bin/runtime/<platform>/`). - `mapvfs.config` defines which `src/vfs/*.vfs` folders combine with which runtime binaries (stored under `bin/runtime/<platform>/`).
- Entry grammar: one line per runtime, `<runtime> {vfs ?kitname? ?kittype? ?targetplatform?} ...`. The file's own header comment is the user-facing spec; keep the two in step.
- **Target platform (4th element, G-122).** A kit config entry may name the canonical punkshell platform its runtime is for (`help platforms` lists the names). It decides which `bin/runtime/<platform>/` tier the runtime is read from (macosx-* collapsing to the universal `macosx` folder), whether the runtime file and the built kit carry `.exe`, and whether the pre-deploy process sweep applies. It is a property of the RUNTIME: entries on one runtime line may repeat it but must not disagree (a conflict, or a name that is not an `<os>-<cpu>` platform-dir name, is a fatal config error). Omitted means the build host's default target - which for an msys2/cygwin-runtime tclsh is `win32-x86_64`, not that host's own canon. The live example is the `tclkit-902-Linux64-intel-dyn` entry (target `linux-x86_64`, suffixless `punkshell902` artifact). The schema's eventual home is the G-024 toml conversion; consumers read the parsed model (`::punkboot::lib::mapvfs_*`), never the file format.
- Runtime executables are placed in `bin/runtime/<platform>/` by the `bin/punk-runtime.cmd` helper, manually, or (G-103 family runtimes) copied from the suite_tcl90 `kit-family` build products under `src/buildsuites/_build/suite_tcl90/out/family/`. - Runtime executables are placed in `bin/runtime/<platform>/` by the `bin/punk-runtime.cmd` helper, manually, or (G-103 family runtimes) copied from the suite_tcl90 `kit-family` build products under `src/buildsuites/_build/suite_tcl90/out/family/`.
- The `_build/` subdirectory holds build intermediates; it can be safely deleted. - The `_build/` subdirectory holds build intermediates; it can be safely deleted.
## Work Guidance ## Work Guidance
- When adding a new platform: create the runtime directory under `bin/runtime/<platform>/`, add a VFS under `src/vfs/`, and update `mapvfs.config`. - When adding a new platform: create the runtime directory under `bin/runtime/<platform>/`, add a VFS under `src/vfs/`, and update `mapvfs.config` - declaring the target platform on the entry when it is not the build host's default.
- Do not commit large binary runtimes to version control unless specifically required. - Do not commit large binary runtimes to version control unless specifically required.
## Verification ## Verification
- `tclsh src/make.tcl bakelist` resolves every entry: each row's runtime presence, target and store tier (add a kit name for the per-kit detail block).
- `tclsh src/make.tcl bake` uses `mapvfs.config` correctly. - `tclsh src/make.tcl bake` uses `mapvfs.config` correctly.
- Built binaries launch and boot the Punk REPL. - Built binaries launch and boot the Punk REPL.

22
src/runtime/mapvfs.config

@ -1,12 +1,22 @@
#single line per runtime executable. Name of runtime followed by list of kit configuration lists. #single line per runtime executable. Name of runtime followed by list of kit configuration lists.
#Each kit config list is from 1 to 3 elements #Each kit config list is from 1 to 4 elements
#1) .vfs folder with path relative to src folder. #1) .vfs folder with path relative to src folder.
#2) name of wrapped kit binary to create (default to rootname of .vfs folder e.g myapp.vfs will create a kit named myapp (or myapp.exe) on windows) #2) name of wrapped kit binary to create (default to rootname of .vfs folder e.g myapp.vfs will create a kit named myapp (or myapp.exe) for a windows target)
#3) Type of kit to create (zip or kit) - default kit. kit for metakit or vlerq based kit, zip for more modern zipkit. #3) Type of kit to create (zip or kit) - default kit. kit for metakit or vlerq based kit, zip for more modern zipkit.
#4) TARGET PLATFORM (G-122) - canonical punkshell platform-dir name ('help platforms' lists them).
# Defaults to the build host's own target: win32-x86_64 on windows INCLUDING from an
# msys2/cygwin-runtime tclsh, whose own canon (msys-x86_64) is a host identity, not a kit target.
# The declared target decides everything the entry emits: which store tier holds the runtime
# (bin/runtime/<target>/, macosx-* collapsing to the universal macosx folder), whether the runtime
# file and the built kit carry .exe, and which process tooling the pre-deploy sweep uses.
# It is a property of the RUNTIME: entries on one runtime line may repeat it but must not disagree.
# #
# e.g The following creates 2 zipkits from the same runtime and 2 different .vfs folder # e.g The following creates 2 zipkits from the same runtime and 2 different .vfs folder
# mytcl90kit.exe {mainapp.vfs myapp.exe zip} {test.vfs testapp.exe zip} # mytcl90kit.exe {mainapp.vfs myapp.exe zip} {test.vfs testapp.exe zip}
# #
# e.g a runtime kept in bin/runtime/linux-x86_64/ producing a suffixless linux kit:
# mylinuxruntime {myapp.vfs myapp kit linux-x86_64}
#
#if runtime has no entry - it will only match a .vfs folder with a matching filename e.g runtime1.exe runtime1.vfs #if runtime has no entry - it will only match a .vfs folder with a matching filename e.g runtime1.exe runtime1.vfs
#Use a runtime with a name of dash (-) to build a .kit file from the .vfs folder using no runtime #Use a runtime with a name of dash (-) to build a .kit file from the .vfs folder using no runtime
#e.g #e.g
@ -96,5 +106,9 @@ tclsh9.0.5-punk-bi.exe {punk9win_for_tkruntime.vfs punk9bi_beta zip}
#testing wrong target kit type for runtime #testing wrong target kit type for runtime
#tclkit902.exe {punk9win_for_tkruntime.vfs punkwrongruntime zip} #tclkit902.exe {punk9win_for_tkruntime.vfs punkwrongruntime zip}
#only tclkit based runtime avail for linux for now - todo make zip-based runtime and install as artifact on gitea1.intx.com.au #only tclkit based runtime avail for linux for now - todo make zip-based runtime and install as artifact on gitea1.intx.com.au
tclkit-902-Linux64-intel-dyn {punk9linux.vfs punkshell902 kit} #Cross-target entry (G-122): declares linux-x86_64 so the runtime is read from
#bin/runtime/linux-x86_64/ with no .exe suffix and the kit deploys as bin/punkshell902.
#Before the target declaration this row read runtime=missing from a windows host,
#because the runtime was looked for as tclkit-902-Linux64-intel-dyn.exe in the windows store.
tclkit-902-Linux64-intel-dyn {punk9linux.vfs punkshell902 kit linux-x86_64}

1
src/tests/shell/AGENTS.md

@ -16,6 +16,7 @@ Tests for shell-level behavior, command-line execution, and stdin/stdout interac
- Avoid relying on installed packages or root-level build outputs unless the test explicitly targets a built executable. - Avoid relying on installed packages or root-level build outputs unless the test explicitly targets a built executable.
- `testsuites/punkexe/` targets a built punk executable: resolved from `env(PUNK_SHELL_TEST_EXE)`, else `<projectroot>/bin/punk902z.exe`, else `<projectroot>/bin/punkshell902`; tests auto-skip via the `punkexeavailable` constraint when none is found. - `testsuites/punkexe/` targets a built punk executable: resolved from `env(PUNK_SHELL_TEST_EXE)`, else `<projectroot>/bin/punk902z.exe`, else `<projectroot>/bin/punkshell902`; tests auto-skip via the `punkexeavailable` constraint when none is found.
- Tests that spawn the built executable must be hang-proof: use the event-loop `punk_run` pattern (pipe stdin, half-close for EOF, timeout then force-kill) rather than plain `exec`, because known failure modes leave the child waiting on a reopened console. - Tests that spawn the built executable must be hang-proof: use the event-loop `punk_run` pattern (pipe stdin, half-close for EOF, timeout then force-kill) rather than plain `exec`, because known failure modes leave the child waiting on a reopened console.
- Three punkexe suites drive the WORKING TREE's `src/make.tcl` through the built executable's `script` subcommand with output captured through a pipe, pinning its reporting surfaces rather than running builds: `maketclcolour.test` (G-113 piped colour policy), `maketclbakelist.test` (G-121 kit matrix report + the selective-bake no-build guard) and `maketclplatform.test` (G-122 host/target platform split - the `check` derivation line, per-kit store tier/target, and cross-target artifact naming). Their per-kit row expectations are characterization of the current `src/runtime/mapvfs.config` matrix, so a deliberate config change legitimately updates them. `maketclplatform.test` additionally carries self-gating cygwin-host tests: they discover an msys2/cygwin-runtime tclsh (`env(PUNK_MSYS_TCLSH)`, else the usual msys2/cygwin roots) and PROBE it - a candidate only counts when it really reports `tcl_platform(platform)` `unix` with a cygwin-family os string - then assert that host derives the same win32 target, store and kit set as the native run. Such a tclsh is a POSIX Tcl: pass it a `/c/...`-spelled script path, never `C:/...`.
- `testsuites/punkexe/shellexit.test` guards piped-stdin termination behaviour of the built executable (no `invalid thread handle` on shutdown races, no console-reopen hang on eof/error paths, exitcode propagation); do not weaken these tests - a red result means a regression in punk::repl shutdown or app-punkshell eof handling. - `testsuites/punkexe/shellexit.test` guards piped-stdin termination behaviour of the built executable (no `invalid thread handle` on shutdown races, no console-reopen hang on eof/error paths, exitcode propagation); do not weaken these tests - a red result means a regression in punk::repl shutdown or app-punkshell eof handling.
- `testsuites/punkexe/shellnavns.test` proves the namespace-navigation session contract end-to-end over a piped 'src'-mode session (dev modules - deliberate, so current punk::repl/nav source is exercised without a rebuild): n/ sets ns_current, the current namespace continues into `subshell punk` and back out (currently via the documented first-subshell shared-code-interp asymmetry - the SHARED marker pins it; the repl::start seeding template for genuinely separate interps is proven in modules/punk/repl nscurrent.test), the punk::repl 0.5.2 stray-namespace fix holds live, and the session exits cleanly. KNOWN GAP pinned there: piped submissions evaluate at :: despite ns_current (interactive and module-level inscope behaviour differ) - investigate alongside G-038. - `testsuites/punkexe/shellnavns.test` proves the namespace-navigation session contract end-to-end over a piped 'src'-mode session (dev modules - deliberate, so current punk::repl/nav source is exercised without a rebuild): n/ sets ns_current, the current namespace continues into `subshell punk` and back out (currently via the documented first-subshell shared-code-interp asymmetry - the SHARED marker pins it; the repl::start seeding template for genuinely separate interps is proven in modules/punk/repl nscurrent.test), the punk::repl 0.5.2 stray-namespace fix holds live, and the session exits cleanly. KNOWN GAP pinned there: piped submissions evaluate at :: despite ns_current (interactive and module-level inscope behaviour differ) - investigate alongside G-038.
- `testsuites/punkexe/scriptexec.test` covers the `script` subcommand (app-punkscript, G-015): piped execution + honest exit codes, result echo, file-form argv, the `lib:<name>` scriptlib-resolution mechanism (incl. punk::path 0.3.0 extensionless-tcl matching: first-line identification, called-spelling precedence over a `.tcl` sibling, sh-trampoline shebang acceptance, and the non-tcl skip note on not-found), and the tclsh-matching Tk main-loop behaviour. The `lib:` and Tk cases resolve committed fixtures under `scriptlib/_punktest/` (test-owned; a `lib:`-resolvable fixture cannot live in a tcltest temp dir since resolution only searches scriptlib locations relative to the exe). Tk cases are gated by the `punktk` constraint, auto-detected by probing whether the kit can load Tk. - `testsuites/punkexe/scriptexec.test` covers the `script` subcommand (app-punkscript, G-015): piped execution + honest exit codes, result echo, file-form argv, the `lib:<name>` scriptlib-resolution mechanism (incl. punk::path 0.3.0 extensionless-tcl matching: first-line identification, called-spelling precedence over a `.tcl` sibling, sh-trampoline shebang acceptance, and the non-tcl skip note on not-found), and the tclsh-matching Tk main-loop behaviour. The `lib:` and Tk cases resolve committed fixtures under `scriptlib/_punktest/` (test-owned; a `lib:`-resolvable fixture cannot live in a tcltest temp dir since resolution only searches scriptlib locations relative to the exe). Tk cases are gated by the `punktk` constraint, auto-detected by probing whether the kit can load Tk.

245
src/tests/shell/testsuites/punkexe/maketclplatform.test

@ -0,0 +1,245 @@
package require tcltest
#Piped characterization of make.tcl's HOST/TARGET platform split (goal G-122).
#
#make.tcl's kit surfaces are keyed by the TARGET platform an artifact is built for,
#not by the driving tclsh's own personality. This file pins the observable contract:
# - 'check' reports the derivation on one stable key=value line
# (host / target / store tier / exe suffix / process tooling)
# - 'bakelist <kit>' detail names the store tier and target of each kit, and a
# default-target kit reports "(host default)"
# - a mapvfs.config entry declaring its own target platform resolves its runtime in
# THAT platform's store tier, names its artifact with that platform's executable
# convention (no .exe for linux-x86_64), and carries a target= note in the report
# - (env-gated) an msys2/cygwin-runtime tclsh - which reports tcl_platform(platform)
# 'unix' on windows and canonizes as msys-x86_64 - drives the SAME win32-x86_64
# kit set, with identical names and store addressing, as the native run
#
#The per-kit row pins are characterization of the current src/runtime/mapvfs.config
#matrix (punk91 = tclsfe-x64 + punk9wintk903.vfs zip, default target; punkshell902 =
#tclkit-902-Linux64-intel-dyn + punk9linux.vfs kit, target linux-x86_64) - a deliberate
#config change legitimately updates them.
#
#Target executable resolved from env(PUNK_SHELL_TEST_EXE), else <projectroot>/bin/punk902z.exe
#then <projectroot>/bin/punkshell902. Skipped (constraint punkexeavailable) if none found.
#The msys host tests additionally need a cygwin-family tclsh: env(PUNK_MSYS_TCLSH), else a
#scan of the usual msys2 roots, VERIFIED by probing that it really reports platform unix
#with a cygwin-family os string (constraint cygwinfamilytclsh).
namespace eval ::testspace {
namespace import ::tcltest::*
variable testdir [file dirname [file normalize [info script]]]
#<projectroot>/src/tests/shell/testsuites/punkexe -> 5 levels up to <projectroot>
variable projectroot [file normalize [file join $testdir .. .. .. .. ..]]
variable maketcl [file join $projectroot src make.tcl]
variable punkexe ""
if {[info exists ::env(PUNK_SHELL_TEST_EXE)] && $::env(PUNK_SHELL_TEST_EXE) ne ""} {
set punkexe [file normalize $::env(PUNK_SHELL_TEST_EXE)]
} else {
foreach candidate [list [file join $projectroot bin punk902z.exe] [file join $projectroot bin punkshell902]] {
if {[file exists $candidate]} {
set punkexe $candidate
break
}
}
}
testConstraint punkexeavailable [expr {$punkexe ne "" && [file exists $punkexe]}]
variable maketcl_run_timeout_ms 60000
variable runstate
array set runstate {}
proc maketcl_run_read {chan} {
variable runstate
append runstate(output) [read $chan]
if {[chan eof $chan]} {
chan event $chan readable {}
set runstate(done) eof
}
}
#Run a command line 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 piped_run {cmdline} {
variable runstate
variable maketcl_run_timeout_ms
array unset runstate
set runstate(output) ""
set runstate(done) ""
set chan [open |[list {*}$cmdline 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 {
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)]
}
#make.tcl driven by the built punk executable's 'script' subcommand
proc maketcl_run {cmdargs} {
variable punkexe
variable maketcl
return [piped_run [list $punkexe script $maketcl {*}$cmdargs]]
}
#make.tcl driven directly by an arbitrary tclsh. A cygwin-family tclsh is a POSIX
#Tcl: 'C:/x/make.tcl' is not absolute to it and would be resolved relative to the
#cwd (make.tcl diagnoses that case), so the script path is spelled the way that
#shell spells paths - exactly what a user in an msys/cygwin shell types.
proc maketcl_run_with {interp cmdargs {posixpaths 0}} {
variable maketcl
set script $maketcl
if {$posixpaths && [regexp {^([A-Za-z]):[/\\](.*)$} $script -> drive rest]} {
set script "/[string tolower $drive]/[string map {\\ /} $rest]"
}
return [piped_run [list $interp $script {*}$cmdargs]]
}
#Kit data rows of a bakelist report, whitespace-normalized for comparison.
proc bakelist_rows {text} {
set rows [list]
foreach ln [split [string map [list \r\n \n \r \n] $text] \n] {
if {[regexp {^[A-Za-z][\w.-]*\s+(kit|zip|zipcat|cookfs|cookit)\s+\S} $ln]} {
lappend rows [regsub -all {\s+} [string trim $ln] " "]
}
}
return $rows
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# cygwin-family (msys2/cygwin runtime) tclsh discovery for the host-parity tests
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
variable cygwintclsh ""
proc find_cygwinfamily_tclsh {} {
set candidates [list]
if {[info exists ::env(PUNK_MSYS_TCLSH)] && $::env(PUNK_MSYS_TCLSH) ne ""} {
lappend candidates $::env(PUNK_MSYS_TCLSH)
} else {
set roots [list]
if {[info exists ::env(USERPROFILE)]} {
lappend roots [file join [string map {\\ /} $::env(USERPROFILE)] scoop apps msys2 current usr bin]
}
lappend roots C:/msys64/usr/bin C:/tools/msys64/usr/bin C:/cygwin64/bin
foreach root $roots {
foreach name {tclsh8.6.exe tclsh.exe} {
set cand [file join $root $name]
if {[file exists $cand]} {
lappend candidates $cand
}
}
}
}
#An existing tclsh is not enough - it must really be a posix-personality
#runtime on windows (that is the whole point of the host/target split).
foreach cand $candidates {
set probe {puts "PROBE $::tcl_platform(platform) [lindex $::tcl_platform(os) 0]"}
if {[catch {exec $cand << $probe} out]} {
continue
}
if {[regexp {PROBE\s+unix\s+(\S+)} $out -> osname]} {
if {[string match -nocase "*_NT-*" $osname] || [string match -nocase "cygwin*" $osname]} {
return $cand
}
}
}
return ""
}
if {$::tcl_platform(platform) eq "windows" || [string match "*_NT-*" $::tcl_platform(os)]} {
catch {set cygwintclsh [find_cygwinfamily_tclsh]}
}
testConstraint cygwinfamilytclsh [expr {$cygwintclsh ne ""}]
#added 2026-07-26 (agent, G-122)
test maketcl_check_platform_report {make.tcl check reports the host/target derivation on one key=value line; a win32 kit host targets win32-x86_64} -constraints {punkexeavailable} -body {
set r [maketcl_run {check}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result line [regexp -line {^platform \(G-122\): host=win32-x86_64 target=win32-x86_64 store=bin/runtime/win32-x86_64 exe-suffix=\.exe process-tooling=windows\s*$} $out]
set result
} -result {timedout 0 exitcode 0 line 1}
#added 2026-07-26 (agent, G-122)
test maketcl_bakelist_default_target_detail {a default-target kit resolves its runtime in the host default store tier and reports target (host default)} -constraints {punkexeavailable} -body {
set r [maketcl_run {bakelist punk91}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result store [regexp {(?n)^\s+runtime file:\s+bin/runtime/win32-x86_64/tclsfe-x64\.exe \(present\)} $out]
lappend result target [regexp {(?n)^\s+target:\s+win32-x86_64 \(host default\)} $out]
#a default-target row carries no target= note (the column is for the minority case)
lappend result nonote [expr {![regexp {(?n)^punk91\s.*target=} $out]}]
set result
} -result {timedout 0 exitcode 0 store 1 target 1 nonote 1}
#added 2026-07-26 (agent, G-122)
test maketcl_bakelist_crosstarget_entry {a mapvfs entry declaring a non-default target lists with a target= note, resolves its own store tier and names a suffixless artifact} -constraints {punkexeavailable} -body {
set r [maketcl_run {bakelist punkshell902}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result row [regexp {(?n)^punkshell902\s+kit\s+tclkit-902-Linux64-intel-dyn\s+punk9linux\.vfs\s+\S+\s+target=linux-x86_64} $out]
lappend result store [regexp {(?n)^\s+runtime file:\s+bin/runtime/linux-x86_64/tclkit-902-Linux64-intel-dyn \(present\)} $out]
lappend result target [regexp {(?n)^\s+target:\s+linux-x86_64 \(declared in mapvfs\.config\)} $out]
#linux target -> no .exe on the build product or the deployed artifact
lappend result nosuffix [regexp {(?n)^\s+build product: src/_build/punkshell902 } $out]
lappend result nosuffix2 [regexp {(?n)^\s+deployed:\s+bin/punkshell902 } $out]
set result
} -result {timedout 0 exitcode 0 row 1 store 1 target 1 nosuffix 1 nosuffix2 1}
#added 2026-07-26 (agent, G-122)
test maketcl_check_platform_report_cygwin_host {a cygwin-family host canonizes as msys/cygwin but still targets - and addresses the store of - win32-x86_64} -constraints {cygwinfamilytclsh} -body {
variable cygwintclsh
set r [maketcl_run_with $cygwintclsh {check} 1]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result hostcanon [regexp -line {^platform \(G-122\): host=(msys|cygwin)-\S+ } $out]
lappend result targetline [regexp -line {^platform \(G-122\): host=\S+ target=win32-x86_64 store=bin/runtime/win32-x86_64 exe-suffix=\.exe process-tooling=windows\s*$} $out]
#the host really is the posix-personality one (this is what used to derive a non-windows store)
lappend result posixhost [regexp {platform='unix'} $out]
set result
} -result {timedout 0 exitcode 0 hostcanon 1 targetline 1 posixhost 1}
#added 2026-07-26 (agent, G-122)
test maketcl_bakelist_cygwin_host_parity {an msys/cygwin-runtime tclsh reports the identical kit set - names, runtimes, targets and store addressing - as the native host} -constraints {punkexeavailable cygwinfamilytclsh} -body {
variable cygwintclsh
set native [maketcl_run {bakelist}]
set cyg [maketcl_run_with $cygwintclsh {bakelist} 1]
set result [list]
lappend result timedout [expr {[dict get $native timedout] || [dict get $cyg timedout]}]
lappend result exitcodes [list [dict get $native exitcode] [dict get $cyg exitcode]]
set nrows [bakelist_rows [dict get $native output]]
set crows [bakelist_rows [dict get $cyg output]]
lappend result haverows [expr {[llength $nrows] > 1}]
lappend result identical [expr {$nrows eq $crows}]
#both must address the same store folder in the report header
lappend result store [expr {[regexp {runtimes: bin/runtime/win32-x86_64} [dict get $native output]]
&& [regexp {runtimes: bin/runtime/win32-x86_64} [dict get $cyg output]]}]
set result
} -result {timedout 0 exitcodes {0 0} haverows 1 identical 1 store 1}
cleanupTests
}
namespace delete ::testspace

17
src/vfs/_config/project_main.tcl

@ -124,16 +124,29 @@ apply { args {
return "${plat}-${cpu}" return "${plat}-${cpu}"
} }
proc platform_normalize_os {os} {
#INLINE COPY of punk::platform::normalize_os - see platform_punk below.
switch -glob -- [string tolower $os] {
macos {return macosx}
msys - msys_nt-* {return msys}
mingw32 - mingw64 - mingw_nt-* - mingw32_nt-* - mingw64_nt-* {return msys}
ucrt64 - ucrt64_nt-* {return msys}
clang32 - clang64 - clangarm64 - clang32_nt-* - clang64_nt-* - clangarm64_nt-* {return msys}
cygwin_nt-* {return cygwin}
}
return $os
}
proc platform_punk {} { proc platform_punk {} {
#canonical punkshell platform-dir name: platform_generic normalized. #canonical punkshell platform-dir name: platform_generic normalized.
#INLINE COPY of punk::platform::normalize (src/modules/punk/platform-*.tm; #INLINE COPY of punk::platform::normalize (src/modules/punk/platform-*.tm;
#'help platforms' documents the canon) - the boot stage cannot package #'help platforms' documents the canon) - the boot stage cannot package
#require, so keep this mapping in sync with that module: #require, so keep this mapping in sync with that module:
#amd64->x86_64, aarch64->arm64, macos->macosx, macosx arm->arm64. #amd64->x86_64, aarch64->arm64, macos->macosx, macosx arm->arm64,
#msys2 family (mingw64/mingw32/ucrt64/clang*) -> msys, CYGWIN_NT -> cygwin.
set parts [split [platform_generic] -] set parts [split [platform_generic] -]
set cpu [lindex $parts end] set cpu [lindex $parts end]
set os [join [lrange $parts 0 end-1] -] set os [join [lrange $parts 0 end-1] -]
if {$os eq "macos"} {set os macosx} set os [platform_normalize_os $os]
switch -- $cpu { switch -- $cpu {
amd64 {set cpu x86_64} amd64 {set cpu x86_64}
aarch64 {set cpu arm64} aarch64 {set cpu arm64}

17
src/vfs/_config/punk_main.tcl

@ -219,16 +219,29 @@ apply { args {
return "${plat}-${cpu}" return "${plat}-${cpu}"
} }
proc platform_normalize_os {os} {
#INLINE COPY of punk::platform::normalize_os - see platform_punk below.
switch -glob -- [string tolower $os] {
macos {return macosx}
msys - msys_nt-* {return msys}
mingw32 - mingw64 - mingw_nt-* - mingw32_nt-* - mingw64_nt-* {return msys}
ucrt64 - ucrt64_nt-* {return msys}
clang32 - clang64 - clangarm64 - clang32_nt-* - clang64_nt-* - clangarm64_nt-* {return msys}
cygwin_nt-* {return cygwin}
}
return $os
}
proc platform_punk {} { proc platform_punk {} {
#canonical punkshell platform-dir name: platform_generic normalized. #canonical punkshell platform-dir name: platform_generic normalized.
#INLINE COPY of punk::platform::normalize (src/modules/punk/platform-*.tm; #INLINE COPY of punk::platform::normalize (src/modules/punk/platform-*.tm;
#'help platforms' documents the canon) - the boot stage cannot package #'help platforms' documents the canon) - the boot stage cannot package
#require, so keep this mapping in sync with that module: #require, so keep this mapping in sync with that module:
#amd64->x86_64, aarch64->arm64, macos->macosx, macosx arm->arm64. #amd64->x86_64, aarch64->arm64, macos->macosx, macosx arm->arm64,
#msys2 family (mingw64/mingw32/ucrt64/clang*) -> msys, CYGWIN_NT -> cygwin.
set parts [split [platform_generic] -] set parts [split [platform_generic] -]
set cpu [lindex $parts end] set cpu [lindex $parts end]
set os [join [lrange $parts 0 end-1] -] set os [join [lrange $parts 0 end-1] -]
if {$os eq "macos"} {set os macosx} set os [platform_normalize_os $os]
switch -- $cpu { switch -- $cpu {
amd64 {set cpu x86_64} amd64 {set cpu x86_64}
aarch64 {set cpu arm64} aarch64 {set cpu arm64}

Loading…
Cancel
Save