mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 09:11:52 -04:00
462 lines
28 KiB
PowerShell
462 lines
28 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)] [string]$Workspace,
|
|
[Parameter(Mandatory)] [string]$Toolkit,
|
|
# 'both' translates once (retro-aware) and compiles the two products from one build
|
|
# graph, which shares every profile-neutral shard object between them. Building the
|
|
# legs separately recompiles all of mkw_base_shared in the second leg, because the
|
|
# retro-aware shard emission changes the content identity of every base_common shard.
|
|
[Parameter(Mandatory)] [ValidateSet('base', 'retro-rewind', 'both')] [string]$Profile,
|
|
[Parameter(Mandatory)] [string]$OutputDirectory,
|
|
[string]$BaseOutputDirectory,
|
|
[string]$RetroRewindPackageDirectory,
|
|
[string]$RetroWfcOfflineDirectory,
|
|
[ValidateSet('offline', 'downloaded')] [string]$RetroWfcPayloadOrigin = 'offline',
|
|
[switch]$SkipRetroWfcPayload,
|
|
# The caller's recomputed cache-reuse identities (see ToolkitFingerprint.ComputeComponents).
|
|
# They are compared against the provenance recorded beside the caches themselves; a missing or
|
|
# mismatched identity degrades that cache to a clean rebuild, never the other way around.
|
|
[string]$TranslationFingerprint = '',
|
|
[string]$NativeToolchainFingerprint = '',
|
|
# Discards every cache first. Used when a product was reported broken/blocked, so a possibly
|
|
# corrupted translation or build directory can never contribute to the repaired product.
|
|
[switch]$ForceCleanBuild,
|
|
[int]$Parallel = 0
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version 3.0
|
|
|
|
# Canonical configure flags (plus Assert-File / Invoke-Checked / Get-MkwFileSha256),
|
|
# shared with launcher/Prepare-NativePrebuilt.ps1 so the precompiled aurora/third-party
|
|
# archives and the objects compiled here can never be produced with different settings.
|
|
# See NativeBuildFlags.ps1; Build-Installer.ps1 stages it beside this script.
|
|
. (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1')
|
|
|
|
function Resolve-NativePrebuiltDirectory([string]$DependenciesDirectory, [string]$ToolchainBin, [string]$AuroraDirectory, [string]$ThirdPartyDirectory) {
|
|
<#
|
|
The shipped precompiled aurora/third-party package, verified against its recorded provenance
|
|
(exact compiler, exact flags) before use. Anything unexpected falls back to building from source.
|
|
#>
|
|
$package = Join-Path $DependenciesDirectory 'native_prebuilt'
|
|
$provenancePath = Join-Path $package 'provenance.json'
|
|
if (-not (Test-Path -LiteralPath $provenancePath -PathType Leaf)) {
|
|
Write-Host 'MKWCBUILD: No precompiled aurora package was shipped; building it from source'
|
|
return ''
|
|
}
|
|
try {
|
|
$provenance = Get-Content -LiteralPath $provenancePath -Raw | ConvertFrom-Json
|
|
$compiler = Join-Path $ToolchainBin 'clang-22.exe'
|
|
$compilerHash = Get-MkwFileSha256 $compiler
|
|
if ($provenance.CompilerSha256 -ne $compilerHash) {
|
|
Write-Host 'MKWCBUILD: The precompiled aurora package was built by a different compiler; building it from source'
|
|
return ''
|
|
}
|
|
if ($provenance.FlagFingerprint -ne (Get-MkwNativeFlagFingerprint)) {
|
|
Write-Host 'MKWCBUILD: The precompiled aurora package was built with different flags; building it from source'
|
|
return ''
|
|
}
|
|
# The archives embed Dawn's headers and link its import library, so a
|
|
# dawn_prebuilt tree that is not the one they were harvested against is
|
|
# an ABI mismatch that only shows up as a crash inside the GPU backend.
|
|
$recordedDawn = $provenance.PSObject.Properties['DawnRuntimeSha256']
|
|
if ($null -eq $recordedDawn -or
|
|
$recordedDawn.Value -ne (Get-MkwDawnRuntimeSha256 $DependenciesDirectory)) {
|
|
Write-Host 'MKWCBUILD: The precompiled aurora package was built against a different Dawn; building it from source'
|
|
return ''
|
|
}
|
|
# The archives are aurora; the runtime compiled here calls its headers.
|
|
# If this workspace's aurora sources are not the ones that were harvested
|
|
# (a newer toolkit, or a locally modified tree), linking the package would
|
|
# mix two versions of aurora, so build it from source instead.
|
|
$recordedAurora = $provenance.PSObject.Properties['AuroraSourceFingerprint']
|
|
if ($null -eq $recordedAurora -or
|
|
$recordedAurora.Value -ne (Get-MkwAuroraSourceFingerprint $AuroraDirectory)) {
|
|
Write-Host 'MKWCBUILD: The precompiled aurora package was built from different aurora sources; building it from source'
|
|
return ''
|
|
}
|
|
# Same reasoning for the vendored Crypto++ the package also carries:
|
|
# first-party TUs compile against this workspace's third_party headers,
|
|
# so a package harvested from an older tree must not be linked here.
|
|
$recordedThirdParty = $provenance.PSObject.Properties['ThirdPartySourceFingerprint']
|
|
if ($null -eq $recordedThirdParty -or
|
|
$recordedThirdParty.Value -ne (Get-MkwThirdPartySourceFingerprint $ThirdPartyDirectory)) {
|
|
Write-Host 'MKWCBUILD: The precompiled aurora package was built from different vendored third-party sources; building it from source'
|
|
return ''
|
|
}
|
|
} catch {
|
|
Write-Host "MKWCBUILD: The precompiled aurora package could not be validated ($($_.Exception.Message)); building it from source"
|
|
return ''
|
|
}
|
|
Write-Host 'MKWCBUILD: Using the precompiled aurora and third-party libraries'
|
|
return $package
|
|
}
|
|
|
|
function Write-MkwBuildStep([string]$StepId, [string]$Message) {
|
|
<#
|
|
Announces a build step. The identifier is the contract (BuildStepIds in
|
|
WiiCompiled.Setup/InstallProgress.cs); the trailing sentence is only for the log.
|
|
#>
|
|
Write-Host "MKWCBUILD:STEP:${StepId} $Message"
|
|
}
|
|
|
|
function Reset-LocalDirectory([string]$Path) {
|
|
$full = [IO.Path]::GetFullPath($Path)
|
|
$root = [IO.Path]::GetFullPath($Workspace).TrimEnd('\') + '\'
|
|
$installRoot = [IO.Path]::GetFullPath((Split-Path -Parent $Workspace)).TrimEnd('\') + '\'
|
|
# The caller-supplied output destinations are legitimate reset targets by
|
|
# definition, wherever the caller placed them: a fresh install's operation
|
|
# scratch lives beside the installation directory rather than inside it.
|
|
$allowedDestinations = @($OutputDirectory, $BaseOutputDirectory) |
|
|
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
|
ForEach-Object { [IO.Path]::GetFullPath($_).TrimEnd('\') }
|
|
$isAllowedDestination = $allowedDestinations |
|
|
Where-Object { $full.TrimEnd('\').Equals($_, [StringComparison]::OrdinalIgnoreCase) }
|
|
if (-not $isAllowedDestination -and
|
|
-not $full.StartsWith($root, [StringComparison]::OrdinalIgnoreCase) -and
|
|
-not $full.StartsWith($installRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Refusing to reset a directory outside the local build workspace: $full"
|
|
}
|
|
if (Test-Path -LiteralPath $full) { Remove-Item -LiteralPath $full -Recurse -Force }
|
|
[IO.Directory]::CreateDirectory($full) | Out-Null
|
|
}
|
|
|
|
$Workspace = [IO.Path]::GetFullPath($Workspace)
|
|
$Toolkit = [IO.Path]::GetFullPath($Toolkit)
|
|
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
|
|
if (-not [string]::IsNullOrWhiteSpace($BaseOutputDirectory)) {
|
|
$BaseOutputDirectory = [IO.Path]::GetFullPath($BaseOutputDirectory)
|
|
}
|
|
if (-not [string]::IsNullOrWhiteSpace($RetroRewindPackageDirectory)) {
|
|
$RetroRewindPackageDirectory = [IO.Path]::GetFullPath($RetroRewindPackageDirectory)
|
|
}
|
|
if (-not [string]::IsNullOrWhiteSpace($RetroWfcOfflineDirectory)) {
|
|
$RetroWfcOfflineDirectory = [IO.Path]::GetFullPath($RetroWfcOfflineDirectory)
|
|
}
|
|
$hasOfflineRetroWfc = -not [string]::IsNullOrWhiteSpace($RetroWfcOfflineDirectory)
|
|
$buildsRetro = $Profile -in @('retro-rewind', 'both')
|
|
if (-not $buildsRetro -and ($hasOfflineRetroWfc -or $SkipRetroWfcPayload)) {
|
|
throw 'Retro-WFC payload options are valid only for a Retro Rewind build.'
|
|
}
|
|
if ($buildsRetro -and ($hasOfflineRetroWfc -eq [bool]$SkipRetroWfcPayload)) {
|
|
throw 'Choose exactly one Retro-WFC mode: -RetroWfcOfflineDirectory or -SkipRetroWfcPayload.'
|
|
}
|
|
if (-not $buildsRetro -and -not [string]::IsNullOrWhiteSpace($RetroRewindPackageDirectory)) {
|
|
throw '-RetroRewindPackageDirectory is valid only for a Retro Rewind build.'
|
|
}
|
|
if ($Profile -eq 'both' -and [string]::IsNullOrWhiteSpace($BaseOutputDirectory)) {
|
|
throw "-BaseOutputDirectory is required with -Profile both; -OutputDirectory receives the Retro Rewind product."
|
|
}
|
|
if ($Profile -ne 'both' -and -not [string]::IsNullOrWhiteSpace($BaseOutputDirectory)) {
|
|
throw '-BaseOutputDirectory is valid only with -Profile both.'
|
|
}
|
|
$translator = Join-Path $Toolkit 'Translator\Translator.Cli.exe'
|
|
$toolchain = Get-MkwShellSafeToolchainRoot $Toolkit
|
|
$cmake = Join-Path $toolchain 'CMake\bin\cmake.exe'
|
|
$ninja = Join-Path $toolchain 'Ninja\ninja.exe'
|
|
$toolchainBin = Join-Path $toolchain 'llvm-mingw\bin'
|
|
$cc = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang.exe'
|
|
$cxx = Join-Path $toolchainBin 'x86_64-w64-mingw32-clang++.exe'
|
|
$windres = Join-Path $toolchainBin 'x86_64-w64-mingw32-windres.exe'
|
|
$project = Join-Path $Workspace 'projects\mkwii\recomp.yml'
|
|
$assets = Join-Path $Workspace 'Assets'
|
|
$generated = Join-Path $Workspace 'generated'
|
|
$functions = Join-Path $generated 'functions'
|
|
$baseMetadata = Join-Path $generated 'base_translation_output.json'
|
|
$baseManifestDir = Join-Path $Workspace 'build\base'
|
|
$baseManifest = Join-Path $baseManifestDir 'mkwii_base_manifest.json'
|
|
$shards = Join-Path $generated 'build_shards'
|
|
$build = Join-Path $Workspace 'native-build'
|
|
$retroRoot = if ([string]::IsNullOrWhiteSpace($RetroRewindPackageDirectory)) {
|
|
Join-Path $Workspace 'PulsarPacks\completed\RetroRewind\RetroRewind6'
|
|
} else {
|
|
$RetroRewindPackageDirectory
|
|
}
|
|
|
|
foreach ($required in @(
|
|
@($translator, 'Bundled translator'), @($cmake, 'Bundled CMake'), @($ninja, 'Bundled Ninja'),
|
|
@($cc, 'Bundled C compiler'), @($cxx, 'Bundled C++ compiler'), @($windres, 'Bundled resource compiler'), @($project, 'Translation project'),
|
|
@((Join-Path $assets 'main.dol'), 'Extracted main.dol'), @((Join-Path $assets 'StaticR.rel'), 'Extracted StaticR.rel')
|
|
)) { Assert-File $required[0] $required[1] }
|
|
|
|
# Everything Mario-Kart-specific this script needs is read from the project file rather than
|
|
# restated here: the translation entry point below, and (through the retro-rewind profile the
|
|
# translator loads itself) the module guest/link bases the mod translation uses.
|
|
$pins = Get-MkwProjectPins $project
|
|
|
|
# Three independent knobs: $translatorThreads (the translator's own worker threads; peak memory is
|
|
# one heap that barely moves with thread count, so clamping it only slows translation), $translatedJobs
|
|
# (the real RAM guard, capping concurrent clang compiles of memory-hungry translated TUs via the Ninja
|
|
# MKW_TRANSLATED_COMPILE_JOBS pool), and $globalJobs (Ninja's overall parallelism for everything else).
|
|
# An explicit -Parallel pins all three.
|
|
$memoryGiB = [math]::Max(1, [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB))
|
|
if ($Parallel -gt 0) {
|
|
$translatorThreads = $Parallel
|
|
$translatedJobs = $Parallel
|
|
$globalJobs = $Parallel
|
|
} else {
|
|
$translatorThreads = [math]::Max(1, [math]::Min([Environment]::ProcessorCount, 16))
|
|
$translatedJobs = [math]::Max(1, [math]::Min([Environment]::ProcessorCount,
|
|
[math]::Floor($memoryGiB / 2)))
|
|
$globalJobs = [math]::Max($translatedJobs, [Environment]::ProcessorCount)
|
|
}
|
|
|
|
$oldPath = $env:PATH
|
|
$oldDotnet = $env:DOTNET_ROOT
|
|
try {
|
|
$env:PATH = Get-MkwToolchainPath $toolchain
|
|
Remove-Item Env:DOTNET_ROOT -ErrorAction SilentlyContinue
|
|
Push-Location $Workspace
|
|
try {
|
|
# This script is the single owner of every cache-reuse decision: it is what would misbuild.
|
|
# The caller only supplies the identities to compare recorded provenance against.
|
|
$translationProvenance = Join-Path $generated 'translation-provenance.json'
|
|
$toolchainProvenance = Join-Path $build 'toolchain-provenance.json'
|
|
|
|
function Get-RecordedFingerprint([string]$Path, [string]$Property) {
|
|
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return '' }
|
|
try {
|
|
$record = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
|
$value = $record.PSObject.Properties[$Property]
|
|
if ($null -eq $value -or [string]::IsNullOrWhiteSpace($value.Value)) { return '' }
|
|
return [string]$value.Value
|
|
} catch { return '' }
|
|
}
|
|
|
|
if ($ForceCleanBuild) {
|
|
Write-Host 'MKWCBUILD: A clean build was requested; discarding every translation and build cache'
|
|
Reset-LocalDirectory $generated
|
|
if (Test-Path -LiteralPath $baseManifestDir) { Remove-Item -LiteralPath $baseManifestDir -Recurse -Force }
|
|
if (Test-Path -LiteralPath $build) { Remove-Item -LiteralPath $build -Recurse -Force }
|
|
}
|
|
|
|
# Reused only when its exact inputs match, its artifacts survived, and (for a modded build) it
|
|
# already knows this Code.pul. Otherwise it retranslates incrementally: content-addressed
|
|
# outputs plus --prune-stale mean Ninja only recompiles shards whose bytes actually moved.
|
|
$reuseBase = $false
|
|
if (-not $ForceCleanBuild -and -not [string]::IsNullOrWhiteSpace($TranslationFingerprint) -and
|
|
(Get-RecordedFingerprint $translationProvenance 'TranslationFingerprint') -eq $TranslationFingerprint) {
|
|
$artifacts = @($baseMetadata, $baseManifest, (Join-Path $generated 'base_translation_sources.bin'),
|
|
(Join-Path $generated 'base_translation_mod_awareness.json'))
|
|
$reuseBase = -not ($artifacts | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) })
|
|
}
|
|
if ($buildsRetro) {
|
|
# The translator discovers mods through the project file's workspace-relative profile
|
|
# paths, and both the base and the mod leg block leaf inlining at every address those
|
|
# profiles patch. A staged install workspace ships without any mod payload, so the
|
|
# selected Retro Rewind Code.pul must sit at the profile's mod_root before either leg
|
|
# runs - and it must be this build's pul, not the one a previous build left behind.
|
|
$awarenessBinaries = Join-Path $Workspace 'PulsarPacks\completed\RetroRewind\RetroRewind6\Binaries'
|
|
$sourcePul = Join-Path $retroRoot 'Binaries\Code.pul'
|
|
Assert-File $sourcePul 'Retro Rewind Code.pul'
|
|
[IO.Directory]::CreateDirectory($awarenessBinaries) | Out-Null
|
|
$stagedPul = Join-Path $awarenessBinaries 'Code.pul'
|
|
if (-not $stagedPul.Equals($sourcePul, [StringComparison]::OrdinalIgnoreCase)) {
|
|
Copy-Item -LiteralPath $sourcePul -Destination $stagedPul -Force
|
|
}
|
|
}
|
|
|
|
if ($reuseBase -and $buildsRetro) {
|
|
# The base translation bakes leaf-inlining and residency decisions around the mod patch
|
|
# sets it knew about when it ran, and it records that knowledge as a modPatchAwareness
|
|
# stamp. A base tree that never saw this Code.pul would silently bake vanilla code into
|
|
# the modded product, so it may only be reused once that has been ruled out.
|
|
$retroCodePul = Join-Path $retroRoot 'Binaries\Code.pul'
|
|
Assert-File $retroCodePul 'Retro Rewind Code.pul'
|
|
$pulSha = Get-MkwFileSha256 $retroCodePul
|
|
# The metadata file is tens of megabytes of machine-written JSON; a raw substring probe
|
|
# for the uniquely named stamp field avoids a multi-minute ConvertFrom-Json parse.
|
|
$rawMetadata = [IO.File]::ReadAllText($baseMetadata)
|
|
if (-not $rawMetadata.Contains('"codePulSha256":"' + $pulSha + '"')) {
|
|
# A different Code.pul is not automatically a different base translation: the base
|
|
# reads a pul for one thing, the addresses it patches, so a pul that patches the
|
|
# same translated functions produces the same base tree. Only the translator can
|
|
# decide that - it takes parsing the Kamek patch set against the recorded translated
|
|
# function ranges - and it fails closed, so anything but exit 0 retranslates.
|
|
& $translator @(
|
|
'check-base-mod-awareness', '--project', $project, '--profile', 'retro-rewind',
|
|
'--translation-output-metadata', $baseMetadata, '--code-pul', $retroCodePul
|
|
) | Write-Host
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-MkwBuildStep 'retranslate-base' 'The base translation is stale; retranslating the base game for the new Code.pul'
|
|
$reuseBase = $false
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($reuseBase) {
|
|
Write-MkwBuildStep 'reuse-base-translation' 'Reusing the completed base translation'
|
|
} else {
|
|
# A failed translation must never leave provenance claiming the old outputs are current.
|
|
if (Test-Path -LiteralPath $translationProvenance) { Remove-Item -LiteralPath $translationProvenance -Force }
|
|
[IO.Directory]::CreateDirectory($generated) | Out-Null
|
|
[IO.Directory]::CreateDirectory($baseManifestDir) | Out-Null
|
|
|
|
# No --clean-outdir: shard names are content-addressed and unchanged files keep their
|
|
# bytes and mtimes, which is exactly what lets Ninja skip them. --prune-stale (active
|
|
# only without --clean-outdir) removes everything the new translation no longer emits.
|
|
Invoke-Checked $translator @(
|
|
'translate-recursive', $pins.EntryPoint, '--project', $project,
|
|
'--outdir', $functions, '--output-metadata', $baseMetadata,
|
|
'--production-source-bundle', (Join-Path $generated 'base_translation_sources.bin'),
|
|
'--no-function-files', '--prune-stale', '--threads', $translatorThreads
|
|
) 'Translating the user-owned base game' -StepId 'translate-base'
|
|
|
|
Invoke-Checked $translator @(
|
|
'emit-base-manifest', '--project', $project, '--out', $baseManifestDir,
|
|
'--functions-dir', $functions, '--translation-output-metadata', $baseMetadata, '--region', 'P'
|
|
) 'Creating the local base translation manifest' -StepId 'emit-base-manifest'
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($TranslationFingerprint)) {
|
|
[ordered]@{ SchemaVersion = 1; TranslationFingerprint = $TranslationFingerprint } |
|
|
ConvertTo-Json | Set-Content -LiteralPath $translationProvenance -Encoding UTF8
|
|
}
|
|
}
|
|
|
|
if ($buildsRetro) {
|
|
$codePul = Join-Path $retroRoot 'Binaries\Code.pul'
|
|
Assert-File $codePul 'Retro Rewind Code.pul'
|
|
$retroOut = Join-Path $Workspace 'build\mods\retro_rewind_full_cpp'
|
|
$translateModArguments = @(
|
|
'translate-mod', '--project', $project, '--profile', 'retro-rewind',
|
|
'--base-manifest', $baseManifest, '--base-translation-output-metadata', $baseMetadata,
|
|
'--code-pul', $codePul, '--mod-root', $retroRoot, '--mod-name', 'Retro Rewind',
|
|
'--region', 'P', '--out', $retroOut, '--prefer-cached-inputs', '--emit-cpp',
|
|
'--threads', $translatorThreads
|
|
)
|
|
if ($SkipRetroWfcPayload) {
|
|
$translateModArguments += '--skip-retro-wfc'
|
|
} elseif (-not [string]::IsNullOrWhiteSpace($RetroWfcOfflineDirectory)) {
|
|
$offlineRoot = [IO.Path]::GetFullPath($RetroWfcOfflineDirectory)
|
|
$offlinePayload = Join-Path $offlineRoot 'binary\payload.RMCPD00.bin'
|
|
Assert-File $offlinePayload 'Offline Retro-WFC shared payload'
|
|
$translateModArguments += @('--retro-wfc-payload', $offlinePayload)
|
|
} else {
|
|
throw 'Retro Rewind requires either -RetroWfcOfflineDirectory or -SkipRetroWfcPayload.'
|
|
}
|
|
Invoke-Checked $translator $translateModArguments 'Translating the selected Retro Rewind Code.pul' `
|
|
-StepId 'translate-mod'
|
|
}
|
|
|
|
Invoke-Checked $translator @('generate-data-init', '--project', $project) `
|
|
'Generating local game data initialization' -StepId 'generate-data-init'
|
|
|
|
$shardArgs = @(
|
|
'emit-build-shards', '--project', $project, '--base-metadata', $baseMetadata,
|
|
'--base-functions-dir', $functions,
|
|
'--native-source-dir', (Join-Path $Workspace 'runtime\src'), '--out', $shards
|
|
)
|
|
if ($buildsRetro) {
|
|
$retroOut = Join-Path $Workspace 'build\mods\retro_rewind_full_cpp'
|
|
$shardArgs += @('--resolved-profile', (Join-Path $retroOut 'resolved_dispatch_profile.json'),
|
|
'--retro-cpp-dir', (Join-Path $retroOut 'cpp'))
|
|
}
|
|
Invoke-Checked $translator $shardArgs 'Preparing local native build shards' -StepId 'emit-build-shards'
|
|
|
|
# The build directory is kept whenever the exact toolchain that filled it is the one
|
|
# installed now and its CMake cache still belongs to this workspace path. Object files are
|
|
# only ever unsafe to reuse when the compiler/flags changed - source changes are what
|
|
# CMake/Ninja exist to track, so a kept directory turns a small runtime or dependency
|
|
# change into a recompile of just the affected objects instead of every shard.
|
|
if (Test-Path -LiteralPath $build) {
|
|
$keepNativeBuild = -not [string]::IsNullOrWhiteSpace($NativeToolchainFingerprint) -and
|
|
(Get-RecordedFingerprint $toolchainProvenance 'NativeToolchainFingerprint') -eq $NativeToolchainFingerprint -and
|
|
(Test-Path -LiteralPath (Join-Path $build 'CMakeCache.txt') -PathType Leaf)
|
|
if ($keepNativeBuild) {
|
|
# A moved workspace invalidates every absolute path in the cache; CMake would hard
|
|
# error rather than reconfigure. Move healing already deletes the directory for
|
|
# portable installs, so this only fires for hand-moved trees.
|
|
$expectedHome = (Join-Path $Workspace 'runtime')
|
|
$cacheHome = Select-String -LiteralPath (Join-Path $build 'CMakeCache.txt') `
|
|
-Pattern '^CMAKE_HOME_DIRECTORY:INTERNAL=(.*)$' | Select-Object -First 1
|
|
if ($null -eq $cacheHome -or
|
|
-not [string]::Equals([IO.Path]::GetFullPath($cacheHome.Matches[0].Groups[1].Value),
|
|
[IO.Path]::GetFullPath($expectedHome), [StringComparison]::OrdinalIgnoreCase)) {
|
|
$keepNativeBuild = $false
|
|
}
|
|
}
|
|
if ($keepNativeBuild) {
|
|
Write-Host 'MKWCBUILD: Reusing the incremental native build directory'
|
|
} else {
|
|
Write-Host 'MKWCBUILD: The native build cache does not belong to this toolchain; rebuilding from scratch'
|
|
Remove-Item -LiteralPath $build -Recurse -Force
|
|
}
|
|
}
|
|
$dependencies = Join-Path $Workspace 'Dependencies'
|
|
$nativePrebuilt = Resolve-NativePrebuiltDirectory $dependencies $toolchainBin `
|
|
(Join-Path $Workspace 'aurora-main') (Join-Path $Workspace 'runtime\third_party')
|
|
$configure = Get-MkwNativeConfigureArguments `
|
|
-SourceDirectory (Join-Path $Workspace 'runtime') -BuildDirectory $build `
|
|
-Ninja $ninja -CCompiler $cc -CxxCompiler $cxx -ResourceCompiler $windres `
|
|
-DependenciesDirectory $dependencies -NativePrebuiltDirectory $nativePrebuilt `
|
|
-AdditionalArguments @("-DMKW_TRANSLATED_COMPILE_JOBS=$translatedJobs")
|
|
Invoke-Checked $cmake $configure 'Configuring the bundled native toolchain' -StepId 'configure-native'
|
|
if (-not [string]::IsNullOrWhiteSpace($NativeToolchainFingerprint)) {
|
|
[ordered]@{ SchemaVersion = 1; NativeToolchainFingerprint = $NativeToolchainFingerprint } |
|
|
ConvertTo-Json | Set-Content -LiteralPath $toolchainProvenance -Encoding UTF8
|
|
}
|
|
$targets = switch ($Profile) {
|
|
'base' { @('WiiCompiled') }
|
|
'retro-rewind' { @('RetroRewind') }
|
|
'both' { @('WiiCompiled', 'RetroRewind') }
|
|
}
|
|
$buildArguments = @('--build', $build)
|
|
foreach ($target in $targets) { $buildArguments += @('--target', $target) }
|
|
$buildArguments += @('--parallel', $globalJobs)
|
|
Invoke-Checked $cmake $buildArguments "Compiling $($targets -join ' and ') locally" -StepId 'compile'
|
|
|
|
# Hashed once here; every published product records the same digests.
|
|
$dolSha = Get-MkwFileSha256 (Join-Path $assets 'main.dol')
|
|
$relSha = Get-MkwFileSha256 (Join-Path $assets 'StaticR.rel')
|
|
$compilerSha = Get-MkwFileSha256 (Join-Path $toolchainBin 'clang-22.exe')
|
|
|
|
function Publish-BuiltProduct([string]$Target, [string]$Destination, [string]$ProvenanceProfile) {
|
|
Reset-LocalDirectory $Destination
|
|
$exe = Join-Path $build "$Target.exe"
|
|
Assert-File $exe 'Locally compiled game executable'
|
|
Copy-Item -LiteralPath $exe -Destination (Join-Path $Destination "$Target.exe")
|
|
foreach ($name in @('dxcompiler.dll','dxil.dll','libc++.dll','libpng16.dll','libunwind.dll','libz.dll','SDL3.dll','sqlite3.dll','webgpu_dawn.dll','z.dll','noshaders.zip','dsp_coef.bin','initial_pipeline_cache.db')) {
|
|
$file = Join-Path $build $name
|
|
if (Test-Path -LiteralPath $file -PathType Leaf) { Copy-Item -LiteralPath $file -Destination $Destination }
|
|
}
|
|
$redist = Join-Path $Toolkit 'Redist'
|
|
if (Test-Path -LiteralPath $redist -PathType Container) {
|
|
Copy-Item -Path (Join-Path $redist '*.dll') -Destination $Destination -Force
|
|
}
|
|
foreach ($name in @('config','wii_bootstrap')) {
|
|
$directory = Join-Path $build $name
|
|
if (Test-Path -LiteralPath $directory -PathType Container) {
|
|
Copy-Item -LiteralPath $directory -Destination (Join-Path $Destination $name) -Recurse
|
|
}
|
|
}
|
|
$isRetro = $ProvenanceProfile -eq 'retro-rewind'
|
|
$provenance = [ordered]@{
|
|
SchemaVersion = 1
|
|
Profile = $ProvenanceProfile
|
|
BuiltUtc = [DateTime]::UtcNow.ToString('O')
|
|
DolSha256 = $dolSha
|
|
RelSha256 = $relSha
|
|
CodePulSha256 = if ($isRetro) { Get-MkwFileSha256 (Join-Path $retroRoot 'Binaries\Code.pul') } else { $null }
|
|
RetroWfcPayloadMode = if (-not $isRetro) { $null } elseif ($SkipRetroWfcPayload) { 'skipped' } else { $RetroWfcPayloadOrigin }
|
|
RetroWfcPayloadSha256 = if ($isRetro -and -not $SkipRetroWfcPayload) { Get-MkwFileSha256 (Join-Path $RetroWfcOfflineDirectory 'binary\payload.RMCPD00.bin') } else { $null }
|
|
RetroWfcPayloadLength = if ($isRetro -and -not $SkipRetroWfcPayload) { (Get-Item -LiteralPath (Join-Path $RetroWfcOfflineDirectory 'binary\payload.RMCPD00.bin')).Length } else { $null }
|
|
Compiler = "llvm-mingw clang-22 sha256:$compilerSha"
|
|
}
|
|
$provenance | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $Destination 'local-build.json') -Encoding UTF8
|
|
}
|
|
|
|
if ($Profile -eq 'both') {
|
|
Publish-BuiltProduct 'WiiCompiled' $BaseOutputDirectory 'base'
|
|
Publish-BuiltProduct 'RetroRewind' $OutputDirectory 'retro-rewind'
|
|
} elseif ($Profile -eq 'retro-rewind') {
|
|
Publish-BuiltProduct 'RetroRewind' $OutputDirectory 'retro-rewind'
|
|
} else {
|
|
Publish-BuiltProduct 'WiiCompiled' $OutputDirectory 'base'
|
|
}
|
|
Write-Host "MKWCBUILD:OUTPUT=$OutputDirectory"
|
|
} finally { Pop-Location }
|
|
} finally {
|
|
$env:PATH = $oldPath
|
|
if ($null -ne $oldDotnet) { $env:DOTNET_ROOT = $oldDotnet }
|
|
}
|