Skip to content

Commit

Permalink
Merge pull request #204 from rabc/handle_json_format
Browse files Browse the repository at this point in the history
Update to handle the new JSON format introduced in Xcode 15.3
  • Loading branch information
polac24 authored Apr 7, 2024
2 parents e536e30 + 64a5516 commit fdde347
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 2 deletions.
61 changes: 61 additions & 0 deletions Sources/XCLogParser/activityparser/ActivityParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public class ActivityParser {
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)
}

Expand All @@ -133,6 +134,7 @@ public class ActivityParser {
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,
testsPassedString: try parseAsString(token: iterator.next()),
durationString: try parseAsString(token: iterator.next()),
Expand Down Expand Up @@ -163,6 +165,8 @@ public class ActivityParser {
localizedResultString: try parseAsString(token: iterator.next()),
xcbuildSignature: try parseAsString(token: iterator.next()),
// swiftlint:disable:next line_length
attachments: try parseIDEActivityLogSectionAttachments(iterator: &iterator),
// swiftlint:disable:next line_length
unknown: isCommandLineLog ? Int(try parseAsInt(token: iterator.next())) : 0,
logConsoleItems: try parseIDEConsoleItems(iterator: &iterator)
)
Expand Down Expand Up @@ -361,6 +365,25 @@ public class ActivityParser {
throw XCLogParserError.parseError("Unexpected className found parsing IDEActivityLogMessage \(className)")
}

private func parseLogSectionAttachment(iterator: inout IndexingIterator<[Token]>)
throws -> IDEActivityLogSectionAttachment {
let classRefToken = try getClassRefToken(iterator: &iterator)
guard case Token.classNameRef(let className) = classRefToken else {
throw XCLogParserError.parseError("Unexpected token found parsing " +
"IDEActivityLogSectionAttachment \(classRefToken)")
}

if className == "IDEFoundation.\(String(describing: IDEActivityLogSectionAttachment.self))" {
let jsonType = IDEActivityLogSectionAttachment.BuildOperationTaskMetrics.self
return try IDEActivityLogSectionAttachment(identifier: try parseAsString(token: iterator.next()),
majorVersion: try parseAsInt(token: iterator.next()),
minorVersion: try parseAsInt(token: iterator.next()),
metrics: try parseAsJson(token: iterator.next(),
type: jsonType))
}
throw XCLogParserError.parseError("Unexpected className found parsing IDEConsoleItem \(className)")
}

private func parseLogSection(iterator: inout IndexingIterator<[Token]>)
throws -> IDEActivityLogSection {
var classRefToken = try getClassRefToken(iterator: &iterator)
Expand Down Expand Up @@ -433,6 +456,27 @@ public class ActivityParser {
}
}

private func parseIDEActivityLogSectionAttachments(iterator: inout IndexingIterator<[Token]>)
throws -> [IDEActivityLogSectionAttachment] {
guard let listToken = iterator.next() else {
throw XCLogParserError.parseError("Unexpected EOF parsing array of IDEActivityLogSectionAttachment")
}
switch listToken {
case .null:
return []
case .list(let count):
var sections = [IDEActivityLogSectionAttachment]()
for _ in 0..<count {
let section = try parseLogSectionAttachment(iterator: &iterator)
sections.append(section)
}
return sections
default:
throw XCLogParserError.parseError("Unexpected token parsing array of " +
"IDEActivityLogSectionAttachment: \(listToken)")
}
}

private func parseIDEConsoleItem(iterator: inout IndexingIterator<[Token]>)
throws -> IDEConsoleItem? {
let classRefToken = try getClassRefToken(iterator: &iterator)
Expand Down Expand Up @@ -555,6 +599,23 @@ public class ActivityParser {
}
}

private func parseAsJson<T: Decodable>(token: Token?, type: T.Type) throws -> T? {
guard let token = token else {
throw XCLogParserError.parseError("Unexpected EOF parsing JSON String")
}
switch token {
case .json(let string):
guard let data = string.data(using: .utf8) else {
throw XCLogParserError.parseError("Unexpected JSON string \(string)")
}
return try JSONDecoder().decode(type, from: data)
case .null:
return nil
default:
throw XCLogParserError.parseError("Unexpected token parsing JSON String: \(token)")
}
}

private func parseAsInt(token: Token?) throws -> UInt64 {
guard let token = token else {
throw XCLogParserError.parseError("Unexpected EOF parsing Int")
Expand Down
36 changes: 36 additions & 0 deletions Sources/XCLogParser/activityparser/IDEActivityModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class IDEActivityLogSection: Encodable {
public let uniqueIdentifier: String
public let localizedResultString: String
public let xcbuildSignature: String
public let attachments: [IDEActivityLogSectionAttachment]
public let unknown: Int

public init(sectionType: Int8,
Expand All @@ -69,6 +70,7 @@ public class IDEActivityLogSection: Encodable {
uniqueIdentifier: String,
localizedResultString: String,
xcbuildSignature: String,
attachments: [IDEActivityLogSectionAttachment],
unknown: Int) {
self.sectionType = sectionType
self.domainType = domainType
Expand All @@ -88,6 +90,7 @@ public class IDEActivityLogSection: Encodable {
self.uniqueIdentifier = uniqueIdentifier
self.localizedResultString = localizedResultString
self.xcbuildSignature = xcbuildSignature
self.attachments = attachments
self.unknown = unknown
}

Expand Down Expand Up @@ -119,6 +122,7 @@ public class IDEActivityLogUnitTestSection: IDEActivityLogSection {
uniqueIdentifier: String,
localizedResultString: String,
xcbuildSignature: String,
attachments: [IDEActivityLogSectionAttachment],
unknown: Int,
testsPassedString: String,
durationString: String,
Expand Down Expand Up @@ -151,6 +155,7 @@ public class IDEActivityLogUnitTestSection: IDEActivityLogSection {
uniqueIdentifier: uniqueIdentifier,
localizedResultString: localizedResultString,
xcbuildSignature: xcbuildSignature,
attachments: attachments,
unknown: unknown)
}

Expand Down Expand Up @@ -421,6 +426,7 @@ public class DBGConsoleLog: IDEActivityLogSection {
uniqueIdentifier: String,
localizedResultString: String,
xcbuildSignature: String,
attachments: [IDEActivityLogSectionAttachment],
unknown: Int,
logConsoleItems: [IDEConsoleItem]) {
self.logConsoleItems = logConsoleItems
Expand All @@ -442,6 +448,7 @@ public class DBGConsoleLog: IDEActivityLogSection {
uniqueIdentifier: uniqueIdentifier,
localizedResultString: localizedResultString,
xcbuildSignature: xcbuildSignature,
attachments: attachments,
unknown: unknown)
}

Expand Down Expand Up @@ -635,3 +642,32 @@ public class DVTMemberDocumentLocation: DVTDocumentLocation, Equatable {
}

}

// MARK: Added in Xcode 15.3

public class IDEActivityLogSectionAttachment: Encodable {
public struct BuildOperationTaskMetrics: Codable {
public let utime: UInt64
public let stime: UInt64
public let maxRSS: UInt64
public let wcStartTime: UInt64
public let wcDuration: UInt64
}

public let identifier: String
public let majorVersion: UInt64
public let minorVersion: UInt64
public let metrics: BuildOperationTaskMetrics?

public init(
identifier: String,
majorVersion: UInt64,
minorVersion: UInt64,
metrics: BuildOperationTaskMetrics?
) throws {
self.identifier = identifier
self.majorVersion = majorVersion
self.minorVersion = minorVersion
self.metrics = metrics
}
}
19 changes: 19 additions & 0 deletions Sources/XCLogParser/lexer/Lexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ public final class Lexer {
return .null
case .list:
return handleListTokenTypeCase(payload: payload)
case .json:
return handleJSONTokenTypeCase(scanner: scanner,
payload: payload,
redacted: redacted,
withoutBuildSpecificInformation: withoutBuildSpecificInformation)
}
}

Expand Down Expand Up @@ -213,6 +218,20 @@ public final class Lexer {
return .string(content)
}

private func handleJSONTokenTypeCase(scanner: Scanner,
payload: String,
redacted: Bool,
withoutBuildSpecificInformation: Bool) -> Token? {
guard let content = scanString(length: payload,
scanner: scanner,
redacted: redacted,
withoutBuildSpecificInformation: withoutBuildSpecificInformation) else {
print("error parsing string")
return nil
}
return .json(content)
}

private func handleDoubleTokenTypeCase(payload: String) -> Token? {
guard let double = hexToInt(payload) else {
print("error parsing double")
Expand Down
4 changes: 4 additions & 0 deletions Sources/XCLogParser/lexer/LexerModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum TokenType: String, CaseIterable {
case double = "^"
case null = "-"
case list = "("
case json = "*"

static func all() -> String {
return TokenType.allCases.reduce(String()) {
Expand All @@ -43,6 +44,7 @@ public enum Token: CustomDebugStringConvertible, Equatable {
case double(Double)
case null
case list(Int)
case json(String)
}

extension Token {
Expand All @@ -62,6 +64,8 @@ extension Token {
return "[type: nil]"
case .list(let count):
return "[type: list, count: \(count)]"
case .json(let json):
return "[type: json, value: \(json)]"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ extension IDEActivityLogSection {
uniqueIdentifier: self.uniqueIdentifier,
localizedResultString: self.localizedResultString,
xcbuildSignature: self.xcbuildSignature,
attachments: self.attachments,
unknown: self.unknown)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ extension IDEActivityLogSection {
uniqueIdentifier: "",
localizedResultString: "",
xcbuildSignature: "",
attachments: section.attachments,
unknown: 0)
}

Expand Down
14 changes: 13 additions & 1 deletion Tests/XCLogParserTests/ActivityParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ class ActivityParserTests: XCTestCase {
Token.string("501796C4-6BE4-4F80-9F9D-3269617ECC17"),
Token.string("localizedResultString"),
Token.string("xcbuildSignature"),
Token.list(1),
Token.classNameRef("IDEFoundation.IDEActivityLogSectionAttachment"),
Token.string("com.apple.dt.ActivityLogSectionAttachment.TaskMetrics"),
Token.int(1),
Token.int(0),
Token.json("{\"wcStartTime\":1,\"maxRSS\":1,\"utime\":1,\"wcDuration\":1,\"stime\":1}"),
Token.int(0)
]
return startTokens + logMessageTokens + endTokens
Expand Down Expand Up @@ -129,7 +135,13 @@ class ActivityParserTests: XCTestCase {
Token.string("79D9C1DE-F736-4743-A7C6-B08ED42A1DFE"),
Token.null,
Token.null,
Token.list(1)
Token.list(1),
Token.classNameRef("IDEFoundation.IDEActivityLogSectionAttachment"),
Token.string("com.apple.dt.ActivityLogSectionAttachment.TaskMetrics"),
Token.int(1),
Token.int(0),
Token.json("{\"wcStartTime\":1,\"maxRSS\":1,\"utime\":1,\"wcDuration\":1,\"stime\":1}"),
Token.list(1),
]
return startTokens + IDEConsoleItemTokens
}()
Expand Down
1 change: 1 addition & 0 deletions Tests/XCLogParserTests/ClangCompilerParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class ClangCompilerParserTests: XCTestCase {
uniqueIdentifier: "",
localizedResultString: "",
xcbuildSignature: "",
attachments: [],
unknown: 0)
}
}
2 changes: 1 addition & 1 deletion Tests/XCLogParserTests/LogManifestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class LogManifestTests: XCTestCase {
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>10</integer>
<integer>11</integer>
<key>logs</key>
<dict>
<key>E8557234-04E4-40E7-A6D6-920AC64BCF21</key>
Expand Down
4 changes: 4 additions & 0 deletions Tests/XCLogParserTests/ParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class ParserTests: XCTestCase {
uniqueIdentifier: uniqueIdentifier,
localizedResultString: "",
xcbuildSignature: "",
attachments: [],
unknown: 0)
let fakeActivityLog = IDEActivityLog(version: 10, mainSection: fakeMainSection)
let buildStep = try parser.parse(activityLog: fakeActivityLog)
Expand Down Expand Up @@ -260,6 +261,7 @@ note: use 'updatedDoSomething' instead\r doSomething()\r ^~~~~~~~~~~\r
uniqueIdentifier: "ABC",
localizedResultString: "",
xcbuildSignature: "",
attachments: [],
unknown: 0)

let parsedTarget = fakeSection.getTargetFromCommand()
Expand Down Expand Up @@ -310,6 +312,7 @@ note: use 'updatedDoSomething' instead\r doSomething()\r ^~~~~~~~~~~\r
uniqueIdentifier: "uniqueIdentifier",
localizedResultString: "",
xcbuildSignature: "",
attachments: [],
unknown: 0)
return IDEActivityLog(version: 10, mainSection: fakeMainStep)
}
Expand Down Expand Up @@ -555,6 +558,7 @@ CompileSwift normal x86_64 (in target 'Alamofire' from project 'Pods')
uniqueIdentifier: "uniqueIdentifier",
localizedResultString: "",
xcbuildSignature: "",
attachments: [],
unknown: 0)
}()
}
1 change: 1 addition & 0 deletions Tests/XCLogParserTests/SwiftCompilerParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class SwiftCompilerParserTests: XCTestCase {
uniqueIdentifier: "",
localizedResultString: "",
xcbuildSignature: "",
attachments: [],
unknown: 0)
}

Expand Down
10 changes: 10 additions & 0 deletions docs/Xcactivitylog Format.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A `SLF` document starts with the header `SLF0`. After the header, the document h
- Class names
- Class instances
- Null
- JSON

A value encoded is formed by 3 parts:

Expand Down Expand Up @@ -81,6 +82,14 @@ In this case, there are three encoded values:

The elements of an `Array` are `Class instances`

### JSON

- Character type delimiter: `*`
- Example: `"{\"wcStartTime\":732791618924407,\"maxRSS\":0,\"utime\":798,\"wcDuration\":852,\"stime\":798}"`
- Left hand side value: An `Integer` with the number of characters that are part of the `JSON` string.

The JSON is of the type `IDEFoundation.IDEActivityLogSectionAttachment`

### Class name

- Character type delimiter: `%`
Expand Down Expand Up @@ -142,6 +151,7 @@ Inside the logs you can find these classes:
- `DVTTextDocumentLocation`
- `IDEActivityLogCommandInvocationSection`
- `IDEActivityLogMajorGroupSection`
- `IDEFoundation.IDEActivityLogSectionAttachment`

If you search for them, you will find that they belong to the IDEFoundation.framework. A private framework part of Xcode. You can class dump it to get the headers of those classes. Once you have the headers, you will have the name and type of the properties that belong to the class. Now, you can match them to the tokens you got from the log. Some of them are in the same order than in the headers, but for others it will be about trial and error.

Expand Down

0 comments on commit fdde347

Please sign in to comment.