function GetDynamicParamDictionary { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true, Mandatory=$true)] [string] $CommandName ) begin { # Get a list of params that should be ignored (they're common to all advanced functions) $CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name } process { # Create the dictionary that this scriptblock will return: $DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Convert to object array and get rid of Common params: (Get-Command $CommandName | Select-Object -exp Parameters).GetEnumerator() | Where-Object { $CommonParameterNames -notcontains $_.Key } | ForEach-Object { $DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ( $_.Key, $_.Value.ParameterType, $_.Value.Attributes ) $DynParamDictionary.Add($_.Key, $DynamicParameter) } # Return the dynamic parameters return $DynParamDictionary } } function ParameterDefinitions { param( [Parameter(ValueFromRemainingArguments=$true,Position = 1)][string[]] $opts ) } #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. #ORDINAL name order: culture-sensitive Sort-Object collates differently between #windows powershell (NLS) and pwsh (ICU) - both launch paths exist (the .ps1 #twin runs under the invoking shell) - and ordinal matches the bash payload's #LC_ALL=C ordering, so listings agree byte-for-byte everywhere. function Get-PunkRuntimeCandidates { param([string] $archfolder) if (-not (Test-Path -Path $archfolder -PathType Container)) { return @() } $files = @(get-childItem -Path $archfolder -File | Where-object Name -Notlike '*_BUILDCOPY*' | Where-object {-not ($(".txt",".toml",".tm",".tmp",".log") -contains $_.Extension) }) if ($files.Count -le 1) { return $files } $byname = @{} foreach ($f in $files) { $byname[$f.Name] = $f } $names = @($byname.Keys) [array]::Sort($names, [System.StringComparer]::Ordinal) $out = @() foreach ($n in $names) { $out += $byname[$n] } return $out } #operator help (the 'help' action; the no-args case shows the Usage block only). #Keep in sync with the bash payload's show_help/show_usage. function Show-PunkRuntimeUsage { write-host "Usage: punk-runtime.cmd {fetch|list|use|run|platforms|help}" write-host " fetch ?? ?-platform

? download+verify a runtime from punkbin" write-host " list ?-remote? ?-platform

? installed (or server) runtimes + metadata" write-host " use ?-platform

? select active / materialize -r artifact" write-host " run ?args...? launch the active local-platform runtime" write-host " platforms ?-remote? local platform folders / platforms the server serves" write-host " help full help (options, env vars, examples)" } function Show-PunkRuntimeHelp { write-host "punk-runtime - manage the plain Tcl runtimes under bin/runtime//" write-host "" Show-PunkRuntimeUsage write-host "" write-host "Actions:" write-host " fetch ?? ?-platform

?" write-host " Download a runtime from the punkbin artifact server into" write-host " bin/runtime/

/ with sha1 verification against the server's" write-host " sha1sums.txt. -r-named family artifacts also fetch their" write-host " .toml metadata record. Omitting fetches the" write-host " platform's recommended default per the server's curated" write-host " defaults.txt (a punkbin release decision; works with -platform" write-host " too) - platforms without a recorded default need an explicit" write-host " ." write-host " list ?-remote? ?-platform

?" write-host " List installed runtimes with artifact-metadata summaries" write-host " (variant, tcl patchlevel, revision, piperepl policy, source" write-host " artifact of a materialized copy; a !TARGET-MISMATCH flag marks" write-host " a runtime filed under the wrong platform folder). -remote" write-host " compares local runtimes against the server's sha1sums, marks" write-host " the locally ACTIVE runtime (*) and annotates the platform's" write-host " recommended runtime ((server default), per the server's" write-host " defaults.txt when available) - so active-vs-default is" write-host " visible at a glance." write-host " use ?-platform

?" write-host " Select the runtime that 'run' launches (records active.toml in" write-host " the platform folder). An immutable -r ARTIFACT name is" write-host " MATERIALIZED into its WORKING name (name minus -r - what" write-host " mapvfs and projects reference), metadata toml copied alongside." write-host " run ?args...?" write-host " Launch the ACTIVE runtime of the LOCAL platform, passing args." write-host " Resolution: PUNK_ACTIVE_RUNTIME env > active.toml > sole" write-host " installed candidate. Takes no -platform (foreign binaries are" write-host " not runnable here)." write-host " platforms ?-remote?" write-host " List local platform folders under bin/runtime/. With -remote," write-host " list the platforms the artifact server serves - read from the" write-host " server's platforms.txt discovery manifest (part of the punkbin" write-host " layout; third-party mirrors carry the same file), with local" write-host " presence marked. Servers without the manifest get an actionable" write-host " message (name platforms explicitly with -platform)." write-host "" write-host "Platforms: punkbin platform-dir names (e.g win32-x86_64, linux-x86_64," write-host " macosx) - not zig triples. Default is the local platform; override" write-host " order: -platform argument > PUNK_RUNTIME_PLATFORM env var." write-host " Foreign-platform folders serve cross-build staging and provisioning:" write-host " active.toml travels with the folder when it is deployed to its real" write-host " platform (unix exec bits are restored at deploy time - e.g rsync/tar/" write-host " chmod on the receiving side)." write-host "" write-host "Env: PUNKBIN_URL (artifact server base url), PUNK_RUNTIME_PLATFORM," write-host " PUNK_ACTIVE_RUNTIME (run-time selection override)" write-host "" write-host "Examples:" write-host " punk-runtime.cmd platforms -remote" write-host " punk-runtime.cmd fetch tclsh9.0.5-punk-r1.exe" write-host " punk-runtime.cmd use tclsh9.0.5-punk-r1.exe (materializes tclsh9.0.5-punk.exe)" write-host " punk-runtime.cmd list -remote -platform linux-x86_64" write-host " punk-runtime.cmd fetch tclsh9.0.5-punk-r1 -platform linux-x86_64" write-host " punk-runtime.cmd use tclsh9.0.5-punk-r1 -platform linux-x86_64" write-host " punk-runtime.cmd run myscript.tcl arg1 arg2" } #platform (target) resolution for fetch/list/use: -platform arg > PUNK_RUNTIME_PLATFORM #env > the local default. Values are punkbin/bin-runtime platform-DIR names (e.g #win32-x86_64, linux-x86_64, macosx) - NOT zig triples (the buildsuite maps triples to #platform dirs at build time; punk-runtime speaks only the punkbin tier). Validation is #deliberately shape-only: the server's sha1sums/404 is the truth for what exists. #This payload runs on windows - the local default is win32-x86_64, or win32-ix86 on #a GENUINE 32-bit windows host (PROCESSOR_ARCHITECTURE x86 with no #PROCESSOR_ARCHITEW6432 - a 32-bit shell on a 64-bit OS keeps the x86_64 default: #the runtime store serves what the OS can run). A windows-arm default becomes a #question when punkbin carries such a folder. $script:PunkLocalPlatform = "win32-x86_64" if ($env:PROCESSOR_ARCHITECTURE -eq 'x86' -and -not $env:PROCESSOR_ARCHITEW6432) { $script:PunkLocalPlatform = "win32-ix86" } function Resolve-PunkRuntimePlatform { param([string] $requested) $plat = $script:PunkLocalPlatform if ($requested) { $plat = $requested } elseif ($env:PUNK_RUNTIME_PLATFORM) { $plat = $env:PUNK_RUNTIME_PLATFORM } #-cnotmatch: platform-dir names are lowercase and the artifact-server URL path #is case-sensitive (default -notmatch is case-insensitive and would pass typos #like Win32-X86_64 through to a server 404); bash payload parity (=~ is #case-sensitive there) if ($plat -cnotmatch '^[a-z0-9][a-z0-9_-]*$') { write-host "invalid platform name '$plat' (expected a punkbin platform-dir name such as win32-x86_64, linux-x86_64, macosx)" exit 1 } return $plat } #name minus a .exe suffix only - dotted tcl patchlevels (tclsh9.0.5-punk) make #generic last-dot extension stripping wrong for extensionless unix names function Get-PunkRuntimeRootName { param([string] $name) if ($name -match '(?i)\.exe$') { return $name.Substring(0, $name.Length - 4) } return $name } #G-103 artifact metadata: a runtime may carry a .toml beside it (emitted #by the buildsuite kit-family-artifacts step / fetched from punkbin). Returns a #short "[variant=... tcl=... rN ...]" summary for list output, "" when absent. function Get-PunkRuntimeMetadataSummary { param([string] $archfolder, [string] $exename, [string] $expectedplatform = "") $tomlfile = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $exename) + ".toml") if (-not (Test-Path -Path $tomlfile -PathType Leaf)) { return "" } $fields = @{} foreach ($line in (Get-Content -Path $tomlfile)) { $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"(.*)"\s*$') if ($m.Success) { $fields[$m.Groups[1].Value] = $m.Groups[2].Value continue } $m = [regex]::Match($line, '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([0-9]+|true|false)\s*$') if ($m.Success) { $fields[$m.Groups[1].Value] = $m.Groups[2].Value } } $parts = @() if ($fields.ContainsKey('variant')) { $parts += "variant=$($fields['variant'])" } if ($fields.ContainsKey('tcl_patchlevel')) { $parts += "tcl=$($fields['tcl_patchlevel'])" } if ($fields.ContainsKey('revision')) { $parts += "r$($fields['revision'])" } if ($fields.ContainsKey('piperepl')) { if ($fields['piperepl'] -eq 'true') { $parts += "piperepl=on" } else { $parts += "piperepl=off" } } #a materialized working copy records which immutable artifact it came from if ($fields.ContainsKey('name') -and $fields['name'] -ne $exename) { $parts += "from=$($fields['name'])" } #integrity flag: a runtime filed under a platform folder its metadata says it #was not built for (cross-platform fetch/staging misfiling) if ($expectedplatform -ne "" -and $fields.ContainsKey('target') -and $fields['target'] -ne $expectedplatform) { $parts += "!TARGET-MISMATCH:$($fields['target'])" } if ($parts.Count -eq 0) { return "" } return "[" + ($parts -join " ") + "]" } function psmain { [CmdletBinding()] #Empty param block (extra params can be added) param( [Parameter(Mandatory=$false, Position = 0)][string] $action = "" ) dynamicparam { if ($action -eq 'list' -or $action -eq 'platforms') { #shared dynamic params: -remote for both; -platform is meaningful to #'list' and ignored by 'platforms' (which enumerates ALL platforms) $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "listruntime" Mandatory = $false } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'remote', [switch], $attributeCollection ) #-platform (G-105 cross-build staging): punkbin platform-dir name to list for $platformAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "listruntime" Mandatory = $false } $platformAttributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $platformAttributeCollection.Add($platformAttribute) $dynParam2 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'platform', [string], $platformAttributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('remote', $dynParam1) $paramDictionary.Add('platform', $dynParam2) return $paramDictionary } elseif ($action -eq 'fetch' -or $action -eq 'use') { #GetDynamicParamDictionary ParameterDefinitions $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "fetchruntime" Mandatory = $false Position = 1 } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'runtime', [string], $attributeCollection ) #-platform (G-105 cross-build staging): punkbin platform-dir name to fetch #into / select within (default: the local platform) $platformAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "fetchruntime" Mandatory = $false } $platformAttributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $platformAttributeCollection.Add($platformAttribute) $dynParam2 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'platform', [string], $platformAttributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('runtime', $dynParam1) $paramDictionary.Add('platform', $dynParam2) return $paramDictionary } elseif ($action -eq 'run') { #GetDynamicParamDictionary ParameterDefinitions $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "runargs" Mandatory = $false ValueFromRemainingArguments = $true } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'opts', [string[]], $attributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('opts', $dynParam1) return $paramDictionary } else { #accept all args when action is unrecognised - so we can go to help anyway $parameterAttribute = [System.Management.Automation.ParameterAttribute]@{ ParameterSetName = "invalidaction" Mandatory = $false ValueFromRemainingArguments = $true } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attributeCollection.Add($parameterAttribute) $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new( 'opts', [string[]], $attributeCollection ) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $paramDictionary.Add('opts', $dynParam1) return $paramDictionary } } process { #Called once - we get a single item being our PSBoundParameters dictionary #write-host "Bound Parameters:$($PSBoundParameters.Keys)" switch ($PSBoundParameters.keys) { 'action' { write-host "got action " $PSBoundParameters.action Set-Variable -Name $_ -Value $PSBoundParameters."$_" $known_actions = @("fetch", "list", "use", "run", "platforms", "help") if (-not($known_actions -contains $action)) { write-host "action '$action' not understood. Known_actions: $known_actions" exit 1 } } 'opts' { # write-warning "Unused parameters: $($PSBoundParameters.$_)" } Default { # write-warning "Unhandled parameter -> [$($_)]" } } #foreach ($boundparam in $PSBoundParameters.Keys) { # write-host "k: $boundparam" #} } end { # PSBoundParameters #write-host "action:'$action'" $outbase = $PSScriptRoot $outbase = Resolve-Path -Path $outbase #expected script location is the bin folder of a punk project $rtfolder = Join-Path -Path $outbase -ChildPath "runtime" #Binary artifact server url. (git is not ideal for this - but will do for now - todo - use artifact system within gitea?) #overridable for mirrors/testing - keep in sync with the bash payload $artifacturl = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master" if ($env:PUNKBIN_URL) { $artifacturl = $env:PUNKBIN_URL } switch ($action) { 'fetch' { $arch = Resolve-PunkRuntimePlatform $PSBoundParameters["platform"] $archfolder = Join-Path -Path $rtfolder -ChildPath "$arch" $archurl = "$artifacturl/$arch" $sha1url = "$archurl/sha1sums.txt" foreach ($boundparam in $PSBoundParameters.Keys) { write-host "fetchopt: $boundparam $($PSBoundParameters[$boundparam])" } $runtime = "" if ( $PSBoundParameters["runtime"].Length ) { $runtime = $PSBoundParameters["runtime"] } else { #no name given: consult the server's curated defaults.txt - the #punkbin release recommendation (updated there as part of each #publication change-set; see punkbin AGENTS.md). Per-platform #server data, so it works for any -platform, not just local. $defaultsurl = "$artifacturl/defaults.txt" $defaultslocal = Join-Path -Path $rtfolder -ChildPath "defaults.txt" if (-not (Test-Path -Path $rtfolder -PathType Container)) { new-item -Path $rtfolder -ItemType Directory -force | out-null } try { Invoke-WebRequest -Uri $defaultsurl -OutFile $defaultslocal -ErrorAction Stop } catch { if (Test-Path -Path $defaultslocal -PathType Leaf) { Write-Host "WARNING: could not fetch ${defaultsurl}: $($_.Exception.Message)" Write-Host "WARNING: using cached copy at $defaultslocal" } else { Write-Host "Could not fetch ${defaultsurl}: $($_.Exception.Message)" Write-Host "No server default available - name a runtime explicitly:" Write-Host " punk-runtime.cmd fetch ?-platform ${arch}?" Write-Host " (see available names: punk-runtime.cmd list -remote -platform $arch)" exit 1 } } foreach ($line in (Get-Content -Path $defaultslocal)) { $line = $line.Trim() if ($line -eq "" -or $line.StartsWith("#")) { continue } $parts = -split $line if ($parts.Count -ge 2 -and $parts[0] -eq $arch) { $runtime = $parts[1] break } } if ($runtime -eq "") { Write-Host "No default recorded for platform '$arch' in the server's defaults.txt - name a runtime explicitly:" Write-Host " punk-runtime.cmd fetch ?-platform ${arch}?" Write-Host " (see available names: punk-runtime.cmd list -remote -platform $arch)" exit 1 } Write-Host "using server default for ${arch}: $runtime" } $fileurl = "$archurl/$runtime" $output = join-path -Path $archfolder -ChildPath $runtime $sha1local = join-path -Path $archfolder -ChildPath "sha1sums.txt" $container = split-path -Path $output -Parent new-item -Path $container -ItemType Directory -force #create with intermediary folders if not already present try { Write-Host "Fetching $sha1url" Invoke-WebRequest -Uri $sha1url -OutFile $sha1local -ErrorAction Stop Write-Host "sha1 saved at $sha1local" } catch { Write-Host "An error occurred while downloading ${sha1url}: $($_.Exception.Message)" if ($_.Exception.Response) { Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)" } if (Test-Path -Path $sha1local -PathType Leaf) { #this path always fell through to the cached copy - now say so Write-Host "WARNING: proceeding with cached copy at $sha1local" } } if (Test-Path -Path $sha1local -PathType Leaf) { $sha1Content = Get-Content -Path $sha1local $stored_sha1 = "" foreach ($line in $sha1Content) { #all sha1sums have * (binary indicator) - review $match = [regex]::Match($line,"(.*) [*]${runtime}$") if ($match.Success) { $stored_sha1 = $match.Groups[1].Value Write-host "stored hash from sha1sums.txt: $stored_sha1" break } } if ($stored_sha1 -eq "") { Write-Host "Unable to locate hash for $runtime in $sha1local - Aborting" Write-Host "Please download and verify manually" return } $need_download = $false if (Test-Path -Path $output -PathType Leaf) { Write-Host "Runtime already found at $output" Write-Host "Checking sha1 checksum of local file versus sha1 of server file" $file_sha1 = Get-FileHash -Path "$output" -Algorithm SHA1 if (${file_sha1}.Hash -ne $stored_sha1) { Write-Host "$runtime on server has different sha1 hash - Download required" $need_download = $true } } else { Write-Host "$runtime not found locally - Download required" $need_download = $true } if ($need_download) { Write-Host "Downloading from $fileurl ..." try { Invoke-WebRequest -Uri $fileurl -OutFile "${output}.tmp" -ErrorAction Stop Write-Host "Runtime saved at $output.tmp" } catch { Write-Host "An error occurred while downloading $fileurl $($_.Exception.Message)" if ($_.Exception.Response) { Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)" } return } Write-Host "comparing sha1 checksum of downloaded file with data in sha1sums.txt" Start-Sleep -Seconds 1 #REVIEW - give at least some time for windows to do its thing? (av filters?) $newfile_sha1 = Get-FileHash -Path "${output}.tmp" -Algorithm SHA1 if (${newfile_sha1}.Hash -eq $stored_sha1) { Write-Host "sha1 checksum ok" Move-Item -Path "${output}.tmp" -Destination "${output}" -Force Write-Host "Runtime is available at ${output}" } else { Write-Host "WARNING! sha1 of downloaded file at $output.tmp does not match stored sha1 from sha1sums.txt" return } } else { Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server." Write-Host "No download required" } #G-103: family artifacts (-r names) carry a metadata toml alongside #on the server - fetch it too; absence is fine (pre-family runtimes) if ($runtime -match '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') { $tomlname = (Get-PunkRuntimeRootName $runtime) + ".toml" $tomlurl = "$archurl/$tomlname" $tomllocal = Join-Path -Path $archfolder -ChildPath $tomlname try { Invoke-WebRequest -Uri $tomlurl -OutFile $tomllocal -ErrorAction Stop Write-Host "artifact metadata saved at $tomllocal" } catch { Write-Host "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)" } } #first fetch establishes the active runtime; later fetches never steal it if ((Get-PunkActiveRuntime $archfolder) -eq "") { Set-PunkActiveRuntime $archfolder $runtime } } else { Write-Host "Unable to consult local copy of sha1sums.txt at $sha1local" if (Test-Path -Path $output -PathType Leaf) { Write-Host "A runtime is available at $output - but we failed to retrieve the list of sha1sums from the server" Write-Host "Unable to check for updated version at this time." } else { Write-Host "Please retry - or manually download a runtime from $archurl and verify checksums" } } } 'use' { #select the active runtime for subsequent 'run' calls. With -platform: #manage THAT platform folder's selection/materialization (cross-build #staging - the active.toml travels with the folder when deployed; 'run' #on this machine only ever consults the local platform). $arch = Resolve-PunkRuntimePlatform $PSBoundParameters["platform"] $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: punk-runtime.cmd use ?-platform

?" write-host "Installed candidates ($arch):" 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 } #G-103 artifact-tier names (-r, immutable): 'use' MATERIALIZES the #artifact into its WORKING name (name minus -r - what mapvfs and #projects reference), copies its metadata toml alongside, and selects #the working name. Republishing artifacts never churns consumers. $am = [regex]::Match($rtname, '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') if ($am.Success) { $working = $am.Groups[1].Value + $am.Groups[3].Value $srcexe = Join-Path -Path $archfolder -ChildPath $rtname $destexe = Join-Path -Path $archfolder -ChildPath $working Copy-Item -Path $srcexe -Destination $destexe -Force $srctoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $rtname) + ".toml") $desttoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $working) + ".toml") if (Test-Path -Path $srctoml -PathType Leaf) { Copy-Item -Path $srctoml -Destination $desttoml -Force write-host "materialized $working from artifact $rtname (metadata toml copied alongside)" } else { write-host "materialized $working from artifact $rtname (no metadata toml found beside the artifact)" } Set-PunkActiveRuntime $archfolder $working } else { Set-PunkActiveRuntime $archfolder $rtname } } 'run' { #launch the active runtime, passing arguments. #resolution order: PUNK_ACTIVE_RUNTIME env override, active.toml, single #installed candidate - otherwise error with candidates (no last-in-list guessing). #LOCAL PLATFORM ONLY - a foreign platform's binaries are not runnable here. #Only a LEADING -platform is rejected: everything after 'run' belongs to #the runtime, so a later arg spelled -platform must pass through untouched. if ($PSBoundParameters.opts.Length -gt 0 -and $PSBoundParameters.opts[0] -eq '-platform') { write-host "'run' launches the LOCAL platform's active runtime - it takes no -platform option" write-host "(foreign-platform folders are cross-build staging; deploy them to their platform to run)" exit 1 } $arch = $script:PunkLocalPlatform $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 'punk-runtime.cmd fetch' to install" } else { $dircontents = Get-PunkRuntimeCandidates $archfolder if ($dircontents.Count -gt 0) { #write-host "run.." write-host "num params: $($PSBoundParameters.opts.count)" $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: punk-runtime.cmd use (or fetch it: punk-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: punk-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 write-host "type of opts: $($optsType.FullName)" foreach ($boundparam in $PSBoundParameters.opts) { write-host $boundparam } Write-Host "opts: $($PSBoundParameters.opts)" Write-Host "args: $args" Write-HOst "argscount: $($args.Count)" $arglist = @() foreach ($o in $PSBoundParameters.opts) { $oquoted = $o -replace '"', "`\`"" #$oquoted = $oquoted -replace "'", "`'" if ($oquoted -match "\s") { $oquoted = "`"$oquoted`"" } $arglist += @($oquoted) } $arglist = $arglist.TrimEnd(' ') write-host "arglist: $arglist" #$arglist = $PSBoundParameters.opts Start-Process -FilePath $active -ArgumentList $arglist -NoNewWindow -Wait } else { #powershell 5.1 and earlier can't accept an empty -ArgumentList value :/ !! #$arglist = @() #Start-Process -FilePath $active -ArgumentList $arglist -NoNewWindow -Wait #Start-Process -FilePath $active -ArgumentList "" -NoNewWindow -Wait Start-Process -FilePath $active -NoNewWindow -Wait } } else { write-host "No files found in $archfolder" write-host "No runtimes seem to be installed for $arch`nPlease use 'punk-runtime.cmd fetch' to install." } } } 'list' { #-platform lists another platform's folder/server dir (cross-build staging) $arch = Resolve-PunkRuntimePlatform $PSBoundParameters["platform"] $archfolder = Join-Path -Path $rtfolder -ChildPath "$arch" $sha1local = join-path -Path $archfolder -ChildPath "sha1sums.txt" $archurl = "$artifacturl/$arch" $sha1url = "$archurl/sha1sums.txt" if ( $PSBoundParameters.ContainsKey('remote') ) { if (-not (test-path -Path $archfolder -Type Container)) { new-item -Path $archfolder -ItemType Directory -force #create with intermediary folders if not already present } write-host "Checking for available remote runtimes for $arch" Write-Host "Fetching $sha1url" #cached fallback (parity with the bash payload): an unreachable server #falls back to a previously fetched sha1sums.txt with a warning try { Invoke-WebRequest -Uri $sha1url -OutFile $sha1local -ErrorAction Stop Write-Host "sha1 saved at $sha1local" } catch { if (Test-Path -Path $sha1local -PathType Leaf) { Write-Host "WARNING: could not fetch ${sha1url}: $($_.Exception.Message)" Write-Host "WARNING: using cached copy at $sha1local" } else { Write-Host "Unable to fetch $sha1url and no cached copy available: $($_.Exception.Message)" if ($_.Exception.Response) { Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)" } return } } $sha1Content = Get-Content -Path $sha1local $remotedict = @{} foreach ($line in $sha1Content) { #all sha1sums have * (binary indicator) - review $match = [regex]::Match($line,"(.*) [*](.*)$") if ($match.Success) { $server_sha1 = $match.Groups[1].Value $server_rt = $match.Groups[2].Value $remotedict[$server_rt] = $server_sha1 } } $localdict = @{} if (test-path -Path $archfolder -Type Container) { #shared candidate filter (bash payload parity - its -remote loop #already uses list_candidates): excludes directories, build #copies and .txt/.toml/.tm/.tmp/.log support files, so #active.toml / metadata tomls / stray logs never show as #local runtimes in the comparison $dircontents = Get-PunkRuntimeCandidates $archfolder foreach ($f in $dircontents) { $local_sha1 = Get-FileHash -Path $(${f}.FullName) -Algorithm SHA1 $localdict[$f.Name] = ${local_sha1}.Hash } } #server-default + local-active surfacing (G-103): the server's #curated defaults.txt names the recommended runtime per platform - #best-effort fetch (cached/absent both fine for a listing), then #mark the matching row and the locally active one so "is my #active the recommended default?" is answerable at a glance. $activename = Get-PunkActiveRuntime $archfolder $platform_default = "" $defaultslocal = Join-Path -Path $rtfolder -ChildPath "defaults.txt" try { Invoke-WebRequest -Uri "$artifacturl/defaults.txt" -OutFile $defaultslocal -ErrorAction Stop } catch { } if (Test-Path -Path $defaultslocal -PathType Leaf) { foreach ($line in (Get-Content -Path $defaultslocal)) { $line = $line.Trim() if ($line -eq "" -or $line.StartsWith("#")) { continue } $parts = -split $line if ($parts.Count -ge 2 -and $parts[0] -eq $arch) { $platform_default = $parts[1] break } } } Write-host "-----------------------------------------------------------------------" Write-host "Runtimes for $arch" Write-host "Local $archfolder" Write-host "Remote $archurl" if ($platform_default -ne "") { Write-host "server default for ${arch}: $platform_default" } if ($activename -ne "") { $matchnote = "" if ($platform_default -ne "" -and $activename -eq $platform_default) { $matchnote = " (= server default)" } Write-host "active (local): $activename$matchnote" } Write-host "-----------------------------------------------------------------------" Write-host " Local Remote" Write-host "-----------------------------------------------------------------------" # 12345678910234567892023456789302345 $G = "`e[32m" #Green $Y = "`e[33m" #Yellow $R = "`e[31m" #Red $RST = "`e[m" #explicit ORDINAL sort: Hashtable key enumeration order is #undefined (and differs between the powershell editions), and #culture-sensitive Sort-Object collates differently too (NLS vs #ICU) - ordinal agrees across editions and with bash LC_ALL=C $localkeys = @($localdict.Keys) [array]::Sort($localkeys, [System.StringComparer]::Ordinal) foreach ($key in $localkeys) { $local_sha1 = $($localdict[$key]) if ($remotedict.ContainsKey($key)) { if ($local_sha1 -eq $remotedict[$key]) { $rhs = "Same version" $C = $G } else { $rhs = "UPDATE AVAILABLE" $C = $Y } } else { $C = $R $rhs = "(not listed on server)" } #ansi problems from cmd.exe not in windows terminal - review $C = "" $RST = "" $mark = " " if ($key -eq $activename) { $mark = "* " } $annot = "" if ($platform_default -ne "" -and $key -eq $platform_default) { $annot = " (server default)" } $lhs = "$key".PadRight(35, ' ') write-host -nonewline "${mark}${C}${lhs}${RST}" write-host "$rhs$annot" } $lhs_missing = "-".PadRight(35, ' ') $remotekeys = @($remotedict.Keys) [array]::Sort($remotekeys, [System.StringComparer]::Ordinal) foreach ($key in $remotekeys) { if (-not ($localdict.ContainsKey($key))) { $annot = "" if ($platform_default -ne "" -and $key -eq $platform_default) { $annot = " (server default)" } write-host -nonewline " $lhs_missing" write-host "$key$annot" } } Write-host "-----------------------------------------------------------------------" Write-host "* = active (local selection)" } else { if (test-path -Path $archfolder -Type Container) { Write-host "-----------------------------------------------------------------------" Write-Host "Local runtimes for $arch" $dircontents = Get-PunkRuntimeCandidates $archfolder $activename = Get-PunkActiveRuntime $archfolder write-host "$(${dircontents}.count) runtime(s) in $archfolder" Write-host "-----------------------------------------------------------------------" foreach ($f in $dircontents) { $meta = Get-PunkRuntimeMetadataSummary $archfolder $f.Name $arch $lhs = "$($f.Name)".PadRight(35, ' ') if ($f.Name -eq $activename) { write-host "* $lhs (active) $meta" } else { write-host " $lhs $meta" } } 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 'punk-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" Write-host "Use: 'use -name>' to materialize an immutable -r artifact into its working name and select it" } else { write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'punk-runtime.cmd fetch' to install." write-host "Use 'punk-runtime.cmd list -remote' to see available runtimes for $arch" } } } 'platforms' { #enumerate platform folders: local (bin/runtime/*) and, with #-remote, the server's platforms.txt discovery manifest (raw-file #servers have no directory listing - the manifest is part of the #punkbin layout contract; third-party mirrors carry the same file) $localdirs = @() if (Test-Path -Path $rtfolder -PathType Container) { $localdirs = @(Get-ChildItem -Path $rtfolder -Directory | Select-Object -ExpandProperty Name) [array]::Sort($localdirs, [System.StringComparer]::Ordinal) } if ( $PSBoundParameters.ContainsKey('remote') ) { $manifesturl = "$artifacturl/platforms.txt" $manifestlocal = Join-Path -Path $rtfolder -ChildPath "platforms.txt" if (-not (Test-Path -Path $rtfolder -PathType Container)) { new-item -Path $rtfolder -ItemType Directory -force | out-null } try { Invoke-WebRequest -Uri $manifesturl -OutFile $manifestlocal -ErrorAction Stop Write-Host "Fetched $manifesturl" } catch { if (Test-Path -Path $manifestlocal -PathType Leaf) { Write-Host "WARNING: could not fetch ${manifesturl}: $($_.Exception.Message)" Write-Host "WARNING: using cached copy at $manifestlocal" } else { Write-Host "No platforms.txt available from ${manifesturl}: $($_.Exception.Message)" Write-Host "(pre-convention punkbin or third-party mirror without the discovery manifest)" Write-Host "Name platforms explicitly with -platform; canonical names: 'help platforms' in the punk shell" exit 1 } } $remoteplatforms = @() foreach ($line in (Get-Content -Path $manifestlocal)) { $line = $line.Trim() if ($line -eq "" -or $line.StartsWith("#")) { continue } $remoteplatforms += $line } Write-host "-----------------------------------------------------------------------" Write-Host "Platforms served by $artifacturl" Write-host "-----------------------------------------------------------------------" foreach ($p in $remoteplatforms) { $marks = @() if ($p -eq $script:PunkLocalPlatform) { $marks += "local platform" } if ($localdirs -contains $p) { $marks += "local dir present" } $lhs = "$p".PadRight(25, ' ') if ($marks.Count -gt 0) { write-host " $lhs ($($marks -join ', '))" } else { write-host " $lhs" } } foreach ($d in $localdirs) { if (-not ($remoteplatforms -contains $d)) { $lhs = "$d".PadRight(25, ' ') write-host " $lhs (local dir only - not served remotely)" } } Write-host "-----------------------------------------------------------------------" Write-Host "Use: 'list -remote -platform ' to see a platform's runtimes" } else { Write-host "-----------------------------------------------------------------------" Write-Host "Local platform folders under $rtfolder" Write-host "-----------------------------------------------------------------------" if ($localdirs.Count -eq 0) { write-host " (none - 'fetch' creates the local platform's folder)" } foreach ($d in $localdirs) { $lhs = "$d".PadRight(25, ' ') if ($d -eq $script:PunkLocalPlatform) { write-host "* $lhs (local platform)" } else { write-host " $lhs" } } Write-host "-----------------------------------------------------------------------" Write-Host "Use: 'platforms -remote' to see platforms served by the artifact server" Write-Host "Canonical platform names: 'help platforms' in the punk shell" } } 'help' { Show-PunkRuntimeHelp } default { #no action given (unknown actions were already rejected in the #process block): show the short usage and point at 'help' Show-PunkRuntimeUsage write-host "" write-host "'punk-runtime.cmd help' gives options, env vars and examples." } } return $PSBoundParameters } } #write-host (psmain @args) #$returnvalue = psmain @args #Write-Host "Function Returned $returnvalue" -ForegroundColor Cyan #return $returnvalue psmain @args | out-null exit 0