diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ad7250..6102052 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,9 +13,9 @@ jobs: args: --strict macOS: - runs-on: macos-13 + runs-on: macos-26 env: - XCODE_VERSION: ${{ '14.1' }} + XCODE_VERSION: ${{ '16.4' }} steps: - name: Select Xcode run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4c3222d..1d5cd94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,9 +12,9 @@ on: jobs: macOS: name: Add macOS binaries to release - runs-on: macos-13 + runs-on: macos-26 env: - XCODE_VERSION: ${{ '14.1' }} + XCODE_VERSION: ${{ '16.4' }} steps: - name: Select Xcode run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app" diff --git a/Sources/XCLogParser/activityparser/ActivityParser.swift b/Sources/XCLogParser/activityparser/ActivityParser.swift index 8aec978..155038e 100644 --- a/Sources/XCLogParser/activityparser/ActivityParser.swift +++ b/Sources/XCLogParser/activityparser/ActivityParser.swift @@ -96,28 +96,63 @@ public class ActivityParser { additionalDescription: try parseAsString(token: iterator.next())) } - public func parseIDEActivityLogSection(iterator: inout IndexingIterator<[Token]>) - throws -> IDEActivityLogSection { - return IDEActivityLogSection(sectionType: Int8(try parseAsInt(token: iterator.next())), - domainType: try parseAsString(token: iterator.next()), - title: try parseAsString(token: iterator.next()), - signature: try parseAsString(token: iterator.next()), - timeStartedRecording: try parseAsDouble(token: iterator.next()), - timeStoppedRecording: try parseAsDouble(token: iterator.next()), - subSections: try parseIDEActivityLogSections(iterator: &iterator), - text: try parseAsString(token: iterator.next()), - messages: try parseMessages(iterator: &iterator), - wasCancelled: try parseBoolean(token: iterator.next()), - isQuiet: try parseBoolean(token: iterator.next()), - wasFetchedFromCache: try parseBoolean(token: iterator.next()), - subtitle: try parseAsString(token: iterator.next()), - location: try parseDocumentLocation(iterator: &iterator), - commandDetailDesc: try parseAsString(token: iterator.next()), - uniqueIdentifier: try parseAsString(token: iterator.next()), - localizedResultString: try parseAsString(token: iterator.next()), - xcbuildSignature: try parseAsString(token: iterator.next()), - attachments: try parseIDEActivityLogSectionAttachments(iterator: &iterator), - unknown: isCommandLineLog ? Int(try parseAsInt(token: iterator.next())) : 0) + // swiftlint:disable:next function_body_length + public func parseIDEActivityLogSection(iterator: inout IndexingIterator<[Token]>) throws -> IDEActivityLogSection { + let sectionType = Int8(try parseAsInt(token: iterator.next())) + let domainType = try parseAsString(token: iterator.next()) + let title = try parseAsString(token: iterator.next()) + let signature = try parseAsString(token: iterator.next()) + 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()) + let messages = try parseMessages(iterator: &iterator) + let wasCancelled = try parseBoolean(token: iterator.next()) + let isQuiet = try parseBoolean(token: iterator.next()) + let wasFetchedFromCache = try parseBoolean(token: iterator.next()) + let nextToken = iterator.next() + var unknown: Int? + let subtitle: String + // On Xcode 26.2+, the unknown integer appears before subtitle + switch nextToken { + case let .some(.int(integer)): + unknown = Int(integer) + subtitle = try String(parseAsString(token: iterator.next())) + default: + subtitle = String(try parseAsString(token: nextToken)) + } + let location = try parseDocumentLocation(iterator: &iterator) + let commandDetailDesc = try parseAsString(token: iterator.next()) + let uniqueIdentifier = try parseAsString(token: iterator.next()) + 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 + } + + return IDEActivityLogSection( + sectionType: sectionType, + domainType: domainType, + title: title, + signature: signature, + timeStartedRecording: timeStartedRecording, + timeStoppedRecording: timeStoppedRecording, + subSections: subSections, + text: text, + messages: messages, + wasCancelled: wasCancelled, + isQuiet: isQuiet, + wasFetchedFromCache: wasFetchedFromCache, + subtitle: subtitle, + location: location, + commandDetailDesc: commandDetailDesc, + uniqueIdentifier: uniqueIdentifier, + localizedResultString: localizedResultString, + xcbuildSignature: xcbuildSignature, + attachments: attachments, + unknown: unknown ?? 0 + ) } public func parseIDEActivityLogUnitTestSection(iterator: inout IndexingIterator<[Token]>) diff --git a/Tests/XCLogParserTests/ActivityParserTests.swift b/Tests/XCLogParserTests/ActivityParserTests.swift index dbb2aa8..e2dbbd4 100644 --- a/Tests/XCLogParserTests/ActivityParserTests.swift +++ b/Tests/XCLogParserTests/ActivityParserTests.swift @@ -148,6 +148,36 @@ class ActivityParserTests: XCTestCase { return startTokens + logMessageTokens + endTokens }() + // Xcode 26.2 format: unknown integer appears before subtitle + lazy var IDEActivityLogSectionTokensXcode262: [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.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), // unknown integer before subtitle (Xcode 26.2+) + 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"), @@ -387,6 +417,34 @@ class ActivityParserTests: XCTestCase { XCTAssertEqual(0, logSection.unknown) } + func testParseIDEActivityLogSectionXcode262() throws { + parser.logVersion = 12 + let tokens = IDEActivityLogSectionTokensXcode262 + 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)