From 61cf903dd97a8c2d9f51e3b261a3225933d452f3 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Wed, 22 Jul 2026 09:25:46 +1000 Subject: [PATCH] G-103: punk-runtime list/use surface artifact metadata; use materializes -rN artifacts Scriptset extension (ps1 + bash payloads in parity, rewrapped polyglot, layout-shipped copy + untracked .ps1 twin refreshed, roundtrip pin PASS): - list: per-runtime summary parsed from the .toml metadata record beside each runtime (variant, tcl patchlevel, revision, piperepl on/off, from= on materialized working copies). - use -name>: materializes the immutable artifact into its WORKING name (minus -r) with the metadata toml copied alongside, then selects it - the G-103 naming decision's artifact->working mapping, so republishing never churns consumers. use unchanged. - fetch of -r artifacts also retrieves the metadata toml from punkbin (absence tolerated for pre-family runtimes). - rootname handling strips only .exe (dotted patchlevels break last-dot stripping for extensionless unix names); same exe_split fix applied to family_artifacts.tcl artifact/toml naming for the G-105 cross-target future. Candidate listing excludes directories and .log files (parity cleanup; bash side previously listed directories). Verified on the wrapped bin/punk-runtime.cmd (powershell 5 branch) and the bash payload under git-bash: all three family members materialized from their -r1 artifacts with metadata shown in list, and 'punk-runtime run' launched the active punk family runtime (9.0.5, piperepl machinery, //zipfs:/app/tcl_library). Project 0.17.7. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- CHANGELOG.md | 4 + bin/AGENTS.md | 12 ++ bin/punk-runtime.cmd | 178 +++++++++++++++++- goals/G-103-runtime-kit-family.md | 40 +++- punkproject.toml | 2 +- .../suite_tcl90/tools/family_artifacts.tcl | 15 +- .../punk/project-0.1/bin/punk-runtime.cmd | 178 +++++++++++++++++- src/scriptapps/bin/punk-runtime.bash | 89 ++++++++- src/scriptapps/bin/punk-runtime.ps1 | 89 ++++++++- 9 files changed, 572 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e128b6f0..18aa0820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.17.7] - 2026-07-22 + +- `bin/punk-runtime.cmd` (G-103): `list` surfaces per-runtime artifact metadata from `.toml` records beside the runtimes (variant, tcl patchlevel, revision, piperepl policy, source artifact of a materialized copy); `use -name>` materializes an immutable `-r` artifact into its working name (metadata toml copied alongside) and selects it; `fetch` of an `-r` artifact also retrieves its metadata toml from punkbin (absence tolerated). Runtime-candidate listing now excludes directories and `.log` files in both payloads. Layout-shipped copy refreshed. + ## [0.17.6] - 2026-07-21 - punk::mix::cli 0.5.2: the modpod zip build survives a failing `zipfs mkzip` by falling back to `punk::zip::mkzip` (the established path for zipfs-less interpreters), clearing the partial zip mkzip leaves behind and noting the interpreter + upstream ticket on stderr. Motivation: Tcl 8.7 builds predating core fix `c971e6c7c4` (tkt 7d5f1c13089d463e7796, "zipfs mkzip broken on Windows dotfiles") die with "non-unique path name" on dot-prefixed entries — hit by the templates modpod's layout `.fossil-custom` payloads when building under 8.7a6. Fallback-built modpods verified mounting identically under 9.0.3 and 8.7a6. diff --git a/bin/AGENTS.md b/bin/AGENTS.md index d88041fe..9b570fc8 100644 --- a/bin/AGENTS.md +++ b/bin/AGENTS.md @@ -87,6 +87,18 @@ written by `punk-runtime.cmd use `, marked in `list`, VCS-ignored via the it errors listing candidates rather than guessing. The first `fetch` sets the active runtime only when none is recorded. +G-103 runtime-family artifact metadata (both payloads): a runtime may carry a +`.toml` metadata record beside it (emitted by the suite_tcl90 +`kit-family-artifacts` step; fetched from punkbin alongside `-r`-named artifacts - +absence tolerated for pre-family runtimes). `list` shows a per-runtime summary from it +(variant, tcl patchlevel, revision, piperepl policy, and - on a materialized working +copy - which immutable artifact it came from). `use -name>` MATERIALIZES +the immutable artifact into its WORKING name (the name minus `-r` - what mapvfs and +projects reference), copies the metadata toml alongside, and selects the working name; +`use ` selects as before. Root-name handling strips only a `.exe` suffix +(dotted tcl patchlevels make generic last-dot stripping wrong for extensionless unix +names). Candidate listing excludes directories and `.txt/.toml/.tm/.tmp/.log` files. + ### Interactive verification shells - Interactive console/repl verification should cover both Tcl generations - behaviour can differ materially (e.g. the Tcl 8.6 windows console channel driver vs the Tcl 9 rewrite). Use a Tcl 8.6-based punk shell (`punksys.exe`) and a current Tcl 9-based punk shell (named for the Tcl release it embeds, e.g. `punk902z.exe` at the time of writing - ask the user which is current rather than assuming). `info patchlevel` in-session confirms the runtime. diff --git a/bin/punk-runtime.cmd b/bin/punk-runtime.cmd index 963a5420..1f713340 100755 --- a/bin/punk-runtime.cmd +++ b/bin/punk-runtime.cmd @@ -1398,12 +1398,58 @@ set_active() { printf 'active = "%s"\n' "$1" > "$active_file" echo "active runtime for $archtail set to: $1 (recorded in $active_file)" } -#installed runtime candidates - exclude build copies and non-runtime files +#installed runtime candidates - exclude directories, build copies and +#non-runtime files (ps1-payload parity: files only, same extension excludes) list_candidates() { + local f if [[ -d "$archdir" ]]; then - ls -1 "$archdir" | grep -v '_BUILDCOPY$' | grep -v '\.txt$' | grep -v '\.toml$' | grep -v '\.tm$' | grep -v '\.tmp$' + for f in "$archdir"/*; do + [[ -f "$f" ]] || continue + f="${f##*/}" + case "$f" in + *_BUILDCOPY*|*.txt|*.toml|*.tm|*.tmp|*.log) continue;; + esac + printf '%s\n' "$f" + done fi } +#name minus a .exe suffix only - dotted tcl patchlevels (tclsh9.0.5-punk) make +#generic last-dot extension stripping wrong for extensionless unix names +rootname_of() { + case "$1" in + *.exe|*.EXE) printf '%s' "${1%.???}";; + *) printf '%s' "$1";; + esac +} +#G-103 artifact metadata: a runtime may carry a .toml beside it (emitted +#by the buildsuite kit-family-artifacts step / fetched from punkbin). Prints a +#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent. +metadata_summary() { + local exename="$1" tomlfile parts variant tclpatch revision piperepl artname + tomlfile="$archdir/$(rootname_of "$exename").toml" + [[ -f "$tomlfile" ]] || return 0 + variant=$(sed -n 's/^[[:space:]]*variant[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + tclpatch=$(sed -n 's/^[[:space:]]*tcl_patchlevel[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + revision=$(sed -n 's/^[[:space:]]*revision[[:space:]]*=[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$tomlfile" | head -n 1) + piperepl=$(sed -n 's/^[[:space:]]*piperepl[[:space:]]*=[[:space:]]*\(true\|false\).*/\1/p' "$tomlfile" | head -n 1) + artname=$(sed -n 's/^[[:space:]]*name[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + parts="" + [[ -n "$variant" ]] && parts="$parts variant=$variant" + [[ -n "$tclpatch" ]] && parts="$parts tcl=$tclpatch" + [[ -n "$revision" ]] && parts="$parts r$revision" + if [[ "$piperepl" == "true" ]]; then parts="$parts piperepl=on" + elif [[ "$piperepl" == "false" ]]; then parts="$parts piperepl=off"; fi + #a materialized working copy records which immutable artifact it came from + [[ -n "$artname" && "$artname" != "$exename" ]] && parts="$parts from=$artname" + [[ -n "$parts" ]] && printf '[%s]' "${parts# }" +} +#G-103 artifact-tier names carry -r before the (optional) .exe suffix; prints +#the WORKING name (artifact minus -r) when the name is artifact-tier, else "" +working_name_of() { + local w + w=$(printf '%s' "$1" | sed -E 's/^(.*)-r[0-9]+(\.[Ee][Xx][Ee])?$/\1\2/') + if [[ "$w" != "$1" ]]; then printf '%s' "$w"; fi +} case "$1" in "fetch") @@ -1480,6 +1526,17 @@ case "$1" in exit 1 fi fi + #G-103: family artifacts (-r names) carry a metadata toml alongside + #on the server - fetch it too; absence is fine (pre-family runtimes) + if [[ -n "$(working_name_of "$runtime")" ]]; then + tomlname="$(rootname_of "$runtime").toml" + if curl -fsSL --output "${archdir}/${tomlname}" "${url_kitbase}/${archtail}/${tomlname}"; then + echo "artifact metadata saved at ${archdir}/${tomlname}" + else + rm -f "${archdir}/${tomlname}" + echo "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)" + fi + fi #first fetch establishes the active runtime; later fetches never steal it if [[ -z "$(get_active)" ]]; then set_active "$runtime" @@ -1543,16 +1600,19 @@ case "$1" in count=$(printf '%s\n' "$candidates" | grep -c . ) echo "$count runtime(s) in $archdir" for f in $candidates; do + meta=$(metadata_summary "$f") if [[ "$f" == "$active" ]]; then - echo "* $f (active)" + printf '* %-35s (active) %s\n' "$f" "$meta" else - echo " $f" + printf ' %-35s %s\n' "$f" "$meta" fi done if [[ -n "$active" && ! -f "$archdir/$active" ]]; then echo "WARNING: active runtime '$active' (from $active_file) is not present - use '$0 use ' to reselect" fi echo "Use: '$0 list -remote' to compare local runtimes with those available on the artifact server" + echo "Use: '$0 use ' to select the runtime that 'run' launches" + echo "Use: '$0 use -name>' to materialize an immutable -r artifact into its working name and select it" else echo "No runtimes available in $archdir" echo " Use '$0 fetch' to install." @@ -1586,7 +1646,26 @@ case "$1" in done exit 1 fi - set_active "$2" + #G-103 artifact-tier names (-r, immutable): 'use' MATERIALIZES the + #artifact into its WORKING name (name minus -r - what mapvfs and + #projects reference), copies its metadata toml alongside, and selects + #the working name. Republishing artifacts never churns consumers. + working=$(working_name_of "$2") + if [[ -n "$working" ]]; then + cp -f "$archdir/$2" "$archdir/$working" + chmod +x "$archdir/$working" + srctoml="$archdir/$(rootname_of "$2").toml" + desttoml="$archdir/$(rootname_of "$working").toml" + if [[ -f "$srctoml" ]]; then + cp -f "$srctoml" "$desttoml" + echo "materialized $working from artifact $2 (metadata toml copied alongside)" + else + echo "materialized $working from artifact $2 (no metadata toml found beside the artifact)" + fi + set_active "$working" + else + set_active "$2" + fi ;; "run") #resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single @@ -2004,7 +2083,51 @@ function Get-PunkRuntimeCandidates { if (-not (Test-Path -Path $archfolder -PathType Container)) { return @() } - return @(get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY*' | Where-object {-not ($(".txt",".toml",".tm",".tmp") -contains $_.Extension) } | Sort-Object Name) + return @(get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY*' | Where-object {-not ($(".txt",".toml",".tm",".tmp",".log") -contains $_.Extension) } | Sort-Object Name) +} +#name minus a .exe suffix only - dotted tcl patchlevels (tclsh9.0.5-punk) make +#generic last-dot extension stripping wrong for extensionless unix names +function Get-PunkRuntimeRootName { + param([string] $name) + if ($name -match '(?i)\.exe$') { + return $name.Substring(0, $name.Length - 4) + } + return $name +} +#G-103 artifact metadata: a runtime may carry a .toml beside it (emitted +#by the buildsuite kit-family-artifacts step / fetched from punkbin). Returns a +#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent. +function Get-PunkRuntimeMetadataSummary { + param([string] $archfolder, [string] $exename) + $tomlfile = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $exename) + ".toml") + if (-not (Test-Path -Path $tomlfile -PathType Leaf)) { + return "" + } + $fields = @{} + foreach ($line in (Get-Content -Path $tomlfile)) { + $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"(.*)"\s*$') + if ($m.Success) { + $fields[$m.Groups[1].Value] = $m.Groups[2].Value + continue + } + $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([0-9]+|true|false)\s*$') + if ($m.Success) { + $fields[$m.Groups[1].Value] = $m.Groups[2].Value + } + } + $parts = @() + if ($fields.ContainsKey('variant')) { $parts += "variant=$($fields['variant'])" } + if ($fields.ContainsKey('tcl_patchlevel')) { $parts += "tcl=$($fields['tcl_patchlevel'])" } + if ($fields.ContainsKey('revision')) { $parts += "r$($fields['revision'])" } + if ($fields.ContainsKey('piperepl')) { + if ($fields['piperepl'] -eq 'true') { $parts += "piperepl=on" } else { $parts += "piperepl=off" } + } + #a materialized working copy records which immutable artifact it came from + if ($fields.ContainsKey('name') -and $fields['name'] -ne $exename) { $parts += "from=$($fields['name'])" } + if ($parts.Count -eq 0) { + return "" + } + return "[" + ($parts -join " ") + "]" } function psmain { @@ -2212,6 +2335,19 @@ function psmain { Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server." Write-Host "No download required" } + #G-103: family artifacts (-r names) carry a metadata toml alongside + #on the server - fetch it too; absence is fine (pre-family runtimes) + if ($runtime -match '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') { + $tomlname = (Get-PunkRuntimeRootName $runtime) + ".toml" + $tomlurl = "$archurl/$tomlname" + $tomllocal = Join-Path -Path $archfolder -ChildPath $tomlname + try { + Invoke-WebRequest -Uri $tomlurl -OutFile $tomllocal -ErrorAction Stop + Write-Host "artifact metadata saved at $tomllocal" + } catch { + Write-Host "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)" + } + } #first fetch establishes the active runtime; later fetches never steal it if ((Get-PunkActiveRuntime $archfolder) -eq "") { Set-PunkActiveRuntime $archfolder $runtime @@ -2255,7 +2391,28 @@ function psmain { } exit 1 } - Set-PunkActiveRuntime $archfolder $rtname + #G-103 artifact-tier names (-r, immutable): 'use' MATERIALIZES the + #artifact into its WORKING name (name minus -r - what mapvfs and + #projects reference), copies its metadata toml alongside, and selects + #the working name. Republishing artifacts never churns consumers. + $am = [regex]::Match($rtname, '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') + if ($am.Success) { + $working = $am.Groups[1].Value + $am.Groups[3].Value + $srcexe = Join-Path -Path $archfolder -ChildPath $rtname + $destexe = Join-Path -Path $archfolder -ChildPath $working + Copy-Item -Path $srcexe -Destination $destexe -Force + $srctoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $rtname) + ".toml") + $desttoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $working) + ".toml") + if (Test-Path -Path $srctoml -PathType Leaf) { + Copy-Item -Path $srctoml -Destination $desttoml -Force + write-host "materialized $working from artifact $rtname (metadata toml copied alongside)" + } else { + write-host "materialized $working from artifact $rtname (no metadata toml found beside the artifact)" + } + Set-PunkActiveRuntime $archfolder $working + } else { + Set-PunkActiveRuntime $archfolder $rtname + } } 'run' { #launch the active runtime, passing arguments. @@ -2438,10 +2595,12 @@ function psmain { write-host "$(${dircontents}.count) runtime(s) in $archfolder" Write-host "-----------------------------------------------------------------------" foreach ($f in $dircontents) { + $meta = Get-PunkRuntimeMetadataSummary $archfolder $f.Name + $lhs = "$($f.Name)".PadRight(35, ' ') if ($f.Name -eq $activename) { - write-host "* $($f.Name) (active)" + write-host "* $lhs (active) $meta" } else { - write-host " $($f.Name)" + write-host " $lhs $meta" } } Write-host "-----------------------------------------------------------------------" @@ -2450,6 +2609,7 @@ function psmain { } Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server" Write-host "Use: 'use ' to select the runtime that 'run' launches" + Write-host "Use: 'use -name>' to materialize an immutable -r artifact into its working name and select it" } else { write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'punk-runtime.cmd fetch' to install." write-host "Use 'punk-runtime.cmd list -remote' to see available runtimes for $arch" diff --git a/goals/G-103-runtime-kit-family.md b/goals/G-103-runtime-kit-family.md index 456d3d77..dd946823 100644 --- a/goals/G-103-runtime-kit-family.md +++ b/goals/G-103-runtime-kit-family.md @@ -324,13 +324,43 @@ from a scratch dir with scrubbed env - full pipeline (9805f035..., 9165 KB) + per-artifact tomls + sha1sums.txt, generated by the plain family kit itself. +### 2026-07-22 increment: punk-runtime list/use artifact-metadata surfacing (acceptance item landed) + +Scriptset extension (both payloads, ps1 + bash, wrapped to bin/punk-runtime.cmd +per the bin/AGENTS.md polyglot workflow; layout-shipped copy + .ps1 twin +refreshed; roundtrip pin test runtimecmd_roundtrip PASS): + +- `list` surfaces a per-runtime metadata summary parsed from the `.toml` + record beside each runtime: variant, tcl patchlevel, revision, piperepl + on/off, and - on a materialized working copy - `from=` (which + immutable -r artifact it came from). +- `use -name>` MATERIALIZES the immutable artifact into its + WORKING name (name minus -r) with the metadata toml copied alongside, then + selects the working name - the naming decision's "runtime use materializes a + chosen -rN artifact" mapping, so republishing never churns consumers. + `use ` selects as before. +- `fetch` of an -r-named artifact also retrieves its metadata toml from the + punkbin layout (absence tolerated - pre-family runtimes have no records). +- Root-name handling strips only `.exe` (dotted patchlevels break last-dot + stripping for extensionless unix names) - the same fix applied to + family_artifacts.tcl's artifact/toml naming (exe_split) for the G-105 + cross-target future. Candidate listing now excludes directories and .log + files in both payloads (parity cleanup). + +VERIFIED 2026-07-22 on the real wrapped bin/punk-runtime.cmd (windows +powershell 5 branch) and the bash payload directly (git-bash): `use +tclsh9.0.5-punk-r1.exe` materialized tclsh9.0.5-punk.exe + toml and selected +it; all three members materialized the same way; `list` shows the summaries in +both payloads identically; `punk-runtime run ` launched the active +punk family runtime and reported 9.0.5 / piperepl machinery present / +tcl_library=//zipfs:/app/tcl_library. Project 0.17.7 (patch bump + changelog: +punk-runtime is shipped product surface). + Remaining for acceptance: -- runtime scriptset (punk-runtime) list/use surfacing of the artifact metadata - (list variant/version facts from the tomls; 'use' materializes a chosen -r - artifact into its working name). - exercise the new mapvfs entries with an actual kit wrap (punk9_beta / - punk9bi_beta) - entries land this increment, the wrap trial is the next one - (kit builds touch user-facing bin/ kits; beta-trial convention applies). + punk9bi_beta) - entries landed 2026-07-22, the wrap trial is the next + increment (kit builds touch user-facing bin/ kits; beta-trial convention + applies). - record the 8.6-family deferral note against G-101 when closing (the acceptance's explicit deferral - no work item here). diff --git a/punkproject.toml b/punkproject.toml index 6e31fc01..3e772edc 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,4 +1,4 @@ [project] name = "punkshell" -version = "0.17.6" +version = "0.17.7" license = "BSD-2-Clause" diff --git a/src/buildsuites/suite_tcl90/tools/family_artifacts.tcl b/src/buildsuites/suite_tcl90/tools/family_artifacts.tcl index 1b7d94c2..83ee3e3c 100644 --- a/src/buildsuites/suite_tcl90/tools/family_artifacts.tcl +++ b/src/buildsuites/suite_tcl90/tools/family_artifacts.tcl @@ -47,6 +47,16 @@ proc toml_str {s} { 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 ""] +} + file mkdir $opt(-outdir) set built [clock format [clock seconds] -format %Y-%m-%dT%H:%M:%SZ -timezone :UTC] set sha1lines {} @@ -56,7 +66,8 @@ 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] - set artifact "[file rootname $working]-r$opt(-rev)[file extension $working]" + 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 @@ -127,7 +138,7 @@ foreach {variant kitpath} $opt(-kits) { lappend m {*}$tlines } } - set mf [file join $opt(-outdir) "[file rootname $artifact].toml"] + 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] diff --git a/src/project_layouts/vendor/punk/project-0.1/bin/punk-runtime.cmd b/src/project_layouts/vendor/punk/project-0.1/bin/punk-runtime.cmd index 963a5420..1f713340 100644 --- a/src/project_layouts/vendor/punk/project-0.1/bin/punk-runtime.cmd +++ b/src/project_layouts/vendor/punk/project-0.1/bin/punk-runtime.cmd @@ -1398,12 +1398,58 @@ set_active() { printf 'active = "%s"\n' "$1" > "$active_file" echo "active runtime for $archtail set to: $1 (recorded in $active_file)" } -#installed runtime candidates - exclude build copies and non-runtime files +#installed runtime candidates - exclude directories, build copies and +#non-runtime files (ps1-payload parity: files only, same extension excludes) list_candidates() { + local f if [[ -d "$archdir" ]]; then - ls -1 "$archdir" | grep -v '_BUILDCOPY$' | grep -v '\.txt$' | grep -v '\.toml$' | grep -v '\.tm$' | grep -v '\.tmp$' + for f in "$archdir"/*; do + [[ -f "$f" ]] || continue + f="${f##*/}" + case "$f" in + *_BUILDCOPY*|*.txt|*.toml|*.tm|*.tmp|*.log) continue;; + esac + printf '%s\n' "$f" + done fi } +#name minus a .exe suffix only - dotted tcl patchlevels (tclsh9.0.5-punk) make +#generic last-dot extension stripping wrong for extensionless unix names +rootname_of() { + case "$1" in + *.exe|*.EXE) printf '%s' "${1%.???}";; + *) printf '%s' "$1";; + esac +} +#G-103 artifact metadata: a runtime may carry a .toml beside it (emitted +#by the buildsuite kit-family-artifacts step / fetched from punkbin). Prints a +#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent. +metadata_summary() { + local exename="$1" tomlfile parts variant tclpatch revision piperepl artname + tomlfile="$archdir/$(rootname_of "$exename").toml" + [[ -f "$tomlfile" ]] || return 0 + variant=$(sed -n 's/^[[:space:]]*variant[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + tclpatch=$(sed -n 's/^[[:space:]]*tcl_patchlevel[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + revision=$(sed -n 's/^[[:space:]]*revision[[:space:]]*=[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$tomlfile" | head -n 1) + piperepl=$(sed -n 's/^[[:space:]]*piperepl[[:space:]]*=[[:space:]]*\(true\|false\).*/\1/p' "$tomlfile" | head -n 1) + artname=$(sed -n 's/^[[:space:]]*name[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + parts="" + [[ -n "$variant" ]] && parts="$parts variant=$variant" + [[ -n "$tclpatch" ]] && parts="$parts tcl=$tclpatch" + [[ -n "$revision" ]] && parts="$parts r$revision" + if [[ "$piperepl" == "true" ]]; then parts="$parts piperepl=on" + elif [[ "$piperepl" == "false" ]]; then parts="$parts piperepl=off"; fi + #a materialized working copy records which immutable artifact it came from + [[ -n "$artname" && "$artname" != "$exename" ]] && parts="$parts from=$artname" + [[ -n "$parts" ]] && printf '[%s]' "${parts# }" +} +#G-103 artifact-tier names carry -r before the (optional) .exe suffix; prints +#the WORKING name (artifact minus -r) when the name is artifact-tier, else "" +working_name_of() { + local w + w=$(printf '%s' "$1" | sed -E 's/^(.*)-r[0-9]+(\.[Ee][Xx][Ee])?$/\1\2/') + if [[ "$w" != "$1" ]]; then printf '%s' "$w"; fi +} case "$1" in "fetch") @@ -1480,6 +1526,17 @@ case "$1" in exit 1 fi fi + #G-103: family artifacts (-r names) carry a metadata toml alongside + #on the server - fetch it too; absence is fine (pre-family runtimes) + if [[ -n "$(working_name_of "$runtime")" ]]; then + tomlname="$(rootname_of "$runtime").toml" + if curl -fsSL --output "${archdir}/${tomlname}" "${url_kitbase}/${archtail}/${tomlname}"; then + echo "artifact metadata saved at ${archdir}/${tomlname}" + else + rm -f "${archdir}/${tomlname}" + echo "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)" + fi + fi #first fetch establishes the active runtime; later fetches never steal it if [[ -z "$(get_active)" ]]; then set_active "$runtime" @@ -1543,16 +1600,19 @@ case "$1" in count=$(printf '%s\n' "$candidates" | grep -c . ) echo "$count runtime(s) in $archdir" for f in $candidates; do + meta=$(metadata_summary "$f") if [[ "$f" == "$active" ]]; then - echo "* $f (active)" + printf '* %-35s (active) %s\n' "$f" "$meta" else - echo " $f" + printf ' %-35s %s\n' "$f" "$meta" fi done if [[ -n "$active" && ! -f "$archdir/$active" ]]; then echo "WARNING: active runtime '$active' (from $active_file) is not present - use '$0 use ' to reselect" fi echo "Use: '$0 list -remote' to compare local runtimes with those available on the artifact server" + echo "Use: '$0 use ' to select the runtime that 'run' launches" + echo "Use: '$0 use -name>' to materialize an immutable -r artifact into its working name and select it" else echo "No runtimes available in $archdir" echo " Use '$0 fetch' to install." @@ -1586,7 +1646,26 @@ case "$1" in done exit 1 fi - set_active "$2" + #G-103 artifact-tier names (-r, immutable): 'use' MATERIALIZES the + #artifact into its WORKING name (name minus -r - what mapvfs and + #projects reference), copies its metadata toml alongside, and selects + #the working name. Republishing artifacts never churns consumers. + working=$(working_name_of "$2") + if [[ -n "$working" ]]; then + cp -f "$archdir/$2" "$archdir/$working" + chmod +x "$archdir/$working" + srctoml="$archdir/$(rootname_of "$2").toml" + desttoml="$archdir/$(rootname_of "$working").toml" + if [[ -f "$srctoml" ]]; then + cp -f "$srctoml" "$desttoml" + echo "materialized $working from artifact $2 (metadata toml copied alongside)" + else + echo "materialized $working from artifact $2 (no metadata toml found beside the artifact)" + fi + set_active "$working" + else + set_active "$2" + fi ;; "run") #resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single @@ -2004,7 +2083,51 @@ function Get-PunkRuntimeCandidates { if (-not (Test-Path -Path $archfolder -PathType Container)) { return @() } - return @(get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY*' | Where-object {-not ($(".txt",".toml",".tm",".tmp") -contains $_.Extension) } | Sort-Object Name) + return @(get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY*' | Where-object {-not ($(".txt",".toml",".tm",".tmp",".log") -contains $_.Extension) } | Sort-Object Name) +} +#name minus a .exe suffix only - dotted tcl patchlevels (tclsh9.0.5-punk) make +#generic last-dot extension stripping wrong for extensionless unix names +function Get-PunkRuntimeRootName { + param([string] $name) + if ($name -match '(?i)\.exe$') { + return $name.Substring(0, $name.Length - 4) + } + return $name +} +#G-103 artifact metadata: a runtime may carry a .toml beside it (emitted +#by the buildsuite kit-family-artifacts step / fetched from punkbin). Returns a +#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent. +function Get-PunkRuntimeMetadataSummary { + param([string] $archfolder, [string] $exename) + $tomlfile = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $exename) + ".toml") + if (-not (Test-Path -Path $tomlfile -PathType Leaf)) { + return "" + } + $fields = @{} + foreach ($line in (Get-Content -Path $tomlfile)) { + $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"(.*)"\s*$') + if ($m.Success) { + $fields[$m.Groups[1].Value] = $m.Groups[2].Value + continue + } + $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([0-9]+|true|false)\s*$') + if ($m.Success) { + $fields[$m.Groups[1].Value] = $m.Groups[2].Value + } + } + $parts = @() + if ($fields.ContainsKey('variant')) { $parts += "variant=$($fields['variant'])" } + if ($fields.ContainsKey('tcl_patchlevel')) { $parts += "tcl=$($fields['tcl_patchlevel'])" } + if ($fields.ContainsKey('revision')) { $parts += "r$($fields['revision'])" } + if ($fields.ContainsKey('piperepl')) { + if ($fields['piperepl'] -eq 'true') { $parts += "piperepl=on" } else { $parts += "piperepl=off" } + } + #a materialized working copy records which immutable artifact it came from + if ($fields.ContainsKey('name') -and $fields['name'] -ne $exename) { $parts += "from=$($fields['name'])" } + if ($parts.Count -eq 0) { + return "" + } + return "[" + ($parts -join " ") + "]" } function psmain { @@ -2212,6 +2335,19 @@ function psmain { Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server." Write-Host "No download required" } + #G-103: family artifacts (-r names) carry a metadata toml alongside + #on the server - fetch it too; absence is fine (pre-family runtimes) + if ($runtime -match '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') { + $tomlname = (Get-PunkRuntimeRootName $runtime) + ".toml" + $tomlurl = "$archurl/$tomlname" + $tomllocal = Join-Path -Path $archfolder -ChildPath $tomlname + try { + Invoke-WebRequest -Uri $tomlurl -OutFile $tomllocal -ErrorAction Stop + Write-Host "artifact metadata saved at $tomllocal" + } catch { + Write-Host "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)" + } + } #first fetch establishes the active runtime; later fetches never steal it if ((Get-PunkActiveRuntime $archfolder) -eq "") { Set-PunkActiveRuntime $archfolder $runtime @@ -2255,7 +2391,28 @@ function psmain { } exit 1 } - Set-PunkActiveRuntime $archfolder $rtname + #G-103 artifact-tier names (-r, immutable): 'use' MATERIALIZES the + #artifact into its WORKING name (name minus -r - what mapvfs and + #projects reference), copies its metadata toml alongside, and selects + #the working name. Republishing artifacts never churns consumers. + $am = [regex]::Match($rtname, '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') + if ($am.Success) { + $working = $am.Groups[1].Value + $am.Groups[3].Value + $srcexe = Join-Path -Path $archfolder -ChildPath $rtname + $destexe = Join-Path -Path $archfolder -ChildPath $working + Copy-Item -Path $srcexe -Destination $destexe -Force + $srctoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $rtname) + ".toml") + $desttoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $working) + ".toml") + if (Test-Path -Path $srctoml -PathType Leaf) { + Copy-Item -Path $srctoml -Destination $desttoml -Force + write-host "materialized $working from artifact $rtname (metadata toml copied alongside)" + } else { + write-host "materialized $working from artifact $rtname (no metadata toml found beside the artifact)" + } + Set-PunkActiveRuntime $archfolder $working + } else { + Set-PunkActiveRuntime $archfolder $rtname + } } 'run' { #launch the active runtime, passing arguments. @@ -2438,10 +2595,12 @@ function psmain { write-host "$(${dircontents}.count) runtime(s) in $archfolder" Write-host "-----------------------------------------------------------------------" foreach ($f in $dircontents) { + $meta = Get-PunkRuntimeMetadataSummary $archfolder $f.Name + $lhs = "$($f.Name)".PadRight(35, ' ') if ($f.Name -eq $activename) { - write-host "* $($f.Name) (active)" + write-host "* $lhs (active) $meta" } else { - write-host " $($f.Name)" + write-host " $lhs $meta" } } Write-host "-----------------------------------------------------------------------" @@ -2450,6 +2609,7 @@ function psmain { } Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server" Write-host "Use: 'use ' to select the runtime that 'run' launches" + Write-host "Use: 'use -name>' to materialize an immutable -r artifact into its working name and select it" } else { write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'punk-runtime.cmd fetch' to install." write-host "Use 'punk-runtime.cmd list -remote' to see available runtimes for $arch" diff --git a/src/scriptapps/bin/punk-runtime.bash b/src/scriptapps/bin/punk-runtime.bash index e2a4aa0c..a5a1d3f0 100644 --- a/src/scriptapps/bin/punk-runtime.bash +++ b/src/scriptapps/bin/punk-runtime.bash @@ -118,12 +118,58 @@ set_active() { printf 'active = "%s"\n' "$1" > "$active_file" echo "active runtime for $archtail set to: $1 (recorded in $active_file)" } -#installed runtime candidates - exclude build copies and non-runtime files +#installed runtime candidates - exclude directories, build copies and +#non-runtime files (ps1-payload parity: files only, same extension excludes) list_candidates() { + local f if [[ -d "$archdir" ]]; then - ls -1 "$archdir" | grep -v '_BUILDCOPY$' | grep -v '\.txt$' | grep -v '\.toml$' | grep -v '\.tm$' | grep -v '\.tmp$' + for f in "$archdir"/*; do + [[ -f "$f" ]] || continue + f="${f##*/}" + case "$f" in + *_BUILDCOPY*|*.txt|*.toml|*.tm|*.tmp|*.log) continue;; + esac + printf '%s\n' "$f" + done fi } +#name minus a .exe suffix only - dotted tcl patchlevels (tclsh9.0.5-punk) make +#generic last-dot extension stripping wrong for extensionless unix names +rootname_of() { + case "$1" in + *.exe|*.EXE) printf '%s' "${1%.???}";; + *) printf '%s' "$1";; + esac +} +#G-103 artifact metadata: a runtime may carry a .toml beside it (emitted +#by the buildsuite kit-family-artifacts step / fetched from punkbin). Prints a +#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent. +metadata_summary() { + local exename="$1" tomlfile parts variant tclpatch revision piperepl artname + tomlfile="$archdir/$(rootname_of "$exename").toml" + [[ -f "$tomlfile" ]] || return 0 + variant=$(sed -n 's/^[[:space:]]*variant[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + tclpatch=$(sed -n 's/^[[:space:]]*tcl_patchlevel[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + revision=$(sed -n 's/^[[:space:]]*revision[[:space:]]*=[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$tomlfile" | head -n 1) + piperepl=$(sed -n 's/^[[:space:]]*piperepl[[:space:]]*=[[:space:]]*\(true\|false\).*/\1/p' "$tomlfile" | head -n 1) + artname=$(sed -n 's/^[[:space:]]*name[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$tomlfile" | head -n 1) + parts="" + [[ -n "$variant" ]] && parts="$parts variant=$variant" + [[ -n "$tclpatch" ]] && parts="$parts tcl=$tclpatch" + [[ -n "$revision" ]] && parts="$parts r$revision" + if [[ "$piperepl" == "true" ]]; then parts="$parts piperepl=on" + elif [[ "$piperepl" == "false" ]]; then parts="$parts piperepl=off"; fi + #a materialized working copy records which immutable artifact it came from + [[ -n "$artname" && "$artname" != "$exename" ]] && parts="$parts from=$artname" + [[ -n "$parts" ]] && printf '[%s]' "${parts# }" +} +#G-103 artifact-tier names carry -r before the (optional) .exe suffix; prints +#the WORKING name (artifact minus -r) when the name is artifact-tier, else "" +working_name_of() { + local w + w=$(printf '%s' "$1" | sed -E 's/^(.*)-r[0-9]+(\.[Ee][Xx][Ee])?$/\1\2/') + if [[ "$w" != "$1" ]]; then printf '%s' "$w"; fi +} case "$1" in "fetch") @@ -200,6 +246,17 @@ case "$1" in exit 1 fi fi + #G-103: family artifacts (-r names) carry a metadata toml alongside + #on the server - fetch it too; absence is fine (pre-family runtimes) + if [[ -n "$(working_name_of "$runtime")" ]]; then + tomlname="$(rootname_of "$runtime").toml" + if curl -fsSL --output "${archdir}/${tomlname}" "${url_kitbase}/${archtail}/${tomlname}"; then + echo "artifact metadata saved at ${archdir}/${tomlname}" + else + rm -f "${archdir}/${tomlname}" + echo "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)" + fi + fi #first fetch establishes the active runtime; later fetches never steal it if [[ -z "$(get_active)" ]]; then set_active "$runtime" @@ -263,16 +320,19 @@ case "$1" in count=$(printf '%s\n' "$candidates" | grep -c . ) echo "$count runtime(s) in $archdir" for f in $candidates; do + meta=$(metadata_summary "$f") if [[ "$f" == "$active" ]]; then - echo "* $f (active)" + printf '* %-35s (active) %s\n' "$f" "$meta" else - echo " $f" + printf ' %-35s %s\n' "$f" "$meta" fi done if [[ -n "$active" && ! -f "$archdir/$active" ]]; then echo "WARNING: active runtime '$active' (from $active_file) is not present - use '$0 use ' to reselect" fi echo "Use: '$0 list -remote' to compare local runtimes with those available on the artifact server" + echo "Use: '$0 use ' to select the runtime that 'run' launches" + echo "Use: '$0 use -name>' to materialize an immutable -r artifact into its working name and select it" else echo "No runtimes available in $archdir" echo " Use '$0 fetch' to install." @@ -306,7 +366,26 @@ case "$1" in done exit 1 fi - set_active "$2" + #G-103 artifact-tier names (-r, immutable): 'use' MATERIALIZES the + #artifact into its WORKING name (name minus -r - what mapvfs and + #projects reference), copies its metadata toml alongside, and selects + #the working name. Republishing artifacts never churns consumers. + working=$(working_name_of "$2") + if [[ -n "$working" ]]; then + cp -f "$archdir/$2" "$archdir/$working" + chmod +x "$archdir/$working" + srctoml="$archdir/$(rootname_of "$2").toml" + desttoml="$archdir/$(rootname_of "$working").toml" + if [[ -f "$srctoml" ]]; then + cp -f "$srctoml" "$desttoml" + echo "materialized $working from artifact $2 (metadata toml copied alongside)" + else + echo "materialized $working from artifact $2 (no metadata toml found beside the artifact)" + fi + set_active "$working" + else + set_active "$2" + fi ;; "run") #resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single diff --git a/src/scriptapps/bin/punk-runtime.ps1 b/src/scriptapps/bin/punk-runtime.ps1 index 48d0e9d1..38b5d655 100644 --- a/src/scriptapps/bin/punk-runtime.ps1 +++ b/src/scriptapps/bin/punk-runtime.ps1 @@ -68,7 +68,51 @@ function Get-PunkRuntimeCandidates { if (-not (Test-Path -Path $archfolder -PathType Container)) { return @() } - return @(get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY*' | Where-object {-not ($(".txt",".toml",".tm",".tmp") -contains $_.Extension) } | Sort-Object Name) + return @(get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY*' | Where-object {-not ($(".txt",".toml",".tm",".tmp",".log") -contains $_.Extension) } | Sort-Object Name) +} +#name minus a .exe suffix only - dotted tcl patchlevels (tclsh9.0.5-punk) make +#generic last-dot extension stripping wrong for extensionless unix names +function Get-PunkRuntimeRootName { + param([string] $name) + if ($name -match '(?i)\.exe$') { + return $name.Substring(0, $name.Length - 4) + } + return $name +} +#G-103 artifact metadata: a runtime may carry a .toml beside it (emitted +#by the buildsuite kit-family-artifacts step / fetched from punkbin). Returns a +#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent. +function Get-PunkRuntimeMetadataSummary { + param([string] $archfolder, [string] $exename) + $tomlfile = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $exename) + ".toml") + if (-not (Test-Path -Path $tomlfile -PathType Leaf)) { + return "" + } + $fields = @{} + foreach ($line in (Get-Content -Path $tomlfile)) { + $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"(.*)"\s*$') + if ($m.Success) { + $fields[$m.Groups[1].Value] = $m.Groups[2].Value + continue + } + $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([0-9]+|true|false)\s*$') + if ($m.Success) { + $fields[$m.Groups[1].Value] = $m.Groups[2].Value + } + } + $parts = @() + if ($fields.ContainsKey('variant')) { $parts += "variant=$($fields['variant'])" } + if ($fields.ContainsKey('tcl_patchlevel')) { $parts += "tcl=$($fields['tcl_patchlevel'])" } + if ($fields.ContainsKey('revision')) { $parts += "r$($fields['revision'])" } + if ($fields.ContainsKey('piperepl')) { + if ($fields['piperepl'] -eq 'true') { $parts += "piperepl=on" } else { $parts += "piperepl=off" } + } + #a materialized working copy records which immutable artifact it came from + if ($fields.ContainsKey('name') -and $fields['name'] -ne $exename) { $parts += "from=$($fields['name'])" } + if ($parts.Count -eq 0) { + return "" + } + return "[" + ($parts -join " ") + "]" } function psmain { @@ -276,6 +320,19 @@ function psmain { Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server." Write-Host "No download required" } + #G-103: family artifacts (-r names) carry a metadata toml alongside + #on the server - fetch it too; absence is fine (pre-family runtimes) + if ($runtime -match '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') { + $tomlname = (Get-PunkRuntimeRootName $runtime) + ".toml" + $tomlurl = "$archurl/$tomlname" + $tomllocal = Join-Path -Path $archfolder -ChildPath $tomlname + try { + Invoke-WebRequest -Uri $tomlurl -OutFile $tomllocal -ErrorAction Stop + Write-Host "artifact metadata saved at $tomllocal" + } catch { + Write-Host "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)" + } + } #first fetch establishes the active runtime; later fetches never steal it if ((Get-PunkActiveRuntime $archfolder) -eq "") { Set-PunkActiveRuntime $archfolder $runtime @@ -319,7 +376,28 @@ function psmain { } exit 1 } - Set-PunkActiveRuntime $archfolder $rtname + #G-103 artifact-tier names (-r, immutable): 'use' MATERIALIZES the + #artifact into its WORKING name (name minus -r - what mapvfs and + #projects reference), copies its metadata toml alongside, and selects + #the working name. Republishing artifacts never churns consumers. + $am = [regex]::Match($rtname, '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') + if ($am.Success) { + $working = $am.Groups[1].Value + $am.Groups[3].Value + $srcexe = Join-Path -Path $archfolder -ChildPath $rtname + $destexe = Join-Path -Path $archfolder -ChildPath $working + Copy-Item -Path $srcexe -Destination $destexe -Force + $srctoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $rtname) + ".toml") + $desttoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $working) + ".toml") + if (Test-Path -Path $srctoml -PathType Leaf) { + Copy-Item -Path $srctoml -Destination $desttoml -Force + write-host "materialized $working from artifact $rtname (metadata toml copied alongside)" + } else { + write-host "materialized $working from artifact $rtname (no metadata toml found beside the artifact)" + } + Set-PunkActiveRuntime $archfolder $working + } else { + Set-PunkActiveRuntime $archfolder $rtname + } } 'run' { #launch the active runtime, passing arguments. @@ -502,10 +580,12 @@ function psmain { write-host "$(${dircontents}.count) runtime(s) in $archfolder" Write-host "-----------------------------------------------------------------------" foreach ($f in $dircontents) { + $meta = Get-PunkRuntimeMetadataSummary $archfolder $f.Name + $lhs = "$($f.Name)".PadRight(35, ' ') if ($f.Name -eq $activename) { - write-host "* $($f.Name) (active)" + write-host "* $lhs (active) $meta" } else { - write-host " $($f.Name)" + write-host " $lhs $meta" } } Write-host "-----------------------------------------------------------------------" @@ -514,6 +594,7 @@ function psmain { } Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server" Write-host "Use: 'use ' to select the runtime that 'run' launches" + Write-host "Use: 'use -name>' to materialize an immutable -r artifact into its working name and select it" } else { write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'punk-runtime.cmd fetch' to install." write-host "Use 'punk-runtime.cmd list -remote' to see available runtimes for $arch"