From e7e2ec81f418bd8b4759c28294eaa56afabb6b73 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Thu, 30 Jul 2026 19:17:44 +1000 Subject: [PATCH] G-139 phase 2 increment 1: PACKAGES_tcl deployment + vfslibs packages-root declarations (0.31.1) make.tcl libs/packages/bakehouse gain the PACKAGES_tcl phase: materialized punkbin lib-tier packages under bin/packages//tcl/ deploy into lib_tcl/allplatforms + lib_tcl/ with punkcheck provenance, mirroring the VENDORLIB_tcl shape (embedded punkbin-artifact.toml records ride into the deployed trees; absent tier = silent per-item no-op with a libfetch pointer). vfslibs declaration schema extended (vendorlib_vfs.toml header documents both): source_root = "packages" resolves an entry's source against the bin/packages tier (missing source errors name 'make.tcl libfetch'); replace = true clean-slates a SAME-NAMED existing target folder before install (supersedes deliberately cannot name the folder being installed - needed because the pre-G-139 tcl9 hand-drops nested md5c + tcllibc INSIDE tcllib2.0). Declarations added for the switchover set (per-kit, never blanket - the trees that carried hand-drops at 2026-07-30): 8 win32 tcl9 kit vfs trees get tcllib2.0-tcl9 + tcllibc-tcl9 (supersedes the stray-named tcllibc2.0); punk9linux (linux target) gets only platform-neutral tcllib2.0 (its linux-built tcllibc siblings stay - a linux lib-tier emission is future work); punk86/punk8win/punk8_statictwapi upgrade tcllib1.21 (+nested tcllibc 0.4 + md5c, removed with it via supersedes) to tcllib2.0-tcl8 + tcllibc-tcl8; punk86bawt refreshes its bare tcllibc only. Verified: make.tcl libs deployed the tier into lib_tcl8|9 (allplatforms + win32-x86_64) with embedded records present; vendored mirrors coexist in the deployed trees until the sequenced retirement. Thin-layout make.tcl sync copies updated by the run (punkcheck-managed outputs, batched per the carve-out). Project 0.31.1 + CHANGELOG. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- CHANGELOG.md | 16 + punkproject.toml | 2 +- src/make.tcl | 71 ++- .../vendor/punk/project-0.1/src/make.tcl | 455 +++++++++++++++++- .../vendor/punk/basic/src/make.tcl | 455 +++++++++++++++++- .../vendor/punk/project-0.1/src/make.tcl | 455 +++++++++++++++++- src/runtime/vendorlib_vfs.toml | 86 +++- 7 files changed, 1517 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2662139f..03d07ab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.31.1] - 2026-07-30 + +- `make.tcl libs`/`packages`/`bakehouse` gain the PACKAGES_tcl phase + (G-139): materialized punkbin lib-tier packages under `bin/packages/` + deploy into `lib_tcl/allplatforms` + `lib_tcl/` with + punkcheck provenance (silent no-op when the tier is absent). The vfslibs + declaration schema (`src/runtime/vendorlib_vfs.toml`) gains `source_root = + "packages"` (sources resolved against the bin/packages tier, with an + actionable libfetch hint when missing) and `replace = true` (clean-slate + same-name refresh - supersedes deliberately cannot name the folder being + installed). tcllib/tcllibc enter the kit vfs declarations: tcl9 kits + switch their hand-dropped tcllib2.0 (which nested md5c/tcllibc) to the + suite-built artifact pair; tcl8 kits upgrade tcllib1.21 (+nested tcllibc + 0.4/md5c) to tcllib 2.0 + current tcllibc via supersedes; punk9linux + (linux target) gets only the platform-neutral tcllib2.0. + ## [0.31.0] - 2026-07-30 - New `make.tcl libfetch` subcommand (G-139 phase 1): fetches the punkbin diff --git a/punkproject.toml b/punkproject.toml index 87a588c2..367a7979 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,4 +1,4 @@ [project] name = "punkshell" -version = "0.31.0" +version = "0.31.1" license = "BSD-2-Clause" diff --git a/src/make.tcl b/src/make.tcl index 61ce8b36..75e7391e 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -5618,6 +5618,36 @@ if {$::punkboot::command in {bakehouse packages libs}} { if {![llength $vendorlibfolders]} { puts stderr "$A(BAD)VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found.$A(RST)" } + #G-139: punkbin lib-tier packages from the bin/packages input tier (populated + #by 'make.tcl libfetch') into the deployed lib_tcl trees - allplatforms + + #this host's platform dir, mirroring the vendorlib_tcl shape above. The + #materialized trees are self-describing (embedded punkbin-artifact.toml + #records - G-138) and the records ride along into the deployed trees, and + #from there into kit vfs payloads via the vfslibs declarations. An absent + #tier (or target/generation subdir) is a silent per-item no-op: the tier is + #fetch-regenerable and optional until artifacts are declared and fetched. + set packages_root $projectroot/bin/packages + if {[file isdirectory $packages_root]} { + foreach ptarget [list allplatforms $this_platform_generic] { + foreach gendir [lsort [glob -nocomplain -dir $packages_root/$ptarget -type d tcl*]] { + set which [file tail $gendir] ;#tcl8|tcl9 ... + foreach pkgdir [lsort [glob -nocomplain -dir $gendir -type d *]] { + set pkgtail [file tail $pkgdir] + set target_lib_folder $projectroot/lib_$which/$ptarget + file mkdir $target_lib_folder + puts stdout "PACKAGES_$which: copying $ptarget/$which/$pkgtail from the bin/packages tier to $target_lib_folder (if source file changed)" + set resultdict [punkcheck::install $pkgdir $target_lib_folder/$pkgtail\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } + } else { + puts stdout "PACKAGES: no bin/packages tier at $packages_root (populate via 'make.tcl libfetch') - skipping lib-tier package deployment" + } } if {$::punkboot::command in {bakehouse bake vfslibs}} { @@ -5672,9 +5702,42 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { if {[dict exists $ientry supersedes]} { set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] } - set source_pkg_folder $sourcefolder/$isource + #source_root (G-139): 'src' (default - the original vendorlib + #declarations) or 'packages' - resolve against the untracked + #bin/packages lib-tier input (populated by 'make.tcl libfetch'). + set isource_root "src" + if {[dict exists $ientry source_root]} { + set isource_root [punkboot::lib::toml_untag [dict get $ientry source_root]] + } + #replace (G-139): clean-slate a SAME-NAMED existing target folder + #before install. supersedes deliberately cannot name the folder being + #installed; replace covers same-name refreshes where the old copy may + #carry strays the new tree lacks (e.g. hand-drops nesting md5c/tcllibc + #inside tcllib2.0). + set ireplace 0 + if {[dict exists $ientry replace]} { + set ireplace [punkboot::lib::toml_untag [dict get $ientry replace]] + if {![string is boolean -strict $ireplace]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname 'replace' must be boolean (got '$ireplace')$A(RST)" + exit 3 + } + } + switch -- $isource_root { + src { + set source_pkg_folder $sourcefolder/$isource + set source_missing_hint "" + } + packages { + set source_pkg_folder $projectroot/bin/packages/$isource + set source_missing_hint " - populate the bin/packages tier first: 'tclsh src/make.tcl libfetch' (declarations in src/runtime/libpackages.toml)" + } + default { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname has unknown source_root '$isource_root' (expected src|packages)$A(RST)" + exit 3 + } + } if {![file isdirectory $source_pkg_folder]} { - puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$source_missing_hint$A(RST)" exit 3 } set pkgtail [file tail $source_pkg_folder] @@ -5704,6 +5767,10 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { file delete -force $suppath } } + if {$ireplace && [file isdirectory $target_lib_folder/$pkgtail]} { + puts stdout "VFSLIBS install.$iname: replace=true - removing existing '$pkgtail' from vfs/$t (clean-slate install)" + file delete -force $target_lib_folder/$pkgtail + } puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ -installer make.tcl\ 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 1a4aafe3..75e7391e 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 tool project vfs] + variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate libfetch bootsupport vfscommonupdate projectversion workflow buildsuite tool project vfs] } @@ -2135,6 +2135,10 @@ proc ::punkboot::punkboot_gethelp {args} { append h " - propagate declared vendored platform-library packages into kit vfs lib_tcl trees" \n append h " - declarations (per-package per-kit, with superseded-version removal) live in src/runtime/vendorlib_vfs.toml" \n append h " - also runs automatically as a phase of '$scriptname bake' (and therefore of '$scriptname bakehouse')" \n \n + append h " $scriptname libfetch ?-serverurl ? ?-trust-server? ?-force?" \n + append h " - fetch declared punkbin lib-tier library artifacts (src/runtime/libpackages.toml) into bin/packages/" \n + append h " - sha1-verified against the server's per-target sha1sums.txt; materializes package trees under bin/packages//tcl" \n + append h " - canonical origin trusted by default; other servers (incl. PUNKBIN_URL overrides) require -trust-server" \n \n append h " $scriptname vfscommonupdate" \n append h " - promotion gate: update the src/vfs/_vfscommon.vfs from compiled src/modules and src/lib etc" \n append h " - before calling this (followed by '$scriptname bake') - you can test using '(.exe) dev'" \n @@ -2323,6 +2327,26 @@ DIAGRAM 2 - KIT ASSEMBLY DETAIL (the 'make.tcl bake' stage; incl. vfslibs phase) running shell still holds the old exe [K5] +LIBRARY-ARTIFACT FETCH (G-139; input tier so far - lib_tcl/kit consumption follows) +---------------------------------------------------------------------------- + + src/runtime/libpackages.toml which punkbin LIB-TIER artifacts this checkout + consumes (declared artifact names + targets) + | + | 'make.tcl libfetch' - sha1-verified download of the declared + | --r.zip artifacts + .toml sidecars from + | /lib// (canonical punkbin origin trusted by + | default; -serverurl/PUNKBIN_URL override needs -trust-server; + | file:// origins serve local mirrors/fixtures), then + | materialization of each verified zip's package tree + v + bin/packages// verified -r zips + toml sidecars (input tier, + untracked - mirrors bin/runtime/) + bin/packages//tcl// + materialized installed-shape trees, self-describing + via their embedded punkbin-artifact.toml records + + KEY / NOTES ---------------------------------------------------------------------------- [K1] Source modules carry the literal magic version 999999.0a1.0 in the filename. @@ -2527,6 +2551,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO vfslibs "Propagate declared vendored platform-library packages into kit vfs lib_tcl trees" bin "Install executables from src/bin into /bin, then build kits as for bake" vendorupdate "Update src/vendormodules based on src/vendormodules/include_modules.config" + libfetch "Fetch declared punkbin lib-tier library artifacts into bin/packages/ (sha1-verified) and materialize the package trees" bootsupport "Promotion gate: update src/bootsupport modules from built project modules" vfscommonupdate "Promotion gate: replace src/vfs/_vfscommon.vfs modules and libs with the project's built set" info "Show the name and base folder of the project to be built" @@ -2630,6 +2655,24 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO Also runs automatically as a phase of 'make.tcl bake' (and therefore of 'make.tcl bakehouse'); this standalone narrowing serves quick iteration on the declarations." + libfetch + " + Fetch the punkbin LIB-TIER library artifacts declared in + src/runtime/libpackages.toml into the untracked input tier + bin/packages// - immutable --r.zip + artifacts plus their .toml sidecars, each verified against the + server's per-target sha1sums.txt - then materialize each verified + zip's installed-shape package tree under + bin/packages//tcl/ (self-describing via the embedded + punkbin-artifact.toml record; G-138/G-139). + Present artifacts that already verify are skipped (-force + re-downloads). The canonical punkbin origin is trusted by + default; -serverurl (or env PUNKBIN_URL) overrides it, and any + NON-canonical origin requires the explicit -trust-server flag + (never an interactive prompt - unattended runs stay unattended). + A file:// origin is supported for local mirrors/fixtures. + Transport for http(s): Tcl http+tls when loadable, else curl, + else PowerShell (windows)." bin " Install executables from src/bin to /bin, then check vfs @@ -2799,6 +2842,30 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO (packages -> test -> bootsupport/vfscommonupdate -> commit -> bake). Pass -dirty-abort 0 to build anyway with PROVENANCE-WARNINGs." } + #libfetch fragments (G-139): consent is keyed to SERVER TRUST (the G-123 + #posture) - the canonical punkbin origin needs no acknowledgement, any + #other origin needs the explicit flag. Never an interactive prompt. + variable OPT_SERVERURL { + -serverurl -type string -default "" -help\ + "Artifact server base url override (default: the canonical punkbin + origin; env PUNKBIN_URL overrides the default, this flag overrides + both). The lib tier is addressed as /lib//. + file:// urls are supported for local mirrors and fixtures. + A NON-canonical origin additionally requires -trust-server." + } + variable OPT_TRUSTSERVER { + -trust-server -type none -default 0 -help\ + "Acknowledge fetching from a NON-canonical artifact server + (consent keyed to server trust - the interim G-123/G-006 + mechanism). Without this flag, a non-canonical -serverurl or + PUNKBIN_URL aborts before any network access. Fetches from the + canonical origin never require it." + } + variable OPT_FORCE { + -force -type none -default 0 -help\ + "Re-download and re-materialize even when a present artifact + already verifies against the server sha1sums." + } #which shared option fragments each subcommand declares #(shell and help have bespoke definitions below: shell passes trailing args # through to the repl untouched, help takes a subject value) @@ -2812,6 +2879,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO vfslibs {OPT_DIRTYABORT OPT_CONFIRM} bin {OPT_FORCEKILL OPT_DIRTYABORT OPT_CONFIRM} vendorupdate {OPT_CONFIRM} + libfetch {OPT_SERVERURL OPT_TRUSTSERVER OPT_FORCE} bootsupport {OPT_DIRTYABORT OPT_CONFIRM} vfscommonupdate {OPT_DIRTYABORT OPT_CONFIRM} info {OPT_CONFIRM} @@ -2829,6 +2897,9 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO OPT_DIRTYABORT "?-dirty-abort?" OPT_DIRTYABORT_ON "?-dirty-abort 1|0?" OPT_CONFIRM "?-confirm 0|1?" + OPT_SERVERURL "?-serverurl ?" + OPT_TRUSTSERVER "?-trust-server?" + OPT_FORCE "?-force?" } #per-subcommand positional values (default: none). bake (and its deprecated #alias vfs) and bakelist take optional kit names (G-121 selective bake). @@ -2940,7 +3011,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO variable SUBGROUPS { "build & bake" {bakehouse packages modules libs bake vfslibs bin} "promotion gates" {bootsupport vfscommonupdate} - "source maintenance" {vendorupdate} + "source maintenance" {vendorupdate libfetch} "buildsuites" {buildsuite} "vendored tools" {tool} "informational" {info check bakelist projectversion workflow help} @@ -2996,6 +3067,9 @@ set help_exitcode 0 set ::punkboot::opt_forcekill 0 set ::punkboot::opt_dirty_abort 0 set ::punkboot::opt_confirm 1 +set ::punkboot::opt_serverurl "" +set ::punkboot::opt_trust_server 0 +set ::punkboot::opt_force 0 set ::punkboot::opt_kitnames [list] ;#G-121: requested kit names for bake/bakelist (empty = all configured) if {$::punkboot::punkargs_ok} { @@ -3046,7 +3120,7 @@ if {$::punkboot::punkargs_ok} { } set ::punkboot::command $subcommand set _opts [dict get $argd opts] - foreach {_optname _var} {-k opt_forcekill -dirty-abort opt_dirty_abort -confirm opt_confirm} { + foreach {_optname _var} {-k opt_forcekill -dirty-abort opt_dirty_abort -confirm opt_confirm -serverurl opt_serverurl -trust-server opt_trust_server -force opt_force} { if {[dict exists $_opts $_optname]} { set ::punkboot::$_var [dict get $_opts $_optname] } @@ -3083,6 +3157,12 @@ if {$::punkboot::punkargs_ok} { switch -- $a { -k {set ::punkboot::opt_forcekill 1} -dirty-abort {set ::punkboot::opt_dirty_abort 1} + -trust-server {set ::punkboot::opt_trust_server 1} + -force {set ::punkboot::opt_force 1} + -serverurl { + set ::punkboot::opt_serverurl [lindex $argsleft 0] + set argsleft [lrange $argsleft 1 end] + } -confirm { set v [lindex $argsleft 0] set argsleft [lrange $argsleft 1 end] @@ -5121,6 +5201,303 @@ if {$::punkboot::command eq "bootsupport"} { +if {$::punkboot::command eq "libfetch"} { + #G-139 phase 1: fetch the punkbin LIB-TIER artifacts declared in + #src/runtime/libpackages.toml into the untracked bin/packages/ input + #tier (mirroring bin/runtime/) with sha1 verification against the + #server's per-target sha1sums.txt, then materialize each verified zip's + #installed-shape package tree under bin/packages//tcl/ (the tree + #is self-describing via its embedded punkbin-artifact.toml - the G-138 + #record). Consumption into lib_tcl/ and kit vfs trees is the remainder + #of G-139 - this entrypoint owns fetch + verify + materialize only. + + proc ::punkboot::libfetch_transfer {url outfile} { + #one url -> local file. file:// is served natively (local mirrors, + #fixtures - no network, no external tools). http(s) transports in + #order: Tcl http (+tls for https) when loadable, curl (ubiquitous, + #including stock windows 10+), powershell Invoke-WebRequest (windows). + #Returns the transport used; errors when all fail. + file mkdir [file dirname $outfile] + if {[string match -nocase "file://*" $url]} { + set path [string range $url 7 end] + if {[regexp {^/+([A-Za-z]:/.*)$} $path -> winpath]} { + set path $winpath ;#file:///C:/x -> C:/x + } + if {![file isfile $path]} { + error "file origin has no such file: $path" + } + file copy -force $path $outfile + return "file-copy" + } + set is_https [string match -nocase "https://*" $url] + set tcl_http_ok 0 + if {![catch {package require http}]} { + if {!$is_https} { + set tcl_http_ok 1 + } elseif {![catch {package require tls}]} { + catch {http::register https 443 [list ::tls::socket -autoservername true]} + set tcl_http_ok 1 + } + } + if {$tcl_http_ok} { + set fd [open $outfile wb] + set ok 0 + if {![catch {http::geturl $url -channel $fd -binary 1} tok]} { + if {[http::status $tok] eq "ok" && [http::ncode $tok] == 200} {set ok 1} + http::cleanup $tok + } + close $fd + if {$ok} {return "tcl-http"} + file delete -force $outfile + } + set curl [auto_execok curl] + if {$curl ne ""} { + if {![catch {exec {*}$curl --fail -sSL -o $outfile $url 2>@1} curlerr]} { + return "curl" + } + file delete -force $outfile + } + if {$::tcl_platform(platform) eq "windows"} { + set ps [auto_execok powershell] + if {$ps ne ""} { + set pscmd "\$ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '$url' -OutFile '$outfile' -ErrorAction Stop" + if {![catch {exec {*}$ps -NoProfile -NonInteractive -Command $pscmd 2>@1} pserr]} { + return "powershell" + } + file delete -force $outfile + } + } + set detail "" + if {[info exists curlerr]} {set detail " (curl: [string range $curlerr 0 200])"} + error "all transports failed for $url$detail" + } + + set libfetch_config $sourcefolder/runtime/libpackages.toml + #Canonical origin: the punkbin raw-file base (the punk-runtime.cmd + #convention; lib tier addressed as /lib//...). + #Server-trust consent (G-139, the G-123 posture): the canonical origin + #never gates; ANY other origin - flag or PUNKBIN_URL env - requires the + #explicit -trust-server flag. Never an interactive prompt: unattended + #runs stay unattended, and refusal happens before any network access. + set libfetch_canonical "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master" + set libfetch_origin $libfetch_canonical + if {[info exists ::env(PUNKBIN_URL)] && $::env(PUNKBIN_URL) ne ""} { + set libfetch_origin $::env(PUNKBIN_URL) + } + if {$::punkboot::opt_serverurl ne ""} { + set libfetch_origin $::punkboot::opt_serverurl + } + set libfetch_origin [string trimright $libfetch_origin /] + if {$libfetch_origin ne $libfetch_canonical && !$::punkboot::opt_trust_server} { + puts stderr "$A(BAD)LIBFETCH: origin '$libfetch_origin' is not the canonical punkbin origin$A(RST)" + puts stderr "LIBFETCH: canonical: $libfetch_canonical" + puts stderr "LIBFETCH: fetching from a non-canonical server requires the explicit -trust-server flag (consent keyed to server trust - G-139/G-123). Nothing was fetched." + exit 3 + } + if {![file exists $libfetch_config]} { + puts stdout "LIBFETCH: no config at $libfetch_config - nothing declared, nothing to fetch" + } else { + if {[catch {package require tomlish} tomlish_err]} { + puts stderr "$A(BAD)LIBFETCH: cannot process $libfetch_config - tomlish package unavailable ($tomlish_err)$A(RST)" + exit 3 + } + if {[catch {package require sha1} sha1_err]} { + puts stderr "$A(BAD)LIBFETCH: sha1 package unavailable ($sha1_err) - cannot verify artifacts$A(RST)" + exit 3 + } + if {[catch {package require punk::zip} punkzip_err]} { + puts stderr "$A(BAD)LIBFETCH: punk::zip package unavailable ($punkzip_err) - cannot materialize package trees$A(RST)" + exit 3 + } + set fd [open $libfetch_config r] + fconfigure $fd -encoding utf-8 + set libfetch_tomldata [read $fd] + close $fd + if {[catch {tomlish::to_dict [tomlish::from_toml $libfetch_tomldata]} libfetch_dict]} { + puts stderr "$A(BAD)LIBFETCH: failed to parse $libfetch_config\n$libfetch_dict$A(RST)" + exit 3 + } + if {![llength [info commands ::punkboot::lib::toml_untag]]} { + #tomlish::to_dict values are type-tagged (also defined by the vfslibs + #phase - that block does not run for the libfetch command) + proc ::punkboot::lib::toml_untag {tagged} { + if {[dict get $tagged type] eq "ARRAY"} { + set plain [list] + foreach el [dict get $tagged value] { + lappend plain [::punkboot::lib::toml_untag $el] + } + return $plain + } + return [dict get $tagged value] + } + } + set libfetch_artifacts [dict create] + if {[dict exists $libfetch_dict artifact]} { + set libfetch_artifacts [dict get $libfetch_dict artifact] + } + if {![dict size $libfetch_artifacts]} { + puts stdout "LIBFETCH: no \[artifact.\] entries found in $libfetch_config" + } + puts stdout "LIBFETCH: origin $libfetch_origin ([dict size $libfetch_artifacts] declared artifact(s))" + set libfetch_sums [dict create] ;#target -> dict(filename -> sha1) from the server's sha1sums.txt + set libfetch_failed 0 + set libfetch_actions [list] + dict for {aname aentry} $libfetch_artifacts { + foreach reqkey {name target} { + if {![dict exists $aentry $reqkey]} { + puts stderr "$A(BAD)LIBFETCH: entry artifact.$aname in $libfetch_config is missing required key '$reqkey'$A(RST)" + exit 3 + } + } + set artifact [punkboot::lib::toml_untag [dict get $aentry name]] + set target [punkboot::lib::toml_untag [dict get $aentry target]] + if {![regexp {^(.+)-(tcl[0-9]+)-r([0-9]+)\.zip$} $artifact -> pkgfolder generation rev]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname name '$artifact' does not match -tcl-r.zip$A(RST)" + exit 3 + } + set tierdir $projectroot/bin/packages/$target + file mkdir $tierdir + #server truth per target: lib//sha1sums.txt (fetched once per + #run per target; the local copy in the tier mirrors the punk-runtime + #store idiom) + if {![dict exists $libfetch_sums $target]} { + set sumsurl "$libfetch_origin/lib/$target/sha1sums.txt" + set sumslocal $tierdir/sha1sums.txt + if {[catch {::punkboot::libfetch_transfer $sumsurl $sumslocal} terr]} { + puts stderr "$A(BAD)LIBFETCH: cannot fetch $sumsurl - $terr$A(RST)" + exit 3 + } + set sfd [open $sumslocal r] + set sumsdata [read $sfd] + close $sfd + set sums [dict create] + foreach ln [split $sumsdata \n] { + set ln [string trim $ln] + if {$ln eq ""} {continue} + set sp [string first " " $ln] + if {$sp < 1} {continue} + set h [string range $ln 0 $sp-1] + set tag [string range $ln $sp+1 end] + dict set sums [string range $tag 1 end] [string tolower $h] + } + dict set libfetch_sums $target $sums + } + set sums [dict get $libfetch_sums $target] + if {![dict exists $sums $artifact]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: '$artifact' is not listed in the server's lib/$target/sha1sums.txt$A(RST)" + set libfetch_failed 1 + continue + } + set want_sha1 [dict get $sums $artifact] + set ziplocal $tierdir/$artifact + set have_ok 0 + if {[file isfile $ziplocal] && !$::punkboot::opt_force} { + if {[string tolower [sha1::sha1 -hex -file $ziplocal]] eq $want_sha1} { + set have_ok 1 + } + } + if {!$have_ok} { + set zipurl "$libfetch_origin/lib/$target/$artifact" + set partfile $ziplocal.part + if {[catch {::punkboot::libfetch_transfer $zipurl $partfile} terr]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: fetch failed - $terr$A(RST)" + set libfetch_failed 1 + continue + } + set got_sha1 [string tolower [sha1::sha1 -hex -file $partfile]] + if {$got_sha1 ne $want_sha1} { + file delete -force $partfile + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sha1 MISMATCH for $artifact (got $got_sha1, server sha1sums says $want_sha1) - deleted$A(RST)" + set libfetch_failed 1 + continue + } + file rename -force $partfile $ziplocal + lappend libfetch_actions "fetched lib/$target/$artifact" + } else { + lappend libfetch_actions "verified-present lib/$target/$artifact" + } + #beside-toml sidecar (also sha1sums-listed by emission) + set sidecar "[string range $artifact 0 end-4].toml" + set sidecarlocal $tierdir/$sidecar + set sidecar_ok 0 + if {[dict exists $sums $sidecar]} { + set want_side [dict get $sums $sidecar] + if {[file isfile $sidecarlocal] && !$::punkboot::opt_force} { + if {[string tolower [sha1::sha1 -hex -file $sidecarlocal]] eq $want_side} { + set sidecar_ok 1 + } + } + if {!$sidecar_ok} { + if {[catch {::punkboot::libfetch_transfer "$libfetch_origin/lib/$target/$sidecar" $sidecarlocal.part} terr]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sidecar fetch failed - $terr$A(RST)" + set libfetch_failed 1 + continue + } + if {[string tolower [sha1::sha1 -hex -file $sidecarlocal.part]] ne $want_side} { + file delete -force $sidecarlocal.part + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sidecar sha1 MISMATCH for $sidecar - deleted$A(RST)" + set libfetch_failed 1 + continue + } + file rename -force $sidecarlocal.part $sidecarlocal + } + } else { + puts stderr "LIBFETCH: note - $sidecar not listed in server sha1sums (artifact accepted; sidecar skipped)" + } + #materialize: installed-shape tree under /tcl/ + #(generation subdir because both generations install identically-named + #folders). Fresh tree per materialization; -mtime restores member + #mtimes so the tree mirrors what was zipped. + set gendir $tierdir/$generation + file mkdir $gendir + set treedir $gendir/$pkgfolder + if {[file isdirectory $treedir] && !$::punkboot::opt_force} { + #re-materialize only when the tree's embedded record no longer + #matches this artifact name (self-describing tier - G-138) + set embrec $treedir/punkbin-artifact.toml + set tree_current 0 + if {[file isfile $embrec]} { + set efd [open $embrec r] + set edata [read $efd] + close $efd + if {[regexp -line "^name = \"$artifact\"$" $edata]} { + set tree_current 1 + } + } + if {$tree_current} { + lappend libfetch_actions "tree-current $target/$generation/$pkgfolder" + continue + } + } + file delete -force $treedir + #defaults: whole archive, -overwrite 1 -mtime 1 (member mtimes restored) + #-verify 1 (per-member crc32) - and accelerator-eligible (punkzip exe) + if {[catch {punk::zip::unzip $ziplocal $gendir} unzip_err]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: materialization failed - $unzip_err$A(RST)" + file delete -force $treedir + set libfetch_failed 1 + continue + } + if {![file isfile $treedir/pkgIndex.tcl] || ![file isfile $treedir/punkbin-artifact.toml]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: materialized tree at $treedir lacks pkgIndex.tcl and/or punkbin-artifact.toml$A(RST)" + set libfetch_failed 1 + continue + } + lappend libfetch_actions "materialized $target/$generation/$pkgfolder" + } + foreach act $libfetch_actions { + puts stdout "LIBFETCH: $act" + } + if {$libfetch_failed} { + puts stderr "$A(BAD)LIBFETCH: FAILED - one or more declared artifacts could not be fetched/verified/materialized (see above)$A(RST)" + exit 3 + } + puts stdout "LIBFETCH: done ([llength $libfetch_actions] action(s)) -> $projectroot/bin/packages" + } + flush stdout + ::exit 0 +} + if {$::punkboot::command ni {bakehouse packages modules libs bake vfslibs bin}} { puts stderr "Command $::punkboot::command not implemented - aborting." flush stderr @@ -5241,6 +5618,36 @@ if {$::punkboot::command in {bakehouse packages libs}} { if {![llength $vendorlibfolders]} { puts stderr "$A(BAD)VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found.$A(RST)" } + #G-139: punkbin lib-tier packages from the bin/packages input tier (populated + #by 'make.tcl libfetch') into the deployed lib_tcl trees - allplatforms + + #this host's platform dir, mirroring the vendorlib_tcl shape above. The + #materialized trees are self-describing (embedded punkbin-artifact.toml + #records - G-138) and the records ride along into the deployed trees, and + #from there into kit vfs payloads via the vfslibs declarations. An absent + #tier (or target/generation subdir) is a silent per-item no-op: the tier is + #fetch-regenerable and optional until artifacts are declared and fetched. + set packages_root $projectroot/bin/packages + if {[file isdirectory $packages_root]} { + foreach ptarget [list allplatforms $this_platform_generic] { + foreach gendir [lsort [glob -nocomplain -dir $packages_root/$ptarget -type d tcl*]] { + set which [file tail $gendir] ;#tcl8|tcl9 ... + foreach pkgdir [lsort [glob -nocomplain -dir $gendir -type d *]] { + set pkgtail [file tail $pkgdir] + set target_lib_folder $projectroot/lib_$which/$ptarget + file mkdir $target_lib_folder + puts stdout "PACKAGES_$which: copying $ptarget/$which/$pkgtail from the bin/packages tier to $target_lib_folder (if source file changed)" + set resultdict [punkcheck::install $pkgdir $target_lib_folder/$pkgtail\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } + } else { + puts stdout "PACKAGES: no bin/packages tier at $packages_root (populate via 'make.tcl libfetch') - skipping lib-tier package deployment" + } } if {$::punkboot::command in {bakehouse bake vfslibs}} { @@ -5295,9 +5702,42 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { if {[dict exists $ientry supersedes]} { set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] } - set source_pkg_folder $sourcefolder/$isource + #source_root (G-139): 'src' (default - the original vendorlib + #declarations) or 'packages' - resolve against the untracked + #bin/packages lib-tier input (populated by 'make.tcl libfetch'). + set isource_root "src" + if {[dict exists $ientry source_root]} { + set isource_root [punkboot::lib::toml_untag [dict get $ientry source_root]] + } + #replace (G-139): clean-slate a SAME-NAMED existing target folder + #before install. supersedes deliberately cannot name the folder being + #installed; replace covers same-name refreshes where the old copy may + #carry strays the new tree lacks (e.g. hand-drops nesting md5c/tcllibc + #inside tcllib2.0). + set ireplace 0 + if {[dict exists $ientry replace]} { + set ireplace [punkboot::lib::toml_untag [dict get $ientry replace]] + if {![string is boolean -strict $ireplace]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname 'replace' must be boolean (got '$ireplace')$A(RST)" + exit 3 + } + } + switch -- $isource_root { + src { + set source_pkg_folder $sourcefolder/$isource + set source_missing_hint "" + } + packages { + set source_pkg_folder $projectroot/bin/packages/$isource + set source_missing_hint " - populate the bin/packages tier first: 'tclsh src/make.tcl libfetch' (declarations in src/runtime/libpackages.toml)" + } + default { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname has unknown source_root '$isource_root' (expected src|packages)$A(RST)" + exit 3 + } + } if {![file isdirectory $source_pkg_folder]} { - puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$source_missing_hint$A(RST)" exit 3 } set pkgtail [file tail $source_pkg_folder] @@ -5327,6 +5767,10 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { file delete -force $suppath } } + if {$ireplace && [file isdirectory $target_lib_folder/$pkgtail]} { + puts stdout "VFSLIBS install.$iname: replace=true - removing existing '$pkgtail' from vfs/$t (clean-slate install)" + file delete -force $target_lib_folder/$pkgtail + } puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ -installer make.tcl\ @@ -5341,6 +5785,7 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { } } + if {$::punkboot::command in {bakehouse packages libs}} { ######################################################## lappend projectlibfolders lib diff --git a/src/project_layouts/vendor/punk/basic/src/make.tcl b/src/project_layouts/vendor/punk/basic/src/make.tcl index 1a4aafe3..75e7391e 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 tool project vfs] + variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate libfetch bootsupport vfscommonupdate projectversion workflow buildsuite tool project vfs] } @@ -2135,6 +2135,10 @@ proc ::punkboot::punkboot_gethelp {args} { append h " - propagate declared vendored platform-library packages into kit vfs lib_tcl trees" \n append h " - declarations (per-package per-kit, with superseded-version removal) live in src/runtime/vendorlib_vfs.toml" \n append h " - also runs automatically as a phase of '$scriptname bake' (and therefore of '$scriptname bakehouse')" \n \n + append h " $scriptname libfetch ?-serverurl ? ?-trust-server? ?-force?" \n + append h " - fetch declared punkbin lib-tier library artifacts (src/runtime/libpackages.toml) into bin/packages/" \n + append h " - sha1-verified against the server's per-target sha1sums.txt; materializes package trees under bin/packages//tcl" \n + append h " - canonical origin trusted by default; other servers (incl. PUNKBIN_URL overrides) require -trust-server" \n \n append h " $scriptname vfscommonupdate" \n append h " - promotion gate: update the src/vfs/_vfscommon.vfs from compiled src/modules and src/lib etc" \n append h " - before calling this (followed by '$scriptname bake') - you can test using '(.exe) dev'" \n @@ -2323,6 +2327,26 @@ DIAGRAM 2 - KIT ASSEMBLY DETAIL (the 'make.tcl bake' stage; incl. vfslibs phase) running shell still holds the old exe [K5] +LIBRARY-ARTIFACT FETCH (G-139; input tier so far - lib_tcl/kit consumption follows) +---------------------------------------------------------------------------- + + src/runtime/libpackages.toml which punkbin LIB-TIER artifacts this checkout + consumes (declared artifact names + targets) + | + | 'make.tcl libfetch' - sha1-verified download of the declared + | --r.zip artifacts + .toml sidecars from + | /lib// (canonical punkbin origin trusted by + | default; -serverurl/PUNKBIN_URL override needs -trust-server; + | file:// origins serve local mirrors/fixtures), then + | materialization of each verified zip's package tree + v + bin/packages// verified -r zips + toml sidecars (input tier, + untracked - mirrors bin/runtime/) + bin/packages//tcl// + materialized installed-shape trees, self-describing + via their embedded punkbin-artifact.toml records + + KEY / NOTES ---------------------------------------------------------------------------- [K1] Source modules carry the literal magic version 999999.0a1.0 in the filename. @@ -2527,6 +2551,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO vfslibs "Propagate declared vendored platform-library packages into kit vfs lib_tcl trees" bin "Install executables from src/bin into /bin, then build kits as for bake" vendorupdate "Update src/vendormodules based on src/vendormodules/include_modules.config" + libfetch "Fetch declared punkbin lib-tier library artifacts into bin/packages/ (sha1-verified) and materialize the package trees" bootsupport "Promotion gate: update src/bootsupport modules from built project modules" vfscommonupdate "Promotion gate: replace src/vfs/_vfscommon.vfs modules and libs with the project's built set" info "Show the name and base folder of the project to be built" @@ -2630,6 +2655,24 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO Also runs automatically as a phase of 'make.tcl bake' (and therefore of 'make.tcl bakehouse'); this standalone narrowing serves quick iteration on the declarations." + libfetch + " + Fetch the punkbin LIB-TIER library artifacts declared in + src/runtime/libpackages.toml into the untracked input tier + bin/packages// - immutable --r.zip + artifacts plus their .toml sidecars, each verified against the + server's per-target sha1sums.txt - then materialize each verified + zip's installed-shape package tree under + bin/packages//tcl/ (self-describing via the embedded + punkbin-artifact.toml record; G-138/G-139). + Present artifacts that already verify are skipped (-force + re-downloads). The canonical punkbin origin is trusted by + default; -serverurl (or env PUNKBIN_URL) overrides it, and any + NON-canonical origin requires the explicit -trust-server flag + (never an interactive prompt - unattended runs stay unattended). + A file:// origin is supported for local mirrors/fixtures. + Transport for http(s): Tcl http+tls when loadable, else curl, + else PowerShell (windows)." bin " Install executables from src/bin to /bin, then check vfs @@ -2799,6 +2842,30 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO (packages -> test -> bootsupport/vfscommonupdate -> commit -> bake). Pass -dirty-abort 0 to build anyway with PROVENANCE-WARNINGs." } + #libfetch fragments (G-139): consent is keyed to SERVER TRUST (the G-123 + #posture) - the canonical punkbin origin needs no acknowledgement, any + #other origin needs the explicit flag. Never an interactive prompt. + variable OPT_SERVERURL { + -serverurl -type string -default "" -help\ + "Artifact server base url override (default: the canonical punkbin + origin; env PUNKBIN_URL overrides the default, this flag overrides + both). The lib tier is addressed as /lib//. + file:// urls are supported for local mirrors and fixtures. + A NON-canonical origin additionally requires -trust-server." + } + variable OPT_TRUSTSERVER { + -trust-server -type none -default 0 -help\ + "Acknowledge fetching from a NON-canonical artifact server + (consent keyed to server trust - the interim G-123/G-006 + mechanism). Without this flag, a non-canonical -serverurl or + PUNKBIN_URL aborts before any network access. Fetches from the + canonical origin never require it." + } + variable OPT_FORCE { + -force -type none -default 0 -help\ + "Re-download and re-materialize even when a present artifact + already verifies against the server sha1sums." + } #which shared option fragments each subcommand declares #(shell and help have bespoke definitions below: shell passes trailing args # through to the repl untouched, help takes a subject value) @@ -2812,6 +2879,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO vfslibs {OPT_DIRTYABORT OPT_CONFIRM} bin {OPT_FORCEKILL OPT_DIRTYABORT OPT_CONFIRM} vendorupdate {OPT_CONFIRM} + libfetch {OPT_SERVERURL OPT_TRUSTSERVER OPT_FORCE} bootsupport {OPT_DIRTYABORT OPT_CONFIRM} vfscommonupdate {OPT_DIRTYABORT OPT_CONFIRM} info {OPT_CONFIRM} @@ -2829,6 +2897,9 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO OPT_DIRTYABORT "?-dirty-abort?" OPT_DIRTYABORT_ON "?-dirty-abort 1|0?" OPT_CONFIRM "?-confirm 0|1?" + OPT_SERVERURL "?-serverurl ?" + OPT_TRUSTSERVER "?-trust-server?" + OPT_FORCE "?-force?" } #per-subcommand positional values (default: none). bake (and its deprecated #alias vfs) and bakelist take optional kit names (G-121 selective bake). @@ -2940,7 +3011,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO variable SUBGROUPS { "build & bake" {bakehouse packages modules libs bake vfslibs bin} "promotion gates" {bootsupport vfscommonupdate} - "source maintenance" {vendorupdate} + "source maintenance" {vendorupdate libfetch} "buildsuites" {buildsuite} "vendored tools" {tool} "informational" {info check bakelist projectversion workflow help} @@ -2996,6 +3067,9 @@ set help_exitcode 0 set ::punkboot::opt_forcekill 0 set ::punkboot::opt_dirty_abort 0 set ::punkboot::opt_confirm 1 +set ::punkboot::opt_serverurl "" +set ::punkboot::opt_trust_server 0 +set ::punkboot::opt_force 0 set ::punkboot::opt_kitnames [list] ;#G-121: requested kit names for bake/bakelist (empty = all configured) if {$::punkboot::punkargs_ok} { @@ -3046,7 +3120,7 @@ if {$::punkboot::punkargs_ok} { } set ::punkboot::command $subcommand set _opts [dict get $argd opts] - foreach {_optname _var} {-k opt_forcekill -dirty-abort opt_dirty_abort -confirm opt_confirm} { + foreach {_optname _var} {-k opt_forcekill -dirty-abort opt_dirty_abort -confirm opt_confirm -serverurl opt_serverurl -trust-server opt_trust_server -force opt_force} { if {[dict exists $_opts $_optname]} { set ::punkboot::$_var [dict get $_opts $_optname] } @@ -3083,6 +3157,12 @@ if {$::punkboot::punkargs_ok} { switch -- $a { -k {set ::punkboot::opt_forcekill 1} -dirty-abort {set ::punkboot::opt_dirty_abort 1} + -trust-server {set ::punkboot::opt_trust_server 1} + -force {set ::punkboot::opt_force 1} + -serverurl { + set ::punkboot::opt_serverurl [lindex $argsleft 0] + set argsleft [lrange $argsleft 1 end] + } -confirm { set v [lindex $argsleft 0] set argsleft [lrange $argsleft 1 end] @@ -5121,6 +5201,303 @@ if {$::punkboot::command eq "bootsupport"} { +if {$::punkboot::command eq "libfetch"} { + #G-139 phase 1: fetch the punkbin LIB-TIER artifacts declared in + #src/runtime/libpackages.toml into the untracked bin/packages/ input + #tier (mirroring bin/runtime/) with sha1 verification against the + #server's per-target sha1sums.txt, then materialize each verified zip's + #installed-shape package tree under bin/packages//tcl/ (the tree + #is self-describing via its embedded punkbin-artifact.toml - the G-138 + #record). Consumption into lib_tcl/ and kit vfs trees is the remainder + #of G-139 - this entrypoint owns fetch + verify + materialize only. + + proc ::punkboot::libfetch_transfer {url outfile} { + #one url -> local file. file:// is served natively (local mirrors, + #fixtures - no network, no external tools). http(s) transports in + #order: Tcl http (+tls for https) when loadable, curl (ubiquitous, + #including stock windows 10+), powershell Invoke-WebRequest (windows). + #Returns the transport used; errors when all fail. + file mkdir [file dirname $outfile] + if {[string match -nocase "file://*" $url]} { + set path [string range $url 7 end] + if {[regexp {^/+([A-Za-z]:/.*)$} $path -> winpath]} { + set path $winpath ;#file:///C:/x -> C:/x + } + if {![file isfile $path]} { + error "file origin has no such file: $path" + } + file copy -force $path $outfile + return "file-copy" + } + set is_https [string match -nocase "https://*" $url] + set tcl_http_ok 0 + if {![catch {package require http}]} { + if {!$is_https} { + set tcl_http_ok 1 + } elseif {![catch {package require tls}]} { + catch {http::register https 443 [list ::tls::socket -autoservername true]} + set tcl_http_ok 1 + } + } + if {$tcl_http_ok} { + set fd [open $outfile wb] + set ok 0 + if {![catch {http::geturl $url -channel $fd -binary 1} tok]} { + if {[http::status $tok] eq "ok" && [http::ncode $tok] == 200} {set ok 1} + http::cleanup $tok + } + close $fd + if {$ok} {return "tcl-http"} + file delete -force $outfile + } + set curl [auto_execok curl] + if {$curl ne ""} { + if {![catch {exec {*}$curl --fail -sSL -o $outfile $url 2>@1} curlerr]} { + return "curl" + } + file delete -force $outfile + } + if {$::tcl_platform(platform) eq "windows"} { + set ps [auto_execok powershell] + if {$ps ne ""} { + set pscmd "\$ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '$url' -OutFile '$outfile' -ErrorAction Stop" + if {![catch {exec {*}$ps -NoProfile -NonInteractive -Command $pscmd 2>@1} pserr]} { + return "powershell" + } + file delete -force $outfile + } + } + set detail "" + if {[info exists curlerr]} {set detail " (curl: [string range $curlerr 0 200])"} + error "all transports failed for $url$detail" + } + + set libfetch_config $sourcefolder/runtime/libpackages.toml + #Canonical origin: the punkbin raw-file base (the punk-runtime.cmd + #convention; lib tier addressed as /lib//...). + #Server-trust consent (G-139, the G-123 posture): the canonical origin + #never gates; ANY other origin - flag or PUNKBIN_URL env - requires the + #explicit -trust-server flag. Never an interactive prompt: unattended + #runs stay unattended, and refusal happens before any network access. + set libfetch_canonical "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master" + set libfetch_origin $libfetch_canonical + if {[info exists ::env(PUNKBIN_URL)] && $::env(PUNKBIN_URL) ne ""} { + set libfetch_origin $::env(PUNKBIN_URL) + } + if {$::punkboot::opt_serverurl ne ""} { + set libfetch_origin $::punkboot::opt_serverurl + } + set libfetch_origin [string trimright $libfetch_origin /] + if {$libfetch_origin ne $libfetch_canonical && !$::punkboot::opt_trust_server} { + puts stderr "$A(BAD)LIBFETCH: origin '$libfetch_origin' is not the canonical punkbin origin$A(RST)" + puts stderr "LIBFETCH: canonical: $libfetch_canonical" + puts stderr "LIBFETCH: fetching from a non-canonical server requires the explicit -trust-server flag (consent keyed to server trust - G-139/G-123). Nothing was fetched." + exit 3 + } + if {![file exists $libfetch_config]} { + puts stdout "LIBFETCH: no config at $libfetch_config - nothing declared, nothing to fetch" + } else { + if {[catch {package require tomlish} tomlish_err]} { + puts stderr "$A(BAD)LIBFETCH: cannot process $libfetch_config - tomlish package unavailable ($tomlish_err)$A(RST)" + exit 3 + } + if {[catch {package require sha1} sha1_err]} { + puts stderr "$A(BAD)LIBFETCH: sha1 package unavailable ($sha1_err) - cannot verify artifacts$A(RST)" + exit 3 + } + if {[catch {package require punk::zip} punkzip_err]} { + puts stderr "$A(BAD)LIBFETCH: punk::zip package unavailable ($punkzip_err) - cannot materialize package trees$A(RST)" + exit 3 + } + set fd [open $libfetch_config r] + fconfigure $fd -encoding utf-8 + set libfetch_tomldata [read $fd] + close $fd + if {[catch {tomlish::to_dict [tomlish::from_toml $libfetch_tomldata]} libfetch_dict]} { + puts stderr "$A(BAD)LIBFETCH: failed to parse $libfetch_config\n$libfetch_dict$A(RST)" + exit 3 + } + if {![llength [info commands ::punkboot::lib::toml_untag]]} { + #tomlish::to_dict values are type-tagged (also defined by the vfslibs + #phase - that block does not run for the libfetch command) + proc ::punkboot::lib::toml_untag {tagged} { + if {[dict get $tagged type] eq "ARRAY"} { + set plain [list] + foreach el [dict get $tagged value] { + lappend plain [::punkboot::lib::toml_untag $el] + } + return $plain + } + return [dict get $tagged value] + } + } + set libfetch_artifacts [dict create] + if {[dict exists $libfetch_dict artifact]} { + set libfetch_artifacts [dict get $libfetch_dict artifact] + } + if {![dict size $libfetch_artifacts]} { + puts stdout "LIBFETCH: no \[artifact.\] entries found in $libfetch_config" + } + puts stdout "LIBFETCH: origin $libfetch_origin ([dict size $libfetch_artifacts] declared artifact(s))" + set libfetch_sums [dict create] ;#target -> dict(filename -> sha1) from the server's sha1sums.txt + set libfetch_failed 0 + set libfetch_actions [list] + dict for {aname aentry} $libfetch_artifacts { + foreach reqkey {name target} { + if {![dict exists $aentry $reqkey]} { + puts stderr "$A(BAD)LIBFETCH: entry artifact.$aname in $libfetch_config is missing required key '$reqkey'$A(RST)" + exit 3 + } + } + set artifact [punkboot::lib::toml_untag [dict get $aentry name]] + set target [punkboot::lib::toml_untag [dict get $aentry target]] + if {![regexp {^(.+)-(tcl[0-9]+)-r([0-9]+)\.zip$} $artifact -> pkgfolder generation rev]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname name '$artifact' does not match -tcl-r.zip$A(RST)" + exit 3 + } + set tierdir $projectroot/bin/packages/$target + file mkdir $tierdir + #server truth per target: lib//sha1sums.txt (fetched once per + #run per target; the local copy in the tier mirrors the punk-runtime + #store idiom) + if {![dict exists $libfetch_sums $target]} { + set sumsurl "$libfetch_origin/lib/$target/sha1sums.txt" + set sumslocal $tierdir/sha1sums.txt + if {[catch {::punkboot::libfetch_transfer $sumsurl $sumslocal} terr]} { + puts stderr "$A(BAD)LIBFETCH: cannot fetch $sumsurl - $terr$A(RST)" + exit 3 + } + set sfd [open $sumslocal r] + set sumsdata [read $sfd] + close $sfd + set sums [dict create] + foreach ln [split $sumsdata \n] { + set ln [string trim $ln] + if {$ln eq ""} {continue} + set sp [string first " " $ln] + if {$sp < 1} {continue} + set h [string range $ln 0 $sp-1] + set tag [string range $ln $sp+1 end] + dict set sums [string range $tag 1 end] [string tolower $h] + } + dict set libfetch_sums $target $sums + } + set sums [dict get $libfetch_sums $target] + if {![dict exists $sums $artifact]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: '$artifact' is not listed in the server's lib/$target/sha1sums.txt$A(RST)" + set libfetch_failed 1 + continue + } + set want_sha1 [dict get $sums $artifact] + set ziplocal $tierdir/$artifact + set have_ok 0 + if {[file isfile $ziplocal] && !$::punkboot::opt_force} { + if {[string tolower [sha1::sha1 -hex -file $ziplocal]] eq $want_sha1} { + set have_ok 1 + } + } + if {!$have_ok} { + set zipurl "$libfetch_origin/lib/$target/$artifact" + set partfile $ziplocal.part + if {[catch {::punkboot::libfetch_transfer $zipurl $partfile} terr]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: fetch failed - $terr$A(RST)" + set libfetch_failed 1 + continue + } + set got_sha1 [string tolower [sha1::sha1 -hex -file $partfile]] + if {$got_sha1 ne $want_sha1} { + file delete -force $partfile + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sha1 MISMATCH for $artifact (got $got_sha1, server sha1sums says $want_sha1) - deleted$A(RST)" + set libfetch_failed 1 + continue + } + file rename -force $partfile $ziplocal + lappend libfetch_actions "fetched lib/$target/$artifact" + } else { + lappend libfetch_actions "verified-present lib/$target/$artifact" + } + #beside-toml sidecar (also sha1sums-listed by emission) + set sidecar "[string range $artifact 0 end-4].toml" + set sidecarlocal $tierdir/$sidecar + set sidecar_ok 0 + if {[dict exists $sums $sidecar]} { + set want_side [dict get $sums $sidecar] + if {[file isfile $sidecarlocal] && !$::punkboot::opt_force} { + if {[string tolower [sha1::sha1 -hex -file $sidecarlocal]] eq $want_side} { + set sidecar_ok 1 + } + } + if {!$sidecar_ok} { + if {[catch {::punkboot::libfetch_transfer "$libfetch_origin/lib/$target/$sidecar" $sidecarlocal.part} terr]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sidecar fetch failed - $terr$A(RST)" + set libfetch_failed 1 + continue + } + if {[string tolower [sha1::sha1 -hex -file $sidecarlocal.part]] ne $want_side} { + file delete -force $sidecarlocal.part + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sidecar sha1 MISMATCH for $sidecar - deleted$A(RST)" + set libfetch_failed 1 + continue + } + file rename -force $sidecarlocal.part $sidecarlocal + } + } else { + puts stderr "LIBFETCH: note - $sidecar not listed in server sha1sums (artifact accepted; sidecar skipped)" + } + #materialize: installed-shape tree under /tcl/ + #(generation subdir because both generations install identically-named + #folders). Fresh tree per materialization; -mtime restores member + #mtimes so the tree mirrors what was zipped. + set gendir $tierdir/$generation + file mkdir $gendir + set treedir $gendir/$pkgfolder + if {[file isdirectory $treedir] && !$::punkboot::opt_force} { + #re-materialize only when the tree's embedded record no longer + #matches this artifact name (self-describing tier - G-138) + set embrec $treedir/punkbin-artifact.toml + set tree_current 0 + if {[file isfile $embrec]} { + set efd [open $embrec r] + set edata [read $efd] + close $efd + if {[regexp -line "^name = \"$artifact\"$" $edata]} { + set tree_current 1 + } + } + if {$tree_current} { + lappend libfetch_actions "tree-current $target/$generation/$pkgfolder" + continue + } + } + file delete -force $treedir + #defaults: whole archive, -overwrite 1 -mtime 1 (member mtimes restored) + #-verify 1 (per-member crc32) - and accelerator-eligible (punkzip exe) + if {[catch {punk::zip::unzip $ziplocal $gendir} unzip_err]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: materialization failed - $unzip_err$A(RST)" + file delete -force $treedir + set libfetch_failed 1 + continue + } + if {![file isfile $treedir/pkgIndex.tcl] || ![file isfile $treedir/punkbin-artifact.toml]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: materialized tree at $treedir lacks pkgIndex.tcl and/or punkbin-artifact.toml$A(RST)" + set libfetch_failed 1 + continue + } + lappend libfetch_actions "materialized $target/$generation/$pkgfolder" + } + foreach act $libfetch_actions { + puts stdout "LIBFETCH: $act" + } + if {$libfetch_failed} { + puts stderr "$A(BAD)LIBFETCH: FAILED - one or more declared artifacts could not be fetched/verified/materialized (see above)$A(RST)" + exit 3 + } + puts stdout "LIBFETCH: done ([llength $libfetch_actions] action(s)) -> $projectroot/bin/packages" + } + flush stdout + ::exit 0 +} + if {$::punkboot::command ni {bakehouse packages modules libs bake vfslibs bin}} { puts stderr "Command $::punkboot::command not implemented - aborting." flush stderr @@ -5241,6 +5618,36 @@ if {$::punkboot::command in {bakehouse packages libs}} { if {![llength $vendorlibfolders]} { puts stderr "$A(BAD)VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found.$A(RST)" } + #G-139: punkbin lib-tier packages from the bin/packages input tier (populated + #by 'make.tcl libfetch') into the deployed lib_tcl trees - allplatforms + + #this host's platform dir, mirroring the vendorlib_tcl shape above. The + #materialized trees are self-describing (embedded punkbin-artifact.toml + #records - G-138) and the records ride along into the deployed trees, and + #from there into kit vfs payloads via the vfslibs declarations. An absent + #tier (or target/generation subdir) is a silent per-item no-op: the tier is + #fetch-regenerable and optional until artifacts are declared and fetched. + set packages_root $projectroot/bin/packages + if {[file isdirectory $packages_root]} { + foreach ptarget [list allplatforms $this_platform_generic] { + foreach gendir [lsort [glob -nocomplain -dir $packages_root/$ptarget -type d tcl*]] { + set which [file tail $gendir] ;#tcl8|tcl9 ... + foreach pkgdir [lsort [glob -nocomplain -dir $gendir -type d *]] { + set pkgtail [file tail $pkgdir] + set target_lib_folder $projectroot/lib_$which/$ptarget + file mkdir $target_lib_folder + puts stdout "PACKAGES_$which: copying $ptarget/$which/$pkgtail from the bin/packages tier to $target_lib_folder (if source file changed)" + set resultdict [punkcheck::install $pkgdir $target_lib_folder/$pkgtail\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } + } else { + puts stdout "PACKAGES: no bin/packages tier at $packages_root (populate via 'make.tcl libfetch') - skipping lib-tier package deployment" + } } if {$::punkboot::command in {bakehouse bake vfslibs}} { @@ -5295,9 +5702,42 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { if {[dict exists $ientry supersedes]} { set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] } - set source_pkg_folder $sourcefolder/$isource + #source_root (G-139): 'src' (default - the original vendorlib + #declarations) or 'packages' - resolve against the untracked + #bin/packages lib-tier input (populated by 'make.tcl libfetch'). + set isource_root "src" + if {[dict exists $ientry source_root]} { + set isource_root [punkboot::lib::toml_untag [dict get $ientry source_root]] + } + #replace (G-139): clean-slate a SAME-NAMED existing target folder + #before install. supersedes deliberately cannot name the folder being + #installed; replace covers same-name refreshes where the old copy may + #carry strays the new tree lacks (e.g. hand-drops nesting md5c/tcllibc + #inside tcllib2.0). + set ireplace 0 + if {[dict exists $ientry replace]} { + set ireplace [punkboot::lib::toml_untag [dict get $ientry replace]] + if {![string is boolean -strict $ireplace]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname 'replace' must be boolean (got '$ireplace')$A(RST)" + exit 3 + } + } + switch -- $isource_root { + src { + set source_pkg_folder $sourcefolder/$isource + set source_missing_hint "" + } + packages { + set source_pkg_folder $projectroot/bin/packages/$isource + set source_missing_hint " - populate the bin/packages tier first: 'tclsh src/make.tcl libfetch' (declarations in src/runtime/libpackages.toml)" + } + default { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname has unknown source_root '$isource_root' (expected src|packages)$A(RST)" + exit 3 + } + } if {![file isdirectory $source_pkg_folder]} { - puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$source_missing_hint$A(RST)" exit 3 } set pkgtail [file tail $source_pkg_folder] @@ -5327,6 +5767,10 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { file delete -force $suppath } } + if {$ireplace && [file isdirectory $target_lib_folder/$pkgtail]} { + puts stdout "VFSLIBS install.$iname: replace=true - removing existing '$pkgtail' from vfs/$t (clean-slate install)" + file delete -force $target_lib_folder/$pkgtail + } puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ -installer make.tcl\ @@ -5341,6 +5785,7 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { } } + if {$::punkboot::command in {bakehouse packages libs}} { ######################################################## lappend projectlibfolders lib 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 1a4aafe3..75e7391e 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 tool project vfs] + variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate libfetch bootsupport vfscommonupdate projectversion workflow buildsuite tool project vfs] } @@ -2135,6 +2135,10 @@ proc ::punkboot::punkboot_gethelp {args} { append h " - propagate declared vendored platform-library packages into kit vfs lib_tcl trees" \n append h " - declarations (per-package per-kit, with superseded-version removal) live in src/runtime/vendorlib_vfs.toml" \n append h " - also runs automatically as a phase of '$scriptname bake' (and therefore of '$scriptname bakehouse')" \n \n + append h " $scriptname libfetch ?-serverurl ? ?-trust-server? ?-force?" \n + append h " - fetch declared punkbin lib-tier library artifacts (src/runtime/libpackages.toml) into bin/packages/" \n + append h " - sha1-verified against the server's per-target sha1sums.txt; materializes package trees under bin/packages//tcl" \n + append h " - canonical origin trusted by default; other servers (incl. PUNKBIN_URL overrides) require -trust-server" \n \n append h " $scriptname vfscommonupdate" \n append h " - promotion gate: update the src/vfs/_vfscommon.vfs from compiled src/modules and src/lib etc" \n append h " - before calling this (followed by '$scriptname bake') - you can test using '(.exe) dev'" \n @@ -2323,6 +2327,26 @@ DIAGRAM 2 - KIT ASSEMBLY DETAIL (the 'make.tcl bake' stage; incl. vfslibs phase) running shell still holds the old exe [K5] +LIBRARY-ARTIFACT FETCH (G-139; input tier so far - lib_tcl/kit consumption follows) +---------------------------------------------------------------------------- + + src/runtime/libpackages.toml which punkbin LIB-TIER artifacts this checkout + consumes (declared artifact names + targets) + | + | 'make.tcl libfetch' - sha1-verified download of the declared + | --r.zip artifacts + .toml sidecars from + | /lib// (canonical punkbin origin trusted by + | default; -serverurl/PUNKBIN_URL override needs -trust-server; + | file:// origins serve local mirrors/fixtures), then + | materialization of each verified zip's package tree + v + bin/packages// verified -r zips + toml sidecars (input tier, + untracked - mirrors bin/runtime/) + bin/packages//tcl// + materialized installed-shape trees, self-describing + via their embedded punkbin-artifact.toml records + + KEY / NOTES ---------------------------------------------------------------------------- [K1] Source modules carry the literal magic version 999999.0a1.0 in the filename. @@ -2527,6 +2551,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO vfslibs "Propagate declared vendored platform-library packages into kit vfs lib_tcl trees" bin "Install executables from src/bin into /bin, then build kits as for bake" vendorupdate "Update src/vendormodules based on src/vendormodules/include_modules.config" + libfetch "Fetch declared punkbin lib-tier library artifacts into bin/packages/ (sha1-verified) and materialize the package trees" bootsupport "Promotion gate: update src/bootsupport modules from built project modules" vfscommonupdate "Promotion gate: replace src/vfs/_vfscommon.vfs modules and libs with the project's built set" info "Show the name and base folder of the project to be built" @@ -2630,6 +2655,24 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO Also runs automatically as a phase of 'make.tcl bake' (and therefore of 'make.tcl bakehouse'); this standalone narrowing serves quick iteration on the declarations." + libfetch + " + Fetch the punkbin LIB-TIER library artifacts declared in + src/runtime/libpackages.toml into the untracked input tier + bin/packages// - immutable --r.zip + artifacts plus their .toml sidecars, each verified against the + server's per-target sha1sums.txt - then materialize each verified + zip's installed-shape package tree under + bin/packages//tcl/ (self-describing via the embedded + punkbin-artifact.toml record; G-138/G-139). + Present artifacts that already verify are skipped (-force + re-downloads). The canonical punkbin origin is trusted by + default; -serverurl (or env PUNKBIN_URL) overrides it, and any + NON-canonical origin requires the explicit -trust-server flag + (never an interactive prompt - unattended runs stay unattended). + A file:// origin is supported for local mirrors/fixtures. + Transport for http(s): Tcl http+tls when loadable, else curl, + else PowerShell (windows)." bin " Install executables from src/bin to /bin, then check vfs @@ -2799,6 +2842,30 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO (packages -> test -> bootsupport/vfscommonupdate -> commit -> bake). Pass -dirty-abort 0 to build anyway with PROVENANCE-WARNINGs." } + #libfetch fragments (G-139): consent is keyed to SERVER TRUST (the G-123 + #posture) - the canonical punkbin origin needs no acknowledgement, any + #other origin needs the explicit flag. Never an interactive prompt. + variable OPT_SERVERURL { + -serverurl -type string -default "" -help\ + "Artifact server base url override (default: the canonical punkbin + origin; env PUNKBIN_URL overrides the default, this flag overrides + both). The lib tier is addressed as /lib//. + file:// urls are supported for local mirrors and fixtures. + A NON-canonical origin additionally requires -trust-server." + } + variable OPT_TRUSTSERVER { + -trust-server -type none -default 0 -help\ + "Acknowledge fetching from a NON-canonical artifact server + (consent keyed to server trust - the interim G-123/G-006 + mechanism). Without this flag, a non-canonical -serverurl or + PUNKBIN_URL aborts before any network access. Fetches from the + canonical origin never require it." + } + variable OPT_FORCE { + -force -type none -default 0 -help\ + "Re-download and re-materialize even when a present artifact + already verifies against the server sha1sums." + } #which shared option fragments each subcommand declares #(shell and help have bespoke definitions below: shell passes trailing args # through to the repl untouched, help takes a subject value) @@ -2812,6 +2879,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO vfslibs {OPT_DIRTYABORT OPT_CONFIRM} bin {OPT_FORCEKILL OPT_DIRTYABORT OPT_CONFIRM} vendorupdate {OPT_CONFIRM} + libfetch {OPT_SERVERURL OPT_TRUSTSERVER OPT_FORCE} bootsupport {OPT_DIRTYABORT OPT_CONFIRM} vfscommonupdate {OPT_DIRTYABORT OPT_CONFIRM} info {OPT_CONFIRM} @@ -2829,6 +2897,9 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO OPT_DIRTYABORT "?-dirty-abort?" OPT_DIRTYABORT_ON "?-dirty-abort 1|0?" OPT_CONFIRM "?-confirm 0|1?" + OPT_SERVERURL "?-serverurl ?" + OPT_TRUSTSERVER "?-trust-server?" + OPT_FORCE "?-force?" } #per-subcommand positional values (default: none). bake (and its deprecated #alias vfs) and bakelist take optional kit names (G-121 selective bake). @@ -2940,7 +3011,7 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO variable SUBGROUPS { "build & bake" {bakehouse packages modules libs bake vfslibs bin} "promotion gates" {bootsupport vfscommonupdate} - "source maintenance" {vendorupdate} + "source maintenance" {vendorupdate libfetch} "buildsuites" {buildsuite} "vendored tools" {tool} "informational" {info check bakelist projectversion workflow help} @@ -2996,6 +3067,9 @@ set help_exitcode 0 set ::punkboot::opt_forcekill 0 set ::punkboot::opt_dirty_abort 0 set ::punkboot::opt_confirm 1 +set ::punkboot::opt_serverurl "" +set ::punkboot::opt_trust_server 0 +set ::punkboot::opt_force 0 set ::punkboot::opt_kitnames [list] ;#G-121: requested kit names for bake/bakelist (empty = all configured) if {$::punkboot::punkargs_ok} { @@ -3046,7 +3120,7 @@ if {$::punkboot::punkargs_ok} { } set ::punkboot::command $subcommand set _opts [dict get $argd opts] - foreach {_optname _var} {-k opt_forcekill -dirty-abort opt_dirty_abort -confirm opt_confirm} { + foreach {_optname _var} {-k opt_forcekill -dirty-abort opt_dirty_abort -confirm opt_confirm -serverurl opt_serverurl -trust-server opt_trust_server -force opt_force} { if {[dict exists $_opts $_optname]} { set ::punkboot::$_var [dict get $_opts $_optname] } @@ -3083,6 +3157,12 @@ if {$::punkboot::punkargs_ok} { switch -- $a { -k {set ::punkboot::opt_forcekill 1} -dirty-abort {set ::punkboot::opt_dirty_abort 1} + -trust-server {set ::punkboot::opt_trust_server 1} + -force {set ::punkboot::opt_force 1} + -serverurl { + set ::punkboot::opt_serverurl [lindex $argsleft 0] + set argsleft [lrange $argsleft 1 end] + } -confirm { set v [lindex $argsleft 0] set argsleft [lrange $argsleft 1 end] @@ -5121,6 +5201,303 @@ if {$::punkboot::command eq "bootsupport"} { +if {$::punkboot::command eq "libfetch"} { + #G-139 phase 1: fetch the punkbin LIB-TIER artifacts declared in + #src/runtime/libpackages.toml into the untracked bin/packages/ input + #tier (mirroring bin/runtime/) with sha1 verification against the + #server's per-target sha1sums.txt, then materialize each verified zip's + #installed-shape package tree under bin/packages//tcl/ (the tree + #is self-describing via its embedded punkbin-artifact.toml - the G-138 + #record). Consumption into lib_tcl/ and kit vfs trees is the remainder + #of G-139 - this entrypoint owns fetch + verify + materialize only. + + proc ::punkboot::libfetch_transfer {url outfile} { + #one url -> local file. file:// is served natively (local mirrors, + #fixtures - no network, no external tools). http(s) transports in + #order: Tcl http (+tls for https) when loadable, curl (ubiquitous, + #including stock windows 10+), powershell Invoke-WebRequest (windows). + #Returns the transport used; errors when all fail. + file mkdir [file dirname $outfile] + if {[string match -nocase "file://*" $url]} { + set path [string range $url 7 end] + if {[regexp {^/+([A-Za-z]:/.*)$} $path -> winpath]} { + set path $winpath ;#file:///C:/x -> C:/x + } + if {![file isfile $path]} { + error "file origin has no such file: $path" + } + file copy -force $path $outfile + return "file-copy" + } + set is_https [string match -nocase "https://*" $url] + set tcl_http_ok 0 + if {![catch {package require http}]} { + if {!$is_https} { + set tcl_http_ok 1 + } elseif {![catch {package require tls}]} { + catch {http::register https 443 [list ::tls::socket -autoservername true]} + set tcl_http_ok 1 + } + } + if {$tcl_http_ok} { + set fd [open $outfile wb] + set ok 0 + if {![catch {http::geturl $url -channel $fd -binary 1} tok]} { + if {[http::status $tok] eq "ok" && [http::ncode $tok] == 200} {set ok 1} + http::cleanup $tok + } + close $fd + if {$ok} {return "tcl-http"} + file delete -force $outfile + } + set curl [auto_execok curl] + if {$curl ne ""} { + if {![catch {exec {*}$curl --fail -sSL -o $outfile $url 2>@1} curlerr]} { + return "curl" + } + file delete -force $outfile + } + if {$::tcl_platform(platform) eq "windows"} { + set ps [auto_execok powershell] + if {$ps ne ""} { + set pscmd "\$ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '$url' -OutFile '$outfile' -ErrorAction Stop" + if {![catch {exec {*}$ps -NoProfile -NonInteractive -Command $pscmd 2>@1} pserr]} { + return "powershell" + } + file delete -force $outfile + } + } + set detail "" + if {[info exists curlerr]} {set detail " (curl: [string range $curlerr 0 200])"} + error "all transports failed for $url$detail" + } + + set libfetch_config $sourcefolder/runtime/libpackages.toml + #Canonical origin: the punkbin raw-file base (the punk-runtime.cmd + #convention; lib tier addressed as /lib//...). + #Server-trust consent (G-139, the G-123 posture): the canonical origin + #never gates; ANY other origin - flag or PUNKBIN_URL env - requires the + #explicit -trust-server flag. Never an interactive prompt: unattended + #runs stay unattended, and refusal happens before any network access. + set libfetch_canonical "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master" + set libfetch_origin $libfetch_canonical + if {[info exists ::env(PUNKBIN_URL)] && $::env(PUNKBIN_URL) ne ""} { + set libfetch_origin $::env(PUNKBIN_URL) + } + if {$::punkboot::opt_serverurl ne ""} { + set libfetch_origin $::punkboot::opt_serverurl + } + set libfetch_origin [string trimright $libfetch_origin /] + if {$libfetch_origin ne $libfetch_canonical && !$::punkboot::opt_trust_server} { + puts stderr "$A(BAD)LIBFETCH: origin '$libfetch_origin' is not the canonical punkbin origin$A(RST)" + puts stderr "LIBFETCH: canonical: $libfetch_canonical" + puts stderr "LIBFETCH: fetching from a non-canonical server requires the explicit -trust-server flag (consent keyed to server trust - G-139/G-123). Nothing was fetched." + exit 3 + } + if {![file exists $libfetch_config]} { + puts stdout "LIBFETCH: no config at $libfetch_config - nothing declared, nothing to fetch" + } else { + if {[catch {package require tomlish} tomlish_err]} { + puts stderr "$A(BAD)LIBFETCH: cannot process $libfetch_config - tomlish package unavailable ($tomlish_err)$A(RST)" + exit 3 + } + if {[catch {package require sha1} sha1_err]} { + puts stderr "$A(BAD)LIBFETCH: sha1 package unavailable ($sha1_err) - cannot verify artifacts$A(RST)" + exit 3 + } + if {[catch {package require punk::zip} punkzip_err]} { + puts stderr "$A(BAD)LIBFETCH: punk::zip package unavailable ($punkzip_err) - cannot materialize package trees$A(RST)" + exit 3 + } + set fd [open $libfetch_config r] + fconfigure $fd -encoding utf-8 + set libfetch_tomldata [read $fd] + close $fd + if {[catch {tomlish::to_dict [tomlish::from_toml $libfetch_tomldata]} libfetch_dict]} { + puts stderr "$A(BAD)LIBFETCH: failed to parse $libfetch_config\n$libfetch_dict$A(RST)" + exit 3 + } + if {![llength [info commands ::punkboot::lib::toml_untag]]} { + #tomlish::to_dict values are type-tagged (also defined by the vfslibs + #phase - that block does not run for the libfetch command) + proc ::punkboot::lib::toml_untag {tagged} { + if {[dict get $tagged type] eq "ARRAY"} { + set plain [list] + foreach el [dict get $tagged value] { + lappend plain [::punkboot::lib::toml_untag $el] + } + return $plain + } + return [dict get $tagged value] + } + } + set libfetch_artifacts [dict create] + if {[dict exists $libfetch_dict artifact]} { + set libfetch_artifacts [dict get $libfetch_dict artifact] + } + if {![dict size $libfetch_artifacts]} { + puts stdout "LIBFETCH: no \[artifact.\] entries found in $libfetch_config" + } + puts stdout "LIBFETCH: origin $libfetch_origin ([dict size $libfetch_artifacts] declared artifact(s))" + set libfetch_sums [dict create] ;#target -> dict(filename -> sha1) from the server's sha1sums.txt + set libfetch_failed 0 + set libfetch_actions [list] + dict for {aname aentry} $libfetch_artifacts { + foreach reqkey {name target} { + if {![dict exists $aentry $reqkey]} { + puts stderr "$A(BAD)LIBFETCH: entry artifact.$aname in $libfetch_config is missing required key '$reqkey'$A(RST)" + exit 3 + } + } + set artifact [punkboot::lib::toml_untag [dict get $aentry name]] + set target [punkboot::lib::toml_untag [dict get $aentry target]] + if {![regexp {^(.+)-(tcl[0-9]+)-r([0-9]+)\.zip$} $artifact -> pkgfolder generation rev]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname name '$artifact' does not match -tcl-r.zip$A(RST)" + exit 3 + } + set tierdir $projectroot/bin/packages/$target + file mkdir $tierdir + #server truth per target: lib//sha1sums.txt (fetched once per + #run per target; the local copy in the tier mirrors the punk-runtime + #store idiom) + if {![dict exists $libfetch_sums $target]} { + set sumsurl "$libfetch_origin/lib/$target/sha1sums.txt" + set sumslocal $tierdir/sha1sums.txt + if {[catch {::punkboot::libfetch_transfer $sumsurl $sumslocal} terr]} { + puts stderr "$A(BAD)LIBFETCH: cannot fetch $sumsurl - $terr$A(RST)" + exit 3 + } + set sfd [open $sumslocal r] + set sumsdata [read $sfd] + close $sfd + set sums [dict create] + foreach ln [split $sumsdata \n] { + set ln [string trim $ln] + if {$ln eq ""} {continue} + set sp [string first " " $ln] + if {$sp < 1} {continue} + set h [string range $ln 0 $sp-1] + set tag [string range $ln $sp+1 end] + dict set sums [string range $tag 1 end] [string tolower $h] + } + dict set libfetch_sums $target $sums + } + set sums [dict get $libfetch_sums $target] + if {![dict exists $sums $artifact]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: '$artifact' is not listed in the server's lib/$target/sha1sums.txt$A(RST)" + set libfetch_failed 1 + continue + } + set want_sha1 [dict get $sums $artifact] + set ziplocal $tierdir/$artifact + set have_ok 0 + if {[file isfile $ziplocal] && !$::punkboot::opt_force} { + if {[string tolower [sha1::sha1 -hex -file $ziplocal]] eq $want_sha1} { + set have_ok 1 + } + } + if {!$have_ok} { + set zipurl "$libfetch_origin/lib/$target/$artifact" + set partfile $ziplocal.part + if {[catch {::punkboot::libfetch_transfer $zipurl $partfile} terr]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: fetch failed - $terr$A(RST)" + set libfetch_failed 1 + continue + } + set got_sha1 [string tolower [sha1::sha1 -hex -file $partfile]] + if {$got_sha1 ne $want_sha1} { + file delete -force $partfile + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sha1 MISMATCH for $artifact (got $got_sha1, server sha1sums says $want_sha1) - deleted$A(RST)" + set libfetch_failed 1 + continue + } + file rename -force $partfile $ziplocal + lappend libfetch_actions "fetched lib/$target/$artifact" + } else { + lappend libfetch_actions "verified-present lib/$target/$artifact" + } + #beside-toml sidecar (also sha1sums-listed by emission) + set sidecar "[string range $artifact 0 end-4].toml" + set sidecarlocal $tierdir/$sidecar + set sidecar_ok 0 + if {[dict exists $sums $sidecar]} { + set want_side [dict get $sums $sidecar] + if {[file isfile $sidecarlocal] && !$::punkboot::opt_force} { + if {[string tolower [sha1::sha1 -hex -file $sidecarlocal]] eq $want_side} { + set sidecar_ok 1 + } + } + if {!$sidecar_ok} { + if {[catch {::punkboot::libfetch_transfer "$libfetch_origin/lib/$target/$sidecar" $sidecarlocal.part} terr]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sidecar fetch failed - $terr$A(RST)" + set libfetch_failed 1 + continue + } + if {[string tolower [sha1::sha1 -hex -file $sidecarlocal.part]] ne $want_side} { + file delete -force $sidecarlocal.part + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: sidecar sha1 MISMATCH for $sidecar - deleted$A(RST)" + set libfetch_failed 1 + continue + } + file rename -force $sidecarlocal.part $sidecarlocal + } + } else { + puts stderr "LIBFETCH: note - $sidecar not listed in server sha1sums (artifact accepted; sidecar skipped)" + } + #materialize: installed-shape tree under /tcl/ + #(generation subdir because both generations install identically-named + #folders). Fresh tree per materialization; -mtime restores member + #mtimes so the tree mirrors what was zipped. + set gendir $tierdir/$generation + file mkdir $gendir + set treedir $gendir/$pkgfolder + if {[file isdirectory $treedir] && !$::punkboot::opt_force} { + #re-materialize only when the tree's embedded record no longer + #matches this artifact name (self-describing tier - G-138) + set embrec $treedir/punkbin-artifact.toml + set tree_current 0 + if {[file isfile $embrec]} { + set efd [open $embrec r] + set edata [read $efd] + close $efd + if {[regexp -line "^name = \"$artifact\"$" $edata]} { + set tree_current 1 + } + } + if {$tree_current} { + lappend libfetch_actions "tree-current $target/$generation/$pkgfolder" + continue + } + } + file delete -force $treedir + #defaults: whole archive, -overwrite 1 -mtime 1 (member mtimes restored) + #-verify 1 (per-member crc32) - and accelerator-eligible (punkzip exe) + if {[catch {punk::zip::unzip $ziplocal $gendir} unzip_err]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: materialization failed - $unzip_err$A(RST)" + file delete -force $treedir + set libfetch_failed 1 + continue + } + if {![file isfile $treedir/pkgIndex.tcl] || ![file isfile $treedir/punkbin-artifact.toml]} { + puts stderr "$A(BAD)LIBFETCH: artifact.$aname: materialized tree at $treedir lacks pkgIndex.tcl and/or punkbin-artifact.toml$A(RST)" + set libfetch_failed 1 + continue + } + lappend libfetch_actions "materialized $target/$generation/$pkgfolder" + } + foreach act $libfetch_actions { + puts stdout "LIBFETCH: $act" + } + if {$libfetch_failed} { + puts stderr "$A(BAD)LIBFETCH: FAILED - one or more declared artifacts could not be fetched/verified/materialized (see above)$A(RST)" + exit 3 + } + puts stdout "LIBFETCH: done ([llength $libfetch_actions] action(s)) -> $projectroot/bin/packages" + } + flush stdout + ::exit 0 +} + if {$::punkboot::command ni {bakehouse packages modules libs bake vfslibs bin}} { puts stderr "Command $::punkboot::command not implemented - aborting." flush stderr @@ -5241,6 +5618,36 @@ if {$::punkboot::command in {bakehouse packages libs}} { if {![llength $vendorlibfolders]} { puts stderr "$A(BAD)VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found.$A(RST)" } + #G-139: punkbin lib-tier packages from the bin/packages input tier (populated + #by 'make.tcl libfetch') into the deployed lib_tcl trees - allplatforms + + #this host's platform dir, mirroring the vendorlib_tcl shape above. The + #materialized trees are self-describing (embedded punkbin-artifact.toml + #records - G-138) and the records ride along into the deployed trees, and + #from there into kit vfs payloads via the vfslibs declarations. An absent + #tier (or target/generation subdir) is a silent per-item no-op: the tier is + #fetch-regenerable and optional until artifacts are declared and fetched. + set packages_root $projectroot/bin/packages + if {[file isdirectory $packages_root]} { + foreach ptarget [list allplatforms $this_platform_generic] { + foreach gendir [lsort [glob -nocomplain -dir $packages_root/$ptarget -type d tcl*]] { + set which [file tail $gendir] ;#tcl8|tcl9 ... + foreach pkgdir [lsort [glob -nocomplain -dir $gendir -type d *]] { + set pkgtail [file tail $pkgdir] + set target_lib_folder $projectroot/lib_$which/$ptarget + file mkdir $target_lib_folder + puts stdout "PACKAGES_$which: copying $ptarget/$which/$pkgtail from the bin/packages tier to $target_lib_folder (if source file changed)" + set resultdict [punkcheck::install $pkgdir $target_lib_folder/$pkgtail\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } + } else { + puts stdout "PACKAGES: no bin/packages tier at $packages_root (populate via 'make.tcl libfetch') - skipping lib-tier package deployment" + } } if {$::punkboot::command in {bakehouse bake vfslibs}} { @@ -5295,9 +5702,42 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { if {[dict exists $ientry supersedes]} { set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] } - set source_pkg_folder $sourcefolder/$isource + #source_root (G-139): 'src' (default - the original vendorlib + #declarations) or 'packages' - resolve against the untracked + #bin/packages lib-tier input (populated by 'make.tcl libfetch'). + set isource_root "src" + if {[dict exists $ientry source_root]} { + set isource_root [punkboot::lib::toml_untag [dict get $ientry source_root]] + } + #replace (G-139): clean-slate a SAME-NAMED existing target folder + #before install. supersedes deliberately cannot name the folder being + #installed; replace covers same-name refreshes where the old copy may + #carry strays the new tree lacks (e.g. hand-drops nesting md5c/tcllibc + #inside tcllib2.0). + set ireplace 0 + if {[dict exists $ientry replace]} { + set ireplace [punkboot::lib::toml_untag [dict get $ientry replace]] + if {![string is boolean -strict $ireplace]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname 'replace' must be boolean (got '$ireplace')$A(RST)" + exit 3 + } + } + switch -- $isource_root { + src { + set source_pkg_folder $sourcefolder/$isource + set source_missing_hint "" + } + packages { + set source_pkg_folder $projectroot/bin/packages/$isource + set source_missing_hint " - populate the bin/packages tier first: 'tclsh src/make.tcl libfetch' (declarations in src/runtime/libpackages.toml)" + } + default { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname has unknown source_root '$isource_root' (expected src|packages)$A(RST)" + exit 3 + } + } if {![file isdirectory $source_pkg_folder]} { - puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$source_missing_hint$A(RST)" exit 3 } set pkgtail [file tail $source_pkg_folder] @@ -5327,6 +5767,10 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { file delete -force $suppath } } + if {$ireplace && [file isdirectory $target_lib_folder/$pkgtail]} { + puts stdout "VFSLIBS install.$iname: replace=true - removing existing '$pkgtail' from vfs/$t (clean-slate install)" + file delete -force $target_lib_folder/$pkgtail + } puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ -installer make.tcl\ @@ -5341,6 +5785,7 @@ if {$::punkboot::command in {bakehouse bake vfslibs}} { } } + if {$::punkboot::command in {bakehouse packages libs}} { ######################################################## lappend projectlibfolders lib diff --git a/src/runtime/vendorlib_vfs.toml b/src/runtime/vendorlib_vfs.toml index deb75d2c..b24aa27d 100644 --- a/src/runtime/vendorlib_vfs.toml +++ b/src/runtime/vendorlib_vfs.toml @@ -16,11 +16,20 @@ # by generation - so the same source folder serves lib_tcl9 and lib_tcl8 targets). # # Keys per [install.] entry: -# source - package folder path relative to src/ -# targets - list of kit vfs lib folders relative to src/vfs/ -# supersedes - optional list of legacy/older package folder names to remove from each -# target before install (explicit - no silent mixed-version provision, per -# the G-035 concerns) +# source - package folder path, resolved per source_root +# source_root - optional: 'src' (default; source is relative to src/ - the +# vendored-tree declarations) or 'packages' (G-139; source is +# relative to /bin/packages - the untracked punkbin +# lib-tier input populated by 'tclsh src/make.tcl libfetch') +# targets - list of kit vfs lib folders relative to src/vfs/ +# supersedes - optional list of legacy/older package folder names to remove from +# each target before install (explicit - no silent mixed-version +# provision, per the G-035 concerns). Cannot name the folder being +# installed (same-name entries are ignored) - use 'replace' for that. +# replace - optional boolean (G-139): clean-slate a SAME-NAMED existing target +# folder before install. For same-name refreshes where the old copy +# may carry strays the new tree lacks (the pre-G-139 hand-drops +# nested md5c/tcllibc INSIDE their tcllib2.0 folder). # # This file is a candidate for absorption into the mapvfs toml conversion (goal G-024). @@ -34,3 +43,70 @@ targets = [ "punk8win.vfs/lib_tcl8", ] supersedes = ["udp1.0.12", "tcludp1.0.12"] + +# -- G-139: suite-built tcllib/tcllibc from the punkbin lib tier --------------- +# Sourced from the bin/packages input tier ('make.tcl libfetch'; artifacts +# declared in libpackages.toml). Targets = the kit vfs trees that carried +# hand-dropped copies at switchover (2026-07-30) - per-kit participation, never +# blanket. replace=true because the old tcl9 hand-drops nested md5c + tcllibc +# INSIDE tcllib2.0 (same folder name as the new install - supersedes cannot +# touch it). The tcl8 kits upgrade tcllib1.21 (+nested tcllibc 0.4 + md5c) -> +# tcllib 2.0 via supersedes. tcllibc is the win32-x86_64 build: punk9linux +# (linux target) deliberately gets only the platform-neutral tcllib2.0 - its +# linux-built tcllibc/tcllibc2.0 sibling folders are left as-is (a linux +# lib-tier emission is future work). punk86bawt carried a bare tcllibc only - +# it gets the tcllibc refresh and no tcllib entry. + +[install.tcllib_tcl9] +source_root = "packages" +source = "allplatforms/tcl9/tcllib2.0" +replace = true +targets = [ + "mkzipfix.vfs/lib_tcl9", + "punk9cook.vfs/lib_tcl9", + "punk9cookbasic.vfs/lib_tcl9", + "punk9linux.vfs/lib_tcl9", + "punk9magicsplat.vfs/lib_tcl9", + "punk9win_for_tkruntime.vfs/lib_tcl9", + "punk9wintk903.vfs/lib_tcl9", + "punk9wintk905.vfs/lib_tcl9", + "punk9wintk90b2.vfs/lib_tcl9", +] + +[install.tcllibc_tcl9] +source_root = "packages" +source = "win32-x86_64/tcl9/tcllibc" +replace = true +targets = [ + "mkzipfix.vfs/lib_tcl9", + "punk9cook.vfs/lib_tcl9", + "punk9cookbasic.vfs/lib_tcl9", + "punk9magicsplat.vfs/lib_tcl9", + "punk9win_for_tkruntime.vfs/lib_tcl9", + "punk9wintk903.vfs/lib_tcl9", + "punk9wintk905.vfs/lib_tcl9", + "punk9wintk90b2.vfs/lib_tcl9", +] +supersedes = ["tcllibc2.0"] + +[install.tcllib_tcl8] +source_root = "packages" +source = "allplatforms/tcl8/tcllib2.0" +replace = true +targets = [ + "punk86.vfs/lib_tcl8", + "punk8win.vfs/lib_tcl8", + "punk8_statictwapi.vfs/lib_tcl8", +] +supersedes = ["tcllib1.21"] + +[install.tcllibc_tcl8] +source_root = "packages" +source = "win32-x86_64/tcl8/tcllibc" +replace = true +targets = [ + "punk86.vfs/lib_tcl8", + "punk8win.vfs/lib_tcl8", + "punk8_statictwapi.vfs/lib_tcl8", + "punk86bawt.vfs/lib_tcl8", +]