From 5ac6c6d57f421e0fe145eb6db213458cf7659ade Mon Sep 17 00:00:00 2001 From: Eduardo Mozart de Oliveira <2974895+eduardomozart@users.noreply.github.com> Date: Mon, 9 Nov 2026 02:10:11 -0300 Subject: [PATCH] fix: classify pack_app disk-space error and prevent false keys fallback on keyless sources --- .../NXLibrary/ViewModels/AppViewModel.swift | 2 +- .../Models/PortingFailureReason.swift | 13 ++++++++++-- .../PortingFailureReasonTests.swift | 21 ++++++++++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Sources/NXLibrary/ViewModels/AppViewModel.swift b/Sources/NXLibrary/ViewModels/AppViewModel.swift index 0d67ae6..f3c78f4 100644 --- a/Sources/NXLibrary/ViewModels/AppViewModel.swift +++ b/Sources/NXLibrary/ViewModels/AppViewModel.swift @@ -2745,7 +2745,7 @@ public final class AppViewModel: ObservableObject { } else { let reason = PortingFailureClassifier.classify( log: buildLogTail(result.logURL), - keysPath: keyStore.keyURL.path, + keysPath: SourceSelection.requiresKeys(game.sources) ? keyStore.keyURL.path : "", sourcePaths: game.sources.map(\.url.path) ) let message = reason.what(game: game.name) diff --git a/Sources/NXLibraryCore/Models/PortingFailureReason.swift b/Sources/NXLibraryCore/Models/PortingFailureReason.swift index c61b8d5..f4a82ab 100644 --- a/Sources/NXLibraryCore/Models/PortingFailureReason.swift +++ b/Sources/NXLibraryCore/Models/PortingFailureReason.swift @@ -189,6 +189,7 @@ public enum PortingFailureClassifier { ("games.noTitleID", .noTitleID, "no (NSP with a readable title ID|game package with a readable content meta) in ", "no game package with a readable content meta in "), ("disk.low", .diskFull, "less than [0-9]+ GB free on the volume of", "GB free on the volume of"), + ("disk.app", .diskFull, #"the app needs [0-9.]+ GB on the volume of"#, "the app needs "), ("disk.full", .diskFull, "No space left on device", nil), ("icloud.detritus", .cloudFolder, "resource fork, Finder information, or similar detritus not allowed", nil), ] @@ -247,8 +248,16 @@ public enum PortingFailureClassifier { options: .regularExpression) else { return nil } return String(message[range].dropFirst(prefix.count)) case .diskFull: - guard let r = log.range(of: "less than [0-9]+ GB free", options: .regularExpression) else { return nil } - return log[r].split(separator: " ").dropFirst(2).first.map(String.init) + if let r = log.range(of: "less than [0-9]+ GB free", options: .regularExpression) { + return log[r].split(separator: " ").dropFirst(2).first.map(String.init) + } + if let r = log.range(of: #"the app needs [0-9.]+ GB on the volume of"#, options: .regularExpression) { + let parts = log[r].split(separator: " ") + if parts.count >= 4, let num = Double(parts[3]) { + return String(Int(ceil(num + 1.0))) + } + } + return nil case .noTitleID: guard let r = log.range(of: #"no (NSP with a readable title ID|game package with a readable content meta) in [^"\n]+"#, options: .regularExpression), diff --git a/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift b/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift index c6f7d85..af5e9c3 100644 --- a/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift +++ b/Tests/NXLibraryCoreTests/PortingFailureReasonTests.swift @@ -23,6 +23,7 @@ final class PortingFailureReasonTests: XCTestCase { ("games.several", .severalGames, "[RED] more than one game in the folder, keep one game per folder: 0100000000010000, 0100000000020000"), ("games.noTitleID", .noTitleID, "[RED] no game package with a readable content meta in /Users/x/Games/Foo"), ("disk.low", .diskFull, "[RED] less than 40 GB free on the volume of /Users/x/work"), + ("disk.app", .diskFull, "the app needs 12.4 GB on the volume of /Users/x/Temp (no APFS clones from the build there) and 13.1 GB are free"), ("disk.full", .diskFull, "OSError: [Errno 28] No space left on device"), ("icloud.detritus", .cloudFolder, "x.app: resource fork, Finder information, or similar detritus not allowed"), ] @@ -103,6 +104,24 @@ final class PortingFailureReasonTests: XCTestCase { XCTAssertEqual(classify("[RED] less than 40 GB free on the volume of /x").detail, "40") } + func testPackAppDiskFullDetailIsRoundedGB() { + let log = """ + the app needs 12.4 GB on the volume of /Users/x/Temp (no APFS clones from the build there) and 13.1 GB are free + [RED] pack_app.py exit code 1 + """ + let reason = classify(log, keys: "/unreadable/prod.keys", readable: false) + XCTAssertEqual(reason.kind, .diskFull) + XCTAssertEqual(reason.detail, "14") + XCTAssertEqual(reason.what(game: "Ocarina of Time"), "The porting needs at least 14 GB free on the work volume.") + XCTAssertEqual(reason.action, .openStorage) + } + + func testSourcesWithoutKeysDoNotFallBackToKeysOnUnrecognizedFailure() { + let log = "some unknown crash or error" + let reason = classify(log, keys: "") + XCTAssertEqual(reason.kind, .unknown) + } + func testNoTitleIDDetailIsFolder() { XCTAssertEqual(classify("[RED] no game package with a readable content meta in /Users/x/Foo").detail, "/Users/x/Foo") // Engines before nxnative 5c4dc18f said it this way. @@ -248,7 +267,7 @@ final class PortingFailureEngineTests: XCTestCase { private func gitGrep(_ fragment: String, at commit: String, in repo: URL) -> Bool { let p = Process() p.executableURL = URL(fileURLWithPath: "/usr/bin/git") - p.arguments = ["-C", repo.path, "grep", "-q", "-F", fragment, commit, "--", "scripts", "dm/gfx/tools", "dm/tools"] + p.arguments = ["-C", repo.path, "grep", "-q", "-F", fragment, commit, "--", "scripts", "dm/gfx/tools", "dm/tools", "dm/mac"] p.standardOutput = FileHandle.nullDevice p.standardError = FileHandle.nullDevice do { try p.run() } catch { return false }