You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

247 lines
12 KiB

#family_artifacts.tcl (G-103; schema v2 per G-117 lineage + G-123 provenance
#class): emit punkbin-layout artifact
#copies + per-artifact toml metadata + sha1sums for the verified runtime kit
#family members. The sidecar's identity fields (schema, build_id, origin,
#packager, project, license, build_host_platform) are DERIVED from the record
#embedded in each kit's attached image at staging (read back by executing the
#kit) - single source of truth; the sidecar adds the finished-binary facts
#(sha1, size, built) per the embed-then-hash ordering.
#
#Deliberately run UNDER THE PLAIN FAMILY KIT itself (not the builder shell): the
#sha1 digests come from the kit's attached tcllib/tcllibc, so every emission run
#doubles as a proof that the family runtime executes real tooling self-contained.
#
#Artifact tier naming (G-103 naming decision): <workingname minus .exe>-r<N>.exe -
#immutable punkbin names; the -r<N> assembly revision comes from the invocation
#(-rev, suite option -Dfamilyrev). The emitted tree mirrors a punkbin platform
#folder: <outdir>/<artifact>.exe + <artifact>.toml + sha1sums.txt (punkbin
#format: '<sha1> *<filename>'). Publication to the real punkbin repo is a
#deliberate user step (copy + build_sha1sums.tcl there); it is DEFERRED per the
#goal notes until the family shape is accepted.
#
#args: -outdir <dir> -rev <N> -target <punkbin platform, e.g win32-x86_64>
# -suite <name> -tclpatch <patchlevel> -zig <version> -optimize <mode>
# -components {<name> <ver> ...} (attached battery versions, all variants)
# -bicomponents {<name> <ver> ...} (additional bi-only batteries)
# -provenance {<name> <uuid> ...} (source checkout uuids)
# -testreports <dir> (G-107 evidence summaries; optional)
# -kits {<variant> <workingexepath> ...}
proc fail {msg} {puts stderr "family_artifacts FAIL: $msg"; flush stderr; exit 1}
proc note {msg} {puts stdout "family_artifacts: $msg"; flush stdout}
array set opt {
-outdir {} -rev 1 -target {} -suite {} -tclpatch {} -zig {} -optimize {}
-components {} -bicomponents {} -provenance {} -testreports {} -kits {}
}
foreach {k v} $argv {
if {![info exists opt($k)]} {fail "unknown option '$k'"}
set opt($k) $v
}
foreach req {-outdir -target -suite -tclpatch -zig -optimize -components -kits} {
if {$opt($req) eq ""} {fail "missing required option $req"}
}
if {![string is integer -strict $opt(-rev)] || $opt(-rev) < 1} {fail "-rev must be a positive integer"}
if {[catch {package require sha1} sha1ver]} {
fail "package require sha1 failed under [info nameofexecutable] - the family kit must carry tcllib: $sha1ver"
}
proc toml_str {s} {
#basic toml string: escape backslash and double-quote (values here are names,
#versions, uuids, iso dates - no control chars expected)
return "\"[string map {\\ \\\\ \" \\\"} $s]\""
}
proc exe_split {name} {
#{root ext} splitting only a .exe suffix - dotted tcl patchlevels make
#[file rootname] wrong for extensionless (unix) artifact names
#(tclsh9.0.5-punk-r1 would truncate at the last version dot)
if {[string match -nocase "*.exe" $name]} {
return [list [string range $name 0 end-4] .exe]
}
return [list $name ""]
}
proc read_embedded {kitpath} {
#G-117: exec the kit itself with a piped reader script - its attached image
#mounts at //zipfs:/app, so the embedded record is a plain file read. Trusted
#just-built kits only; identifying STRAY binaries without execution is the
#punk-runtime 'info' zip-central-directory read.
set reader {
fconfigure stdout -translation binary
if {[catch {
set f [open //zipfs:/app/punkbin-artifact.toml r]
fconfigure $f -translation binary
puts -nonewline [read $f]
close $f
} errM]} {
puts -nonewline "PUNKBIN-EMBED-MISSING: $errM"
}
exit 0
}
if {[catch {exec $kitpath << $reader} out]} {
fail "embedded-record read failed for $kitpath: $out"
}
if {[string match "PUNKBIN-EMBED-MISSING*" $out]} {
fail "no embedded record in $kitpath ([string range $out 0 200]) - kit predates the G-117 staging embed?"
}
return [string map [list \r ""] $out]
}
proc record_field {recordtext key} {
#line-based tolerant lookup (same consumer style as punk-runtime): quoted
#string or bare integer values; '#' comment lines never match
foreach line [split $recordtext \n] {
set line [string trim $line]
if {[regexp -- [format {^%s\s*=\s*"(.*)"\s*$} $key] $line -> v]} {return $v}
if {[regexp -- [format {^%s\s*=\s*([0-9]+)\s*$} $key] $line -> v]} {return $v}
}
return ""
}
file mkdir $opt(-outdir)
set built [clock format [clock seconds] -format %Y-%m-%dT%H:%M:%SZ -timezone :UTC]
set sha1lines {}
set emitted {}
foreach {variant kitpath} $opt(-kits) {
set kitpath [file normalize $kitpath]
if {![file exists $kitpath]} {fail "kit exe not found: $kitpath"}
set working [file tail $kitpath]
lassign [exe_split $working] wroot wext
set artifact "$wroot-r$opt(-rev)$wext"
set dest [file join $opt(-outdir) $artifact]
file delete -force $dest
file copy $kitpath $dest
set sha1 [sha1::sha1 -hex -file $dest]
set size [file size $dest]
lappend sha1lines "$sha1 *$artifact"
set components $opt(-components)
if {$variant eq "punk-bi"} {lappend components {*}$opt(-bicomponents)}
set batteries {}
foreach {n v} $components {lappend batteries [toml_str "$n $v"]}
set batteries_line "attached_batteries = \[[join $batteries {, }]\]"
#G-117: read the EMBEDDED record back out of the finished kit and derive the
#sidecar's shared identity fields from it - single source of truth, identical
#build_id by construction; every emission inherently verifies the embed exists
#and matches this invocation (stale-staging tripwire).
set embedded [read_embedded $kitpath]
foreach {ekey expect} [list schema 2 name $artifact variant $variant working_name $working revision $opt(-rev) target $opt(-target) tcl_patchlevel $opt(-tclpatch)] {
set got [record_field $embedded $ekey]
if {$got ne $expect} {fail "$artifact embedded record mismatch: $ekey = '$got', expected '$expect' (stale staging?)"}
}
if {[string first $batteries_line $embedded] < 0} {
fail "$artifact embedded record attached_batteries does not match the emission components"
}
set e_build_id [record_field $embedded build_id]
if {$e_build_id eq ""} {fail "$artifact embedded record has no build_id"}
set evars [dict create]
foreach k {origin packager project project_url license build_host_platform} {
set v [record_field $embedded $k]
if {$v eq ""} {fail "$artifact embedded record missing v1 field '$k'"}
dict set evars $k $v
}
set m {}
lappend m "#punkshell runtime artifact metadata (schema v2, class \"runtime\") - generated by"
lappend m "#family_artifacts.tcl. Sidecar copy: the embedded record carried inside the"
lappend m "#artifact's attached image (punkbin-artifact.toml) plus the finished-binary"
lappend m "#facts (sha1, size, built). The sidecar + sha1sums.txt remain the integrity"
lappend m "#authority."
lappend m "schema = 2"
lappend m ""
lappend m "\[artifact\]"
lappend m "name = [toml_str $artifact]"
lappend m "class = \"runtime\""
lappend m "variant = [toml_str $variant]"
lappend m "working_name = [toml_str $working]"
lappend m "revision = $opt(-rev)"
lappend m "target = [toml_str $opt(-target)]"
lappend m "sha1 = [toml_str $sha1]"
lappend m "size = $size"
lappend m "built = [toml_str $built]"
lappend m "#build_id: offline correlation key re-joining a renamed copy to its record;"
lappend m "#identical in the embedded and sidecar copies (a deterministic identity digest,"
lappend m "#not an integrity key - sha1 above is the integrity fact)."
lappend m "build_id = [toml_str $e_build_id]"
lappend m "#origin: canonical artifact repo this artifact was BUILT FOR - not necessarily"
lappend m "#where it is hosted; mirrors preserve it."
lappend m "origin = [toml_str [dict get $evars origin]]"
lappend m "#packager: declared identity, not proof - signing (minisign sidecars) is the"
lappend m "#verification layer."
lappend m "packager = [toml_str [dict get $evars packager]]"
lappend m "project = [toml_str [dict get $evars project]]"
lappend m "project_url = [toml_str [dict get $evars project_url]]"
lappend m "#license: summary for the distributed artifact; component license texts ride"
lappend m "#inside the attached image (tcl_library/license.terms etc)."
lappend m "license = [toml_str [dict get $evars license]]"
lappend m "build_host_platform = [toml_str [dict get $evars build_host_platform]]"
lappend m ""
lappend m "\[runtime\]"
lappend m "tcl_patchlevel = [toml_str $opt(-tclpatch)]"
set pr [expr {$variant ne "plain"}]
lappend m "piperepl = [expr {$pr ? "true" : "false"}]"
if {$pr} {
lappend m "piperepl_default = \"on\""
lappend m "piperepl_opt_out = \"TCLSH_PIPEREPL=0\""
}
lappend m $batteries_line
lappend m ""
lappend m "\[provenance\]"
lappend m "#class: build-origin class (schema v2): suite-built | third-party | local."
lappend m "#NOTE for line-based consumers: '\[artifact\] class' above is the first 'class ='"
lappend m "#line in the record by construction - whole-text single-key scans see that one."
lappend m "class = \"suite-built\""
lappend m "suite = [toml_str $opt(-suite)]"
lappend m "toolchain = [toml_str "zig $opt(-zig)"]"
lappend m "optimize = [toml_str $opt(-optimize)]"
foreach {n uuid} $opt(-provenance) {
lappend m "${n}_checkout = [toml_str $uuid]"
}
#G-107 evidence summaries available at emission time (result lines only; the
#full line-record summaries stay the canonical evidence artifacts)
if {$opt(-testreports) ne "" && [file isdirectory $opt(-testreports)]} {
set tlines {}
foreach sf [lsort [glob -nocomplain -directory $opt(-testreports) *.summary]] {
set rec [dict create]
set f [open $sf r]
foreach line [split [read $f] \n] {
set line [string trim $line]
if {$line eq "" || [string index $line 0] eq "#"} continue
if {[catch {llength $line} n] || $n < 2} continue
dict set rec [lindex $line 0] [lrange $line 1 end]
}
close $f
if {![dict exists $rec library] || ![dict exists $rec result]} continue
set lib [dict get $rec library]
set parts [list "result=[dict get $rec result]"]
foreach fkey {mode total passed skipped failed} {
if {[dict exists $rec $fkey]} {lappend parts "$fkey=[dict get $rec $fkey]"}
}
lappend tlines "$lib = [toml_str [join $parts { }]]"
}
if {[llength $tlines]} {
lappend m ""
lappend m "\[tests\]"
lappend m {*}$tlines
}
}
set mf [file join $opt(-outdir) "[lindex [exe_split $artifact] 0].toml"]
set f [open $mf w]
fconfigure $f -translation lf
puts $f [join $m \n]
close $f
lappend emitted "$variant -> $artifact"
note "emitted $artifact (sha1 $sha1, [expr {$size/1024}] KB) + [file tail $mf]"
}
set f [open [file join $opt(-outdir) sha1sums.txt] w]
fconfigure $f -translation lf
puts $f [join $sha1lines \n]
close $f
note "sha1sums.txt written ([llength $sha1lines] artifacts)"
puts "family_artifacts OK: [join $emitted {; }] -> $opt(-outdir)"
exit 0