mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-21 22:04:15 -04:00
5e337e6ecf
Co-authored-by: DarthMDev <mgracer48@yahoo.com> Co-authored-by: rooklz <rookieb@muzo-works.com>
150 lines
8.5 KiB
PowerShell
150 lines
8.5 KiB
PowerShell
# Fails the release build when a fact duplicated across the repo stops agreeing with the copy
|
|
# that owns it (each projects/*/recomp.yml). Scripts read pinned facts through Get-MkwProjectPins
|
|
# or build-cache.sh's mkw_project_pin, but some consumers can't read YAML (the C++ runtime header,
|
|
# the C# Retro-WFC table, and hand-written lists on both sides of the C#/PowerShell boundary), so
|
|
# those are checked here instead.
|
|
[CmdletBinding()]
|
|
param([string]$RepositoryRoot)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version 3.0
|
|
|
|
. (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1')
|
|
|
|
if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
|
|
$RepositoryRoot = Join-Path $PSScriptRoot '..'
|
|
}
|
|
$repoRoot = [IO.Path]::GetFullPath($RepositoryRoot)
|
|
$launcher = Join-Path $repoRoot 'Launcher'
|
|
$setup = Join-Path $launcher 'WiiCompiled.Setup.Windows'
|
|
$common = Join-Path $launcher 'WiiCompiled.Setup.Common'
|
|
|
|
$failures = [Collections.Generic.List[string]]::new()
|
|
function Add-Failure([string]$Message) { $failures.Add($Message) }
|
|
|
|
function Read-SourceFile([string]$Path, [string]$Description) {
|
|
Assert-File $Path $Description
|
|
return [IO.File]::ReadAllText($Path)
|
|
}
|
|
|
|
function Get-CapturedValue([string]$Text, [string]$Pattern, [string]$Description) {
|
|
$match = [regex]::Match($Text, $Pattern)
|
|
if (-not $match.Success) { throw "$Description could not be located; update Test-PinnedFacts.ps1." }
|
|
return $match.Groups[1].Value
|
|
}
|
|
|
|
function Get-QuotedList([string]$Text, [string]$BlockPattern, [string]$ItemPattern, [string]$Description) {
|
|
$block = Get-CapturedValue $Text $BlockPattern $Description
|
|
$items = [regex]::Matches($block, $ItemPattern) | ForEach-Object { $_.Groups[1].Value }
|
|
if (@($items).Count -eq 0) { throw "$Description is empty; update Test-PinnedFacts.ps1." }
|
|
return [string[]]@($items)
|
|
}
|
|
|
|
function Compare-Set([string[]]$Expected, [string[]]$Actual, [string]$ExpectedName, [string]$ActualName) {
|
|
$missing = @($Expected | Where-Object { $Actual -notcontains $_ })
|
|
$extra = @($Actual | Where-Object { $Expected -notcontains $_ })
|
|
if ($missing.Count -gt 0) { Add-Failure "$ActualName is missing: $($missing -join ', ') (present in $ExpectedName)." }
|
|
if ($extra.Count -gt 0) { Add-Failure "$ActualName has entries $ExpectedName does not: $($extra -join ', ')." }
|
|
}
|
|
|
|
# Every supported release is one projects/<name>/recomp.yml, the same set Build-Installer.ps1 ships.
|
|
$releases = @(Get-ChildItem -LiteralPath (Join-Path $repoRoot 'projects') -Directory | Sort-Object Name |
|
|
ForEach-Object { Join-Path $_.FullName 'recomp.yml' } | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } |
|
|
ForEach-Object { $pins = Get-MkwProjectPins $_; $pins.Project = $_; [pscustomobject]$pins })
|
|
if ($releases.Count -eq 0) { throw 'No projects/*/recomp.yml was found; update Test-PinnedFacts.ps1.' }
|
|
|
|
# --- The Retro-WFC endpoints: each recomp.yml owns its release's; the host derives the same URL
|
|
# --- from its game table so a redirected or rewritten endpoint cannot be fetched from.
|
|
$retroWfcPayload = Read-SourceFile (Join-Path $common 'RetroWfcPayload.cs') 'RetroWfcPayload.cs'
|
|
$payloadGames = @{}
|
|
foreach ($entry in [regex]::Matches((Get-CapturedValue $retroWfcPayload `
|
|
'(?s)PayloadGames\s*=\s*new\([^)]*\)\s*\{(.*?)\};' 'The host Retro-WFC game table'),
|
|
'\["([^"]+)"\]\s*=\s*"([^"]+)"')) {
|
|
$payloadGames[$entry.Groups[1].Value] = $entry.Groups[2].Value
|
|
}
|
|
$uriTemplate = Get-CapturedValue $retroWfcPayload 'PayloadUri\(string gameId\)\s*=>\s*\$"([^"]+)"' `
|
|
'The host Retro-WFC endpoint template'
|
|
Compare-Set ([string[]]@($releases.GameId)) ([string[]]@($payloadGames.Keys)) `
|
|
'projects/*/recomp.yml' 'RetroWfcPayload.PayloadGames'
|
|
foreach ($release in $releases) {
|
|
if (-not $payloadGames.ContainsKey($release.GameId)) { continue }
|
|
$hostUri = $uriTemplate.Replace('{PayloadGame(gameId)}', $payloadGames[$release.GameId])
|
|
if ($hostUri -cne $release.RetroWfcPayloadUri) {
|
|
Add-Failure "RetroWfcPayload.PayloadUri($($release.GameId)) is '$hostUri' but $($release.Project) pins '$($release.RetroWfcPayloadUri)'."
|
|
}
|
|
}
|
|
foreach ($key in @('GameId', 'DolSha256')) {
|
|
foreach ($duplicate in @($releases | Group-Object $key | Where-Object Count -gt 1)) {
|
|
Add-Failure "Several projects pin the same $($key) $($duplicate.Name)."
|
|
}
|
|
}
|
|
|
|
# --- The translation entry point: the translator is told it by LocalBuild.ps1 (which reads
|
|
# --- recomp.yml), while the runtime enters the translated image at its own constant.
|
|
$systemBridge = Read-SourceFile (Join-Path $repoRoot 'runtime\include\system_bridge.h') 'system_bridge.h'
|
|
$runtimeEntry = (Get-CapturedValue $systemBridge 'kDefaultEntryAddress\s*=\s*(0[xX][0-9a-fA-F]+)' `
|
|
'The runtime default entry address').ToLowerInvariant()
|
|
foreach ($release in $releases) {
|
|
if ($runtimeEntry -ne $release.EntryPoint) {
|
|
Add-Failure "system_bridge.h enters at $runtimeEntry but $($release.Project) translates from $($release.EntryPoint)."
|
|
}
|
|
}
|
|
|
|
# --- The pinned dependency set: Build-Installer.ps1 stages it, the installed host requires it.
|
|
$installedLayout = Read-SourceFile (Join-Path $setup 'InstalledLayout.cs') 'InstalledLayout.cs'
|
|
$buildInstaller = Read-SourceFile (Join-Path $launcher 'Build-Installer.ps1') 'Build-Installer.ps1'
|
|
Compare-Set (Get-QuotedList $installedLayout '(?s)DependencyNames\s*=\s*\[(.*?)\]' '"([^"]+)"' `
|
|
'InstalledLayout.DependencyNames') `
|
|
(Get-QuotedList $buildInstaller '(?s)\$requiredDependencies\s*=\s*@\((.*?)\)' "'([^']+)'" `
|
|
'Build-Installer.ps1 $requiredDependencies') `
|
|
'InstalledLayout.DependencyNames' 'Build-Installer.ps1 $requiredDependencies'
|
|
|
|
# --- The runtime assets copied beside a product: the host hashes exactly these names, so the build
|
|
# --- script must publish every one of them or every product reports its support files as stale.
|
|
$localBuild = Read-SourceFile (Join-Path $launcher 'LocalBuild.ps1') 'LocalBuild.ps1'
|
|
$productAssets = @(Get-CapturedValue $installedLayout 'ProductBootstrapDirectoryName\s*=\s*"([^"]+)"' `
|
|
'The product bootstrap directory name') +
|
|
@(Get-QuotedList $installedLayout '(?s)ProductFileName\)\[\]\s*Files\s*=\s*\[(.*?)\n\s*\];' `
|
|
'\]\s*,\s*"([^"]+)"\s*\)' 'ProductRuntimeAssets.Files')
|
|
foreach ($asset in $productAssets) {
|
|
if ($localBuild.IndexOf("'$asset'", [StringComparison]::Ordinal) -lt 0) {
|
|
Add-Failure "LocalBuild.ps1 never publishes the product runtime asset '$asset'."
|
|
}
|
|
}
|
|
|
|
# --- The command-line contract between the host and the build script.
|
|
$localBuildService = Read-SourceFile (Join-Path $setup 'LocalBuildService.cs') 'LocalBuildService.cs'
|
|
$parameterBlock = Get-CapturedValue $localBuild '(?sm)^param\((.*?)\r?\n\)' 'The LocalBuild.ps1 param block'
|
|
$scriptParameters = [regex]::Matches($parameterBlock, '\$(\w+)') | ForEach-Object { $_.Groups[1].Value }
|
|
# powershell.exe's own switches travel in the same argument list and are not script parameters.
|
|
$hostSwitches = @('NoLogo', 'NoProfile', 'NonInteractive', 'ExecutionPolicy', 'File')
|
|
$passedSwitches = [regex]::Matches($localBuildService, '"-([A-Za-z]\w*)"') |
|
|
ForEach-Object { $_.Groups[1].Value } | Where-Object { $hostSwitches -notcontains $_ } |
|
|
Sort-Object -Unique
|
|
foreach ($switch in $passedSwitches) {
|
|
if ($scriptParameters -notcontains $switch) {
|
|
Add-Failure "LocalBuildService.cs passes -$switch, which LocalBuild.ps1 does not declare."
|
|
}
|
|
}
|
|
foreach ($mandatory in ([regex]::Matches($parameterBlock,
|
|
'\[Parameter\(Mandatory\)\][^$]*\$(\w+)') | ForEach-Object { $_.Groups[1].Value })) {
|
|
if ($passedSwitches -notcontains $mandatory) {
|
|
Add-Failure "LocalBuild.ps1 requires -$mandatory, which LocalBuildService.cs never passes."
|
|
}
|
|
}
|
|
|
|
# --- The build steps the installer's progress bar acts on.
|
|
$installProgress = Read-SourceFile (Join-Path $common 'InstallProgress.cs') 'InstallProgress.cs'
|
|
$knownSteps = Get-QuotedList $installProgress '(?s)class BuildStepIds\s*\{(.*?)\n\}' `
|
|
'const string \w+ = "([^"]+)";' 'BuildStepIds'
|
|
$emittedSteps = @([regex]::Matches($localBuild, "(?:-StepId|Write-MkwBuildStep)\s+'([^']+)'") |
|
|
ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique)
|
|
Compare-Set $knownSteps $emittedSteps 'BuildStepIds' 'the steps LocalBuild.ps1 emits'
|
|
|
|
if ($failures.Count -gt 0) {
|
|
throw "Pinned facts disagree between their copies:`n $($failures -join "`n ")"
|
|
}
|
|
Write-Host ("Pinned-fact audit passed: games {0}, entry {1}; Retro-WFC endpoint, dependency, runtime-asset, " -f
|
|
($releases.GameId -join ', '), $runtimeEntry) -NoNewline
|
|
Write-Host 'build-step, and build-script parameter contracts all agree.'
|