You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

957 lines
53 KiB

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 ?<name>? ?-platform <p>? download+verify a runtime from punkbin"
write-host " list ?-remote? ?-platform <p>? installed (or server) runtimes + metadata"
write-host " use <name> ?-platform <p>? select active / materialize -r<N> 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/<platform>/"
write-host ""
Show-PunkRuntimeUsage
write-host ""
write-host "Actions:"
write-host " fetch ?<name>? ?-platform <p>?"
write-host " Download a runtime from the punkbin artifact server into"
write-host " bin/runtime/<p>/ with sha1 verification against the server's"
write-host " sha1sums.txt. -r<N>-named family artifacts also fetch their"
write-host " <rootname>.toml metadata record. Omitting <name> 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 " <name>."
write-host " list ?-remote? ?-platform <p>?"
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 <name> ?-platform <p>?"
write-host " Select the runtime that 'run' launches (records active.toml in"
write-host " the platform folder). An immutable -r<N> ARTIFACT name is"
write-host " MATERIALIZED into its WORKING name (name minus -r<N> - 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 <rootname>.toml beside it (emitted
#by the buildsuite kit-family-artifacts step / fetched from punkbin). Returns a
#short "[variant=... tcl=... rN ...]" summary for list output, "" when absent.
function Get-PunkRuntimeMetadataSummary {
param([string] $archfolder, [string] $exename, [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 <runtimename> ?-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 <runtimename> ?-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<N> names) carry a metadata toml alongside
#on the server - fetch it too; absence is fine (pre-family runtimes)
if ($runtime -match '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$') {
$tomlname = (Get-PunkRuntimeRootName $runtime) + ".toml"
$tomlurl = "$archurl/$tomlname"
$tomllocal = Join-Path -Path $archfolder -ChildPath $tomlname
try {
Invoke-WebRequest -Uri $tomlurl -OutFile $tomllocal -ErrorAction Stop
Write-Host "artifact metadata saved at $tomllocal"
} catch {
Write-Host "no artifact metadata toml on server for $runtime (ok for pre-family runtimes)"
}
}
#first fetch establishes the active runtime; later fetches never steal it
if ((Get-PunkActiveRuntime $archfolder) -eq "") {
Set-PunkActiveRuntime $archfolder $runtime
}
} 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 <runtimename> ?-platform <p>?"
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<N>, immutable): 'use' MATERIALIZES the
#artifact into its WORKING name (name minus -r<N> - what mapvfs and
#projects reference), copies its metadata toml alongside, and selects
#the working name. Republishing artifacts never churns consumers.
$am = [regex]::Match($rtname, '^(.*)-r([0-9]+)(\.[Ee][Xx][Ee])?$')
if ($am.Success) {
$working = $am.Groups[1].Value + $am.Groups[3].Value
$srcexe = Join-Path -Path $archfolder -ChildPath $rtname
$destexe = Join-Path -Path $archfolder -ChildPath $working
Copy-Item -Path $srcexe -Destination $destexe -Force
$srctoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $rtname) + ".toml")
$desttoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $working) + ".toml")
if (Test-Path -Path $srctoml -PathType Leaf) {
Copy-Item -Path $srctoml -Destination $desttoml -Force
write-host "materialized $working from artifact $rtname (metadata toml copied alongside)"
} else {
write-host "materialized $working from artifact $rtname (no metadata toml found beside the artifact)"
}
Set-PunkActiveRuntime $archfolder $working
} else {
Set-PunkActiveRuntime $archfolder $rtname
}
}
'run' {
#launch the active runtime, passing arguments.
#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 <name> (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 <name>"
write-host "Installed candidates:"
foreach ($f in $dircontents) {
write-host " $($f.Name)"
}
exit 1
}
}
}
$active = Join-Path -Path $archfolder -ChildPath $activename
write-host "using: $active"
if ($PSBoundParameters.opts.Length -gt 0) {
$optsType = $PSBoundParameters.opts.GetType() #method can only be called if .opts is not null
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 <name>' to reselect"
}
Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server"
Write-host "Use: 'use <name>' to select the runtime that 'run' launches"
Write-host "Use: 'use <artifact-r<N>-name>' to materialize an immutable -r<N> artifact into its working name and select it"
} else {
write-host "No runtimes seem to be installed for $arch in $archfolder`nPlease use 'punk-runtime.cmd fetch' to install."
write-host "Use 'punk-runtime.cmd list -remote' to see available runtimes for $arch"
}
}
}
'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 <name>' 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