Compare commits

...

2 Commits

Author SHA1 Message Date
Julian Noble 25b3dcc667 make.tcl-generated outputs: layout sync copies of the tool subcommand 3 days ago
Julian Noble 91d3594570 punkcheck 0.6.1: installtrack self-heals damaged persisted event history 3 days ago
  1. 4
      src/bootsupport/modules/oolib-0.1.4.tm
  2. BIN
      src/bootsupport/modules/punk/mix/templates-0.2.0.tm
  3. 31
      src/bootsupport/modules/punkcheck-0.6.1.tm
  4. 2
      src/modules/oolib-999999.0a1.0.tm
  5. 5
      src/modules/oolib-buildversion.txt
  6. 411
      src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl
  7. 23
      src/modules/punkcheck-999999.0a1.0.tm
  8. 3
      src/modules/punkcheck-buildversion.txt
  9. 411
      src/project_layouts/vendor/punk/basic/src/make.tcl
  10. 411
      src/project_layouts/vendor/punk/project-0.1/src/make.tcl
  11. 50
      src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test

4
src/bootsupport/modules/oolib-0.1.3.tm → src/bootsupport/modules/oolib-0.1.4.tm

@ -136,7 +136,7 @@ namespace eval oolib {
error "[self object] collection key must not be an integer. Use another structure if integer keys required"
}
if {[dict exists $o_data $key]} {
error "[self object] col_processors object error: key '$key' already exists in collection"
error "[self object] collection error: key '$key' already exists in collection"
}
dict set o_data $key $value
return [expr {[dict size $o_data] - 1}] ;#return index of item
@ -196,5 +196,5 @@ namespace eval oolib {
package provide oolib [namespace eval oolib {
variable version
set version 0.1.3
set version 0.1.4
}]

BIN
src/bootsupport/modules/punk/mix/templates-0.2.0.tm

Binary file not shown.

31
src/bootsupport/modules/punkcheck-0.6.0.tm → src/bootsupport/modules/punkcheck-0.6.1.tm

@ -910,14 +910,31 @@ namespace eval punkcheck {
#(the record keys are -ts_begin/-ts_end - passing the raw record gave the constructor's
# -tsbegin/-tsend defaults, so reconstructed events took 'now' as begin and empty end,
# and any later flush/save_installer_record rewrote prior event history with those values)
set eid [punkcheck::dict_getwithdefault $e -id ""]
#self-heal damaged persisted history rather than abort the caller's operation:
#files written by punkcheck < 0.3.2 carry EVENT records whose -id (and
#-source/-targets/-config) hold the LITERAL strings of the old writer's braced
#template ('$eventid' etc - the 0.3.2 buildversion note fixed the writer, but
#files keep the damage) and any two such records collide on the duplicate key
#here. History is advisory - reconstruct the record under a synthesized unique
#id and warn naming the file, instead of failing e.g a whole bake's vfslibs
#phase. Synthesized ids match no INSTALL record's -eventid reference, so
#healed events age out through normal -keep_events pruning.
if {$eid eq "" || $eid in [my events keys]} {
set healed_id "damaged-id-[my events count]-[clock microseconds]"
set shown [string range $eid 0 60]
set why [expr {$eid eq "" ? "missing" : "duplicates an earlier event"}]
puts stderr "punkcheck: WARNING damaged event history in '$o_checkfile': event -id '$shown' $why - keeping record under synthesized id '$healed_id' (safe to ignore; delete the file to reset history)"
set eid $healed_id
}
set eargs [list]
lappend eargs -id [punkcheck::dict_getwithdefault $e -id ""]
lappend eargs -id $eid
lappend eargs -tsbegin [punkcheck::dict_getwithdefault $e -ts_begin ""]
lappend eargs -tsend [punkcheck::dict_getwithdefault $e -ts_end ""]
lappend eargs -types [punkcheck::dict_getwithdefault $e -types {}]
lappend eargs -config [punkcheck::dict_getwithdefault $e -config {}]
set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [dict get $e -source] [dict get $e -targets] {*}$eargs]
$o_events add $eobj [dict get $e -id]
set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [punkcheck::dict_getwithdefault $e -source ""] [punkcheck::dict_getwithdefault $e -targets ""] {*}$eargs]
$o_events add $eobj $eid
}
}
@ -1426,7 +1443,7 @@ namespace eval punkcheck {
#The -changed flag is computed by comparing -value against the matching virtual SOURCE in the last completed
#install record (no filesystem access) - so virtual sources participate in targetset_source_changes like file sources.
#Typical use: recording the resolved build version of a module target, so records identify which product of an
#unchanging source fileset (e.g <module>-0.6.0.tm + <module>-buildversion.txt) the target represents.
#unchanging source fileset (e.g <module>-0.6.1.tm + <module>-buildversion.txt) the target represents.
proc installsource_add_virtual {file_record id value} {
if {![lib::is_file_record_inprogress $file_record]} {
error "installsource_add_virtual error: bad file_record - expected FILEINFO with last body element *-INPROGRESS ($file_record)"
@ -2069,11 +2086,11 @@ namespace eval punkcheck {
#FILEINFO -targets jjjetc-0.1.0.tm -keep_installrecords 2 -keep_skipped 1 -keep_inprogress 2 {
# INSTALL-RECORD -tsiso 2023-09-20T07:30:30 -ts 1695159030266610 -installer punk::mix::cli::build_modules_from_source_to_base -metadata_us 18426 -ts_start_transfer 1695159030285036 -transfer_us 10194 -elapsed_us 28620 {
# SOURCE -type file -path ../src/modules/jjjetc-buildversion.txt -cksum c7c71839c36b3d21c8370fed106192fcd659eca9 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 3423
# SOURCE -type file -path ../src/modules/jjjetc-0.6.0.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 3413
# SOURCE -type file -path ../src/modules/jjjetc-0.6.1.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 3413
# }
# INSTALL-SKIPPED -tsiso 2023-09-20T08:14:26 -ts 1695161666087880 -installer punk::mix::cli::build_modules_from_source_to_base -elapsed_us 18914 {
# SOURCE -type file -path ../src/modules/jjjetc-buildversion.txt -cksum c7c71839c36b3d21c8370fed106192fcd659eca9 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 3435
# SOURCE -type file -path ../src/modules/jjjetc-0.6.0.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 3338
# SOURCE -type file -path ../src/modules/jjjetc-0.6.1.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 3338
# }
#}
@ -2778,6 +2795,6 @@ namespace eval ::punk::args::register {
package provide punkcheck [namespace eval punkcheck {
set pkg punkcheck
variable version
set version 0.6.0
set version 0.6.1
}]
return

2
src/modules/oolib-999999.0a1.0.tm

@ -136,7 +136,7 @@ namespace eval oolib {
error "[self object] collection key must not be an integer. Use another structure if integer keys required"
}
if {[dict exists $o_data $key]} {
error "[self object] col_processors object error: key '$key' already exists in collection"
error "[self object] collection error: key '$key' already exists in collection"
}
dict set o_data $key $value
return [expr {[dict size $o_data] - 1}] ;#return index of item

5
src/modules/oolib-buildversion.txt

@ -1,3 +1,4 @@
0.1.3
0.1.4
#First line must be a tm version number
#all other lines are ignored.
#all other lines are ignored.
#0.1.4 - collection add duplicate-key error message no longer misnames the object as 'col_processors' (stray copy-paste in the generic collection class)

411
src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl vendored

@ -187,7 +187,7 @@ namespace eval ::punkboot {
variable foldername [file tail $scriptfolder]
variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list]
variable help_flags [list -help --help /? -h]
variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion workflow buildsuite project vfs]
variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion workflow buildsuite tool project vfs]
}
@ -2152,6 +2152,11 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " $scriptname buildsuite list|info|build ?<suitename>? ?driver-args ...?" \n
append h " - list the defined buildsuites under src/buildsuites (zig runtime factory), show a suite's configured" \n
append h " detail (sources/zig pin/products from its sources.config), or run its driver forwarding the args" \n \n
append h " $scriptname tool list|info|build|test ?<toolname> ...? ?-test 0|1?" \n
append h " - vendored first-party build tools under src/tools (zig source; G-126): list/detail them, run their" \n
append h " test suites, or build (test-gated) and install to <projectdir>/bin" \n
append h " - zig is OPTIONAL: packages/bake never require this step; without a suitable toolchain the state is" \n
append h " reported and build/test exit nonzero with fetch guidance (bin/punk-getzig.cmd)" \n \n
append h " $scriptname help ?subcommand?" \n
append h " - show usage for $scriptname or one of its subcommands (equivalent: $scriptname <subcommand> -help)" \n \n
append h " Deprecated aliases (transition window):" \n
@ -2402,6 +2407,14 @@ KEY / NOTES
with binary dependencies, and version-preference outcomes are visible only to
the smoke probe - and only for declared packages. 'make.tcl check' reports both.
OUT-OF-BAND SUBSYSTEMS (not part of the packages/bake data flow above)
'make.tcl tool' builds the vendored first-party zig tools under src/tools
(G-126; punkzip first) into <projectdir>/bin, gated on each tree's own
'zig build test' exit code. zig is OPTIONAL: packages/bake never require it.
'make.tcl buildsuite' is the analogous arm's-length factory for whole Tcl
runtimes built from EXTERNAL sources under src/buildsuites (fetch + test-gate
contract per suite; products land in the bin/runtime store tiers).
MAINTENANCE (agents take note)
----------------------------------------------------------------------------
This text is embedded in make.tcl (::punkboot::workflow_text). When the build
@ -2514,6 +2527,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
shell "Run the punk shell using bootsupport libraries"
help "Show usage for make.tcl or one of its subcommands"
buildsuite "List, describe or run the defined buildsuites under src/buildsuites (zig runtime factory)"
tool "List, test or build the vendored tools under src/tools (zig) and install them to <projectdir>/bin"
project "DEPRECATED alias for bakehouse (one-line notice, then runs bakehouse)"
vfs "DEPRECATED alias for bake (one-line notice, then runs bake)"
}
@ -2683,6 +2697,45 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
(copy-and-tweak workflow) appears here automatically with no
make.tcl edits. The contract is documented in
src/buildsuites/README.md."
tool
"
Surface for the vendored first-party build tools under src/tools
(G-126: zig source vendored in full; boundary and editing policy in
src/tools/AGENTS.md, per-tree origin/licence records in
<tool>/PROVENANCE.md). Discovery is convention-based: a directory
under src/tools carrying build.zig is a tool; its installed
artifact is <projectdir>/bin/<name> with the host executable
suffix.
Actions:
tool list
Discover the tools with version (build.zig.zon), declared zig
floor, installed-binary state (current|stale|absent) and the
vendored upstream commit; report the resolved toolchain.
tool info <name> ...
Per-tool detail: paths, provenance records, toolchain
resolution.
tool build ?<name> ...? ?-test 0|1?
Resolve a zig satisfying the tool's declared floor, run the
'zig build test' gate (default on; the gate is the EXIT CODE -
expected warnings on stderr do not fail it), build ReleaseSafe
and install to <projectdir>/bin. Nothing is installed when the
gate or the build fails. With no name, all tools are
processed.
tool test ?<name> ...?
Run each tool's 'zig build test' only.
Toolchain resolution: PUNK_ZIG=<path-to-zig-or-its-folder>
overrides; otherwise <projectdir>/bin/tools/zig* is scanned and
the LOWEST release satisfying the floor wins (deterministic as
newer toolchains appear beside the pinned one; dev builds count
only when their base version exceeds the floor). zig is OPTIONAL
for punkshell builds: packages/bake never require this step -
without a suitable toolchain, list reports the state and
build/test exit nonzero with fetch guidance
(bin/punk-getzig.cmd). Build caches (.zig-cache/zig-out) are
deliberately left in place between runs for rebuild speed (both
git- and fossil-ignored)."
project
"
DEPRECATED alias for 'bakehouse' (G-112 stage-true rename) - prints a
@ -2855,6 +2908,15 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
arg -type any -optional 1 -multiple 1 -help\
"Action (list|info|build), suite name, and - for build - driver
arguments forwarded to the suite's suite.tcl untouched."}
punk::args::define\
"@id -id (script)::punkboot::tool"\
"@normalize"\
"@cmd -name [list "make.tcl tool"] -summary [list [dict get $SUMMARIES tool]] -help [list [dict get $HELPTEXTS tool]]"\
{@form -synopsis "make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"}\
{@values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help\
"Action (list|info|build|test), tool name(s), and - for build -
the -test 0|1 gate flag. Parsed by the handler."}
punk::args::define\
"@id -id (script)::punkboot::help"\
"@normalize"\
@ -2871,6 +2933,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
"promotion gates" {bootsupport vfscommonupdate}
"source maintenance" {vendorupdate}
"buildsuites" {buildsuite}
"vendored tools" {tool}
"informational" {info check bakelist projectversion workflow help}
"interactive" {shell}
"deprecated aliases" {project vfs}
@ -2963,6 +3026,10 @@ if {$::punkboot::punkargs_ok} {
#args (arbitrary suite flags) are forwarded untouched (G-104)
set ::punkboot::command buildsuite
set ::punkboot::bs_args $subargs
} elseif {$subcommand eq "tool"} {
#declared passthrough: the handler parses action/names/-test itself (G-126)
set ::punkboot::command tool
set ::punkboot::tool_args $subargs
} else {
if {[catch {punk::args::parse $subargs -errorstyle $::punkboot::errstyle withid (script)::punkboot::$subcommand} argd]} {
puts stderr $argd
@ -2993,6 +3060,12 @@ if {$::punkboot::punkargs_ok} {
set commands_found [list buildsuite]
set argsleft [list]
set ::punkboot::bs_args [lrange $scriptargs 1 end]
} elseif {[lindex $scriptargs 0] eq "tool"} {
#declared passthrough (as in the punk::args path) - the handler parses
#action/names/-test itself (G-126)
set commands_found [list tool]
set argsleft [list]
set ::punkboot::tool_args [lrange $scriptargs 1 end]
}
while {[llength $argsleft]} {
set a [lindex $argsleft 0]
@ -3116,7 +3189,8 @@ set forcekill $::punkboot::opt_forcekill
# - patch bump -> warn and proceed (non-breaking)
# 'check' is exempt in all cases the warning is shown at the end of its output instead.
# 'projectversion', 'workflow' and 'bakelist' are informational/read-only and are likewise exempt.
if {[info exists ::punkboot::stale_bootsupport] && $::punkboot::command ni {check projectversion workflow bakelist buildsuite}} {
# 'buildsuite' and 'tool' are arm's-length subsystems that do not consume bootsupport modules.
if {[info exists ::punkboot::stale_bootsupport] && $::punkboot::command ni {check projectversion workflow bakelist buildsuite tool}} {
set _stale $::punkboot::stale_bootsupport
set _have_major 0
set _have_minor 0
@ -4242,6 +4316,339 @@ if {$::punkboot::command eq "buildsuite"} {
}
}
if {$::punkboot::command eq "tool"} {
#G-126: vendored first-party build tools under src/tools (zig source vendored in
#full - boundary and editing policy in src/tools/AGENTS.md; per-tree origin,
#re-vendor procedure and licence records in <tool>/PROVENANCE.md). Discovery is
#convention-based: a directory under src/tools carrying build.zig is a tool, its
#name is the directory name, its build artifact is <tooldir>/zig-out/bin/<name>
#and its installed copy is <projectdir>/bin/<name> (host executable suffix).
#zig stays OPTIONAL for punkshell builds: nothing in packages/bake depends on
#this step; explicit build/test without a suitable toolchain exits nonzero with
#fetch guidance. Build caches (.zig-cache/zig-out) are deliberately left in
#place between builds for rebuild speed (both git- and fossil-ignored).
set tool_root [file join $sourcefolder tools]
if {![info exists ::punkboot::tool_args]} {set ::punkboot::tool_args [list]}
set targs $::punkboot::tool_args
set tool_action [lindex $targs 0]
if {$tool_action eq ""} {set tool_action list}
#args after the action: tool names plus an optional '-test 0|1' flag (build)
set tool_dotest 1
set tool_names [list]
set _i 1
while {$_i < [llength $targs]} {
set a [lindex $targs $_i]
if {$a eq "-test"} {
incr _i
set v [lindex $targs $_i]
if {![string is boolean -strict $v]} {
puts stderr "make.tcl tool: -test requires a boolean value (got '$v')"
exit 2
}
set tool_dotest [expr {bool($v)}]
} else {
lappend tool_names $a
}
incr _i
}
unset -nocomplain _i a v
proc ::punkboot::tool_dirs {tool_root} {
set dirs [list]
foreach dir [lsort [glob -nocomplain -dir $tool_root -type d *]] {
if {![file exists [file join $dir build.zig]]} {continue} ;#not a tool
lappend dirs $dir
}
return $dirs
}
#tolerant build.zig.zon reader: tool version + declared minimum zig version
proc ::punkboot::tool_zoninfo {tooldir} {
set m [dict create version "" zigfloor ""]
if {![catch {
set f [open [file join $tooldir build.zig.zon] r]
set data [read $f]
close $f
}]} {
if {[regexp {\.version\s*=\s*"([^"]+)"} $data -> _v]} {dict set m version $_v}
if {[regexp {\.minimum_zig_version\s*=\s*"([^"]+)"} $data -> _fl]} {dict set m zigfloor $_fl}
}
return $m
}
#tolerant PROVENANCE.md reader: vendored upstream commit + upstream location
proc ::punkboot::tool_provenance {tooldir} {
set m [dict create commit "" upstream ""]
if {![catch {
set f [open [file join $tooldir PROVENANCE.md] r]
set data [read $f]
close $f
}]} {
if {[regexp {Vendored state: commit\s+`?([0-9a-fA-F]{7,40})} $data -> _c]} {dict set m commit $_c}
if {[regexp {Upstream repository:\s+`?([^`\n]+)} $data -> _u]} {dict set m upstream [string trim $_u]}
}
return $m
}
proc ::punkboot::tool_newest_src_mtime {dir} {
set newest 0
foreach f [glob -nocomplain -dir $dir -type f *] {
if {[file mtime $f] > $newest} {set newest [file mtime $f]}
}
foreach d [glob -nocomplain -dir $dir -type d *] {
if {[file tail $d] in {zig-out .zig-cache zig-cache}} {continue}
set sub [::punkboot::tool_newest_src_mtime $d]
if {$sub > $newest} {set newest $sub}
}
return $newest
}
proc ::punkboot::tool_zig_version {zigexe} {
if {[catch {exec $zigexe version 2>@1} v]} {return ""}
return [string trim [lindex [split $v \n] 0]]
}
#a dev build's base version must EXCEED the floor (e.g 0.16.0-dev < 0.16.0)
proc ::punkboot::tool_zig_satisfies {ver floor} {
if {$floor eq ""} {return 1}
set base $ver ; set isdev 0
if {[regexp {^([0-9][0-9.]*)[-+]} $ver -> b]} {set base $b ; set isdev 1}
if {[catch {package vcompare $base $floor} cmp]} {return 0}
if {$cmp > 0} {return 1}
if {$cmp == 0 && !$isdev} {return 1}
return 0
}
#resolve a zig for the given floor: PUNK_ZIG env override (path to the zig
#executable or its folder), else scan <projectdir>/bin/tools/zig* preferring
#releases over dev builds and the LOWEST satisfying version - a deterministic
#choice that stays stable as newer toolchains get unpacked beside the pinned one
proc ::punkboot::tool_resolve_zig {projectroot floor} {
if {[info exists ::env(PUNK_ZIG)] && $::env(PUNK_ZIG) ne ""} {
set cand $::env(PUNK_ZIG)
if {[file isdirectory $cand]} {
foreach n {zig.exe zig} {
if {[file exists [file join $cand $n]]} {set cand [file join $cand $n] ; break}
}
}
set v [::punkboot::tool_zig_version $cand]
if {$v eq ""} {
return [dict create note "PUNK_ZIG='$::env(PUNK_ZIG)' did not answer 'zig version'"]
}
if {![::punkboot::tool_zig_satisfies $v $floor]} {
return [dict create note "PUNK_ZIG zig is $v which does not satisfy this tool's floor (>= $floor)"]
}
return [dict create zig $cand version $v via PUNK_ZIG]
}
set best "" ; set bestv "" ; set bestbase "" ; set bestdev 1
foreach dir [lsort [glob -nocomplain -dir [file join $projectroot bin tools] -type d zig*]] {
set p ""
foreach n {zig.exe zig} {
if {[file exists [file join $dir $n]]} {set p [file join $dir $n] ; break}
}
if {$p eq ""} {continue}
set v [::punkboot::tool_zig_version $p]
if {$v eq "" || ![::punkboot::tool_zig_satisfies $v $floor]} {continue}
set base $v ; set isdev 0
if {[regexp {^([0-9][0-9.]*)[-+]} $v -> b]} {set base $b ; set isdev 1}
set take 0
if {$best eq ""} {
set take 1
} elseif {$bestdev && !$isdev} {
set take 1
} elseif {$bestdev == $isdev && [package vcompare $base $bestbase] < 0} {
set take 1
}
if {$take} {set best $p ; set bestv $v ; set bestbase $base ; set bestdev $isdev}
}
if {$best eq ""} {
set want [expr {$floor eq "" ? "any version" : ">= $floor"}]
return [dict create note "no suitable zig toolchain ($want) under [file join $projectroot bin tools] - 'bin/punk-getzig.cmd <version>' fetches one; PUNK_ZIG=<path-to-zig> overrides"]
}
return [dict create zig $best version $bestv via scan]
}
proc ::punkboot::tool_installed_exe {projectroot name} {
set suffix [punkboot::lib::platform_exe_suffix $::punkboot::target_platform]
return [file join $projectroot bin ${name}$suffix]
}
proc ::punkboot::tool_install_state {projectroot tooldir} {
set exe [::punkboot::tool_installed_exe $projectroot [file tail $tooldir]]
if {![file exists $exe]} {return absent}
if {[file mtime $exe] >= [::punkboot::tool_newest_src_mtime $tooldir]} {return current}
return stale
}
#run 'zig <argl>' with cwd at the tool tree (the tree's CLI tests resolve
#fixtures and zig-out relative to the build root). The gate is the EXIT CODE:
#stderr legitimately carries expected warnings (punkzip's zip suite negative
#tests print EOCD warnings while passing). Returns the child's exit status.
proc ::punkboot::tool_zig_run {zig tooldir argl} {
set saved [pwd]
cd $tooldir
set rc 0
if {[catch {exec $zig {*}$argl >@stdout 2>@stderr} _e _o]} {
set ecode [dict get $_o -errorcode]
if {[lindex $ecode 0] eq "CHILDSTATUS"} {
set rc [lindex $ecode 2]
} else {
puts stderr "tool: failed to run '$zig $argl' in $tooldir: $_e"
set rc 1
}
}
cd $saved
return $rc
}
set alldirs [::punkboot::tool_dirs $tool_root]
set byname [dict create]
foreach d $alldirs {dict set byname [file tail $d] $d}
if {[llength $tool_names]} {
set selected [list]
foreach n $tool_names {
if {![dict exists $byname $n]} {
puts stderr "tool '$n' not found under $tool_root - configured tools: [dict keys $byname]"
exit 2
}
lappend selected [dict get $byname $n]
}
} else {
set selected $alldirs
}
if {![llength $alldirs] && $tool_action ne "list"} {
puts stderr "no vendored tools found under $tool_root (a tool = a directory carrying build.zig)"
exit 2
}
switch -- $tool_action {
list {
if {![llength $alldirs]} {
puts stdout "no vendored tools found under $tool_root (a tool = a directory carrying build.zig)"
exit 0
}
puts stdout "vendored tools under $tool_root (boundary: src/tools/AGENTS.md; per-tree PROVENANCE.md):"
puts stdout [format " %-10s %-9s %-10s %-8s %s" name version zig-floor bin vendored-commit]
set maxfloor ""
foreach dir $alldirs {
set name [file tail $dir]
set zon [::punkboot::tool_zoninfo $dir]
set prov [::punkboot::tool_provenance $dir]
set floor [dict get $zon zigfloor]
if {$floor ne "" && ($maxfloor eq "" || [package vcompare $floor $maxfloor] > 0)} {
set maxfloor $floor
}
set commit [dict get $prov commit]
set commit [expr {$commit eq "" ? "-" : [string range $commit 0 11]}]
set floorshow [expr {$floor eq "" ? "-" : ">=$floor"}]
puts stdout [format " %-10s %-9s %-10s %-8s %s" $name [dict get $zon version] $floorshow [::punkboot::tool_install_state $projectroot $dir] $commit]
}
set zres [::punkboot::tool_resolve_zig $projectroot $maxfloor]
if {[dict exists $zres zig]} {
puts stdout "toolchain: zig [dict get $zres version] via [dict get $zres via] ([dict get $zres zig])"
} else {
puts stdout "toolchain: [dict get $zres note]"
}
puts stdout ""
puts stdout " - 'make.tcl tool info <name>' shows a tool's detail"
puts stdout " - 'make.tcl tool build ?<name> ...? ?-test 0|1?' builds (test-gated) and installs to bin/"
puts stdout " - zig is OPTIONAL for punkshell builds: packages/bake never require this step"
exit 0
}
info {
if {![llength $tool_names]} {
puts stderr "usage: make.tcl tool info <toolname> ('make.tcl tool list' shows the names)"
exit 2
}
foreach dir $selected {
set name [file tail $dir]
set zon [::punkboot::tool_zoninfo $dir]
set prov [::punkboot::tool_provenance $dir]
set floor [dict get $zon zigfloor]
puts stdout "tool: $name"
puts stdout " path: $dir"
puts stdout " version: [dict get $zon version] (build.zig.zon)"
puts stdout " zig floor: [expr {$floor eq "" ? "(none declared)" : ">= $floor"}]"
puts stdout " upstream: [expr {[dict get $prov upstream] eq "" ? "(no PROVENANCE.md record)" : [dict get $prov upstream]}]"
puts stdout " vendored commit: [expr {[dict get $prov commit] eq "" ? "(no PROVENANCE.md record)" : [dict get $prov commit]}]"
puts stdout " provenance: [file join $dir PROVENANCE.md]"
set exe [::punkboot::tool_installed_exe $projectroot $name]
puts stdout " installed: $exe ([::punkboot::tool_install_state $projectroot $dir])"
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {[dict exists $zres zig]} {
puts stdout " toolchain: zig [dict get $zres version] via [dict get $zres via] ([dict get $zres zig])"
} else {
puts stdout " toolchain: [dict get $zres note]"
}
}
exit 0
}
build {
foreach dir $selected {
set name [file tail $dir]
set floor [dict get [::punkboot::tool_zoninfo $dir] zigfloor]
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {![dict exists $zres zig]} {
puts stderr "tool build $name: [dict get $zres note]"
exit 1
}
set zig [dict get $zres zig]
puts stdout "tool build $name: zig [dict get $zres version] via [dict get $zres via] ($zig)"
flush stdout
if {$tool_dotest} {
puts stdout "tool build $name: test gate ('zig build test') ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build test]]
if {$rc != 0} {
puts stderr "tool build $name: test gate FAILED (exit $rc) - nothing installed"
exit 1
}
puts stdout "tool build $name: test gate passed"
} else {
puts stdout "tool build $name: test gate SKIPPED (-test 0)"
}
puts stdout "tool build $name: building (ReleaseSafe) ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build -Doptimize=ReleaseSafe]]
if {$rc != 0} {
puts stderr "tool build $name: build FAILED (exit $rc) - nothing installed"
exit 1
}
set suffix [punkboot::lib::platform_exe_suffix $::punkboot::target_platform]
set built [file join $dir zig-out bin ${name}$suffix]
if {![file exists $built]} {
puts stderr "tool build $name: build reported success but $built is missing"
exit 1
}
set exe [::punkboot::tool_installed_exe $projectroot $name]
if {[catch {file copy -force $built $exe} cperr]} {
puts stderr "tool build $name: could not install $exe ($cperr) - is a copy currently running?"
exit 1
}
puts stdout "tool build $name: installed $exe ([file size $exe] bytes)"
}
exit 0
}
test {
foreach dir $selected {
set name [file tail $dir]
set floor [dict get [::punkboot::tool_zoninfo $dir] zigfloor]
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {![dict exists $zres zig]} {
puts stderr "tool test $name: [dict get $zres note]"
exit 1
}
set zig [dict get $zres zig]
puts stdout "tool test $name: zig [dict get $zres version] via [dict get $zres via] - running 'zig build test' ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build test]]
if {$rc != 0} {
puts stderr "tool test $name: FAILED (exit $rc)"
exit 1
}
puts stdout "tool test $name: passed"
}
exit 0
}
default {
puts stderr "unknown tool action '$tool_action' - expected list|info|build|test"
puts stderr "usage: make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"
exit 2
}
}
}
if {$::punkboot::command eq "shell"} {
#G-113: the ansistrip transform serves make.tcl's own output only - the repl's
#colour behaviour is the shell's own concern (colour on/off + NO_COLOR there).

23
src/modules/punkcheck-999999.0a1.0.tm

@ -910,14 +910,31 @@ namespace eval punkcheck {
#(the record keys are -ts_begin/-ts_end - passing the raw record gave the constructor's
# -tsbegin/-tsend defaults, so reconstructed events took 'now' as begin and empty end,
# and any later flush/save_installer_record rewrote prior event history with those values)
set eid [punkcheck::dict_getwithdefault $e -id ""]
#self-heal damaged persisted history rather than abort the caller's operation:
#files written by punkcheck < 0.3.2 carry EVENT records whose -id (and
#-source/-targets/-config) hold the LITERAL strings of the old writer's braced
#template ('$eventid' etc - the 0.3.2 buildversion note fixed the writer, but
#files keep the damage) and any two such records collide on the duplicate key
#here. History is advisory - reconstruct the record under a synthesized unique
#id and warn naming the file, instead of failing e.g a whole bake's vfslibs
#phase. Synthesized ids match no INSTALL record's -eventid reference, so
#healed events age out through normal -keep_events pruning.
if {$eid eq "" || $eid in [my events keys]} {
set healed_id "damaged-id-[my events count]-[clock microseconds]"
set shown [string range $eid 0 60]
set why [expr {$eid eq "" ? "missing" : "duplicates an earlier event"}]
puts stderr "punkcheck: WARNING damaged event history in '$o_checkfile': event -id '$shown' $why - keeping record under synthesized id '$healed_id' (safe to ignore; delete the file to reset history)"
set eid $healed_id
}
set eargs [list]
lappend eargs -id [punkcheck::dict_getwithdefault $e -id ""]
lappend eargs -id $eid
lappend eargs -tsbegin [punkcheck::dict_getwithdefault $e -ts_begin ""]
lappend eargs -tsend [punkcheck::dict_getwithdefault $e -ts_end ""]
lappend eargs -types [punkcheck::dict_getwithdefault $e -types {}]
lappend eargs -config [punkcheck::dict_getwithdefault $e -config {}]
set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [dict get $e -source] [dict get $e -targets] {*}$eargs]
$o_events add $eobj [dict get $e -id]
set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [punkcheck::dict_getwithdefault $e -source ""] [punkcheck::dict_getwithdefault $e -targets ""] {*}$eargs]
$o_events add $eobj $eid
}
}

3
src/modules/punkcheck-buildversion.txt

@ -1,6 +1,7 @@
0.6.0
0.6.1
#First line must be a semantic version number
#all other lines are ignored.
#0.6.1 - installtrack constructor self-heals damaged persisted event history instead of aborting the caller: EVENT records with a missing or duplicate -id (notably the literal '$eventid' records written by punkcheck < 0.3.2 - the 0.3.2 note fixed the writer but existing files keep the damage, and the modern keyed event reload collided on them, killing e.g a whole bake's vfslibs phase; first field hit 2026-07-27 on a machine whose src/vfs/.punkcheck predated 0.3.2) are reconstructed under synthesized unique ids ('damaged-id-...') with a stderr warning naming the file. Synthesized ids match no INSTALL record's -eventid reference, so healed events age out through normal -keep_events pruning. Event reload also tolerates missing -source/-targets keys. Regression test: src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test (installtrack_damaged_event_history_selfheal).
#0.6.0 - G-095 concurrent-writer safety: saves are write-temp-then-atomic-rename (bounded retry for Windows sharing violations) so readers never see torn content, and load_records_from_file retries transient open denials during a writer's replace window (parse errors stay strict). New punkcheck::lock namespace: advisory sibling lockfile <punkcheckfile>.lock (open WRONLY CREAT EXCL, holder pid/host/installer/timestamp contents, channel held open, in-process refcounted re-acquisition) serializes whole installer events - acquired at start_event, released at event end / installtrack destroy; flushes and chokepoint saves outside an event take a transient lock; contention retries with backoff to -timeout (env PUNKCHECK_LOCK_TIMEOUT) then errors naming the holder (errorcode PUNKCHECK LOCK TIMEOUT); stale locks break on provably-dead holder pid (twapi/kill -0, capability-gated) or age (-staleage / PUNKCHECK_LOCK_STALEAGE). Deferred flushes merge own records (INSTALLER by -name, touched FILEINFO by -targets) into freshly-loaded file state instead of wholesale overwrite; an mtime/size tripwire warns when a non-protocol writer touched the file between load and save. Duplicate-INSTALLER recovery degrades to a clean actionable error when stdin is non-interactive (new punkcheck::lib::stdin_is_interactive; twapi GetConsoleMode on the real stdin handle / unix -inputmode / test -t 0), and the interactive prompt gains the synced-targets deletion-wedge caveat. default_excludefiletail_core adds .punkcheck.* so lock/temp siblings are never treated as installable content.
#0.5.0 - G-094 single record lifecycle: installtrack/installevent OO layer is the sole implementation (targetset init/add-source/started/end) with a per-event persistence policy (start_event ?-persistence eager|deferred?; deferred = in-memory working recordset + $installer flush, the mode punkcheck::install now uses as a tree-walking OO consumer). Legacy proc pipeline (start_installer_event, installfile_begin/started_install/finished_install/skipped_install) retired to error-with-pointer shims. All saves flow through one chokepoint (installtrack save_working_recordset - G-095 attachment point). Fixes: start_event refreshes the working recordset (sequential events no longer clobber prior targetset writes; end/end_event now clears the active event so sequential events work), installtrack as_record no longer writes -name with a trailing space, event reconstruction maps -ts_begin/-ts_end (flush no longer rewrites prior event history timestamps), get_recordlist misspelled variable, targetset_dict called a nonexistent helper. punkcheck::install records now carry -metadata_us/-ts_start_transfer/-transfer_us/-cksum_us like OO-installed records, and all-targets mode now stores -targets_cksums. recordlist::extract_or_create_fileset_record no longer prints to stdout (isnew returned; targetset_init reports via debugchannel). Dead targetset class removed.
#0.4.0 - cross-root sources: installfile_add_source_and_fetch_metadata now records sources that share no common root with the punkcheck folder (e.g //zipfs:/ module-mounted sources installing to the filesystem) as independent absolute paths via the cksum helpers' empty-base mode, instead of erroring 'don't share a common root'. Enables punkcheck::install from module-carried (vfs-mounted) layout payloads - G-087 stage 3 bare-kit project generation.

411
src/project_layouts/vendor/punk/basic/src/make.tcl vendored

@ -187,7 +187,7 @@ namespace eval ::punkboot {
variable foldername [file tail $scriptfolder]
variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list]
variable help_flags [list -help --help /? -h]
variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion workflow buildsuite project vfs]
variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion workflow buildsuite tool project vfs]
}
@ -2152,6 +2152,11 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " $scriptname buildsuite list|info|build ?<suitename>? ?driver-args ...?" \n
append h " - list the defined buildsuites under src/buildsuites (zig runtime factory), show a suite's configured" \n
append h " detail (sources/zig pin/products from its sources.config), or run its driver forwarding the args" \n \n
append h " $scriptname tool list|info|build|test ?<toolname> ...? ?-test 0|1?" \n
append h " - vendored first-party build tools under src/tools (zig source; G-126): list/detail them, run their" \n
append h " test suites, or build (test-gated) and install to <projectdir>/bin" \n
append h " - zig is OPTIONAL: packages/bake never require this step; without a suitable toolchain the state is" \n
append h " reported and build/test exit nonzero with fetch guidance (bin/punk-getzig.cmd)" \n \n
append h " $scriptname help ?subcommand?" \n
append h " - show usage for $scriptname or one of its subcommands (equivalent: $scriptname <subcommand> -help)" \n \n
append h " Deprecated aliases (transition window):" \n
@ -2402,6 +2407,14 @@ KEY / NOTES
with binary dependencies, and version-preference outcomes are visible only to
the smoke probe - and only for declared packages. 'make.tcl check' reports both.
OUT-OF-BAND SUBSYSTEMS (not part of the packages/bake data flow above)
'make.tcl tool' builds the vendored first-party zig tools under src/tools
(G-126; punkzip first) into <projectdir>/bin, gated on each tree's own
'zig build test' exit code. zig is OPTIONAL: packages/bake never require it.
'make.tcl buildsuite' is the analogous arm's-length factory for whole Tcl
runtimes built from EXTERNAL sources under src/buildsuites (fetch + test-gate
contract per suite; products land in the bin/runtime store tiers).
MAINTENANCE (agents take note)
----------------------------------------------------------------------------
This text is embedded in make.tcl (::punkboot::workflow_text). When the build
@ -2514,6 +2527,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
shell "Run the punk shell using bootsupport libraries"
help "Show usage for make.tcl or one of its subcommands"
buildsuite "List, describe or run the defined buildsuites under src/buildsuites (zig runtime factory)"
tool "List, test or build the vendored tools under src/tools (zig) and install them to <projectdir>/bin"
project "DEPRECATED alias for bakehouse (one-line notice, then runs bakehouse)"
vfs "DEPRECATED alias for bake (one-line notice, then runs bake)"
}
@ -2683,6 +2697,45 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
(copy-and-tweak workflow) appears here automatically with no
make.tcl edits. The contract is documented in
src/buildsuites/README.md."
tool
"
Surface for the vendored first-party build tools under src/tools
(G-126: zig source vendored in full; boundary and editing policy in
src/tools/AGENTS.md, per-tree origin/licence records in
<tool>/PROVENANCE.md). Discovery is convention-based: a directory
under src/tools carrying build.zig is a tool; its installed
artifact is <projectdir>/bin/<name> with the host executable
suffix.
Actions:
tool list
Discover the tools with version (build.zig.zon), declared zig
floor, installed-binary state (current|stale|absent) and the
vendored upstream commit; report the resolved toolchain.
tool info <name> ...
Per-tool detail: paths, provenance records, toolchain
resolution.
tool build ?<name> ...? ?-test 0|1?
Resolve a zig satisfying the tool's declared floor, run the
'zig build test' gate (default on; the gate is the EXIT CODE -
expected warnings on stderr do not fail it), build ReleaseSafe
and install to <projectdir>/bin. Nothing is installed when the
gate or the build fails. With no name, all tools are
processed.
tool test ?<name> ...?
Run each tool's 'zig build test' only.
Toolchain resolution: PUNK_ZIG=<path-to-zig-or-its-folder>
overrides; otherwise <projectdir>/bin/tools/zig* is scanned and
the LOWEST release satisfying the floor wins (deterministic as
newer toolchains appear beside the pinned one; dev builds count
only when their base version exceeds the floor). zig is OPTIONAL
for punkshell builds: packages/bake never require this step -
without a suitable toolchain, list reports the state and
build/test exit nonzero with fetch guidance
(bin/punk-getzig.cmd). Build caches (.zig-cache/zig-out) are
deliberately left in place between runs for rebuild speed (both
git- and fossil-ignored)."
project
"
DEPRECATED alias for 'bakehouse' (G-112 stage-true rename) - prints a
@ -2855,6 +2908,15 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
arg -type any -optional 1 -multiple 1 -help\
"Action (list|info|build), suite name, and - for build - driver
arguments forwarded to the suite's suite.tcl untouched."}
punk::args::define\
"@id -id (script)::punkboot::tool"\
"@normalize"\
"@cmd -name [list "make.tcl tool"] -summary [list [dict get $SUMMARIES tool]] -help [list [dict get $HELPTEXTS tool]]"\
{@form -synopsis "make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"}\
{@values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help\
"Action (list|info|build|test), tool name(s), and - for build -
the -test 0|1 gate flag. Parsed by the handler."}
punk::args::define\
"@id -id (script)::punkboot::help"\
"@normalize"\
@ -2871,6 +2933,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
"promotion gates" {bootsupport vfscommonupdate}
"source maintenance" {vendorupdate}
"buildsuites" {buildsuite}
"vendored tools" {tool}
"informational" {info check bakelist projectversion workflow help}
"interactive" {shell}
"deprecated aliases" {project vfs}
@ -2963,6 +3026,10 @@ if {$::punkboot::punkargs_ok} {
#args (arbitrary suite flags) are forwarded untouched (G-104)
set ::punkboot::command buildsuite
set ::punkboot::bs_args $subargs
} elseif {$subcommand eq "tool"} {
#declared passthrough: the handler parses action/names/-test itself (G-126)
set ::punkboot::command tool
set ::punkboot::tool_args $subargs
} else {
if {[catch {punk::args::parse $subargs -errorstyle $::punkboot::errstyle withid (script)::punkboot::$subcommand} argd]} {
puts stderr $argd
@ -2993,6 +3060,12 @@ if {$::punkboot::punkargs_ok} {
set commands_found [list buildsuite]
set argsleft [list]
set ::punkboot::bs_args [lrange $scriptargs 1 end]
} elseif {[lindex $scriptargs 0] eq "tool"} {
#declared passthrough (as in the punk::args path) - the handler parses
#action/names/-test itself (G-126)
set commands_found [list tool]
set argsleft [list]
set ::punkboot::tool_args [lrange $scriptargs 1 end]
}
while {[llength $argsleft]} {
set a [lindex $argsleft 0]
@ -3116,7 +3189,8 @@ set forcekill $::punkboot::opt_forcekill
# - patch bump -> warn and proceed (non-breaking)
# 'check' is exempt in all cases the warning is shown at the end of its output instead.
# 'projectversion', 'workflow' and 'bakelist' are informational/read-only and are likewise exempt.
if {[info exists ::punkboot::stale_bootsupport] && $::punkboot::command ni {check projectversion workflow bakelist buildsuite}} {
# 'buildsuite' and 'tool' are arm's-length subsystems that do not consume bootsupport modules.
if {[info exists ::punkboot::stale_bootsupport] && $::punkboot::command ni {check projectversion workflow bakelist buildsuite tool}} {
set _stale $::punkboot::stale_bootsupport
set _have_major 0
set _have_minor 0
@ -4242,6 +4316,339 @@ if {$::punkboot::command eq "buildsuite"} {
}
}
if {$::punkboot::command eq "tool"} {
#G-126: vendored first-party build tools under src/tools (zig source vendored in
#full - boundary and editing policy in src/tools/AGENTS.md; per-tree origin,
#re-vendor procedure and licence records in <tool>/PROVENANCE.md). Discovery is
#convention-based: a directory under src/tools carrying build.zig is a tool, its
#name is the directory name, its build artifact is <tooldir>/zig-out/bin/<name>
#and its installed copy is <projectdir>/bin/<name> (host executable suffix).
#zig stays OPTIONAL for punkshell builds: nothing in packages/bake depends on
#this step; explicit build/test without a suitable toolchain exits nonzero with
#fetch guidance. Build caches (.zig-cache/zig-out) are deliberately left in
#place between builds for rebuild speed (both git- and fossil-ignored).
set tool_root [file join $sourcefolder tools]
if {![info exists ::punkboot::tool_args]} {set ::punkboot::tool_args [list]}
set targs $::punkboot::tool_args
set tool_action [lindex $targs 0]
if {$tool_action eq ""} {set tool_action list}
#args after the action: tool names plus an optional '-test 0|1' flag (build)
set tool_dotest 1
set tool_names [list]
set _i 1
while {$_i < [llength $targs]} {
set a [lindex $targs $_i]
if {$a eq "-test"} {
incr _i
set v [lindex $targs $_i]
if {![string is boolean -strict $v]} {
puts stderr "make.tcl tool: -test requires a boolean value (got '$v')"
exit 2
}
set tool_dotest [expr {bool($v)}]
} else {
lappend tool_names $a
}
incr _i
}
unset -nocomplain _i a v
proc ::punkboot::tool_dirs {tool_root} {
set dirs [list]
foreach dir [lsort [glob -nocomplain -dir $tool_root -type d *]] {
if {![file exists [file join $dir build.zig]]} {continue} ;#not a tool
lappend dirs $dir
}
return $dirs
}
#tolerant build.zig.zon reader: tool version + declared minimum zig version
proc ::punkboot::tool_zoninfo {tooldir} {
set m [dict create version "" zigfloor ""]
if {![catch {
set f [open [file join $tooldir build.zig.zon] r]
set data [read $f]
close $f
}]} {
if {[regexp {\.version\s*=\s*"([^"]+)"} $data -> _v]} {dict set m version $_v}
if {[regexp {\.minimum_zig_version\s*=\s*"([^"]+)"} $data -> _fl]} {dict set m zigfloor $_fl}
}
return $m
}
#tolerant PROVENANCE.md reader: vendored upstream commit + upstream location
proc ::punkboot::tool_provenance {tooldir} {
set m [dict create commit "" upstream ""]
if {![catch {
set f [open [file join $tooldir PROVENANCE.md] r]
set data [read $f]
close $f
}]} {
if {[regexp {Vendored state: commit\s+`?([0-9a-fA-F]{7,40})} $data -> _c]} {dict set m commit $_c}
if {[regexp {Upstream repository:\s+`?([^`\n]+)} $data -> _u]} {dict set m upstream [string trim $_u]}
}
return $m
}
proc ::punkboot::tool_newest_src_mtime {dir} {
set newest 0
foreach f [glob -nocomplain -dir $dir -type f *] {
if {[file mtime $f] > $newest} {set newest [file mtime $f]}
}
foreach d [glob -nocomplain -dir $dir -type d *] {
if {[file tail $d] in {zig-out .zig-cache zig-cache}} {continue}
set sub [::punkboot::tool_newest_src_mtime $d]
if {$sub > $newest} {set newest $sub}
}
return $newest
}
proc ::punkboot::tool_zig_version {zigexe} {
if {[catch {exec $zigexe version 2>@1} v]} {return ""}
return [string trim [lindex [split $v \n] 0]]
}
#a dev build's base version must EXCEED the floor (e.g 0.16.0-dev < 0.16.0)
proc ::punkboot::tool_zig_satisfies {ver floor} {
if {$floor eq ""} {return 1}
set base $ver ; set isdev 0
if {[regexp {^([0-9][0-9.]*)[-+]} $ver -> b]} {set base $b ; set isdev 1}
if {[catch {package vcompare $base $floor} cmp]} {return 0}
if {$cmp > 0} {return 1}
if {$cmp == 0 && !$isdev} {return 1}
return 0
}
#resolve a zig for the given floor: PUNK_ZIG env override (path to the zig
#executable or its folder), else scan <projectdir>/bin/tools/zig* preferring
#releases over dev builds and the LOWEST satisfying version - a deterministic
#choice that stays stable as newer toolchains get unpacked beside the pinned one
proc ::punkboot::tool_resolve_zig {projectroot floor} {
if {[info exists ::env(PUNK_ZIG)] && $::env(PUNK_ZIG) ne ""} {
set cand $::env(PUNK_ZIG)
if {[file isdirectory $cand]} {
foreach n {zig.exe zig} {
if {[file exists [file join $cand $n]]} {set cand [file join $cand $n] ; break}
}
}
set v [::punkboot::tool_zig_version $cand]
if {$v eq ""} {
return [dict create note "PUNK_ZIG='$::env(PUNK_ZIG)' did not answer 'zig version'"]
}
if {![::punkboot::tool_zig_satisfies $v $floor]} {
return [dict create note "PUNK_ZIG zig is $v which does not satisfy this tool's floor (>= $floor)"]
}
return [dict create zig $cand version $v via PUNK_ZIG]
}
set best "" ; set bestv "" ; set bestbase "" ; set bestdev 1
foreach dir [lsort [glob -nocomplain -dir [file join $projectroot bin tools] -type d zig*]] {
set p ""
foreach n {zig.exe zig} {
if {[file exists [file join $dir $n]]} {set p [file join $dir $n] ; break}
}
if {$p eq ""} {continue}
set v [::punkboot::tool_zig_version $p]
if {$v eq "" || ![::punkboot::tool_zig_satisfies $v $floor]} {continue}
set base $v ; set isdev 0
if {[regexp {^([0-9][0-9.]*)[-+]} $v -> b]} {set base $b ; set isdev 1}
set take 0
if {$best eq ""} {
set take 1
} elseif {$bestdev && !$isdev} {
set take 1
} elseif {$bestdev == $isdev && [package vcompare $base $bestbase] < 0} {
set take 1
}
if {$take} {set best $p ; set bestv $v ; set bestbase $base ; set bestdev $isdev}
}
if {$best eq ""} {
set want [expr {$floor eq "" ? "any version" : ">= $floor"}]
return [dict create note "no suitable zig toolchain ($want) under [file join $projectroot bin tools] - 'bin/punk-getzig.cmd <version>' fetches one; PUNK_ZIG=<path-to-zig> overrides"]
}
return [dict create zig $best version $bestv via scan]
}
proc ::punkboot::tool_installed_exe {projectroot name} {
set suffix [punkboot::lib::platform_exe_suffix $::punkboot::target_platform]
return [file join $projectroot bin ${name}$suffix]
}
proc ::punkboot::tool_install_state {projectroot tooldir} {
set exe [::punkboot::tool_installed_exe $projectroot [file tail $tooldir]]
if {![file exists $exe]} {return absent}
if {[file mtime $exe] >= [::punkboot::tool_newest_src_mtime $tooldir]} {return current}
return stale
}
#run 'zig <argl>' with cwd at the tool tree (the tree's CLI tests resolve
#fixtures and zig-out relative to the build root). The gate is the EXIT CODE:
#stderr legitimately carries expected warnings (punkzip's zip suite negative
#tests print EOCD warnings while passing). Returns the child's exit status.
proc ::punkboot::tool_zig_run {zig tooldir argl} {
set saved [pwd]
cd $tooldir
set rc 0
if {[catch {exec $zig {*}$argl >@stdout 2>@stderr} _e _o]} {
set ecode [dict get $_o -errorcode]
if {[lindex $ecode 0] eq "CHILDSTATUS"} {
set rc [lindex $ecode 2]
} else {
puts stderr "tool: failed to run '$zig $argl' in $tooldir: $_e"
set rc 1
}
}
cd $saved
return $rc
}
set alldirs [::punkboot::tool_dirs $tool_root]
set byname [dict create]
foreach d $alldirs {dict set byname [file tail $d] $d}
if {[llength $tool_names]} {
set selected [list]
foreach n $tool_names {
if {![dict exists $byname $n]} {
puts stderr "tool '$n' not found under $tool_root - configured tools: [dict keys $byname]"
exit 2
}
lappend selected [dict get $byname $n]
}
} else {
set selected $alldirs
}
if {![llength $alldirs] && $tool_action ne "list"} {
puts stderr "no vendored tools found under $tool_root (a tool = a directory carrying build.zig)"
exit 2
}
switch -- $tool_action {
list {
if {![llength $alldirs]} {
puts stdout "no vendored tools found under $tool_root (a tool = a directory carrying build.zig)"
exit 0
}
puts stdout "vendored tools under $tool_root (boundary: src/tools/AGENTS.md; per-tree PROVENANCE.md):"
puts stdout [format " %-10s %-9s %-10s %-8s %s" name version zig-floor bin vendored-commit]
set maxfloor ""
foreach dir $alldirs {
set name [file tail $dir]
set zon [::punkboot::tool_zoninfo $dir]
set prov [::punkboot::tool_provenance $dir]
set floor [dict get $zon zigfloor]
if {$floor ne "" && ($maxfloor eq "" || [package vcompare $floor $maxfloor] > 0)} {
set maxfloor $floor
}
set commit [dict get $prov commit]
set commit [expr {$commit eq "" ? "-" : [string range $commit 0 11]}]
set floorshow [expr {$floor eq "" ? "-" : ">=$floor"}]
puts stdout [format " %-10s %-9s %-10s %-8s %s" $name [dict get $zon version] $floorshow [::punkboot::tool_install_state $projectroot $dir] $commit]
}
set zres [::punkboot::tool_resolve_zig $projectroot $maxfloor]
if {[dict exists $zres zig]} {
puts stdout "toolchain: zig [dict get $zres version] via [dict get $zres via] ([dict get $zres zig])"
} else {
puts stdout "toolchain: [dict get $zres note]"
}
puts stdout ""
puts stdout " - 'make.tcl tool info <name>' shows a tool's detail"
puts stdout " - 'make.tcl tool build ?<name> ...? ?-test 0|1?' builds (test-gated) and installs to bin/"
puts stdout " - zig is OPTIONAL for punkshell builds: packages/bake never require this step"
exit 0
}
info {
if {![llength $tool_names]} {
puts stderr "usage: make.tcl tool info <toolname> ('make.tcl tool list' shows the names)"
exit 2
}
foreach dir $selected {
set name [file tail $dir]
set zon [::punkboot::tool_zoninfo $dir]
set prov [::punkboot::tool_provenance $dir]
set floor [dict get $zon zigfloor]
puts stdout "tool: $name"
puts stdout " path: $dir"
puts stdout " version: [dict get $zon version] (build.zig.zon)"
puts stdout " zig floor: [expr {$floor eq "" ? "(none declared)" : ">= $floor"}]"
puts stdout " upstream: [expr {[dict get $prov upstream] eq "" ? "(no PROVENANCE.md record)" : [dict get $prov upstream]}]"
puts stdout " vendored commit: [expr {[dict get $prov commit] eq "" ? "(no PROVENANCE.md record)" : [dict get $prov commit]}]"
puts stdout " provenance: [file join $dir PROVENANCE.md]"
set exe [::punkboot::tool_installed_exe $projectroot $name]
puts stdout " installed: $exe ([::punkboot::tool_install_state $projectroot $dir])"
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {[dict exists $zres zig]} {
puts stdout " toolchain: zig [dict get $zres version] via [dict get $zres via] ([dict get $zres zig])"
} else {
puts stdout " toolchain: [dict get $zres note]"
}
}
exit 0
}
build {
foreach dir $selected {
set name [file tail $dir]
set floor [dict get [::punkboot::tool_zoninfo $dir] zigfloor]
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {![dict exists $zres zig]} {
puts stderr "tool build $name: [dict get $zres note]"
exit 1
}
set zig [dict get $zres zig]
puts stdout "tool build $name: zig [dict get $zres version] via [dict get $zres via] ($zig)"
flush stdout
if {$tool_dotest} {
puts stdout "tool build $name: test gate ('zig build test') ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build test]]
if {$rc != 0} {
puts stderr "tool build $name: test gate FAILED (exit $rc) - nothing installed"
exit 1
}
puts stdout "tool build $name: test gate passed"
} else {
puts stdout "tool build $name: test gate SKIPPED (-test 0)"
}
puts stdout "tool build $name: building (ReleaseSafe) ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build -Doptimize=ReleaseSafe]]
if {$rc != 0} {
puts stderr "tool build $name: build FAILED (exit $rc) - nothing installed"
exit 1
}
set suffix [punkboot::lib::platform_exe_suffix $::punkboot::target_platform]
set built [file join $dir zig-out bin ${name}$suffix]
if {![file exists $built]} {
puts stderr "tool build $name: build reported success but $built is missing"
exit 1
}
set exe [::punkboot::tool_installed_exe $projectroot $name]
if {[catch {file copy -force $built $exe} cperr]} {
puts stderr "tool build $name: could not install $exe ($cperr) - is a copy currently running?"
exit 1
}
puts stdout "tool build $name: installed $exe ([file size $exe] bytes)"
}
exit 0
}
test {
foreach dir $selected {
set name [file tail $dir]
set floor [dict get [::punkboot::tool_zoninfo $dir] zigfloor]
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {![dict exists $zres zig]} {
puts stderr "tool test $name: [dict get $zres note]"
exit 1
}
set zig [dict get $zres zig]
puts stdout "tool test $name: zig [dict get $zres version] via [dict get $zres via] - running 'zig build test' ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build test]]
if {$rc != 0} {
puts stderr "tool test $name: FAILED (exit $rc)"
exit 1
}
puts stdout "tool test $name: passed"
}
exit 0
}
default {
puts stderr "unknown tool action '$tool_action' - expected list|info|build|test"
puts stderr "usage: make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"
exit 2
}
}
}
if {$::punkboot::command eq "shell"} {
#G-113: the ansistrip transform serves make.tcl's own output only - the repl's
#colour behaviour is the shell's own concern (colour on/off + NO_COLOR there).

411
src/project_layouts/vendor/punk/project-0.1/src/make.tcl vendored

@ -187,7 +187,7 @@ namespace eval ::punkboot {
variable foldername [file tail $scriptfolder]
variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list]
variable help_flags [list -help --help /? -h]
variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion workflow buildsuite project vfs]
variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion workflow buildsuite tool project vfs]
}
@ -2152,6 +2152,11 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " $scriptname buildsuite list|info|build ?<suitename>? ?driver-args ...?" \n
append h " - list the defined buildsuites under src/buildsuites (zig runtime factory), show a suite's configured" \n
append h " detail (sources/zig pin/products from its sources.config), or run its driver forwarding the args" \n \n
append h " $scriptname tool list|info|build|test ?<toolname> ...? ?-test 0|1?" \n
append h " - vendored first-party build tools under src/tools (zig source; G-126): list/detail them, run their" \n
append h " test suites, or build (test-gated) and install to <projectdir>/bin" \n
append h " - zig is OPTIONAL: packages/bake never require this step; without a suitable toolchain the state is" \n
append h " reported and build/test exit nonzero with fetch guidance (bin/punk-getzig.cmd)" \n \n
append h " $scriptname help ?subcommand?" \n
append h " - show usage for $scriptname or one of its subcommands (equivalent: $scriptname <subcommand> -help)" \n \n
append h " Deprecated aliases (transition window):" \n
@ -2402,6 +2407,14 @@ KEY / NOTES
with binary dependencies, and version-preference outcomes are visible only to
the smoke probe - and only for declared packages. 'make.tcl check' reports both.
OUT-OF-BAND SUBSYSTEMS (not part of the packages/bake data flow above)
'make.tcl tool' builds the vendored first-party zig tools under src/tools
(G-126; punkzip first) into <projectdir>/bin, gated on each tree's own
'zig build test' exit code. zig is OPTIONAL: packages/bake never require it.
'make.tcl buildsuite' is the analogous arm's-length factory for whole Tcl
runtimes built from EXTERNAL sources under src/buildsuites (fetch + test-gate
contract per suite; products land in the bin/runtime store tiers).
MAINTENANCE (agents take note)
----------------------------------------------------------------------------
This text is embedded in make.tcl (::punkboot::workflow_text). When the build
@ -2514,6 +2527,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
shell "Run the punk shell using bootsupport libraries"
help "Show usage for make.tcl or one of its subcommands"
buildsuite "List, describe or run the defined buildsuites under src/buildsuites (zig runtime factory)"
tool "List, test or build the vendored tools under src/tools (zig) and install them to <projectdir>/bin"
project "DEPRECATED alias for bakehouse (one-line notice, then runs bakehouse)"
vfs "DEPRECATED alias for bake (one-line notice, then runs bake)"
}
@ -2683,6 +2697,45 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
(copy-and-tweak workflow) appears here automatically with no
make.tcl edits. The contract is documented in
src/buildsuites/README.md."
tool
"
Surface for the vendored first-party build tools under src/tools
(G-126: zig source vendored in full; boundary and editing policy in
src/tools/AGENTS.md, per-tree origin/licence records in
<tool>/PROVENANCE.md). Discovery is convention-based: a directory
under src/tools carrying build.zig is a tool; its installed
artifact is <projectdir>/bin/<name> with the host executable
suffix.
Actions:
tool list
Discover the tools with version (build.zig.zon), declared zig
floor, installed-binary state (current|stale|absent) and the
vendored upstream commit; report the resolved toolchain.
tool info <name> ...
Per-tool detail: paths, provenance records, toolchain
resolution.
tool build ?<name> ...? ?-test 0|1?
Resolve a zig satisfying the tool's declared floor, run the
'zig build test' gate (default on; the gate is the EXIT CODE -
expected warnings on stderr do not fail it), build ReleaseSafe
and install to <projectdir>/bin. Nothing is installed when the
gate or the build fails. With no name, all tools are
processed.
tool test ?<name> ...?
Run each tool's 'zig build test' only.
Toolchain resolution: PUNK_ZIG=<path-to-zig-or-its-folder>
overrides; otherwise <projectdir>/bin/tools/zig* is scanned and
the LOWEST release satisfying the floor wins (deterministic as
newer toolchains appear beside the pinned one; dev builds count
only when their base version exceeds the floor). zig is OPTIONAL
for punkshell builds: packages/bake never require this step -
without a suitable toolchain, list reports the state and
build/test exit nonzero with fetch guidance
(bin/punk-getzig.cmd). Build caches (.zig-cache/zig-out) are
deliberately left in place between runs for rebuild speed (both
git- and fossil-ignored)."
project
"
DEPRECATED alias for 'bakehouse' (G-112 stage-true rename) - prints a
@ -2855,6 +2908,15 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
arg -type any -optional 1 -multiple 1 -help\
"Action (list|info|build), suite name, and - for build - driver
arguments forwarded to the suite's suite.tcl untouched."}
punk::args::define\
"@id -id (script)::punkboot::tool"\
"@normalize"\
"@cmd -name [list "make.tcl tool"] -summary [list [dict get $SUMMARIES tool]] -help [list [dict get $HELPTEXTS tool]]"\
{@form -synopsis "make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"}\
{@values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help\
"Action (list|info|build|test), tool name(s), and - for build -
the -test 0|1 gate flag. Parsed by the handler."}
punk::args::define\
"@id -id (script)::punkboot::help"\
"@normalize"\
@ -2871,6 +2933,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
"promotion gates" {bootsupport vfscommonupdate}
"source maintenance" {vendorupdate}
"buildsuites" {buildsuite}
"vendored tools" {tool}
"informational" {info check bakelist projectversion workflow help}
"interactive" {shell}
"deprecated aliases" {project vfs}
@ -2963,6 +3026,10 @@ if {$::punkboot::punkargs_ok} {
#args (arbitrary suite flags) are forwarded untouched (G-104)
set ::punkboot::command buildsuite
set ::punkboot::bs_args $subargs
} elseif {$subcommand eq "tool"} {
#declared passthrough: the handler parses action/names/-test itself (G-126)
set ::punkboot::command tool
set ::punkboot::tool_args $subargs
} else {
if {[catch {punk::args::parse $subargs -errorstyle $::punkboot::errstyle withid (script)::punkboot::$subcommand} argd]} {
puts stderr $argd
@ -2993,6 +3060,12 @@ if {$::punkboot::punkargs_ok} {
set commands_found [list buildsuite]
set argsleft [list]
set ::punkboot::bs_args [lrange $scriptargs 1 end]
} elseif {[lindex $scriptargs 0] eq "tool"} {
#declared passthrough (as in the punk::args path) - the handler parses
#action/names/-test itself (G-126)
set commands_found [list tool]
set argsleft [list]
set ::punkboot::tool_args [lrange $scriptargs 1 end]
}
while {[llength $argsleft]} {
set a [lindex $argsleft 0]
@ -3116,7 +3189,8 @@ set forcekill $::punkboot::opt_forcekill
# - patch bump -> warn and proceed (non-breaking)
# 'check' is exempt in all cases the warning is shown at the end of its output instead.
# 'projectversion', 'workflow' and 'bakelist' are informational/read-only and are likewise exempt.
if {[info exists ::punkboot::stale_bootsupport] && $::punkboot::command ni {check projectversion workflow bakelist buildsuite}} {
# 'buildsuite' and 'tool' are arm's-length subsystems that do not consume bootsupport modules.
if {[info exists ::punkboot::stale_bootsupport] && $::punkboot::command ni {check projectversion workflow bakelist buildsuite tool}} {
set _stale $::punkboot::stale_bootsupport
set _have_major 0
set _have_minor 0
@ -4242,6 +4316,339 @@ if {$::punkboot::command eq "buildsuite"} {
}
}
if {$::punkboot::command eq "tool"} {
#G-126: vendored first-party build tools under src/tools (zig source vendored in
#full - boundary and editing policy in src/tools/AGENTS.md; per-tree origin,
#re-vendor procedure and licence records in <tool>/PROVENANCE.md). Discovery is
#convention-based: a directory under src/tools carrying build.zig is a tool, its
#name is the directory name, its build artifact is <tooldir>/zig-out/bin/<name>
#and its installed copy is <projectdir>/bin/<name> (host executable suffix).
#zig stays OPTIONAL for punkshell builds: nothing in packages/bake depends on
#this step; explicit build/test without a suitable toolchain exits nonzero with
#fetch guidance. Build caches (.zig-cache/zig-out) are deliberately left in
#place between builds for rebuild speed (both git- and fossil-ignored).
set tool_root [file join $sourcefolder tools]
if {![info exists ::punkboot::tool_args]} {set ::punkboot::tool_args [list]}
set targs $::punkboot::tool_args
set tool_action [lindex $targs 0]
if {$tool_action eq ""} {set tool_action list}
#args after the action: tool names plus an optional '-test 0|1' flag (build)
set tool_dotest 1
set tool_names [list]
set _i 1
while {$_i < [llength $targs]} {
set a [lindex $targs $_i]
if {$a eq "-test"} {
incr _i
set v [lindex $targs $_i]
if {![string is boolean -strict $v]} {
puts stderr "make.tcl tool: -test requires a boolean value (got '$v')"
exit 2
}
set tool_dotest [expr {bool($v)}]
} else {
lappend tool_names $a
}
incr _i
}
unset -nocomplain _i a v
proc ::punkboot::tool_dirs {tool_root} {
set dirs [list]
foreach dir [lsort [glob -nocomplain -dir $tool_root -type d *]] {
if {![file exists [file join $dir build.zig]]} {continue} ;#not a tool
lappend dirs $dir
}
return $dirs
}
#tolerant build.zig.zon reader: tool version + declared minimum zig version
proc ::punkboot::tool_zoninfo {tooldir} {
set m [dict create version "" zigfloor ""]
if {![catch {
set f [open [file join $tooldir build.zig.zon] r]
set data [read $f]
close $f
}]} {
if {[regexp {\.version\s*=\s*"([^"]+)"} $data -> _v]} {dict set m version $_v}
if {[regexp {\.minimum_zig_version\s*=\s*"([^"]+)"} $data -> _fl]} {dict set m zigfloor $_fl}
}
return $m
}
#tolerant PROVENANCE.md reader: vendored upstream commit + upstream location
proc ::punkboot::tool_provenance {tooldir} {
set m [dict create commit "" upstream ""]
if {![catch {
set f [open [file join $tooldir PROVENANCE.md] r]
set data [read $f]
close $f
}]} {
if {[regexp {Vendored state: commit\s+`?([0-9a-fA-F]{7,40})} $data -> _c]} {dict set m commit $_c}
if {[regexp {Upstream repository:\s+`?([^`\n]+)} $data -> _u]} {dict set m upstream [string trim $_u]}
}
return $m
}
proc ::punkboot::tool_newest_src_mtime {dir} {
set newest 0
foreach f [glob -nocomplain -dir $dir -type f *] {
if {[file mtime $f] > $newest} {set newest [file mtime $f]}
}
foreach d [glob -nocomplain -dir $dir -type d *] {
if {[file tail $d] in {zig-out .zig-cache zig-cache}} {continue}
set sub [::punkboot::tool_newest_src_mtime $d]
if {$sub > $newest} {set newest $sub}
}
return $newest
}
proc ::punkboot::tool_zig_version {zigexe} {
if {[catch {exec $zigexe version 2>@1} v]} {return ""}
return [string trim [lindex [split $v \n] 0]]
}
#a dev build's base version must EXCEED the floor (e.g 0.16.0-dev < 0.16.0)
proc ::punkboot::tool_zig_satisfies {ver floor} {
if {$floor eq ""} {return 1}
set base $ver ; set isdev 0
if {[regexp {^([0-9][0-9.]*)[-+]} $ver -> b]} {set base $b ; set isdev 1}
if {[catch {package vcompare $base $floor} cmp]} {return 0}
if {$cmp > 0} {return 1}
if {$cmp == 0 && !$isdev} {return 1}
return 0
}
#resolve a zig for the given floor: PUNK_ZIG env override (path to the zig
#executable or its folder), else scan <projectdir>/bin/tools/zig* preferring
#releases over dev builds and the LOWEST satisfying version - a deterministic
#choice that stays stable as newer toolchains get unpacked beside the pinned one
proc ::punkboot::tool_resolve_zig {projectroot floor} {
if {[info exists ::env(PUNK_ZIG)] && $::env(PUNK_ZIG) ne ""} {
set cand $::env(PUNK_ZIG)
if {[file isdirectory $cand]} {
foreach n {zig.exe zig} {
if {[file exists [file join $cand $n]]} {set cand [file join $cand $n] ; break}
}
}
set v [::punkboot::tool_zig_version $cand]
if {$v eq ""} {
return [dict create note "PUNK_ZIG='$::env(PUNK_ZIG)' did not answer 'zig version'"]
}
if {![::punkboot::tool_zig_satisfies $v $floor]} {
return [dict create note "PUNK_ZIG zig is $v which does not satisfy this tool's floor (>= $floor)"]
}
return [dict create zig $cand version $v via PUNK_ZIG]
}
set best "" ; set bestv "" ; set bestbase "" ; set bestdev 1
foreach dir [lsort [glob -nocomplain -dir [file join $projectroot bin tools] -type d zig*]] {
set p ""
foreach n {zig.exe zig} {
if {[file exists [file join $dir $n]]} {set p [file join $dir $n] ; break}
}
if {$p eq ""} {continue}
set v [::punkboot::tool_zig_version $p]
if {$v eq "" || ![::punkboot::tool_zig_satisfies $v $floor]} {continue}
set base $v ; set isdev 0
if {[regexp {^([0-9][0-9.]*)[-+]} $v -> b]} {set base $b ; set isdev 1}
set take 0
if {$best eq ""} {
set take 1
} elseif {$bestdev && !$isdev} {
set take 1
} elseif {$bestdev == $isdev && [package vcompare $base $bestbase] < 0} {
set take 1
}
if {$take} {set best $p ; set bestv $v ; set bestbase $base ; set bestdev $isdev}
}
if {$best eq ""} {
set want [expr {$floor eq "" ? "any version" : ">= $floor"}]
return [dict create note "no suitable zig toolchain ($want) under [file join $projectroot bin tools] - 'bin/punk-getzig.cmd <version>' fetches one; PUNK_ZIG=<path-to-zig> overrides"]
}
return [dict create zig $best version $bestv via scan]
}
proc ::punkboot::tool_installed_exe {projectroot name} {
set suffix [punkboot::lib::platform_exe_suffix $::punkboot::target_platform]
return [file join $projectroot bin ${name}$suffix]
}
proc ::punkboot::tool_install_state {projectroot tooldir} {
set exe [::punkboot::tool_installed_exe $projectroot [file tail $tooldir]]
if {![file exists $exe]} {return absent}
if {[file mtime $exe] >= [::punkboot::tool_newest_src_mtime $tooldir]} {return current}
return stale
}
#run 'zig <argl>' with cwd at the tool tree (the tree's CLI tests resolve
#fixtures and zig-out relative to the build root). The gate is the EXIT CODE:
#stderr legitimately carries expected warnings (punkzip's zip suite negative
#tests print EOCD warnings while passing). Returns the child's exit status.
proc ::punkboot::tool_zig_run {zig tooldir argl} {
set saved [pwd]
cd $tooldir
set rc 0
if {[catch {exec $zig {*}$argl >@stdout 2>@stderr} _e _o]} {
set ecode [dict get $_o -errorcode]
if {[lindex $ecode 0] eq "CHILDSTATUS"} {
set rc [lindex $ecode 2]
} else {
puts stderr "tool: failed to run '$zig $argl' in $tooldir: $_e"
set rc 1
}
}
cd $saved
return $rc
}
set alldirs [::punkboot::tool_dirs $tool_root]
set byname [dict create]
foreach d $alldirs {dict set byname [file tail $d] $d}
if {[llength $tool_names]} {
set selected [list]
foreach n $tool_names {
if {![dict exists $byname $n]} {
puts stderr "tool '$n' not found under $tool_root - configured tools: [dict keys $byname]"
exit 2
}
lappend selected [dict get $byname $n]
}
} else {
set selected $alldirs
}
if {![llength $alldirs] && $tool_action ne "list"} {
puts stderr "no vendored tools found under $tool_root (a tool = a directory carrying build.zig)"
exit 2
}
switch -- $tool_action {
list {
if {![llength $alldirs]} {
puts stdout "no vendored tools found under $tool_root (a tool = a directory carrying build.zig)"
exit 0
}
puts stdout "vendored tools under $tool_root (boundary: src/tools/AGENTS.md; per-tree PROVENANCE.md):"
puts stdout [format " %-10s %-9s %-10s %-8s %s" name version zig-floor bin vendored-commit]
set maxfloor ""
foreach dir $alldirs {
set name [file tail $dir]
set zon [::punkboot::tool_zoninfo $dir]
set prov [::punkboot::tool_provenance $dir]
set floor [dict get $zon zigfloor]
if {$floor ne "" && ($maxfloor eq "" || [package vcompare $floor $maxfloor] > 0)} {
set maxfloor $floor
}
set commit [dict get $prov commit]
set commit [expr {$commit eq "" ? "-" : [string range $commit 0 11]}]
set floorshow [expr {$floor eq "" ? "-" : ">=$floor"}]
puts stdout [format " %-10s %-9s %-10s %-8s %s" $name [dict get $zon version] $floorshow [::punkboot::tool_install_state $projectroot $dir] $commit]
}
set zres [::punkboot::tool_resolve_zig $projectroot $maxfloor]
if {[dict exists $zres zig]} {
puts stdout "toolchain: zig [dict get $zres version] via [dict get $zres via] ([dict get $zres zig])"
} else {
puts stdout "toolchain: [dict get $zres note]"
}
puts stdout ""
puts stdout " - 'make.tcl tool info <name>' shows a tool's detail"
puts stdout " - 'make.tcl tool build ?<name> ...? ?-test 0|1?' builds (test-gated) and installs to bin/"
puts stdout " - zig is OPTIONAL for punkshell builds: packages/bake never require this step"
exit 0
}
info {
if {![llength $tool_names]} {
puts stderr "usage: make.tcl tool info <toolname> ('make.tcl tool list' shows the names)"
exit 2
}
foreach dir $selected {
set name [file tail $dir]
set zon [::punkboot::tool_zoninfo $dir]
set prov [::punkboot::tool_provenance $dir]
set floor [dict get $zon zigfloor]
puts stdout "tool: $name"
puts stdout " path: $dir"
puts stdout " version: [dict get $zon version] (build.zig.zon)"
puts stdout " zig floor: [expr {$floor eq "" ? "(none declared)" : ">= $floor"}]"
puts stdout " upstream: [expr {[dict get $prov upstream] eq "" ? "(no PROVENANCE.md record)" : [dict get $prov upstream]}]"
puts stdout " vendored commit: [expr {[dict get $prov commit] eq "" ? "(no PROVENANCE.md record)" : [dict get $prov commit]}]"
puts stdout " provenance: [file join $dir PROVENANCE.md]"
set exe [::punkboot::tool_installed_exe $projectroot $name]
puts stdout " installed: $exe ([::punkboot::tool_install_state $projectroot $dir])"
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {[dict exists $zres zig]} {
puts stdout " toolchain: zig [dict get $zres version] via [dict get $zres via] ([dict get $zres zig])"
} else {
puts stdout " toolchain: [dict get $zres note]"
}
}
exit 0
}
build {
foreach dir $selected {
set name [file tail $dir]
set floor [dict get [::punkboot::tool_zoninfo $dir] zigfloor]
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {![dict exists $zres zig]} {
puts stderr "tool build $name: [dict get $zres note]"
exit 1
}
set zig [dict get $zres zig]
puts stdout "tool build $name: zig [dict get $zres version] via [dict get $zres via] ($zig)"
flush stdout
if {$tool_dotest} {
puts stdout "tool build $name: test gate ('zig build test') ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build test]]
if {$rc != 0} {
puts stderr "tool build $name: test gate FAILED (exit $rc) - nothing installed"
exit 1
}
puts stdout "tool build $name: test gate passed"
} else {
puts stdout "tool build $name: test gate SKIPPED (-test 0)"
}
puts stdout "tool build $name: building (ReleaseSafe) ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build -Doptimize=ReleaseSafe]]
if {$rc != 0} {
puts stderr "tool build $name: build FAILED (exit $rc) - nothing installed"
exit 1
}
set suffix [punkboot::lib::platform_exe_suffix $::punkboot::target_platform]
set built [file join $dir zig-out bin ${name}$suffix]
if {![file exists $built]} {
puts stderr "tool build $name: build reported success but $built is missing"
exit 1
}
set exe [::punkboot::tool_installed_exe $projectroot $name]
if {[catch {file copy -force $built $exe} cperr]} {
puts stderr "tool build $name: could not install $exe ($cperr) - is a copy currently running?"
exit 1
}
puts stdout "tool build $name: installed $exe ([file size $exe] bytes)"
}
exit 0
}
test {
foreach dir $selected {
set name [file tail $dir]
set floor [dict get [::punkboot::tool_zoninfo $dir] zigfloor]
set zres [::punkboot::tool_resolve_zig $projectroot $floor]
if {![dict exists $zres zig]} {
puts stderr "tool test $name: [dict get $zres note]"
exit 1
}
set zig [dict get $zres zig]
puts stdout "tool test $name: zig [dict get $zres version] via [dict get $zres via] - running 'zig build test' ..."
flush stdout
set rc [::punkboot::tool_zig_run $zig $dir [list build test]]
if {$rc != 0} {
puts stderr "tool test $name: FAILED (exit $rc)"
exit 1
}
puts stdout "tool test $name: passed"
}
exit 0
}
default {
puts stderr "unknown tool action '$tool_action' - expected list|info|build|test"
puts stderr "usage: make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"
exit 2
}
}
}
if {$::punkboot::command eq "shell"} {
#G-113: the ansistrip transform serves make.tcl's own output only - the repl's
#colour behaviour is the shell's own concern (colour on/off + NO_COLOR there).

50
src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test

@ -613,5 +613,55 @@ namespace eval ::testspace {
file delete -force [dict get $ws root]
}\
-result 0
# --- installtrack: reload self-heals damaged persisted event history ---
# Files written by punkcheck < 0.3.2 carry EVENT records whose -id is the
# LITERAL string {$eventid} (the 0.3.2 buildversion note fixed the writer;
# existing files keep the damage). Two such records collide when the modern
# constructor reloads events keyed by -id - which killed whole make.tcl
# bakes (vfslibs phase) on checkouts with old .punkcheck files. The
# constructor must reconstruct such records under synthesized unique ids
# (with a stderr warning) rather than error. Added 2026-07-27 (agent,
# punkcheck 0.6.1).
test installtrack_damaged_event_history_selfheal {reload of a file with duplicate literal \$eventid EVENT records self-heals instead of erroring}\
-setup {
set ws [make_workspace]
set punkcheck_file [dict get $ws punkcheck_file]
#seed two completed events so the persisted INSTALLER body carries two EVENT records
foreach step {one two} {
set installer [punkcheck::installtrack new "test" $punkcheck_file]
$installer set_source_target [dict get $ws srcdir] [dict get $ws tgtdir]
set event [$installer start_event [list -install_step $step]]
$event end
$installer destroy
}
#simulate the pre-0.3.2 damage: every persisted event -id becomes the
#literal string $eventid - BRACED, as the damaged files carry it (the
#record loader evaluates records, so only a braced literal survives
#as the string; the -eventid keys of INSTALL records are a different
#token and are not touched by the \s-id\s pattern)
set fd [open $punkcheck_file r]
set data [read $fd]
close $fd
regsub -all {(\s-id\s+)\S+} $data {\1{$eventid}} data
set fd [open $punkcheck_file w]
puts -nonewline $fd $data
close $fd
}\
-body {
set result [list]
set installer [punkcheck::installtrack new "test" $punkcheck_file]
lappend result reloaded_events [$installer events count]
set keys [$installer events keys]
lappend result distinct_keys [expr {[llength $keys] == [llength [lsort -unique $keys]]}]
lappend result healed_count [llength [lsearch -all -glob $keys damaged-id-*]]
lappend result literal_kept [expr {{$eventid} in $keys}]
$installer destroy
set result
}\
-cleanup {
file delete -force [dict get $ws root]
}\
-result {reloaded_events 2 distinct_keys 1 healed_count 1 literal_kept 1}
}
tcltest::cleanupTests ;#needed to produce test summary line.
Loading…
Cancel
Save