Files
wiicompiled/Launcher/NativeBuildFlags.ps1
Wubbzee a135beb201 Reduce CI time using caching (#180)
* caching a little bit

* provide CMake with the explicit path to sccache.exe

* map ACTIONS_RESULTS_URL to ACTIONS_CACHE_URL so sccache can upload the
files...

* i removed the parallel oops

* small change

* doing a little bit of flag editing

* update sccache and cache nuget stuff
2026-09-07 09:18:53 +02:00

308 lines
16 KiB
PowerShell

# Canonical native (CMake) configure flags, plus the shared helpers (file assertions, checked
# process spawn, file hashing) both LocalBuild.ps1 and Prepare-NativePrebuilt.ps1 dot-source.
# They MUST stay byte-identical: the prebuilt package ships static archives linked into objects
# the user's machine compiles, so any flag divergence is an ABI/ODR hazard, not just a rebuild.
# The flag fingerprint below is recorded in the package provenance and re-checked before use.
Set-StrictMode -Version 3.0
function Assert-File([string]$Path, [string]$Description) {
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "$Description is missing: $Path" }
}
function Assert-Directory([string]$Path, [string]$Description) {
if (-not (Test-Path -LiteralPath $Path -PathType Container)) { throw "$Description is missing: $Path" }
}
function Get-MkwFileSha256([string]$Path) {
<#
Lower-case SHA-256 of one file. -LiteralPath is required: Get-FileHash treats a positional
path as a wildcard, so an install directory containing [, ] or * would hash the wrong file.
#>
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
function Get-MkwToolchainPath([string]$ToolchainRoot) {
<#
The scrubbed PATH a bundled-toolchain build runs with. The bundled toolchain must be the only
one visible, or an ambient Visual Studio/MSYS install on PATH could pull in its own headers,
libraries, or linker. Shared by LocalBuild.ps1 and Prepare-NativePrebuilt.ps1 since the
installed Toolkit and the maintainer portable-tools tree share the same subdirectory layout.
#>
if ([string]::IsNullOrWhiteSpace($ToolchainRoot)) { throw 'A toolchain root is required.' }
$root = [IO.Path]::GetFullPath($ToolchainRoot)
return (@(
(Join-Path $root 'llvm-mingw\bin'),
(Join-Path $root 'CMake\bin'),
(Join-Path $root 'Ninja'),
(Join-Path $env:SystemRoot 'System32'),
$env:SystemRoot
) -join ';')
}
function Get-MkwShellSafeToolchainRoot([string]$ToolchainRoot) {
if ([string]::IsNullOrWhiteSpace($ToolchainRoot)) { throw 'A toolchain root is required.' }
$full = [IO.Path]::GetFullPath($ToolchainRoot)
# A drive root keeps its separator: "C:" is relative to the current directory on that drive.
if ($full -ne [IO.Path]::GetPathRoot($full)) { $full = $full.TrimEnd('\') }
if ($full -notmatch '[()&^%!]') { return $full }
$sha = [Security.Cryptography.SHA256]::Create()
try {
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($full.ToLowerInvariant()))
} finally { $sha.Dispose() }
$linkName = 'toolchain-' + ((($bytes[0..7]) | ForEach-Object { $_.ToString('x2') }) -join '')
$failures = @()
foreach ($base in @($env:ProgramData, $env:PUBLIC)) {
if ([string]::IsNullOrWhiteSpace($base) -or $base -match '[()&^%! ]') { continue }
$link = Join-Path (Join-Path $base 'WiiCompiled') $linkName
try {
[IO.Directory]::CreateDirectory((Split-Path -Parent $link)) | Out-Null
# The name already identifies the target, so an existing junction that still resolves is
# this one; only a broken leftover is replaced. Directory.Delete removes the reparse
# point itself, where Remove-Item -Recurse would delete the toolchain it points at.
if (-not (Test-Path -LiteralPath (Join-Path $link 'CMake\bin\cmake.exe') -PathType Leaf)) {
if (Test-Path -LiteralPath $link) { [IO.Directory]::Delete($link) }
New-Item -ItemType Junction -Path $link -Target $full -ErrorAction Stop | Out-Null
}
Write-Host "MKWCBUILD: Building through $link, because $full contains characters cmd.exe cannot parse"
return $link
} catch {
$failures += "$link ($($_.Exception.Message))"
}
}
throw ("The toolchain path $full contains a character (one of ( ) & ^ % !) that the compiler " +
'cannot be invoked through, and no junction to it could be created: ' + ($failures -join '; ') +
'. Install to a path without those characters.')
}
function Get-MkwProjectPins([string]$ProjectFile) {
<#
The Mario Kart Wii facts pinned by projects/mkwii/recomp.yml (game identity, clean input
digests, translation entry point, Retro-WFC endpoint), read from one place instead of being
restated; Test-PinnedFacts.ps1 checks the copies that can't read YAML (C++ header, C#
constants) against these values. Parsing is literal, not a YAML dependency, since the file is
machine-written with a fixed shape.
#>
Assert-File $ProjectFile 'Translation project file'
$pins = [ordered]@{
GameId = ''; DolSha256 = ''; RelSha256 = ''; EntryPoint = ''; RetroWfcPayloadUri = ''
}
$section = ''
$inputKey = ''
foreach ($raw in [IO.File]::ReadAllLines($ProjectFile)) {
$line = ($raw -replace '#.*$', '')
if ([string]::IsNullOrWhiteSpace($line)) { continue }
if ($line -match '^([A-Za-z0-9_]+):') { $section = $Matches[1]; $inputKey = ''; continue }
if ($section -eq 'inputs' -and $line -match '^\s{2}([A-Za-z0-9_]+):\s*$') { $inputKey = $Matches[1]; continue }
if ($section -eq 'project' -and $line -match '^\s*game_id:\s*(\S+)\s*$') {
$pins.GameId = $Matches[1]
} elseif ($section -eq 'inputs' -and $line -match '^\s*sha256:\s*([0-9a-fA-F]{64})\s*$') {
if ($inputKey -eq 'dol') { $pins.DolSha256 = $Matches[1].ToLowerInvariant() }
elseif ($inputKey -eq 'rel') { $pins.RelSha256 = $Matches[1].ToLowerInvariant() }
} elseif ($section -eq 'translation' -and $pins.EntryPoint -eq '' -and
$line -match '^\s*-\s*(0[xX][0-9a-fA-F]+)\s*$') {
$pins.EntryPoint = $Matches[1].ToLowerInvariant()
} elseif ($line -match '^\s*retro_wfc_payload:\s*(\S+)\s*$') {
$pins.RetroWfcPayloadUri = $Matches[1]
}
}
foreach ($name in @($pins.Keys)) {
if ([string]::IsNullOrWhiteSpace($pins[$name])) {
throw "$ProjectFile does not pin $name; the project file is not the shape this build expects."
}
}
return $pins
}
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '', [bool]$WaitForProcessTree = $true) {
<#
Runs a build tool and turns a non-zero exit code into a described failure. By default,
Start-Process -Wait waits for the whole process tree, since a .NET single-file bundle host may
hand off to an extracted child that PowerShell's call operator would not wait for. Callers that
need to avoid waiting on unrelated descendants can opt into the call-operator path.
Start-Process doesn't publish $LASTEXITCODE, so this sets it manually for callers that check it.
-StepId emits the machine-readable form the installer's progress bar consumes (BuildStepIds in
WiiCompiled.Setup/InstallProgress.cs); the human sentence stays on the same log line.
#>
if ($StepId) { Write-Host "${LogPrefix}:STEP:${StepId} $Description" }
else { Write-Host "${LogPrefix}: $Description" }
$quotedArguments = @($Arguments | ForEach-Object {
if ($_.Contains('"')) { throw "A native build argument contains an unsupported quote: $_" }
'"' + $_ + '"'
})
if ($WaitForProcessTree) {
$process = Start-Process -FilePath $FilePath -ArgumentList $quotedArguments `
-NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
} else {
& $FilePath @Arguments
$exitCode = $LASTEXITCODE
}
$global:LASTEXITCODE = $exitCode
if ($exitCode -ne 0) { throw "$Description failed with exit code $exitCode." }
}
function Get-MkwNativeFixedConfigureFlags {
<#
The path-independent half of the configure command line, identical on every machine, which is
what makes one precompiled aurora/third-party package valid for all users. Path-bearing
arguments are added separately by Get-MkwNativeConfigureArguments and excluded from the fingerprint.
#>
return @(
'-DCMAKE_BUILD_TYPE=Release',
'-DCMAKE_SYSTEM_PROCESSOR=x86_64',
'-DAURORA_DAWN_PROVIDER=package', '-DAURORA_SDL3_PROVIDER=vendor',
'-DCMAKE_POLICY_DEFAULT_CMP0168=NEW', '-DFETCHCONTENT_FULLY_DISCONNECTED=ON',
'-DAWK:FILEPATH='
)
}
function Get-MkwNativeFlagFingerprint {
<#
.SYNOPSIS
SHA-256 over the canonical flag list, used to reject a stale package.
#>
$canonical = (Get-MkwNativeFixedConfigureFlags) -join "`n"
$sha = [Security.Cryptography.SHA256]::Create()
try {
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonical))
} finally { $sha.Dispose() }
return (($bytes | ForEach-Object { $_.ToString('x2') }) -join '')
}
function Get-MkwDawnRuntimeSha256([string]$DependenciesDirectory) {
<#
SHA-256 of the pinned Dawn runtime the prebuilt aurora must match. The precompiled aurora
archives are compiled and linked against the pinned dawn_prebuilt tree's headers/import
library, but the upstream release asset is mutable (the same version tag has already served
two different windows-amd64 archives), so the version string alone can't prove agreement.
A mismatch surfaces as an access violation at surface creation, not a link error, so the
digest is recorded at harvest and re-checked before use. Returns '' when the tree is absent.
#>
if ([string]::IsNullOrWhiteSpace($DependenciesDirectory)) { return '' }
$runtime = Join-Path $DependenciesDirectory 'dawn_prebuilt\bin\webgpu_dawn.dll'
if (-not (Test-Path -LiteralPath $runtime -PathType Leaf)) { return '' }
return Get-MkwFileSha256 $runtime
}
function Get-MkwThirdPartySourceFingerprint([string]$ThirdPartyDirectory) {
<#
SHA-256 over runtime/third_party, the vendored sources behind the prebuilt package. The
precompiled Crypto++ archive is linked while first-party TUs compile against the same headers,
so a workspace whose vendored tree moved on must not consume archives harvested from an older
one (same reasoning as the aurora fingerprint below). Unlike that scan this hashes hidden files
too, since Build-Installer stages this tree with Copy-Item -Force. Returns '' when absent.
#>
if ([string]::IsNullOrWhiteSpace($ThirdPartyDirectory)) { return '' }
if (-not (Test-Path -LiteralPath $ThirdPartyDirectory -PathType Container)) { return '' }
$root = [IO.Path]::GetFullPath($ThirdPartyDirectory).TrimEnd('\')
$files = Get-ChildItem -LiteralPath $root -Recurse -File |
Sort-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
$sha = [Security.Cryptography.SHA256]::Create()
try {
$builder = [Text.StringBuilder]::new()
foreach ($file in $files) {
$relative = $file.FullName.Substring($root.Length + 1).Replace('\', '/')
$content = Get-MkwFileSha256 $file.FullName
[void]$builder.Append($relative).Append(' ').Append($content).Append("`n")
}
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($builder.ToString()))
} finally { $sha.Dispose() }
return (($bytes | ForEach-Object { $_.ToString('x2') }) -join '')
}
function Get-MkwAuroraSourceFingerprint([string]$AuroraDirectory) {
<#
SHA-256 over the aurora sources the precompiled archives were harvested from. The compiler,
flag, and Dawn digests only prove the package agrees with its target environment; none notice
aurora itself moving on, and since the runtime calls aurora headers directly, a package
harvested before an aurora change links old code against new call sites (undefined symbol at
best, silently stale renderer at worst). extern/ is excluded because the payload ships those
trees separately. Returns '' when the tree is absent.
#>
if ([string]::IsNullOrWhiteSpace($AuroraDirectory)) { return '' }
if (-not (Test-Path -LiteralPath $AuroraDirectory -PathType Container)) { return '' }
$root = [IO.Path]::GetFullPath($AuroraDirectory).TrimEnd('\')
$excludedPrefixes = @(
(Join-Path $root 'extern') + '\',
(Join-Path $root 'build') + '\'
)
# Hidden files are skipped deliberately: Build-Installer.ps1 stages this tree
# with Copy-Item -Recurse (no -Force), so they never reach the payload and
# hashing them would make the two sides disagree by construction.
$files = Get-ChildItem -LiteralPath $root -Recurse -File |
Where-Object {
$full = $_.FullName
-not ($excludedPrefixes | Where-Object { $full.StartsWith($_, [StringComparison]::OrdinalIgnoreCase) })
} |
Sort-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
$sha = [Security.Cryptography.SHA256]::Create()
try {
$builder = [Text.StringBuilder]::new()
foreach ($file in $files) {
$relative = $file.FullName.Substring($root.Length + 1).Replace('\', '/')
$content = Get-MkwFileSha256 $file.FullName
[void]$builder.Append($relative).Append(' ').Append($content).Append("`n")
}
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($builder.ToString()))
} finally { $sha.Dispose() }
return (($bytes | ForEach-Object { $_.ToString('x2') }) -join '')
}
function ConvertTo-MkwCMakePath([string]$Path) {
return [IO.Path]::GetFullPath($Path).Replace('\', '/')
}
function Get-MkwNativeConfigureArguments {
<# The complete `cmake` configure argument list for a local runtime build. #>
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string]$SourceDirectory,
[Parameter(Mandatory)] [string]$BuildDirectory,
[Parameter(Mandatory)] [string]$Ninja,
[Parameter(Mandatory)] [string]$CCompiler,
[Parameter(Mandatory)] [string]$CxxCompiler,
[Parameter(Mandatory)] [string]$ResourceCompiler,
[string]$DependenciesDirectory,
[string]$NativePrebuiltDirectory,
[string[]]$AdditionalArguments = @()
)
$arguments = @(
'-S', (ConvertTo-MkwCMakePath $SourceDirectory),
'-B', (ConvertTo-MkwCMakePath $BuildDirectory),
'-G', 'Ninja',
"-DCMAKE_MAKE_PROGRAM=$(ConvertTo-MkwCMakePath $Ninja)",
"-DCMAKE_C_COMPILER=$(ConvertTo-MkwCMakePath $CCompiler)",
"-DCMAKE_CXX_COMPILER=$(ConvertTo-MkwCMakePath $CxxCompiler)",
"-DCMAKE_RC_COMPILER=$(ConvertTo-MkwCMakePath $ResourceCompiler)"
)
$arguments += Get-MkwNativeFixedConfigureFlags
if (-not [string]::IsNullOrWhiteSpace($DependenciesDirectory) -and
(Test-Path -LiteralPath $DependenciesDirectory -PathType Container)) {
$cppWinRt = Join-Path $DependenciesDirectory 'cppwinrt'
if (Test-Path -LiteralPath (Join-Path $cppWinRt 'winrt\base.h') -PathType Leaf) {
$arguments += "-DMKW_CPPWINRT_INCLUDE_DIR=$(ConvertTo-MkwCMakePath $cppWinRt)"
}
foreach ($directory in Get-ChildItem -LiteralPath $DependenciesDirectory -Directory) {
# native_prebuilt holds compiled output, not a FetchContent source tree.
if ($directory.Name -eq 'native_prebuilt') { continue }
# FetchContent uppercases the declared name but preserves punctuation
# (for example abseil-cpp -> FETCHCONTENT_SOURCE_DIR_ABSEIL-CPP).
$name = $directory.Name.ToUpperInvariant()
$arguments += "-DFETCHCONTENT_SOURCE_DIR_$name=$(ConvertTo-MkwCMakePath $directory.FullName)"
}
}
if (-not [string]::IsNullOrWhiteSpace($NativePrebuiltDirectory)) {
$arguments += "-DMKW_NATIVE_PREBUILT_DIR=$(ConvertTo-MkwCMakePath $NativePrebuiltDirectory)"
}
$arguments += $AdditionalArguments
return $arguments
}