From 0a2ad0d7108c7495e63eb1475ee250cc560241c5 Mon Sep 17 00:00:00 2001 From: Yuhan Chen Date: Mon, 26 Oct 2026 18:43:58 +0900 Subject: [PATCH] fix: decode activity-section flags by log format version Read resultCode before the Boolean flags in SLF 13 and later. Store the SLF 13 integer after attachments in the existing unknown field. Keep the command-line compatibility path for earlier formats. Preserve resultCode when replacing section messages. Update internal callers to pass the new argument explicitly. BREAKING CHANGE: IDEActivityLogSection.init now requires resultCode. Pass nil when no result code is available. Co-authored-by: Codex Signed-off-by: Yuhan Chen --- .../activityparser/ActivityParser.swift | 9 ++- .../activityparser/IDEActivityModel.swift | 5 ++ .../IDEActivityLogSection+Builders.swift | 1 + .../IDEActivityLogSection+Parsing.swift | 1 + .../ActivityParserTests.swift | 65 +++++++++++++++++-- .../ClangCompilerParserTests.swift | 1 + Tests/XCLogParserTests/ParserTests.swift | 4 ++ .../SwiftCompilerParserTests.swift | 1 + Tests/XCLogParserTests/XCTestManifests.swift | 2 + 9 files changed, 83 insertions(+), 6 deletions(-) diff --git a/Sources/XCLogParser/activityparser/ActivityParser.swift b/Sources/XCLogParser/activityparser/ActivityParser.swift index 79c8492..fb23e68 100644 --- a/Sources/XCLogParser/activityparser/ActivityParser.swift +++ b/Sources/XCLogParser/activityparser/ActivityParser.swift @@ -98,6 +98,9 @@ public class ActivityParser { // swiftlint:disable:next function_body_length public func parseIDEActivityLogSection(iterator: inout IndexingIterator<[Token]>) throws -> IDEActivityLogSection { + guard let logVersion else { + throw XCLogParserError.parseError("Log version not parsed before parsing IDEActivityLogSection") + } let sectionType = Int8(try parseAsInt(token: iterator.next())) let domainType = try parseAsString(token: iterator.next()) let title = try parseAsString(token: iterator.next()) @@ -114,6 +117,7 @@ public class ActivityParser { } let text = try parseAsString(token: textToken) let messages = try parseMessages(iterator: &iterator) + let resultCode = logVersion >= 13 ? try parseAsInt(token: iterator.next()) : nil let wasCancelled = try parseBoolean(token: iterator.next()) let isQuiet = try parseBoolean(token: iterator.next()) let wasFetchedFromCache = try parseBoolean(token: iterator.next()) @@ -134,8 +138,8 @@ public class ActivityParser { let localizedResultString = try parseAsString(token: iterator.next()) let xcbuildSignature = try parseAsString(token: iterator.next()) let attachments = try parseIDEActivityLogSectionAttachments(iterator: &iterator) - if unknown == nil { - unknown = isCommandLineLog ? Int(try parseAsInt(token: iterator.next())) : 0 + if logVersion >= 13 || (unknown == nil && isCommandLineLog) { + unknown = Int(try parseAsInt(token: iterator.next())) } return IDEActivityLogSection( @@ -148,6 +152,7 @@ public class ActivityParser { subSections: subSections, text: text, messages: messages, + resultCode: resultCode, wasCancelled: wasCancelled, isQuiet: isQuiet, wasFetchedFromCache: wasFetchedFromCache, diff --git a/Sources/XCLogParser/activityparser/IDEActivityModel.swift b/Sources/XCLogParser/activityparser/IDEActivityModel.swift index a2bd441..eeac77b 100644 --- a/Sources/XCLogParser/activityparser/IDEActivityModel.swift +++ b/Sources/XCLogParser/activityparser/IDEActivityModel.swift @@ -40,6 +40,7 @@ public class IDEActivityLogSection: Encodable { public var subSections: [IDEActivityLogSection] public let text: String public let messages: [IDEActivityLogMessage] + public let resultCode: UInt64? public let wasCancelled: Bool public let isQuiet: Bool public var wasFetchedFromCache: Bool @@ -61,6 +62,7 @@ public class IDEActivityLogSection: Encodable { subSections: [IDEActivityLogSection], text: String, messages: [IDEActivityLogMessage], + resultCode: UInt64?, wasCancelled: Bool, isQuiet: Bool, wasFetchedFromCache: Bool, @@ -81,6 +83,7 @@ public class IDEActivityLogSection: Encodable { self.subSections = subSections self.text = text self.messages = messages + self.resultCode = resultCode self.wasCancelled = wasCancelled self.isQuiet = isQuiet self.wasFetchedFromCache = wasFetchedFromCache @@ -146,6 +149,7 @@ public class IDEActivityLogUnitTestSection: IDEActivityLogSection { subSections: subSections, text: text, messages: messages, + resultCode: nil, wasCancelled: wasCancelled, isQuiet: isQuiet, wasFetchedFromCache: wasFetchedFromCache, @@ -439,6 +443,7 @@ public class DBGConsoleLog: IDEActivityLogSection { subSections: subSections, text: text, messages: messages, + resultCode: nil, wasCancelled: wasCancelled, isQuiet: isQuiet, wasFetchedFromCache: wasFetchedFromCache, diff --git a/Sources/XCLogParser/parser/IDEActivityLogSection+Builders.swift b/Sources/XCLogParser/parser/IDEActivityLogSection+Builders.swift index fd04da3..5e5598e 100644 --- a/Sources/XCLogParser/parser/IDEActivityLogSection+Builders.swift +++ b/Sources/XCLogParser/parser/IDEActivityLogSection+Builders.swift @@ -31,6 +31,7 @@ extension IDEActivityLogSection { subSections: self.subSections, text: self.text, messages: newMessages, + resultCode: self.resultCode, wasCancelled: self.wasCancelled, isQuiet: self.isQuiet, wasFetchedFromCache: self.wasFetchedFromCache, diff --git a/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift b/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift index 189a1eb..77e8d78 100644 --- a/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift +++ b/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift @@ -146,6 +146,7 @@ extension IDEActivityLogSection { subSections: [IDEActivityLogSection](), text: "", messages: [IDEActivityLogMessage](), + resultCode: nil, wasCancelled: section.wasCancelled, isQuiet: section.isQuiet, wasFetchedFromCache: section.wasFetchedFromCache, diff --git a/Tests/XCLogParserTests/ActivityParserTests.swift b/Tests/XCLogParserTests/ActivityParserTests.swift index 6829862..1c7987a 100644 --- a/Tests/XCLogParserTests/ActivityParserTests.swift +++ b/Tests/XCLogParserTests/ActivityParserTests.swift @@ -232,10 +232,10 @@ class ActivityParserTests: XCTestCase { Token.classNameRef("IDEActivityLogMessage"), ] let logMessageTokens = IDEActivityLogMessageTokens - let endTokens = [Token.int(1), + let endTokens = [Token.int(1), // resultCode + Token.int(0), Token.int(0), Token.int(1), - Token.int(42), Token.string("subtitle"), Token.null, Token.string("commandDetailDesc"), @@ -243,10 +243,40 @@ class ActivityParserTests: XCTestCase { Token.string("localizedResultString"), Token.string("xcbuildSignature"), Token.list(0), // attachments + Token.int(7), ] return startTokens + logMessageTokens + endTokens }() + lazy var IDEActivityLogSectionTokensWithSubSectionsXcode270: [Token] = { + let startTokens = [Token.int(2), + Token.string("com.apple.dt.IDE.BuildLogSection"), + Token.string("Prepare build"), + Token.string("Prepare build"), + Token.double(575479851.278759), + Token.double(575479851.778325), + Token.list(2), + ] + let subSectionTokens = [Token.classNameRef("IDEActivityLogSection")] + IDEActivityLogSectionTokensXcode270 + let endTokens = [Token.int(0), + Token.string("note: Using legacy build system"), + Token.null, + Token.int(1), + Token.int(0), + Token.int(0), + Token.int(1), + Token.string("subtitle"), + Token.null, + Token.string("commandDetailDesc"), + Token.string("501796C4-6BE4-4F80-9F9D-3269617ECC17"), + Token.string("localizedResultString"), + Token.string("xcbuildSignature"), + Token.list(0), + Token.int(8), + ] + return startTokens + subSectionTokens + subSectionTokens + endTokens + }() + let IDEConsoleItemTokens: [Token] = [ Token.className("IDEConsoleItem"), Token.classNameRef("IDEConsoleItem"), @@ -460,6 +490,7 @@ class ActivityParserTests: XCTestCase { } XCTAssertEqual(logSection.attachments[2].metrics?.wcDuration, 1) XCTAssertEqual(0, logSection.unknown) + XCTAssertNil(logSection.resultCode) } func testParseIDEActivityLogSection_version10() throws { @@ -488,6 +519,7 @@ class ActivityParserTests: XCTestCase { XCTAssertEqual("xcbuildSignature", logSection.xcbuildSignature) XCTAssertEqual(0, logSection.attachments.count) XCTAssertEqual(0, logSection.unknown) + XCTAssertNil(logSection.resultCode) } func testParseIDEActivityLogSectionXcode262() throws { @@ -566,7 +598,7 @@ class ActivityParserTests: XCTestCase { XCTAssertEqual(0, logSection.subSections.count) XCTAssertEqual("note: Using legacy build system", logSection.text) XCTAssertEqual(1, logSection.messages.count) - XCTAssertTrue(logSection.wasCancelled) + XCTAssertFalse(logSection.wasCancelled) XCTAssertFalse(logSection.isQuiet) XCTAssertTrue(logSection.wasFetchedFromCache) XCTAssertEqual("subtitle", logSection.subtitle) @@ -577,7 +609,32 @@ class ActivityParserTests: XCTestCase { XCTAssertEqual("localizedResultString", logSection.localizedResultString) XCTAssertEqual("xcbuildSignature", logSection.xcbuildSignature) XCTAssertEqual(0, logSection.attachments.count) - XCTAssertEqual(42, logSection.unknown) + XCTAssertEqual(7, logSection.unknown) + XCTAssertEqual(1, logSection.resultCode) + XCTAssertNil(iterator.next()) + } + + func testParseNestedIDEActivityLogSectionsXcode270() throws { + parser.logVersion = 13 + let tokens = IDEActivityLogSectionTokensWithSubSectionsXcode270 + var iterator = tokens.makeIterator() + let logSection = try parser.parseIDEActivityLogSection(iterator: &iterator) + XCTAssertEqual(2, logSection.subSections.count) + XCTAssertEqual([7, 7], logSection.subSections.map(\.unknown)) + XCTAssertEqual(8, logSection.unknown) + XCTAssertNil(iterator.next()) + } + + func testParseNestedIDEActivityLogSectionsXcode270CommandLine() throws { + parser.logVersion = 13 + parser.isCommandLineLog = true + let tokens = IDEActivityLogSectionTokensWithSubSectionsXcode270 + var iterator = tokens.makeIterator() + let logSection = try parser.parseIDEActivityLogSection(iterator: &iterator) + XCTAssertEqual(2, logSection.subSections.count) + XCTAssertEqual([7, 7], logSection.subSections.map(\.unknown)) + XCTAssertEqual(8, logSection.unknown) + XCTAssertNil(iterator.next()) } func testParseActivityLog() throws { diff --git a/Tests/XCLogParserTests/ClangCompilerParserTests.swift b/Tests/XCLogParserTests/ClangCompilerParserTests.swift index 1a87e02..991c80c 100644 --- a/Tests/XCLogParserTests/ClangCompilerParserTests.swift +++ b/Tests/XCLogParserTests/ClangCompilerParserTests.swift @@ -86,6 +86,7 @@ class ClangCompilerParserTests: XCTestCase { subSections: [], text: text, messages: [], + resultCode: nil, wasCancelled: false, isQuiet: false, wasFetchedFromCache: false, diff --git a/Tests/XCLogParserTests/ParserTests.swift b/Tests/XCLogParserTests/ParserTests.swift index 5c01c54..345c908 100644 --- a/Tests/XCLogParserTests/ParserTests.swift +++ b/Tests/XCLogParserTests/ParserTests.swift @@ -54,6 +54,7 @@ class ParserTests: XCTestCase { subSections: [], text: "", messages: [], + resultCode: nil, wasCancelled: false, isQuiet: false, wasFetchedFromCache: false, @@ -251,6 +252,7 @@ note: use 'updatedDoSomething' instead\r doSomething()\r ^~~~~~~~~~~\r subSections: [], text: "", messages: [], + resultCode: nil, wasCancelled: false, isQuiet: false, wasFetchedFromCache: false, @@ -303,6 +305,7 @@ note: use 'updatedDoSomething' instead\r doSomething()\r ^~~~~~~~~~~\r subSections: [], text: text, messages: messages, + resultCode: nil, wasCancelled: false, isQuiet: true, wasFetchedFromCache: true, @@ -573,6 +576,7 @@ CompileSwift normal x86_64 (in target 'Alamofire' from project 'Pods') subSections: [], text: "", messages: [], + resultCode: nil, wasCancelled: false, isQuiet: true, wasFetchedFromCache: true, diff --git a/Tests/XCLogParserTests/SwiftCompilerParserTests.swift b/Tests/XCLogParserTests/SwiftCompilerParserTests.swift index 686a117..956e837 100644 --- a/Tests/XCLogParserTests/SwiftCompilerParserTests.swift +++ b/Tests/XCLogParserTests/SwiftCompilerParserTests.swift @@ -118,6 +118,7 @@ class SwiftCompilerParserTests: XCTestCase { subSections: [], text: text, messages: [], + resultCode: nil, wasCancelled: false, isQuiet: false, wasFetchedFromCache: false, diff --git a/Tests/XCLogParserTests/XCTestManifests.swift b/Tests/XCLogParserTests/XCTestManifests.swift index e18faed..a7daa75 100644 --- a/Tests/XCLogParserTests/XCTestManifests.swift +++ b/Tests/XCLogParserTests/XCTestManifests.swift @@ -15,6 +15,8 @@ extension ActivityParserTests { ("testParseIDEActivityLogMessage", testParseIDEActivityLogMessage), ("testParseIDEActivityLogSection", testParseIDEActivityLogSection), ("testParseIDEActivityLogSectionXcode270", testParseIDEActivityLogSectionXcode270), + ("testParseNestedIDEActivityLogSectionsXcode270", testParseNestedIDEActivityLogSectionsXcode270), + ("testParseNestedIDEActivityLogSectionsXcode270CommandLine", testParseNestedIDEActivityLogSectionsXcode270CommandLine), ("testParseXcode3ProjectLocation", testParseXcode3ProjectLocation), ] }