Browse Source

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 <rootname>.toml metadata record
  beside each runtime (variant, tcl patchlevel, revision, piperepl on/off,
  from=<artifact> on materialized working copies).
- use <artifact-r<N>-name>: materializes the immutable artifact into its
  WORKING name (minus -r<N>) with the metadata toml copied alongside, then
  selects it - the G-103 naming decision's artifact->working mapping, so
  republishing never churns consumers. use <workingname> unchanged.
- fetch of -r<N> 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
master
Julian Noble 1 week ago
parent
commit
61cf903dd9
  1. 4
      CHANGELOG.md
  2. 12
      bin/AGENTS.md
  3. 178
      bin/punk-runtime.cmd
  4. 40
      goals/G-103-runtime-kit-family.md
  5. 2
      punkproject.toml
  6. 15
      src/buildsuites/suite_tcl90/tools/family_artifacts.tcl
  7. 178
      src/project_layouts/vendor/punk/project-0.1/bin/punk-runtime.cmd
  8. 89
      src/scriptapps/bin/punk-runtime.bash
  9. 89
      src/scriptapps/bin/punk-runtime.ps1

4
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 `<rootname>.toml` records beside the runtimes (variant, tcl patchlevel, revision, piperepl policy, source artifact of a materialized copy); `use <artifact-r<N>-name>` materializes an immutable `-r<N>` artifact into its working name (metadata toml copied alongside) and selects it; `fetch` of an `-r<N>` 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.

12
bin/AGENTS.md

@ -87,6 +87,18 @@ written by `punk-runtime.cmd use <name>`, 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
`<rootname>.toml` metadata record beside it (emitted by the suite_tcl90
`kit-family-artifacts` step; fetched from punkbin alongside `-r<N>`-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 <artifact-r<N>-name>` MATERIALIZES
the immutable artifact into its WORKING name (the name minus `-r<N>` - what mapvfs and
projects reference), copies the metadata toml alongside, and selects the working name;
`use <workingname>` 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.

178
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 <rootname>.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<N> before the (optional) .exe suffix; prints
#the WORKING name (artifact minus -r<N>) 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<N> 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 <name>' to reselect"
fi
echo "Use: '$0 list -remote' to compare local runtimes with those available on the artifact server"
echo "Use: '$0 use <name>' to select the runtime that 'run' launches"
echo "Use: '$0 use <artifact-r<N>-name>' to materialize an immutable -r<N> 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<N>, immutable): 'use' MATERIALIZES the
#artifact into its WORKING name (name minus -r<N> - 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 <rootname>.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<N> 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<N>, immutable): 'use' MATERIALIZES the
#artifact into its WORKING name (name minus -r<N> - 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 <name>' to select the runtime that 'run' launches"
Write-host "Use: 'use <artifact-r<N>-name>' to materialize an immutable -r<N> 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"

40
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 `<rootname>.toml`
record beside each runtime: variant, tcl patchlevel, revision, piperepl
on/off, and - on a materialized working copy - `from=<artifact>` (which
immutable -r<N> artifact it came from).
- `use <artifact-r<N>-name>` MATERIALIZES the immutable artifact into its
WORKING name (name minus -r<N>) 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 <workingname>` selects as before.
- `fetch` of an -r<N>-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 <probe.tcl>` 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<N>
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).

2
punkproject.toml

@ -1,4 +1,4 @@
[project]
name = "punkshell"
version = "0.17.6"
version = "0.17.7"
license = "BSD-2-Clause"

15
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]

178
src/project_layouts/vendor/punk/project-0.1/bin/punk-runtime.cmd vendored

@ -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 <rootname>.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<N> before the (optional) .exe suffix; prints
#the WORKING name (artifact minus -r<N>) 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<N> 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 <name>' to reselect"
fi
echo "Use: '$0 list -remote' to compare local runtimes with those available on the artifact server"
echo "Use: '$0 use <name>' to select the runtime that 'run' launches"
echo "Use: '$0 use <artifact-r<N>-name>' to materialize an immutable -r<N> 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<N>, immutable): 'use' MATERIALIZES the
#artifact into its WORKING name (name minus -r<N> - 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 <rootname>.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<N> 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<N>, immutable): 'use' MATERIALIZES the
#artifact into its WORKING name (name minus -r<N> - 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 <name>' to select the runtime that 'run' launches"
Write-host "Use: 'use <artifact-r<N>-name>' to materialize an immutable -r<N> 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"

89
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 <rootname>.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<N> before the (optional) .exe suffix; prints
#the WORKING name (artifact minus -r<N>) 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<N> 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 <name>' to reselect"
fi
echo "Use: '$0 list -remote' to compare local runtimes with those available on the artifact server"
echo "Use: '$0 use <name>' to select the runtime that 'run' launches"
echo "Use: '$0 use <artifact-r<N>-name>' to materialize an immutable -r<N> 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<N>, immutable): 'use' MATERIALIZES the
#artifact into its WORKING name (name minus -r<N> - 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

89
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 <rootname>.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<N> 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<N>, immutable): 'use' MATERIALIZES the
#artifact into its WORKING name (name minus -r<N> - 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 <name>' to select the runtime that 'run' launches"
Write-host "Use: 'use <artifact-r<N>-name>' to materialize an immutable -r<N> 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"

Loading…
Cancel
Save