diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc7cb8b..1e3e0622 100644 --- a/CHANGELOG.md +++ b/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//active.toml` (`active = ""`, 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 {load {} }` 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`. diff --git a/bin/AGENTS.md b/bin/AGENTS.md index 511c7026..1656658f 100644 --- a/bin/AGENTS.md +++ b/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 (`.ps1`, `.bash`, ...) plus a `_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 (`.ps1`, `.bash`, ...) plus a `_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/.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 -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 -askme 0` (output defaults to `/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`, `src/bootsupport/modules{,_tcl}` and `src/vendormodules{,_tcl}` 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 ` 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//` (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//active.toml` (constrained single-key toml `active = ""`, +written by `runtime.cmd use `, 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. diff --git a/bin/runtime.cmd b/bin/runtime.cmd index 9cbba7e2..486277d2 100755 --- a/bin/runtime.cmd +++ b/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: * (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 " 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 ' 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 " + 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 (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 " + 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 " + 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 (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 " + 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 ' to reselect" + } Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server" + Write-host "Use: 'use ' to select the runtime that 'run' launches" } 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) { diff --git a/punkproject.toml b/punkproject.toml index c23d3e9e..59c2d9fc 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,3 +1,3 @@ [project] name = "punkshell" -version = "0.6.0" +version = "0.7.0" diff --git a/src/scriptapps/AGENTS.md b/src/scriptapps/AGENTS.md index 1749e1ab..45440379 100644 --- a/src/scriptapps/AGENTS.md +++ b/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. -- `_wrap.toml` scriptsets are the SOURCE of the generated polyglot `.cmd` scripts in `/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`. +- `_wrap.toml` scriptsets are the SOURCE of the generated polyglot `.cmd` scripts in `/bin`, and bin-deployed scriptsets live in the `bin/` subfolder here (e.g `bin/runtime.ps1` + `bin/runtime.bash` + `bin/runtime_wrap.toml` -> `/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. diff --git a/src/scriptapps/bin/runtime.bash b/src/scriptapps/bin/runtime.bash new file mode 100644 index 00000000..b59e3601 --- /dev/null +++ b/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: * (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 " + 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 ' 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 " + 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 (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 " + 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 diff --git a/src/scriptapps/runtime.ps1 b/src/scriptapps/bin/runtime.ps1 similarity index 75% rename from src/scriptapps/runtime.ps1 rename to src/scriptapps/bin/runtime.ps1 index bc00d1f2..9a5a2221 100644 --- a/src/scriptapps/runtime.ps1 +++ b/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 " + 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 (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 " + 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 ' to reselect" + } Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server" + Write-host "Use: 'use ' to select the runtime that 'run' launches" } 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) { diff --git a/src/scriptapps/runtime_wrap.toml b/src/scriptapps/bin/runtime_wrap.toml similarity index 100% rename from src/scriptapps/runtime_wrap.toml rename to src/scriptapps/bin/runtime_wrap.toml diff --git a/src/scriptapps/runtime.bash b/src/scriptapps/runtime.bash deleted file mode 100644 index 7b554d52..00000000 --- a/src/scriptapps/runtime.bash +++ /dev/null @@ -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 diff --git a/src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test b/src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test index f2c82f8d..f6f6f3f3 100644 --- a/src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test +++ b/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 {