Browse Source

runtime.cmd: active-runtime selection (use subcommand + active.toml), bash checksum parity, scriptset moved to src/scriptapps/bin (punkshell 0.7.0)

- run no longer launches the last runtime alphabetically: resolution is PUNK_ACTIVE_RUNTIME env override -> bin/runtime/<platform>/active.toml (constrained single-key toml, written by the new 'use <name>' subcommand, marked with * in list, covered by the existing bin/* VCS ignores) -> sole installed candidate -> error listing candidates. First fetch sets active only when none recorded; later fetches never steal it. Stale active (file removed) reported with reselect guidance.
- bash/zsh payload fetch reaches checksum parity with the powershell payload: fetches sha1sums.txt, locates the stored hash, skips when the local copy matches, downloads to .tmp and installs only on sha1 match; sha1 tool detection (sha1sum/shasum/sha1/openssl) with refusal of unverified downloads when none present; optional runtime-name argument added (fetch <name>)
- fixes: powershell "stored hash from sha1sums.txt" printed an undefined variable ($storedhash -> $stored_sha1); bash MSYS branch invalid assignment (interp = ...); candidate listings for run/list/use exclude .tmp leftovers and non-runtime files (a stray .txt could previously be selected by run)
- runtime scriptset sources relocated to src/scriptapps/bin (proper home alongside getzig.*); bin/runtime.cmd regenerated via scriptwrap from the new location - roundtrip test path updated, suite green on 9.0.3 (8 pass + unix-gated skip), runtime.bash syntax-checked under bash and zsh
- verified live on windows: multiple-runtimes-no-active errors with candidates (previously silently launched tksfe-twapi-x64), use/list/run against tclsfe-x64 and env override to tclsh902z, fetch no-steal + first-fetch auto-set, checksum-match no-download path
- docs: bin/AGENTS.md documents the fetch/use/run contract and updated generated-polyglot workflow paths; src/scriptapps/AGENTS.md scriptset-home wording updated

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 5 days ago
parent
commit
7f32862d2f
  1. 6
      CHANGELOG.md
  2. 31
      bin/AGENTS.md
  3. 383
      bin/runtime.cmd
  4. 2
      punkproject.toml
  5. 2
      src/scriptapps/AGENTS.md
  6. 309
      src/scriptapps/bin/runtime.bash
  7. 131
      src/scriptapps/bin/runtime.ps1
  8. 0
      src/scriptapps/bin/runtime_wrap.toml
  9. 139
      src/scriptapps/runtime.bash
  10. 4
      src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test

6
CHANGELOG.md

@ -5,6 +5,12 @@ 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.7.0] - 2026-07-11
- `bin/runtime.cmd` gains a `use` subcommand and an active-runtime selection: which runtime `run` launches is now the per-machine `bin/runtime/<platform>/active.toml` (`active = "<name>"`, shown with a `*` in `list`), resolved as `PUNK_ACTIVE_RUNTIME` env override → `active.toml` → sole installed candidate — with multiple runtimes and no selection, `run` errors listing candidates instead of silently launching the last name alphabetically. The first `fetch` sets the active runtime only when none is recorded; later fetches never steal it.
- `runtime.cmd fetch` checksum parity for the unix (bash/zsh) payload: it now fetches `sha1sums.txt`, skips when the local file already matches, downloads to `.tmp` and installs only on sha1 match (tool detection across `sha1sum`/`shasum`/`sha1`/`openssl`; refuses unverified downloads when no tool is present), and accepts an optional runtime name like the powershell payload. Bug fixes: the powershell "stored hash" message printed an empty variable; the bash MSYS branch had an invalid `interp =` assignment; `.tmp` leftovers and non-runtime files no longer appear as run/list/use candidates.
- The runtime scriptset sources moved to `src/scriptapps/bin/` (alongside `getzig.*`), the proper home for bin-deployed scriptsets; `bin/runtime.cmd` regenerated via the scriptwrap workflow (round-trip pinned byte-identical by the multishell test suite).
## [0.6.0] - 2026-07-10
- G-058: punkshell boot honours statically-linked runtime packages. Kits built on runtimes with static extensions (e.g `tclsfe-x64.exe`: Thread/twapi/sqlite3/tdbc — the punk91 kit) previously lost `package require` access to them in every interp/thread punkshell fabricates: the appended vfs replaces the runtime's own `//zipfs:/app` mount (taking its pkgIndex files with it) and punkshell's boot path replacement discards what remains — the repl code interp failed with `can't find package Thread` and punk::console couldn't load. Now `punk_main.tcl` captures the static baseline at boot (probe-loading each empty-filename `info loaded` prefix in a throwaway interp and recording only packages the load actually provides; configurable denylist for side-effecting/composite inits), and the baseline seeds `package ifneeded <name> <ver> {load {} <prefix>}` mappings in the boot interp, the repl codethread and code interp (punk::lib 0.4.0 `interp_sync_package_paths`/`snapshot_package_paths`, punk::repl 0.3.0), and shellthread 1.7.0 workers — introspectable at the repl via `::punkboot::static_packages`.

31
bin/AGENTS.md

@ -7,15 +7,18 @@ Built punk shell executables (kits with the punk boot layer), assorted build/exp
### Generated polyglot .cmd scripts - never edit in place
The `.cmd` scripts here (e.g `runtime.cmd`) are punk MULTISHELL polyglots GENERATED by
`punk::mix::commandset::scriptwrap::multishell` from sources under `src/scriptapps/`:
payload scripts (`<name>.ps1`, `<name>.bash`, ...) plus a `<name>_wrap.toml` config,
spliced into the `punk.multishell.cmd` template. The polyglot structure is deliberately
fragile (mutual shell-hiding tricks, LF-only endings, cmd.exe's 512-byte label-scanner
constraints) - a hand-edit can silently break one of the participating shells.
`punk::mix::commandset::scriptwrap::multishell` from scriptset sources under
`src/scriptapps/bin/` (the home for bin-deployed scriptsets, e.g `runtime.*` alongside
`getzig.*`): payload scripts (`<name>.ps1`, `<name>.bash`, ...) plus a `<name>_wrap.toml`
config, spliced into the `punk.multishell.cmd` template. The polyglot structure is
deliberately fragile (mutual shell-hiding tricks, LF-only endings, cmd.exe's 512-byte
label-scanner constraints) - a hand-edit can silently break one of the participating
shells.
When asked to "fix bin/<name>.cmd":
1. Edit the payload/config sources under `src/scriptapps/` (never the output file).
2. Re-wrap from `src/scriptapps/`: `punk::mix::commandset::scriptwrap::multishell <name> -askme 0`
1. Edit the payload/config sources under `src/scriptapps/bin/` (never the output file).
2. Re-wrap from the scriptset's folder: cd `src/scriptapps/bin` then
`punk::mix::commandset::scriptwrap::multishell <name> -askme 0`
(output defaults to `<projectroot>/bin`; requires Thread + the punk::mix modules, e.g
a punk shell or a tclsh with project module paths).
3. The wrapper runs `checkfile` (the 512-byte label/boundary validator) automatically -
@ -37,6 +40,20 @@ The first argument to a built punk shell may be a dash-delimited package mode co
- Prepends `src/modules`, `src/modules_tcl<N>`, `src/bootsupport/modules{,_tcl<N>}` and `src/vendormodules{,_tcl<N>}` to the module path, sets `package prefer latest` so dev-numbered `999999.0a1.0` source modules beat kit-stamped snapshots on unversioned `package require`, and registers `#modpod` modules from `src/modules` (startup notice: `src mode: registered N #modpod modules from .../src/modules`).
- Consequence: `punksys src` / `punk902z src` exercises current working-tree source with no kit rebuild; a plain launch (`punksys`) exercises the module snapshots baked into the kit at build time. `package present <pkg>` reporting `999999.0a1.0` (vs a release version like `0.7.1`) tells you which set a session is running - runbooks with version expectations must state the intended launch mode.
### Runtime fetch/selection (`runtime.cmd`)
`bin/runtime.cmd {fetch|list|use|run}` manages the plain runtimes under
`bin/runtime/<platform>/` (retrieved from the punkbin artifact repo with sha1
verification against its `sha1sums.txt` - both the powershell payload on windows and the
bash payload on unix verify; a fetch whose checksum fails leaves only a `.tmp`).
Which runtime `run` launches is the per-machine "active" selection in
`bin/runtime/<platform>/active.toml` (constrained single-key toml `active = "<name>"`,
written by `runtime.cmd use <name>`, marked in `list`, VCS-ignored via the existing
`bin/*` globs). Resolution order for `run`: `PUNK_ACTIVE_RUNTIME` env override, then
`active.toml`, then a sole installed candidate - with multiple runtimes and no selection
it errors listing candidates rather than guessing. The first `fetch` sets the active
runtime only when none is recorded.
### 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.

383
bin/runtime.cmd

@ -1298,16 +1298,17 @@ arch=$(uname -m) #machine/architecture
plat=$(uname -s) #platform/system
#even though most of the platform prongs are very similar,
#we keep the code separate so it can be tweaked easily for unexpected differences
#each prong sets archdir (local folder) and, where a runtime is published for the
#platform, rt_default (default runtime name). archtail (server folder) is derived
#from archdir below.
if [[ "$plat" = "Linux"* ]]; then
if [[ "$arch" = "x86_64"* ]]; then
url="${url_kitbase}/linux-x86_64/tclkit-902-Linux64-intel-dyn"
archdir="${scriptdir}/runtime/linux-x86_64"
output="${archdir}/tclkit-902-Linux64-intel-dyn"
rt_default="tclkit-902-Linux64-intel-dyn"
runtime_available=1
elif [[ "$arch" = "arm"* ]]; then
url="${url_kitbase}/linux-arm/tclkit-902-Linux64-arm-dyn"
archdir="${scriptdir}/runtime/linux-arm"
output="${archdir}/tclkit-902-Linux64-arm-dyn"
rt_default="tclkit-902-Linux64-arm-dyn"
runtime_available=1
else
archdir="${scriptdir}/runtime/linux-$arch"
@ -1316,9 +1317,8 @@ if [[ "$plat" = "Linux"* ]]; then
elif [[ "$plat" = "Darwin"* ]]; then
os="macosx"
#assumed to be Mach-O 'universal binaries' for both x86-64 and arm? - REVIEW
url="${url_kitbase}/macosx/tclkit-902-Darwin64-dyn"
archdir="${scriptdir}/runtime/macosx/"
output="${archdir}/tclkit-902-Darwin64-dyn"
archdir="${scriptdir}/runtime/macosx"
rt_default="tclkit-902-Darwin64-dyn"
runtime_available=1
elif [[ "$plat" = "FreeBSD"* ]]; then
archdir="${scriptdir}/runtime/freebsd-amd64"
@ -1335,76 +1335,246 @@ elif [[ "$plat" == "OpenBSD"* ]]; then
elif [[ "$plat" == "MINGW32"* ]]; then
#REVIEW
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64"
rt_default="tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "MINGW64"* ]]; then
#REVIEW
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64"
rt_default="tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64"
rt_default="tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "MSYS_NT"* ]]; then
echo MSYS
os="win32"
#use 'command -v' (shell builtin preferred over external which)
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
interp=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
shellpath=`command -v $interp`
shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64"
output="${archdir}/tclsh902z.exe"
rt_default="tclsh902z.exe"
runtime_available=1
else
archdir="${scriptdir}/runtime/other"
os="other"
fi
archtail="${archdir##*/}" #server folder name e.g linux-x86_64
active_file="${archdir}/active.toml"
#sha1 of a file - tool varies by platform (sha1sum: linux/coreutils, shasum: macosx,
#sha1: BSDs, openssl: common fallback). Empty result means no tool available.
sha1_of() {
if command -v sha1sum >/dev/null 2>&1; then
sha1sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 1 "$1" | awk '{print $1}'
elif command -v sha1 >/dev/null 2>&1; then
sha1 -q "$1"
elif command -v openssl >/dev/null 2>&1; then
openssl dgst -sha1 -r "$1" | awk '{print $1}'
else
echo ""
fi
}
lcase() { printf '%s' "$1" | tr 'A-F' 'a-f'; }
#active runtime marker: constrained single-key toml, per platform folder
#(local per-machine state - ignored by git and fossil)
get_active() {
if [[ -f "$active_file" ]]; then
sed -n 's/^[[:space:]]*active[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$active_file" | head -n 1
fi
}
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
list_candidates() {
if [[ -d "$archdir" ]]; then
ls -1 "$archdir" | grep -v '_BUILDCOPY$' | grep -v '\.txt$' | grep -v '\.toml$' | grep -v '\.tm$' | grep -v '\.tmp$'
fi
}
case "$1" in
"fetch")
if [[ "$runtime_available" -eq 1 ]]; then
#test win32
mkdir -p $archdir
echo "Attempting to download $url"
#wget $url -O $output
curl -SL --output "$output" "$url"
if [[ $? -eq 0 ]]; then
echo "File downloaded to $output"
chmod +x $output
if [[ "$plat" == "Linux" ]]; then
echo "Please ensure libxFt.so.2 is available"
echo "e.g on Ubuntu: sudo apt-get install libxft2"
runtime="$rt_default"
if [[ -n "$2" ]]; then
runtime="$2"
fi
if [[ ( "$runtime_available" -eq 1 || -n "$2" ) && "$archtail" != "other" && -n "$runtime" ]]; then
url="${url_kitbase}/${archtail}/${runtime}"
output="${archdir}/${runtime}"
sha1url="${url_kitbase}/${archtail}/sha1sums.txt"
sha1local="${archdir}/sha1sums.txt"
mkdir -p "$archdir"
echo "Fetching $sha1url"
if ! curl -fsSL --output "$sha1local" "$sha1url"; then
echo "An error occurred while downloading $sha1url"
if [[ -f "$output" ]]; then
echo "A runtime is available at $output - but we failed to retrieve the list of sha1sums from the server"
echo "Unable to check for updated version at this time."
else
echo "Please retry - or manually download a runtime from ${url_kitbase}/${archtail} and verify checksums"
fi
exit 1
fi
#sha1sums.txt line format: <sha1> *<filename> (binary indicator)
#(dots in $runtime are regex-any here - acceptable for these filenames)
stored_sha1=$(grep -E "^[0-9a-fA-F]{40} \*${runtime}\$" "$sha1local" | head -n 1 | cut -d' ' -f1)
if [[ -z "$stored_sha1" ]]; then
echo "Unable to locate hash for $runtime in $sha1local - Aborting"
echo "Please download and verify manually (or check available names with: $0 list)"
exit 1
fi
probe=$(sha1_of "$sha1local")
if [[ -z "$probe" ]]; then
echo "No sha1 tool found (tried sha1sum, shasum, sha1, openssl) - refusing unverified download."
echo "Please install one (e.g coreutils or openssl) and retry."
exit 1
fi
need_download=1
if [[ -f "$output" ]]; then
echo "Runtime already found at $output"
echo "Checking sha1 checksum of local file versus sha1 of server file"
local_sha1=$(sha1_of "$output")
if [[ "$(lcase "$local_sha1")" == "$(lcase "$stored_sha1")" ]]; then
need_download=0
echo "Local copy of runtime at $output matches sha1 checksum of file on server."
echo "No download required"
else
echo "$runtime on server has different sha1 hash - Download required"
fi
else
echo "Error: Failed to download to $output"
echo "$runtime not found locally - Download required"
fi
else
echo "No runtime currently available for $os"
if [[ "$need_download" -eq 1 ]]; then
echo "Downloading from $url ..."
if ! curl -fSL --output "${output}.tmp" "$url"; then
echo "Error: Failed to download to ${output}.tmp"
exit 1
fi
echo "comparing sha1 checksum of downloaded file with data in sha1sums.txt"
new_sha1=$(sha1_of "${output}.tmp")
if [[ "$(lcase "$new_sha1")" == "$(lcase "$stored_sha1")" ]]; then
echo "sha1 checksum ok"
mv -f "${output}.tmp" "$output"
chmod +x "$output"
echo "Runtime is available at $output"
if [[ "$plat" == "Linux"* ]]; then
echo "Please ensure libxFt.so.2 is available"
echo "e.g on Ubuntu: sudo apt-get install libxft2"
fi
else
echo "WARNING! sha1 of downloaded file at ${output}.tmp does not match stored sha1 from sha1sums.txt"
echo "The .tmp file has been left in place for inspection - it has NOT been installed."
exit 1
fi
fi
#first fetch establishes the active runtime; later fetches never steal it
if [[ -z "$(get_active)" ]]; then
set_active "$runtime"
fi
else
echo "No default runtime currently published for $os ($archtail)"
echo "If one exists on the server, name it explicitly: $0 fetch <runtimename>"
fi
;;
"list")
if [[ -d "$archdir" ]]; then
echo "$(ls $archdir -1 | grep -v '_BUILDCOPY$' | wc -l) files in $archdir"
echo $(ls $archdir -1 | grep -v '_BUILDCOPY$')
candidates=$(list_candidates)
active=$(get_active)
count=$(printf '%s\n' "$candidates" | grep -c . )
echo "$count runtime(s) in $archdir"
for f in $candidates; do
if [[ "$f" == "$active" ]]; then
echo "* $f (active)"
else
echo " $f"
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
else
echo -e "No runtimes available in $archdir\n Use '$0 fetch' to install."
echo "No runtimes available in $archdir"
echo " Use '$0 fetch' to install."
fi
;;
"use")
if [[ -z "$2" ]]; then
echo "Usage: $0 use <runtimename>"
echo "Installed candidates:"
for f in $(list_candidates); do
echo " $f"
done
active=$(get_active)
if [[ -n "$active" ]]; then
echo "Currently active: $active"
fi
exit 1
fi
case "$2" in
*_BUILDCOPY|*.txt|*.toml|*.tm|*.tmp)
echo "'$2' is not a selectable runtime"
exit 1
;;
esac
if [[ ! -f "$archdir/$2" ]]; then
echo "No runtime named '$2' found in $archdir"
echo "Installed candidates:"
for f in $(list_candidates); do
echo " $f"
done
exit 1
fi
set_active "$2"
;;
"run")
#todo - lookup active runtime for os-arch from .toml file
activeruntime=$(ls $archdir -1 | grep -v '_BUILDCOPY$' | tail -n 1)
#resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single
#installed candidate - otherwise error with candidates (no last-in-list guessing)
activeruntime=""
if [[ -n "$PUNK_ACTIVE_RUNTIME" ]]; then
activeruntime="$PUNK_ACTIVE_RUNTIME"
if [[ ! -f "$archdir/$activeruntime" ]]; then
echo "PUNK_ACTIVE_RUNTIME '$activeruntime' not found in $archdir"
exit 1
fi
else
activeruntime=$(get_active)
if [[ -n "$activeruntime" && ! -f "$archdir/$activeruntime" ]]; then
echo "active runtime '$activeruntime' (from $active_file) is not present in $archdir"
echo "Reselect with: $0 use <name> (or fetch it: $0 fetch $activeruntime)"
exit 1
fi
if [[ -z "$activeruntime" ]]; then
candidates=$(list_candidates)
count=$(printf '%s\n' "$candidates" | grep -c . )
if [[ "$count" -eq 1 ]]; then
activeruntime="$candidates"
elif [[ "$count" -eq 0 ]]; then
echo "No runtimes seem to be installed in $archdir"
echo "Please use '$0 fetch' to install."
exit 1
else
echo "Multiple runtimes installed and no active runtime selected."
echo "Select one with: $0 use <name>"
echo "Installed candidates:"
for f in $candidates; do
echo " $f"
done
exit 1
fi
fi
fi
activeruntime_fullpath="$archdir/$activeruntime"
#echo "using $activeruntime_fullpath"
shift
@ -1412,7 +1582,7 @@ case "$1" in
$activeruntime_fullpath "$@"
;;
*)
echo "Usage: $0 {fetch|list|run}"
echo "Usage: $0 {fetch|list|use|run}"
echo "received $@"
exit 1
;;
@ -1753,6 +1923,37 @@ function ParameterDefinitions {
)
}
#active runtime marker: constrained single-key toml (active = "name") per platform
#folder - local per-machine state, ignored by git and fossil
function Get-PunkActiveRuntime {
param([string] $archfolder)
$activefile = Join-Path -Path $archfolder -ChildPath "active.toml"
if (Test-Path -Path $activefile -PathType Leaf) {
foreach ($line in (Get-Content -Path $activefile)) {
$match = [regex]::Match($line, '^\s*active\s*=\s*"(.*)"\s*$')
if ($match.Success) {
return $match.Groups[1].Value
}
}
}
return ""
}
function Set-PunkActiveRuntime {
param([string] $archfolder, [string] $name)
$activefile = Join-Path -Path $archfolder -ChildPath "active.toml"
#LF-terminated, no BOM - the bash payload reads the same file
[System.IO.File]::WriteAllText($activefile, "active = `"$name`"`n", [System.Text.UTF8Encoding]::new($false))
Write-Host "active runtime set to: $name (recorded in $activefile)"
}
#installed runtime candidates - exclude build copies and non-runtime files
function Get-PunkRuntimeCandidates {
param([string] $archfolder)
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)
}
function psmain {
[CmdletBinding()]
#Empty param block (extra params can be added)
@ -1773,7 +1974,7 @@ function psmain {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('remote', $dynParam1)
return $paramDictionary
} elseif ($action -eq 'fetch') {
} elseif ($action -eq 'fetch' -or $action -eq 'use') {
#GetDynamicParamDictionary ParameterDefinitions
$parameterAttribute = [System.Management.Automation.ParameterAttribute]@{
ParameterSetName = "fetchruntime"
@ -1833,9 +2034,9 @@ function psmain {
'action' {
write-host "got action " $PSBoundParameters.action
Set-Variable -Name $_ -Value $PSBoundParameters."$_"
$known_actions = @("fetch", "list", "run")
$known_actions = @("fetch", "list", "use", "run")
if (-not($known_actions -contains $action)) {
write-host "fetch '$action' not understood. Known_actions: $known_actions"
write-host "action '$action' not understood. Known_actions: $known_actions"
exit 1
}
}
@ -1898,7 +2099,7 @@ function psmain {
$match = [regex]::Match($line,"(.*) [*]${runtime}$")
if ($match.Success) {
$stored_sha1 = $match.Groups[1].Value
Write-host "stored hash from sha1sums.txt: $storedhash"
Write-host "stored hash from sha1sums.txt: $stored_sha1"
break
}
}
@ -1950,6 +2151,10 @@ function psmain {
Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server."
Write-Host "No download required"
}
#first fetch establishes the active runtime; later fetches never steal it
if ((Get-PunkActiveRuntime $archfolder) -eq "") {
Set-PunkActiveRuntime $archfolder $runtime
}
} else {
Write-Host "Unable to consult local copy of sha1sums.txt at $sha1local"
if (Test-Path -Path $output -PathType Leaf) {
@ -1960,21 +2165,80 @@ function psmain {
}
}
}
'use' {
#select the active runtime for subsequent 'run' calls
$arch = "win32-x86_64"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
$candidates = Get-PunkRuntimeCandidates $archfolder
$rtname = ""
if ( $PSBoundParameters["runtime"].Length ) {
$rtname = $PSBoundParameters["runtime"]
}
if ($rtname -eq "") {
write-host "Usage: runtime.cmd use <runtimename>"
write-host "Installed candidates:"
foreach ($f in $candidates) {
write-host " $($f.Name)"
}
$current = Get-PunkActiveRuntime $archfolder
if ($current -ne "") {
write-host "Currently active: $current"
}
exit 1
}
if (-not ($candidates | Where-Object Name -eq $rtname)) {
write-host "No runtime named '$rtname' found in $archfolder"
write-host "Installed candidates:"
foreach ($f in $candidates) {
write-host " $($f.Name)"
}
exit 1
}
Set-PunkActiveRuntime $archfolder $rtname
}
'run' {
#select first (or configured default) runtime and launch, passing arguments
#launch the active runtime, passing arguments.
#resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single
#installed candidate - otherwise error with candidates (no last-in-list guessing)
$arch = "win32-x86_64"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
if (-not(Test-Path -Path $archfolder -PathType Container)) {
write-host "No runtimes seem to be installed for $arch`nPlease use 'runtime.cmd fetch' to install"
} else {
$dircontents = (get-childItem -Path $archfolder -File | where-object Name -Notlike '*_BUILDCOPY.*' | Sort-Object Name)
$dircontents = Get-PunkRuntimeCandidates $archfolder
if ($dircontents.Count -gt 0) {
#write-host "run.."
write-host "num params: $($PSBoundParameters.opts.count)"
#todo - use 'active' runtime - need to lookup (PSToml?)
#when no 'active' runtime for this os-arch - use last item (sorted in dictionary order)
$active = $dircontents[-1].FullName
$activename = ""
if ($env:PUNK_ACTIVE_RUNTIME) {
$activename = $env:PUNK_ACTIVE_RUNTIME
if (-not (Test-Path -Path (Join-Path -Path $archfolder -ChildPath $activename) -PathType Leaf)) {
write-host "PUNK_ACTIVE_RUNTIME '$activename' not found in $archfolder"
exit 1
}
} else {
$activename = Get-PunkActiveRuntime $archfolder
if ($activename -ne "" -and -not (Test-Path -Path (Join-Path -Path $archfolder -ChildPath $activename) -PathType Leaf)) {
write-host "active runtime '$activename' (from active.toml) is not present in $archfolder"
write-host "Reselect with: runtime.cmd use <name> (or fetch it: runtime.cmd fetch $activename)"
exit 1
}
if ($activename -eq "") {
if ($dircontents.Count -eq 1) {
$activename = $dircontents[0].Name
} else {
write-host "Multiple runtimes installed and no active runtime selected."
write-host "Select one with: runtime.cmd use <name>"
write-host "Installed candidates:"
foreach ($f in $dircontents) {
write-host " $($f.Name)"
}
exit 1
}
}
}
$active = Join-Path -Path $archfolder -ChildPath $activename
write-host "using: $active"
if ($PSBoundParameters.opts.Length -gt 0) {
$optsType = $PSBoundParameters.opts.GetType() #method can only be called if .opts is not null
@ -2093,14 +2357,23 @@ function psmain {
if (test-path -Path $archfolder -Type Container) {
Write-host "-----------------------------------------------------------------------"
Write-Host "Local runtimes for $arch"
$dircontents = (get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY.*' | Where-object {-not ($(".txt",".tm") -contains $_.Extension) })
write-host "$(${dircontents}.count) files in $archfolder"
$dircontents = Get-PunkRuntimeCandidates $archfolder
$activename = Get-PunkActiveRuntime $archfolder
write-host "$(${dircontents}.count) runtime(s) in $archfolder"
Write-host "-----------------------------------------------------------------------"
foreach ($f in $dircontents) {
write-host $f.Name
if ($f.Name -eq $activename) {
write-host "* $($f.Name) (active)"
} else {
write-host " $($f.Name)"
}
}
Write-host "-----------------------------------------------------------------------"
if ($activename -ne "" -and -not ($dircontents | Where-Object Name -eq $activename)) {
write-host "WARNING: active runtime '$activename' (from active.toml) is not present - use 'runtime.cmd use <name>' to reselect"
}
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"
} else {
write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'runtime.cmd fetch' to install."
write-host "Use 'runtime.cmd list -remote' to see available runtimes for $arch"
@ -2108,7 +2381,7 @@ function psmain {
}
}
default {
$actions = @("fetch", "list", "run")
$actions = @("fetch", "list", "use", "run")
write-host "Available actions: $actions"
write-host "received"
foreach ($boundparam in $PSBoundParameters.opts) {

2
punkproject.toml

@ -1,3 +1,3 @@
[project]
name = "punkshell"
version = "0.6.0"
version = "0.7.0"

2
src/scriptapps/AGENTS.md

@ -13,7 +13,7 @@ Standalone Tcl scripts that serve as entry points for Punk applications and util
- Scripts here are invoked directly by the user or by build tools, not loaded as packages.
- Wrapper configs (`.toml` files) define how scripts are packaged as platform executables.
- `<name>_wrap.toml` scriptsets are the SOURCE of the generated polyglot `.cmd` scripts in `<projectroot>/bin` (e.g `runtime.ps1` + `runtime.bash` + `runtime_wrap.toml` -> `bin/runtime.cmd` via `punk::mix::commandset::scriptwrap::multishell runtime -askme 0`, run from this folder). Fixes to a `bin/*.cmd` polyglot are made HERE and re-wrapped - never in the output file. Regenerate and commit the bin output together with payload changes: `src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test` pins the runtime scriptset round-trip byte-identical against `bin/runtime.cmd`.
- `<name>_wrap.toml` scriptsets are the SOURCE of the generated polyglot `.cmd` scripts in `<projectroot>/bin`, and bin-deployed scriptsets live in the `bin/` subfolder here (e.g `bin/runtime.ps1` + `bin/runtime.bash` + `bin/runtime_wrap.toml` -> `<projectroot>/bin/runtime.cmd` via `punk::mix::commandset::scriptwrap::multishell runtime -askme 0`, run from `src/scriptapps/bin`, alongside the `getzig.*` scriptset). Fixes to a `bin/*.cmd` polyglot are made in the scriptset sources and re-wrapped - never in the output file. Regenerate and commit the bin output together with payload changes: `src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test` pins the runtime scriptset round-trip byte-identical against `bin/runtime.cmd`.
- The `spud/` directory holds the spud build tool's app scripts.
- The `tools/` directory holds miscellaneous build and deployment utilities.

309
src/scriptapps/bin/runtime.bash

@ -0,0 +1,309 @@
wdir="$(pwd)"; [ "$(pwd)" = "/" ] && wdir=""
case "$0" in
/*) scriptpath="${0}";;
*) scriptpath="$wdir/${0#./}";;
esac
scriptdir="${scriptpath%/*}"
scriptdir=$(realpath $scriptdir)
scriptpath=$(realpath $scriptpath)
basename=$(basename "$scriptpath") #e.g fetchruntime.bash
scriptroot="${basename%.*}" #e.g "fetchruntime"
url_kitbase="https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master"
runtime_available=0
#$OSTYPE varies in capitalization across for example zsh and bash
#uname probably a more consistent bet
arch=$(uname -m) #machine/architecture
plat=$(uname -s) #platform/system
#even though most of the platform prongs are very similar,
#we keep the code separate so it can be tweaked easily for unexpected differences
#each prong sets archdir (local folder) and, where a runtime is published for the
#platform, rt_default (default runtime name). archtail (server folder) is derived
#from archdir below.
if [[ "$plat" = "Linux"* ]]; then
if [[ "$arch" = "x86_64"* ]]; then
archdir="${scriptdir}/runtime/linux-x86_64"
rt_default="tclkit-902-Linux64-intel-dyn"
runtime_available=1
elif [[ "$arch" = "arm"* ]]; then
archdir="${scriptdir}/runtime/linux-arm"
rt_default="tclkit-902-Linux64-arm-dyn"
runtime_available=1
else
archdir="${scriptdir}/runtime/linux-$arch"
fi
os="linux"
elif [[ "$plat" = "Darwin"* ]]; then
os="macosx"
#assumed to be Mach-O 'universal binaries' for both x86-64 and arm? - REVIEW
archdir="${scriptdir}/runtime/macosx"
rt_default="tclkit-902-Darwin64-dyn"
runtime_available=1
elif [[ "$plat" = "FreeBSD"* ]]; then
archdir="${scriptdir}/runtime/freebsd-amd64"
os="freebsd"
elif [[ "$plat" == "DragonFly"* ]]; then
archdir="${scriptdir}/runtime/dragonflybsd-$arch"
os="dragonflybsd"
elif [[ "$plat" == "NetBSD"* ]]; then
archdir="${scriptdir}/runtime/netbsd-$arch"
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
archdir="${scriptdir}/runtime/openbsd-amd64"
os="openbsd"
elif [[ "$plat" == "MINGW32"* ]]; then
#REVIEW
os="win32"
archdir="${scriptdir}/runtime/win32-x86_64"
rt_default="tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "MINGW64"* ]]; then
#REVIEW
os="win32"
archdir="${scriptdir}/runtime/win32-x86_64"
rt_default="tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
archdir="${scriptdir}/runtime/win32-x86_64"
rt_default="tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "MSYS_NT"* ]]; then
echo MSYS
os="win32"
#use 'command -v' (shell builtin preferred over external which)
interp=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
shellpath=`command -v $interp`
shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
archdir="${scriptdir}/runtime/win32-x86_64"
rt_default="tclsh902z.exe"
runtime_available=1
else
archdir="${scriptdir}/runtime/other"
os="other"
fi
archtail="${archdir##*/}" #server folder name e.g linux-x86_64
active_file="${archdir}/active.toml"
#sha1 of a file - tool varies by platform (sha1sum: linux/coreutils, shasum: macosx,
#sha1: BSDs, openssl: common fallback). Empty result means no tool available.
sha1_of() {
if command -v sha1sum >/dev/null 2>&1; then
sha1sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 1 "$1" | awk '{print $1}'
elif command -v sha1 >/dev/null 2>&1; then
sha1 -q "$1"
elif command -v openssl >/dev/null 2>&1; then
openssl dgst -sha1 -r "$1" | awk '{print $1}'
else
echo ""
fi
}
lcase() { printf '%s' "$1" | tr 'A-F' 'a-f'; }
#active runtime marker: constrained single-key toml, per platform folder
#(local per-machine state - ignored by git and fossil)
get_active() {
if [[ -f "$active_file" ]]; then
sed -n 's/^[[:space:]]*active[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$active_file" | head -n 1
fi
}
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
list_candidates() {
if [[ -d "$archdir" ]]; then
ls -1 "$archdir" | grep -v '_BUILDCOPY$' | grep -v '\.txt$' | grep -v '\.toml$' | grep -v '\.tm$' | grep -v '\.tmp$'
fi
}
case "$1" in
"fetch")
runtime="$rt_default"
if [[ -n "$2" ]]; then
runtime="$2"
fi
if [[ ( "$runtime_available" -eq 1 || -n "$2" ) && "$archtail" != "other" && -n "$runtime" ]]; then
url="${url_kitbase}/${archtail}/${runtime}"
output="${archdir}/${runtime}"
sha1url="${url_kitbase}/${archtail}/sha1sums.txt"
sha1local="${archdir}/sha1sums.txt"
mkdir -p "$archdir"
echo "Fetching $sha1url"
if ! curl -fsSL --output "$sha1local" "$sha1url"; then
echo "An error occurred while downloading $sha1url"
if [[ -f "$output" ]]; then
echo "A runtime is available at $output - but we failed to retrieve the list of sha1sums from the server"
echo "Unable to check for updated version at this time."
else
echo "Please retry - or manually download a runtime from ${url_kitbase}/${archtail} and verify checksums"
fi
exit 1
fi
#sha1sums.txt line format: <sha1> *<filename> (binary indicator)
#(dots in $runtime are regex-any here - acceptable for these filenames)
stored_sha1=$(grep -E "^[0-9a-fA-F]{40} \*${runtime}\$" "$sha1local" | head -n 1 | cut -d' ' -f1)
if [[ -z "$stored_sha1" ]]; then
echo "Unable to locate hash for $runtime in $sha1local - Aborting"
echo "Please download and verify manually (or check available names with: $0 list)"
exit 1
fi
probe=$(sha1_of "$sha1local")
if [[ -z "$probe" ]]; then
echo "No sha1 tool found (tried sha1sum, shasum, sha1, openssl) - refusing unverified download."
echo "Please install one (e.g coreutils or openssl) and retry."
exit 1
fi
need_download=1
if [[ -f "$output" ]]; then
echo "Runtime already found at $output"
echo "Checking sha1 checksum of local file versus sha1 of server file"
local_sha1=$(sha1_of "$output")
if [[ "$(lcase "$local_sha1")" == "$(lcase "$stored_sha1")" ]]; then
need_download=0
echo "Local copy of runtime at $output matches sha1 checksum of file on server."
echo "No download required"
else
echo "$runtime on server has different sha1 hash - Download required"
fi
else
echo "$runtime not found locally - Download required"
fi
if [[ "$need_download" -eq 1 ]]; then
echo "Downloading from $url ..."
if ! curl -fSL --output "${output}.tmp" "$url"; then
echo "Error: Failed to download to ${output}.tmp"
exit 1
fi
echo "comparing sha1 checksum of downloaded file with data in sha1sums.txt"
new_sha1=$(sha1_of "${output}.tmp")
if [[ "$(lcase "$new_sha1")" == "$(lcase "$stored_sha1")" ]]; then
echo "sha1 checksum ok"
mv -f "${output}.tmp" "$output"
chmod +x "$output"
echo "Runtime is available at $output"
if [[ "$plat" == "Linux"* ]]; then
echo "Please ensure libxFt.so.2 is available"
echo "e.g on Ubuntu: sudo apt-get install libxft2"
fi
else
echo "WARNING! sha1 of downloaded file at ${output}.tmp does not match stored sha1 from sha1sums.txt"
echo "The .tmp file has been left in place for inspection - it has NOT been installed."
exit 1
fi
fi
#first fetch establishes the active runtime; later fetches never steal it
if [[ -z "$(get_active)" ]]; then
set_active "$runtime"
fi
else
echo "No default runtime currently published for $os ($archtail)"
echo "If one exists on the server, name it explicitly: $0 fetch <runtimename>"
fi
;;
"list")
if [[ -d "$archdir" ]]; then
candidates=$(list_candidates)
active=$(get_active)
count=$(printf '%s\n' "$candidates" | grep -c . )
echo "$count runtime(s) in $archdir"
for f in $candidates; do
if [[ "$f" == "$active" ]]; then
echo "* $f (active)"
else
echo " $f"
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
else
echo "No runtimes available in $archdir"
echo " Use '$0 fetch' to install."
fi
;;
"use")
if [[ -z "$2" ]]; then
echo "Usage: $0 use <runtimename>"
echo "Installed candidates:"
for f in $(list_candidates); do
echo " $f"
done
active=$(get_active)
if [[ -n "$active" ]]; then
echo "Currently active: $active"
fi
exit 1
fi
case "$2" in
*_BUILDCOPY|*.txt|*.toml|*.tm|*.tmp)
echo "'$2' is not a selectable runtime"
exit 1
;;
esac
if [[ ! -f "$archdir/$2" ]]; then
echo "No runtime named '$2' found in $archdir"
echo "Installed candidates:"
for f in $(list_candidates); do
echo " $f"
done
exit 1
fi
set_active "$2"
;;
"run")
#resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single
#installed candidate - otherwise error with candidates (no last-in-list guessing)
activeruntime=""
if [[ -n "$PUNK_ACTIVE_RUNTIME" ]]; then
activeruntime="$PUNK_ACTIVE_RUNTIME"
if [[ ! -f "$archdir/$activeruntime" ]]; then
echo "PUNK_ACTIVE_RUNTIME '$activeruntime' not found in $archdir"
exit 1
fi
else
activeruntime=$(get_active)
if [[ -n "$activeruntime" && ! -f "$archdir/$activeruntime" ]]; then
echo "active runtime '$activeruntime' (from $active_file) is not present in $archdir"
echo "Reselect with: $0 use <name> (or fetch it: $0 fetch $activeruntime)"
exit 1
fi
if [[ -z "$activeruntime" ]]; then
candidates=$(list_candidates)
count=$(printf '%s\n' "$candidates" | grep -c . )
if [[ "$count" -eq 1 ]]; then
activeruntime="$candidates"
elif [[ "$count" -eq 0 ]]; then
echo "No runtimes seem to be installed in $archdir"
echo "Please use '$0 fetch' to install."
exit 1
else
echo "Multiple runtimes installed and no active runtime selected."
echo "Select one with: $0 use <name>"
echo "Installed candidates:"
for f in $candidates; do
echo " $f"
done
exit 1
fi
fi
fi
activeruntime_fullpath="$archdir/$activeruntime"
#echo "using $activeruntime_fullpath"
shift
#echo "args: $@"
$activeruntime_fullpath "$@"
;;
*)
echo "Usage: $0 {fetch|list|use|run}"
echo "received $@"
exit 1
;;
esac

131
src/scriptapps/runtime.ps1 → src/scriptapps/bin/runtime.ps1

@ -40,6 +40,37 @@ function ParameterDefinitions {
)
}
#active runtime marker: constrained single-key toml (active = "name") per platform
#folder - local per-machine state, ignored by git and fossil
function Get-PunkActiveRuntime {
param([string] $archfolder)
$activefile = Join-Path -Path $archfolder -ChildPath "active.toml"
if (Test-Path -Path $activefile -PathType Leaf) {
foreach ($line in (Get-Content -Path $activefile)) {
$match = [regex]::Match($line, '^\s*active\s*=\s*"(.*)"\s*$')
if ($match.Success) {
return $match.Groups[1].Value
}
}
}
return ""
}
function Set-PunkActiveRuntime {
param([string] $archfolder, [string] $name)
$activefile = Join-Path -Path $archfolder -ChildPath "active.toml"
#LF-terminated, no BOM - the bash payload reads the same file
[System.IO.File]::WriteAllText($activefile, "active = `"$name`"`n", [System.Text.UTF8Encoding]::new($false))
Write-Host "active runtime set to: $name (recorded in $activefile)"
}
#installed runtime candidates - exclude build copies and non-runtime files
function Get-PunkRuntimeCandidates {
param([string] $archfolder)
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)
}
function psmain {
[CmdletBinding()]
#Empty param block (extra params can be added)
@ -60,7 +91,7 @@ function psmain {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('remote', $dynParam1)
return $paramDictionary
} elseif ($action -eq 'fetch') {
} elseif ($action -eq 'fetch' -or $action -eq 'use') {
#GetDynamicParamDictionary ParameterDefinitions
$parameterAttribute = [System.Management.Automation.ParameterAttribute]@{
ParameterSetName = "fetchruntime"
@ -120,9 +151,9 @@ function psmain {
'action' {
write-host "got action " $PSBoundParameters.action
Set-Variable -Name $_ -Value $PSBoundParameters."$_"
$known_actions = @("fetch", "list", "run")
$known_actions = @("fetch", "list", "use", "run")
if (-not($known_actions -contains $action)) {
write-host "fetch '$action' not understood. Known_actions: $known_actions"
write-host "action '$action' not understood. Known_actions: $known_actions"
exit 1
}
}
@ -185,7 +216,7 @@ function psmain {
$match = [regex]::Match($line,"(.*) [*]${runtime}$")
if ($match.Success) {
$stored_sha1 = $match.Groups[1].Value
Write-host "stored hash from sha1sums.txt: $storedhash"
Write-host "stored hash from sha1sums.txt: $stored_sha1"
break
}
}
@ -237,6 +268,10 @@ function psmain {
Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server."
Write-Host "No download required"
}
#first fetch establishes the active runtime; later fetches never steal it
if ((Get-PunkActiveRuntime $archfolder) -eq "") {
Set-PunkActiveRuntime $archfolder $runtime
}
} else {
Write-Host "Unable to consult local copy of sha1sums.txt at $sha1local"
if (Test-Path -Path $output -PathType Leaf) {
@ -247,21 +282,80 @@ function psmain {
}
}
}
'use' {
#select the active runtime for subsequent 'run' calls
$arch = "win32-x86_64"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
$candidates = Get-PunkRuntimeCandidates $archfolder
$rtname = ""
if ( $PSBoundParameters["runtime"].Length ) {
$rtname = $PSBoundParameters["runtime"]
}
if ($rtname -eq "") {
write-host "Usage: runtime.cmd use <runtimename>"
write-host "Installed candidates:"
foreach ($f in $candidates) {
write-host " $($f.Name)"
}
$current = Get-PunkActiveRuntime $archfolder
if ($current -ne "") {
write-host "Currently active: $current"
}
exit 1
}
if (-not ($candidates | Where-Object Name -eq $rtname)) {
write-host "No runtime named '$rtname' found in $archfolder"
write-host "Installed candidates:"
foreach ($f in $candidates) {
write-host " $($f.Name)"
}
exit 1
}
Set-PunkActiveRuntime $archfolder $rtname
}
'run' {
#select first (or configured default) runtime and launch, passing arguments
#launch the active runtime, passing arguments.
#resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single
#installed candidate - otherwise error with candidates (no last-in-list guessing)
$arch = "win32-x86_64"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
if (-not(Test-Path -Path $archfolder -PathType Container)) {
write-host "No runtimes seem to be installed for $arch`nPlease use 'runtime.cmd fetch' to install"
} else {
$dircontents = (get-childItem -Path $archfolder -File | where-object Name -Notlike '*_BUILDCOPY.*' | Sort-Object Name)
$dircontents = Get-PunkRuntimeCandidates $archfolder
if ($dircontents.Count -gt 0) {
#write-host "run.."
write-host "num params: $($PSBoundParameters.opts.count)"
#todo - use 'active' runtime - need to lookup (PSToml?)
#when no 'active' runtime for this os-arch - use last item (sorted in dictionary order)
$active = $dircontents[-1].FullName
$activename = ""
if ($env:PUNK_ACTIVE_RUNTIME) {
$activename = $env:PUNK_ACTIVE_RUNTIME
if (-not (Test-Path -Path (Join-Path -Path $archfolder -ChildPath $activename) -PathType Leaf)) {
write-host "PUNK_ACTIVE_RUNTIME '$activename' not found in $archfolder"
exit 1
}
} else {
$activename = Get-PunkActiveRuntime $archfolder
if ($activename -ne "" -and -not (Test-Path -Path (Join-Path -Path $archfolder -ChildPath $activename) -PathType Leaf)) {
write-host "active runtime '$activename' (from active.toml) is not present in $archfolder"
write-host "Reselect with: runtime.cmd use <name> (or fetch it: runtime.cmd fetch $activename)"
exit 1
}
if ($activename -eq "") {
if ($dircontents.Count -eq 1) {
$activename = $dircontents[0].Name
} else {
write-host "Multiple runtimes installed and no active runtime selected."
write-host "Select one with: runtime.cmd use <name>"
write-host "Installed candidates:"
foreach ($f in $dircontents) {
write-host " $($f.Name)"
}
exit 1
}
}
}
$active = Join-Path -Path $archfolder -ChildPath $activename
write-host "using: $active"
if ($PSBoundParameters.opts.Length -gt 0) {
$optsType = $PSBoundParameters.opts.GetType() #method can only be called if .opts is not null
@ -380,14 +474,23 @@ function psmain {
if (test-path -Path $archfolder -Type Container) {
Write-host "-----------------------------------------------------------------------"
Write-Host "Local runtimes for $arch"
$dircontents = (get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY.*' | Where-object {-not ($(".txt",".tm") -contains $_.Extension) })
write-host "$(${dircontents}.count) files in $archfolder"
$dircontents = Get-PunkRuntimeCandidates $archfolder
$activename = Get-PunkActiveRuntime $archfolder
write-host "$(${dircontents}.count) runtime(s) in $archfolder"
Write-host "-----------------------------------------------------------------------"
foreach ($f in $dircontents) {
write-host $f.Name
if ($f.Name -eq $activename) {
write-host "* $($f.Name) (active)"
} else {
write-host " $($f.Name)"
}
}
Write-host "-----------------------------------------------------------------------"
if ($activename -ne "" -and -not ($dircontents | Where-Object Name -eq $activename)) {
write-host "WARNING: active runtime '$activename' (from active.toml) is not present - use 'runtime.cmd use <name>' to reselect"
}
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"
} else {
write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'runtime.cmd fetch' to install."
write-host "Use 'runtime.cmd list -remote' to see available runtimes for $arch"
@ -395,7 +498,7 @@ function psmain {
}
}
default {
$actions = @("fetch", "list", "run")
$actions = @("fetch", "list", "use", "run")
write-host "Available actions: $actions"
write-host "received"
foreach ($boundparam in $PSBoundParameters.opts) {

0
src/scriptapps/runtime_wrap.toml → src/scriptapps/bin/runtime_wrap.toml

139
src/scriptapps/runtime.bash

@ -1,139 +0,0 @@
wdir="$(pwd)"; [ "$(pwd)" = "/" ] && wdir=""
case "$0" in
/*) scriptpath="${0}";;
*) scriptpath="$wdir/${0#./}";;
esac
scriptdir="${scriptpath%/*}"
scriptdir=$(realpath $scriptdir)
scriptpath=$(realpath $scriptpath)
basename=$(basename "$scriptpath") #e.g fetchruntime.bash
scriptroot="${basename%.*}" #e.g "fetchruntime"
url_kitbase="https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master"
runtime_available=0
#$OSTYPE varies in capitalization across for example zsh and bash
#uname probably a more consistent bet
arch=$(uname -m) #machine/architecture
plat=$(uname -s) #platform/system
#even though most of the platform prongs are very similar,
#we keep the code separate so it can be tweaked easily for unexpected differences
if [[ "$plat" = "Linux"* ]]; then
if [[ "$arch" = "x86_64"* ]]; then
url="${url_kitbase}/linux-x86_64/tclkit-902-Linux64-intel-dyn"
archdir="${scriptdir}/runtime/linux-x86_64"
output="${archdir}/tclkit-902-Linux64-intel-dyn"
runtime_available=1
elif [[ "$arch" = "arm"* ]]; then
url="${url_kitbase}/linux-arm/tclkit-902-Linux64-arm-dyn"
archdir="${scriptdir}/runtime/linux-arm"
output="${archdir}/tclkit-902-Linux64-arm-dyn"
runtime_available=1
else
archdir="${scriptdir}/runtime/linux-$arch"
fi
os="linux"
elif [[ "$plat" = "Darwin"* ]]; then
os="macosx"
#assumed to be Mach-O 'universal binaries' for both x86-64 and arm? - REVIEW
url="${url_kitbase}/macosx/tclkit-902-Darwin64-dyn"
archdir="${scriptdir}/runtime/macosx/"
output="${archdir}/tclkit-902-Darwin64-dyn"
runtime_available=1
elif [[ "$plat" = "FreeBSD"* ]]; then
archdir="${scriptdir}/runtime/freebsd-amd64"
os="freebsd"
elif [[ "$plat" == "DragonFly"* ]]; then
archdir="${scriptdir}/runtime/dragonflybsd-$arch"
os="dragonflybsd"
elif [[ "$plat" == "NetBSD"* ]]; then
archdir="${scriptdir}/runtime/netbsd-$arch"
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
archdir="${scriptdir}/runtime/openbsd-amd64"
os="openbsd"
elif [[ "$plat" == "MINGW32"* ]]; then
#REVIEW
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "MINGW64"* ]]; then
#REVIEW
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "MSYS_NT"* ]]; then
echo MSYS
os="win32"
#use 'command -v' (shell builtin preferred over external which)
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
shellpath=`command -v $interp`
shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64"
output="${archdir}/tclsh902z.exe"
runtime_available=1
else
archdir="${scriptdir}/runtime/other"
os="other"
fi
case "$1" in
"fetch")
if [[ "$runtime_available" -eq 1 ]]; then
#test win32
mkdir -p $archdir
echo "Attempting to download $url"
#wget $url -O $output
curl -SL --output "$output" "$url"
if [[ $? -eq 0 ]]; then
echo "File downloaded to $output"
chmod +x $output
if [[ "$plat" == "Linux" ]]; then
echo "Please ensure libxFt.so.2 is available"
echo "e.g on Ubuntu: sudo apt-get install libxft2"
fi
else
echo "Error: Failed to download to $output"
fi
else
echo "No runtime currently available for $os"
fi
;;
"list")
if [[ -d "$archdir" ]]; then
echo "$(ls $archdir -1 | grep -v '_BUILDCOPY$' | wc -l) files in $archdir"
echo $(ls $archdir -1 | grep -v '_BUILDCOPY$')
else
echo -e "No runtimes available in $archdir\n Use '$0 fetch' to install."
fi
;;
"run")
#todo - lookup active runtime for os-arch from .toml file
activeruntime=$(ls $archdir -1 | grep -v '_BUILDCOPY$' | tail -n 1)
activeruntime_fullpath="$archdir/$activeruntime"
#echo "using $activeruntime_fullpath"
shift
#echo "args: $@"
$activeruntime_fullpath "$@"
;;
*)
echo "Usage: $0 {fetch|list|run}"
echo "received $@"
exit 1
;;
esac

4
src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test

@ -183,12 +183,12 @@ namespace eval ::testspace {
}\
-result [list 1 0]
test scriptwrap_runtime_cmd_roundtrip_no_drift {re-wrapping the runtime scriptset from src/scriptapps reproduces the committed bin/runtime.cmd byte for byte - the artifact is in sync with its sources and un-hand-edited}\
test scriptwrap_runtime_cmd_roundtrip_no_drift {re-wrapping the runtime scriptset from src/scriptapps/bin reproduces the committed bin/runtime.cmd byte for byte - the artifact is in sync with its sources and un-hand-edited}\
-setup $common -body {
variable projectroot
variable startdir
set target [file join $projectroot bin runtime.cmd]
set scriptapps [file join $projectroot src scriptapps]
set scriptapps [file join $projectroot src scriptapps bin]
if {![file exists $target] || ![file exists $scriptapps/runtime_wrap.toml]} {
lappend result no_runtime_sources
} else {

Loading…
Cancel
Save