12 KiB
G-057 Kit builds carry a configurable icon: text sidecar for every target, embedded PE resources for windows targets
Status: proposed
Scope: src/make.tcl (kit/zipkit wrap steps); src/runtime/punkshell.ico (project default, existing); src/vfs/*.vfs (override placement convention); src/runtime/mapvfs.config (only if an explicit config element is the chosen override mechanism); TEMP_REFERENCE/tcl-sfe (read-only reference); helper proc location decided in the work (make.tcl inline vs punk::mix lib); kit icon sidecar record (emission points recorded in the work)
Goal: kit/zipkit builds record a build-time icon choice for every target - defaulting to the project icon src/runtime/punkshell.ico and overridable per kit by its .vfs folder - in a text sidecar that ships with the kit whatever its target, and embed it as PE icon resources when the target has them and the build host can write them - by replacing the icon resources in the built executable using the twapi-based mechanism demonstrated in tcl-sfe (TEMP_REFERENCE/tcl-sfe, by twapi author and Tcl core member Ashok P. Nadkarni): RT_ICON/RT_GROUP_ICON replacement via twapi resource-update APIs, applied so the appended vfs payload stays intact (icon the stub before appending, or sfe-style split/update/reattach), behind a single mechanism seam so the embedding implementation can be replaced without touching its callers.
Acceptance: a make.tcl bake on a windows host producing a win32-target kit produces kit executables whose embedded icon resources are the project default punkshell.ico, and a kit whose .vfs supplies an override icon gets that icon instead (verified by resource inspection, e.g twapi::extract_resources, not just Explorer eyeballing); the icon-replaced executables still boot to a working punk shell reading their vfs payload for the kit types we build (kit, zip, zipcat per mapvfs.config); runtimes under src/runtime are never modified - replacement applies to the built copies only; a target with no PE resources (linux, freebsd, macosx) skips embedding as NOT APPLICABLE rather than as degradation and still gets its sidecar, while a win32 target on a host that cannot perform the update (twapi unavailable, or a non-windows host cross-baking a windows kit) skips embedding with a DISTINCT notice naming which condition applied, still writes the sidecar, and the build otherwise completes unchanged - the cross-host case is recorded here as a known shortcoming with its remedy; the sidecar is produced for every kit on every target, carries the same icon data the PE embedding uses (source .ico identity and provenance) in a documented plain-text form, is byte-stable across rebuilds at unchanged input, and is defined so the parked RT_VERSION follow-on extends the same file rather than adding a second one; the embedding implementation sits behind a single internal entry point with the mechanism selected inside it, so a later portable replacement is a substitution rather than a rewrite; the twapi mechanism remains available INDEFINITELY as the no-toolchain windows path - the seam SELECTS between mechanisms rather than replacing one with the other - and a fresh checkout that fetches a runtime and bakes produces iconed kits with no build toolchain installed; rebuilds are idempotent (re-wrapping an already-iconed build copy converges, no resource accumulation); the override convention (filename/location in the kit's custom .vfs folder vs a mapvfs.config element) and the stub-vs-split ordering decision are recorded in the detail file with the tcl-sfe attribution - the override is SHARED across a kit definition's targets and carries no target dimension (decided 2026-07-26): it is used where embedding applies and ignored elsewhere.
Context
Built punk kit executables currently carry whatever icon resource their runtime stub shipped with. The project icon src/runtime/punkshell.ico exists and is COPIED into vfs trees (a tclkit-era convention), but nothing embeds it into the produced executable's Windows resources - so all kits look like their runtime in Explorer and the taskbar.
Renamed 2026-07-28 from punk1.ico, and its artwork replaced from the src/assets/logo/ master at the same time. The four build-consumed copies (src/runtime/, _vfscommon.vfs, mkzipfix.vfs, punk8_statictwapi.vfs) are now punkshell.ico and are copies of src/assets/logo/punk-mark.ico; the pre-2026-07 punk1 artwork is retained at src/assets/logo/legacy/ and is the only place that name survives.
Host/target reopened this (2026-07-26). The goal was drafted before G-122 separated
what the driving tclsh IS from what a bake is FOR, so its skip condition read as one
host-shaped test ("non-Windows platform") while its Notes used target language
("Linux/other-platform kit outputs have no PE resources"). Those are now two independent
conditions: a target with no PE resources is not applicable, and a windows target on a
host that cannot run twapi is a genuine shortcoming. Measured the same day on our own
runtimes: .rsrc is generally NOT the last section (tclsfe-x64/punk91: .text .rdata
.data .pdata .rsrc .reloc; suite-built tclsh9.0.5-punk: ... .rsrc .reloc), so any
future non-twapi implementation must append a section and repoint the resource data
directory rather than grow .rsrc in place.
The reference mechanism is tcl-sfe (TEMP_REFERENCE/tcl-sfe, read-only), written by twapi author and Tcl core member Ashok P. Nadkarni: library/sfe-0.2.tm method replaceIcon / ReplaceIconInStub replaces RT_ICON (type 3) and the icon group (RT_GROUP_ICON, type 14) via twapi::extract_resources / twapi::begin_resource_update / twapi::delete_resource / twapi::update_resource / twapi::end_resource_update, building the group entry from the .ico file's own image directory.
Approach
- Hook the icon step into make.tcl's kit wrap processing (driven by src/runtime/mapvfs.config), operating on the BUILD COPY of the runtime stub - never on the originals under src/runtime.
- Payload integrity is the critical ordering constraint: a Windows resource update rewrites the PE image and corrupts any already-appended vfs payload. Either icon the stub copy BEFORE appending the zip/metakit payload, or use the sfe approach of splitting payload off, updating resources, and reattaching (sfe-0.2.tm does the split/update/reattach dance for exactly this reason). Chosen ordering to be recorded here.
- Default icon: src/runtime/punkshell.ico. Per-kit override supplied by the kit's .vfs. IMPORTANT wrinkle for the override convention: _vfscommon.vfs already merges a punkshell.ico into every built vfs tree, so override detection must consult the kit's own CUSTOM .vfs folder (pre-merge, under src/vfs/) - or use an explicit mapvfs.config element - otherwise every kit would appear to "override" with the same common file. Chosen convention to be recorded here.
- Sidecar first, embedding second. The icon CHOICE is recorded in a plain-text record emitted for every kit on every target; PE embedding is one platform-specific projection of that record. Non-PE targets therefore carry the same information, and the parked RT_VERSION follow-on extends the same file rather than inventing another.
- Mechanism seam: one internal entry point performs the embedding and selects the mechanism inside itself. twapi is the mechanism now (demonstrated prior art, windows host, no new build infrastructure); a portable post-hoc PE writer is the intended replacement and must be a substitution, not a rewrite of the callers.
- Graceful degradation, split by axis: a target with no PE resources skips as not applicable; a windows target on a host that cannot run twapi skips with its own notice. Both still write the sidecar and the build proceeds unchanged (per the src/AGENTS.md optional-extension guidance).
- Idempotence: re-running a wrap on an already-processed build copy must converge (sfe deletes existing icon/group resources before writing - follow that).
Follow-on candidate goal (flagged, not yet a GOALS.md entry)
Version-information resource stamping (RT_VERSION, type 16) - sfe-0.2.tm already demonstrates the resource construction (BuildVersionResource, string tables, delete-then-write). Requirements sketch agreed 2026-07-10:
- Copyright: defaults to the copyright already present in the INPUT kit runtime executable's version resource (carry it over - the runtime authors' copyright is not ours to silently drop), while being easily configurable - initially from punkproject.toml and/or per-vfs config.
- Product Name: should indicate the core Tcl version of the runtime by default (e.g. carrying the Tcl major.minor the kit embeds), similarly configurable via punkproject.toml / per-vfs config.
- Other fields (FileVersion/ProductVersion from the project version, FileDescription etc.) to be settled when the goal is drafted.
- Config surface should anticipate the toml direction already agreed for build configs (punkproject.toml now; buildsuites toml era later).
Notes
- tclsfe-x64.exe is already one of our mapped runtimes (mapvfs.config) - the same author's stub and the reference mechanism, so it makes a good first test subject alongside a tclkit-style runtime.
- Resource inspection for acceptance: twapi::extract_resources on the built exe (compare icon group/images against the source .ico), not visual inspection.
- Linux/other-platform kit outputs have no PE resources - explicitly out of scope; the skip path covers them.
- G-023 relationship (recorded 2026-07-24 after overlap review; this file previously referenced nothing): its versioned/dev outputs are rewritten every build - the icon step hooks the same kit wrap steps, and the icon-before-payload-append ordering is settled against that rebuild cadence.
- G-024 relationship: the per-kit icon override convention may become a mapvfs element; if so it lands in G-024's toml grammar, not the legacy line format.
- G-028 relationship: a locked kit exe blocks the resource update exactly as it blocks deploy - G-028's locker-report helper should name the holder on icon-step failure too.
- G-025/G-117 note (G-117 achieved 2026-07-24 - see goals/archive/G-117-self-describing-runtimes.md): the parked RT_VERSION follow-on above stamps version/copyright resources from punkproject.toml - when it is drafted as a goal, reconcile with G-025's kit stamp and G-117's embedded artifact record so the three provenance surfaces tell one story.
- G-122 (achieved) - separated host from target across the kit surfaces, which is what split this goal's skip condition in two; see goals/archive/G-122-host-target-platform-split.md.
- No-toolchain windows path, verified 2026-07-26: twapi is VENDORED and git-tracked (src/vendorlib_tcl8|9/win32-x86_64/twapi-5.0b1) and make.tcl puts $startdir/vendorlib_tcl/ on auto_path unconditionally (sourcesupport_library_paths), so the fresh-checkout persona - fetch a runtime from punkbin, bake - reaches the resource-update APIs with nothing built. Probed under both a stock system tclsh 9.0.3 (twapi 5.2.0) and a fetched plain family runtime tclsh9.0.5-punk (vendored 5.0b1): twapi_resource plus begin_resource_update / update_resource / extract_resources present in both. This is why the twapi arm is permanent rather than transitional - deleting it when a portable tool lands would silently break that user.
- Standing tension: that path is load-bearing on a COMMITTED BINARY, which is what G-004 wants gone. The route that retires it without a toolchain is a punkbin-served stamping tool (G-128 + G-123/G-006) - the same fetch the persona already performs for runtimes.
- Related: G-128 - the portable post-hoc implementation this goal's seam is built for.
- Related: G-127 - once one vfs definition bakes for several targets, "which kits get an icon" becomes a target question; that is what prompted the 2026-07-26 decision recorded in the Acceptance that the override is shared across a kit definition's targets.
- The sidecar is the natural reconciliation point for the three provenance surfaces this file already flags (G-025's kit stamp, G-117's embedded artifact record, the parked RT_VERSION follow-on) - one text record per kit, on every platform.
- G-121 (archived) parameterized the wrap steps this goal's acceptance runs through: 'make.tcl bake ' now rebuilds a single kit for cheap icon-override verification, and a per-kit icon override would slot into bakelist's filtered per-kit detail - see goals/archive/G-121-bakelist-selective-bake.md