diff --git a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl index b3263dba..0658f3c4 100644 --- a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl +++ b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl @@ -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 ?? ?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 ? ...? ?-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 /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 -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 /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 /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 + /PROVENANCE.md). Discovery is convention-based: a directory + under src/tools carrying build.zig is a tool; its installed + artifact is /bin/ 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 ... + Per-tool detail: paths, provenance records, toolchain + resolution. + tool build ? ...? ?-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 /bin. Nothing is installed when the + gate or the build fails. With no name, all tools are + processed. + tool test ? ...? + Run each tool's 'zig build test' only. + + Toolchain resolution: PUNK_ZIG= + overrides; otherwise /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 ? ...? ?-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 /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 /zig-out/bin/ + #and its installed copy is /bin/ (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 /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 ' fetches one; PUNK_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 ' 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 ' shows a tool's detail" + puts stdout " - 'make.tcl tool build ? ...? ?-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 ('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 ? ...? ?-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). diff --git a/src/project_layouts/vendor/punk/basic/src/make.tcl b/src/project_layouts/vendor/punk/basic/src/make.tcl index b3263dba..0658f3c4 100644 --- a/src/project_layouts/vendor/punk/basic/src/make.tcl +++ b/src/project_layouts/vendor/punk/basic/src/make.tcl @@ -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 ?? ?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 ? ...? ?-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 /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 -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 /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 /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 + /PROVENANCE.md). Discovery is convention-based: a directory + under src/tools carrying build.zig is a tool; its installed + artifact is /bin/ 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 ... + Per-tool detail: paths, provenance records, toolchain + resolution. + tool build ? ...? ?-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 /bin. Nothing is installed when the + gate or the build fails. With no name, all tools are + processed. + tool test ? ...? + Run each tool's 'zig build test' only. + + Toolchain resolution: PUNK_ZIG= + overrides; otherwise /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 ? ...? ?-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 /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 /zig-out/bin/ + #and its installed copy is /bin/ (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 /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 ' fetches one; PUNK_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 ' 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 ' shows a tool's detail" + puts stdout " - 'make.tcl tool build ? ...? ?-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 ('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 ? ...? ?-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). diff --git a/src/project_layouts/vendor/punk/project-0.1/src/make.tcl b/src/project_layouts/vendor/punk/project-0.1/src/make.tcl index b3263dba..0658f3c4 100644 --- a/src/project_layouts/vendor/punk/project-0.1/src/make.tcl +++ b/src/project_layouts/vendor/punk/project-0.1/src/make.tcl @@ -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 ?? ?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 ? ...? ?-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 /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 -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 /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 /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 + /PROVENANCE.md). Discovery is convention-based: a directory + under src/tools carrying build.zig is a tool; its installed + artifact is /bin/ 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 ... + Per-tool detail: paths, provenance records, toolchain + resolution. + tool build ? ...? ?-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 /bin. Nothing is installed when the + gate or the build fails. With no name, all tools are + processed. + tool test ? ...? + Run each tool's 'zig build test' only. + + Toolchain resolution: PUNK_ZIG= + overrides; otherwise /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 ? ...? ?-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 /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 /zig-out/bin/ + #and its installed copy is /bin/ (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 /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 ' fetches one; PUNK_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 ' 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 ' shows a tool's detail" + puts stdout " - 'make.tcl tool build ? ...? ?-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 ('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 ? ...? ?-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).