#Requires -Version 5.1 # Shared prebuilt-artifact packaging helpers. No builds or downloads. function Assert-PackageOrdinaryFile([string]$Path) { $item = Get-Item -LiteralPath $Path if ($item.PSIsContainer) { throw "Package input is a directory: $Path" } $current = $item while ($null -ne $current) { if (($current.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw "Package input has a symlink/reparse ancestor: $($current.FullName)" } $current = if ($current -is [IO.FileInfo]) { $current.Directory } else { $current.Parent } } } function Invoke-PackageHelper([string]$Python, [string[]]$Arguments) { $raw = @(& $Python (Join-Path $PSScriptRoot 'package-artifacts.py') @Arguments) if ($LASTEXITCODE -ne 0) { throw 'Package artifact preflight/verification failed.' } if ($raw.Count) { return (($raw -join "`n") | ConvertFrom-Json) } } function Get-PackageDocumentation([string]$Repository, [string]$Platform, [string]$Python) { $result = Invoke-PackageHelper $Python @('docs', '--repository', $Repository, '--platform', $Platform) $inputs = [ordered]@{} foreach ($entry in $result.files) { $inputs[$entry.path] = $entry.source } return $inputs } function Read-PackageBuildEvidence([string]$Path, [string]$Target, $BinaryHashes) { if (-not $Path) { return $null } $evidence = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json foreach ($field in @('source_identity', 'profile', 'compiler', 'target', 'binary_sha256')) { if (-not $evidence.PSObject.Properties[$field]) { throw "Build evidence missing $field" } } if ($evidence.target -ne $Target) { throw 'Build evidence target differs from requested package target.' } foreach ($field in @('source_identity', 'profile', 'compiler')) { if ($evidence.$field -isnot [string] -or [string]::IsNullOrWhiteSpace($evidence.$field)) { throw "Build evidence $field must be explicit nonempty text." } } foreach ($name in $BinaryHashes.Keys) { if (-not $evidence.binary_sha256.PSObject.Properties[$name] -or $evidence.binary_sha256.$name -cne $BinaryHashes[$name]) { throw "Build evidence binary hash mismatch: $name" } } return [ordered]@{ assertion = 'Caller-supplied build evidence; target and binary hashes checked, source/compiler/profile statements not inferred or independently proven by packaging.'; evidence = $evidence } } function Write-PackageNotices([string]$Stage, $Notices) { $encoding = New-Object Text.UTF8Encoding($false) [IO.File]::WriteAllText((Join-Path $Stage 'dependency-notices.json'), ($Notices.Inventory | ConvertTo-Json -Depth 18) + "`n", $encoding) $index = @('Third-party notice inventory', '', 'Exact versions, license expressions, source hashes and notice paths: dependency-notices.json', 'License texts and embedded-asset notices: licenses/', 'Rust distribution notices: licenses/rust/', 'Collection does not establish binary/source equivalence or a universal license audit.', '') foreach ($package in $Notices.Inventory.packages) { $index += "$($package.name) $($package.version) -- $($package.license) -- licenses/dependencies/$($package.name)-$($package.version)/" } [IO.File]::WriteAllText((Join-Path $Stage 'THIRD-PARTY-NOTICES.txt'), ($index -join "`n") + "`n", $encoding) } function Copy-PackageInputs([string]$Stage, $Inputs, $InspectedHashes) { $hashes = [ordered]@{} foreach ($name in $Inputs.Keys) { Assert-PackageOrdinaryFile $Inputs[$name] $source = Get-Item -LiteralPath $Inputs[$name] if ($source.PSIsContainer -or ($source.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw "Package input must be an ordinary file: $name" } $destination = Join-Path $Stage $name [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination)) $before = (Get-FileHash -LiteralPath $source.FullName -Algorithm SHA256).Hash.ToLowerInvariant() if ($InspectedHashes.ContainsKey($name) -and $InspectedHashes[$name] -ne $before) { throw "Input changed after inspection: $name" } [IO.File]::Copy($source.FullName, $destination, $false) if ((Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash.ToLowerInvariant() -ne $before) { throw "Input changed during staging: $name" } $hashes[$name] = $before } return $hashes } function Write-PackageChecksums([string]$Stage) { $encoding = New-Object Text.UTF8Encoding($false) $lines = New-Object 'Collections.Generic.List[string]' foreach ($file in Get-ChildItem -LiteralPath $Stage -Recurse -File | Sort-Object FullName) { $relative = $file.FullName.Substring($Stage.Length + 1).Replace('\', '/') if ($relative -eq 'SHA256SUMS.txt') { throw 'Package checksum manifest already exists.' } $lines.Add((Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + ' ' + $relative) } [IO.File]::WriteAllText((Join-Path $Stage 'SHA256SUMS.txt'), ($lines -join "`n") + "`n", $encoding) }