14 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.
- Related: G-135 (achieved 2026-07-28, archived - see goals/archive/G-135-asset-provenance-records.md) - the source-tree provenance records this goal's sidecar can read. The sidecar is emitted per kit at bake time and describes what got embedded; G-135's per-asset assetorigin sidecars are committed and describe a derivation. Populating the sidecar's "source .ico identity and provenance" from those records closes the gap where this goal's provenance otherwise stops at the .ico bytes, one tier short of the SVG master they were generated from.
- G-135 (archived) delivered concrete inputs for this goal: the record format (src/assets/logo/AGENTS.md "Provenance records"), the checker (scriptlib/developer/assetorigin_check.tcl), and a verified derived-copy sidecar beside this goal's default icon at src/runtime/punkshell.ico (hash equals source_hash, source ../assets/logo/punk-mark.ico). The kit-shipping call its notes carried is now made (user-approved 2026-07-28): src/vfs/_vfscommon.vfs/punkshell.ico carries a sidecar that ships inside every kit merging the common payload from that kit's next bake - inside a kit it truthfully reads source-absent (bytes hash-confirmed, source not shipped). The per-kit mkzipfix.vfs / punk8_statictwapi.vfs copies stay unrecorded.
- 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