Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
79 changes: 57 additions & 22 deletions Sources/XCLogParser/activityparser/ActivityParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]>)
Expand Down
58 changes: 58 additions & 0 deletions Tests/XCLogParserTests/ActivityParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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)
Expand Down