From 31ad2531c16a2c65c365cc252305aa54d9b2227c Mon Sep 17 00:00:00 2001 From: fortmarek Date: Tue, 9 Jun 2026 10:11:25 +0200 Subject: [PATCH] fix: parse Xcode 27 activity log sections --- .../activityparser/ActivityParser.swift | 9 ++- .../ActivityParserTests.swift | 59 +++++++++++++++++++ Tests/XCLogParserTests/XCTestManifests.swift | 1 + 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Sources/XCLogParser/activityparser/ActivityParser.swift b/Sources/XCLogParser/activityparser/ActivityParser.swift index 5e1b5f2..79c8492 100644 --- a/Sources/XCLogParser/activityparser/ActivityParser.swift +++ b/Sources/XCLogParser/activityparser/ActivityParser.swift @@ -105,7 +105,14 @@ public class ActivityParser { let timeStartedRecording = try parseAsDouble(token: iterator.next()) let timeStoppedRecording = try parseAsDouble(token: iterator.next()) let subSections = try parseIDEActivityLogSections(iterator: &iterator) - let text = try parseAsString(token: iterator.next()) + var textToken = iterator.next() + // On Xcode 27.0+, an integer that most likely represents the ended status + // appears before text. XCLogParser does not currently model it because + // there is no use case for exposing it. + if case .some(.int) = textToken { + textToken = iterator.next() + } + let text = try parseAsString(token: textToken) let messages = try parseMessages(iterator: &iterator) let wasCancelled = try parseBoolean(token: iterator.next()) let isQuiet = try parseBoolean(token: iterator.next()) diff --git a/Tests/XCLogParserTests/ActivityParserTests.swift b/Tests/XCLogParserTests/ActivityParserTests.swift index 75c805c..9064afc 100644 --- a/Tests/XCLogParserTests/ActivityParserTests.swift +++ b/Tests/XCLogParserTests/ActivityParserTests.swift @@ -214,6 +214,37 @@ class ActivityParserTests: XCTestCase { return startTokens + logMessageTokens + endTokens }() + // Xcode 27.0 format: ended status likely appears before text + lazy var IDEActivityLogSectionTokensXcode270: [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.null, + Token.int(0), + Token.string("note: Using legacy build system"), + Token.list(1), + Token.className("IDEActivityLogMessage"), + Token.classNameRef("IDEActivityLogMessage"), + ] + let logMessageTokens = IDEActivityLogMessageTokens + let endTokens = [Token.int(1), + Token.int(0), + Token.int(1), + Token.int(42), + Token.string("subtitle"), + Token.null, + Token.string("commandDetailDesc"), + Token.string("501796C4-6BE4-4F80-9F9D-3269617ECC17"), + Token.string("localizedResultString"), + Token.string("xcbuildSignature"), + Token.list(0), // attachments + ] + return startTokens + logMessageTokens + endTokens + }() + let IDEConsoleItemTokens: [Token] = [ Token.className("IDEConsoleItem"), Token.classNameRef("IDEConsoleItem"), @@ -519,6 +550,34 @@ class ActivityParserTests: XCTestCase { XCTAssertEqual(42, logSection.unknown) } + func testParseIDEActivityLogSectionXcode270() throws { + parser.logVersion = 13 + let tokens = IDEActivityLogSectionTokensXcode270 + var iterator = tokens.makeIterator() + let logSection = try parser.parseIDEActivityLogSection(iterator: &iterator) + XCTAssertEqual(2, logSection.sectionType) + XCTAssertEqual("com.apple.dt.IDE.BuildLogSection", logSection.domainType) + XCTAssertEqual("Prepare build", logSection.title) + XCTAssertEqual("Prepare build", logSection.signature) + XCTAssertEqual(575479851.278759, logSection.timeStartedRecording) + XCTAssertEqual(575479851.778325, logSection.timeStoppedRecording) + XCTAssertEqual(0, logSection.subSections.count) + XCTAssertEqual("note: Using legacy build system", logSection.text) + XCTAssertEqual(1, logSection.messages.count) + XCTAssertTrue(logSection.wasCancelled) + XCTAssertFalse(logSection.isQuiet) + XCTAssertTrue(logSection.wasFetchedFromCache) + XCTAssertEqual("subtitle", logSection.subtitle) + XCTAssertEqual("", logSection.location.documentURLString) + XCTAssertEqual(0, logSection.location.timestamp) + XCTAssertEqual("commandDetailDesc", logSection.commandDetailDesc) + XCTAssertEqual("501796C4-6BE4-4F80-9F9D-3269617ECC17", logSection.uniqueIdentifier) + XCTAssertEqual("localizedResultString", logSection.localizedResultString) + XCTAssertEqual("xcbuildSignature", logSection.xcbuildSignature) + XCTAssertEqual(0, logSection.attachments.count) + XCTAssertEqual(42, logSection.unknown) + } + func testParseActivityLog() throws { let activityLog = try parser.parseIDEActiviyLogFromTokens(IDEActivityLogTokens) XCTAssertEqual(10, activityLog.version) diff --git a/Tests/XCLogParserTests/XCTestManifests.swift b/Tests/XCLogParserTests/XCTestManifests.swift index 9858ff6..54d327a 100644 --- a/Tests/XCLogParserTests/XCTestManifests.swift +++ b/Tests/XCLogParserTests/XCTestManifests.swift @@ -13,6 +13,7 @@ extension ActivityParserTests { ("testParseIDEActivityLogAnalyzerResultMessage", testParseIDEActivityLogAnalyzerResultMessage), ("testParseIDEActivityLogMessage", testParseIDEActivityLogMessage), ("testParseIDEActivityLogSection", testParseIDEActivityLogSection), + ("testParseIDEActivityLogSectionXcode270", testParseIDEActivityLogSectionXcode270), ("testParseXcode3ProjectLocation", testParseXcode3ProjectLocation), ] }