Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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-14
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-14
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
Loading