diff --git a/Launcher/Build-Installer.ps1 b/Launcher/Build-Installer.ps1 index 8da01bd..d90f677 100644 --- a/Launcher/Build-Installer.ps1 +++ b/Launcher/Build-Installer.ps1 @@ -253,7 +253,7 @@ foreach ($required in @('ToolkitFingerprint','TranslationFingerprint','NativeToo $manifest = [ordered]@{ SchemaVersion = 2 - ProductVersion = '0.2.15' + ProductVersion = '0.2.17' ExpectedGameId = $pins.GameId ExpectedDolSha256 = $pins.DolSha256 ExpectedRelSha256 = $pins.RelSha256 diff --git a/Launcher/WiiCompiled.Setup/ProductRepairService.cs b/Launcher/WiiCompiled.Setup/ProductRepairService.cs index 3d99ec8..f1a75ca 100644 --- a/Launcher/WiiCompiled.Setup/ProductRepairService.cs +++ b/Launcher/WiiCompiled.Setup/ProductRepairService.cs @@ -233,12 +233,13 @@ internal sealed class ProductRepairService retroOutput = Path.Combine(scratchRoot, "retro-output"); _reporter.Progress(InstallStages.BuildBase, "Recompiling Mario Kart Wii and Retro Rewind with the installed toolkit...", 5); - await builder.BuildAsync(_installation.Root, BuildProfile.Both, retroOutput, - requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken, - toolkitComponents, forceCleanBuild, - progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92), - retroRewindPackageDirectory: compileInputs.RetroRewindRoot, - baseOutputDirectory: baseOutput); + await BuildWithCleanRetryAsync(forceCleanBuild, clean => + builder.BuildAsync(_installation.Root, BuildProfile.Both, retroOutput, + requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken, + toolkitComponents, clean, + progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92), + retroRewindPackageDirectory: compileInputs.RetroRewindRoot, + baseOutputDirectory: baseOutput)); LocalBuildService.WriteFingerprint(baseOutput, BuildProfile.Base, toolkitFingerprint, dolSha, relSha, "", RetroWfcPayloadMode.NotApplicable); LocalBuildService.WriteFingerprint(retroOutput, BuildProfile.RetroRewind, @@ -251,10 +252,11 @@ internal sealed class ProductRepairService baseOutput = Path.Combine(scratchRoot, "base-output"); _reporter.Progress(InstallStages.BuildBase, "Recompiling Mario Kart Wii with the installed toolkit...", 5); - await builder.BuildAsync(_installation.Root, BuildProfile.Base, baseOutput, - RetroWfcPayloadMode.NotApplicable, null, cancellationToken, - toolkitComponents, forceCleanBuild, - progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92)); + await BuildWithCleanRetryAsync(forceCleanBuild, clean => + builder.BuildAsync(_installation.Root, BuildProfile.Base, baseOutput, + RetroWfcPayloadMode.NotApplicable, null, cancellationToken, + toolkitComponents, clean, + progress: new BuildProgressWindow(_reporter, InstallStages.BuildBase, 5, 92))); LocalBuildService.WriteFingerprint(baseOutput, BuildProfile.Base, toolkitFingerprint, dolSha, relSha, "", RetroWfcPayloadMode.NotApplicable); } @@ -265,11 +267,12 @@ internal sealed class ProductRepairService retroOutput = Path.Combine(scratchRoot, "retro-output"); _reporter.Progress(InstallStages.BuildRetro, "Recompiling Retro Rewind for the canonical Code.pul...", 5); - await builder.BuildAsync(_installation.Root, BuildProfile.RetroRewind, retroOutput, - requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken, - toolkitComponents, forceCleanBuild, - progress: new BuildProgressWindow(_reporter, InstallStages.BuildRetro, 5, 92), - retroRewindPackageDirectory: compileInputs.RetroRewindRoot); + await BuildWithCleanRetryAsync(forceCleanBuild, clean => + builder.BuildAsync(_installation.Root, BuildProfile.RetroRewind, retroOutput, + requestedPayloadMode, payloadSnapshot?.Directory, cancellationToken, + toolkitComponents, clean, + progress: new BuildProgressWindow(_reporter, InstallStages.BuildRetro, 5, 92), + retroRewindPackageDirectory: compileInputs.RetroRewindRoot)); LocalBuildService.WriteFingerprint(retroOutput, BuildProfile.RetroRewind, toolkitFingerprint, dolSha, relSha, compileInputs.CodePulSha256, requestedPayloadMode, compileInputs.CompileInputsSha256, @@ -465,6 +468,20 @@ internal sealed class ProductRepairService transaction.Commit(); } + private async Task BuildWithCleanRetryAsync(bool forceCleanBuild, Func build) + { + try + { + await build(forceCleanBuild); + } + catch (InvalidOperationException ex) when (!forceCleanBuild) + { + _reporter.Diagnostic( + $"Incremental recompilation failed ({ex.Message}); retrying with a clean build."); + await build(true); + } + } + private void EnsureSufficientRepairDiskSpace() { const long localBuildAllowance = 14L * 1024 * 1024 * 1024; diff --git a/Launcher/WiiCompiled.Setup/Program.cs b/Launcher/WiiCompiled.Setup/Program.cs index 52eac2e..36d934f 100644 --- a/Launcher/WiiCompiled.Setup/Program.cs +++ b/Launcher/WiiCompiled.Setup/Program.cs @@ -121,7 +121,7 @@ internal static class PlatformChecks internal static class ProductInfo { public const string Name = "WiiCompiled"; - public const string Version = "0.2.15"; + public const string Version = "0.2.17"; /// /// The setup executable is copied into the installation under this name. It is the launcher and diff --git a/Launcher/WiiCompiled.Setup/SelfTests.cs b/Launcher/WiiCompiled.Setup/SelfTests.cs index 00bb8af..3a807f3 100644 --- a/Launcher/WiiCompiled.Setup/SelfTests.cs +++ b/Launcher/WiiCompiled.Setup/SelfTests.cs @@ -109,6 +109,9 @@ internal static class SelfTests Directory.CreateDirectory(Path.Combine(staged, "runtime", "src")); File.WriteAllText(Path.Combine(installed, "runtime", "src", "same.cpp"), "int same();"); File.WriteAllText(Path.Combine(staged, "runtime", "src", "same.cpp"), "int same();"); + var installedSameStamp = new DateTime(2026, 1, 2, 3, 4, 5, DateTimeKind.Utc); + File.SetLastWriteTimeUtc(Path.Combine(installed, "runtime", "src", "same.cpp"), + installedSameStamp); File.WriteAllText(Path.Combine(installed, "runtime", "src", "edited.cpp"), "int old();"); File.WriteAllText(Path.Combine(staged, "runtime", "src", "edited.cpp"), "int new_();"); File.WriteAllText(Path.Combine(staged, "runtime", "src", "added.cpp"), "int added();"); @@ -117,8 +120,10 @@ internal static class SelfTests WorkspaceTimestamps.MarkChangedFiles(installed, staged, _ => { }); - if (File.GetLastWriteTimeUtc(Path.Combine(staged, "runtime", "src", "same.cpp")).Year != 2000) - throw new Exception("An unchanged workspace file lost its normalized timestamp."); + if (File.GetLastWriteTimeUtc(Path.Combine(staged, "runtime", "src", "same.cpp")) != + installedSameStamp) + throw new Exception("An unchanged workspace file did not inherit the installed " + + "timestamp; clang would reject the previous build's PCHs."); if (File.GetLastWriteTimeUtc(Path.Combine(staged, "runtime", "src", "edited.cpp")).Year == 2000) throw new Exception("A changed workspace file kept its normalized timestamp; " + "Ninja would never recompile it."); diff --git a/Launcher/WiiCompiled.Setup/WiiCompiled.Setup.csproj b/Launcher/WiiCompiled.Setup/WiiCompiled.Setup.csproj index 94ce524..e114462 100644 --- a/Launcher/WiiCompiled.Setup/WiiCompiled.Setup.csproj +++ b/Launcher/WiiCompiled.Setup/WiiCompiled.Setup.csproj @@ -7,7 +7,7 @@ WiiCompiled.Setup WiiCompiled.Setup app.manifest - 0.2.15 + 0.2.17 patchzy WiiCompiled Command-line installer and launcher for WiiCompiled diff --git a/Launcher/WiiCompiled.Setup/WorkspaceTimestamps.cs b/Launcher/WiiCompiled.Setup/WorkspaceTimestamps.cs index b3a0941..ae99f12 100644 --- a/Launcher/WiiCompiled.Setup/WorkspaceTimestamps.cs +++ b/Launcher/WiiCompiled.Setup/WorkspaceTimestamps.cs @@ -18,8 +18,12 @@ internal static class WorkspaceTimestamps { cancellationToken.ThrowIfCancellationRequested(); var relative = Path.GetRelativePath(stagedWorkspace, stagedFile); - if (FileUnchanged(Path.Combine(installedWorkspace, relative), stagedFile)) + var installedFile = Path.Combine(installedWorkspace, relative); + if (FileUnchanged(installedFile, stagedFile)) { + // Clang validates PCH dependency mtimes by equality, so an unchanged file must + // keep the exact timestamp the previous build recorded, not the normalized one. + InheritTimestamp(installedFile, stagedFile); unchanged++; continue; } @@ -30,6 +34,17 @@ internal static class WorkspaceTimestamps $"{unchanged} unchanged file(s) keep the incremental build cache valid."); } + private static void InheritTimestamp(string installedFile, string stagedFile) + { + try + { + File.SetLastWriteTimeUtc(stagedFile, File.GetLastWriteTimeUtc(installedFile)); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + } + } + private static bool FileUnchanged(string installedFile, string stagedFile) { try