diff --git a/Sources/XCLogParser/activityparser/ActivityParser.swift b/Sources/XCLogParser/activityparser/ActivityParser.swift index 07d8f27..49a98e9 100644 --- a/Sources/XCLogParser/activityparser/ActivityParser.swift +++ b/Sources/XCLogParser/activityparser/ActivityParser.swift @@ -380,12 +380,27 @@ public class ActivityParser { } 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)) + let identifier = try parseAsString(token: iterator.next()) + switch identifier.components(separatedBy: "ActivityLogSectionAttachment.").last { + case .some("TaskMetrics"): + let jsonType = IDEActivityLogSectionAttachment.BuildOperationTaskMetrics.self + return try IDEActivityLogSectionAttachment(identifier: identifier, + majorVersion: try parseAsInt(token: iterator.next()), + minorVersion: try parseAsInt(token: iterator.next()), + metrics: try parseAsJson(token: iterator.next(), + type: jsonType), + backtrace: nil) + case .some("TaskBacktrace"): + let jsonType = IDEActivityLogSectionAttachment.BuildOperationTaskBacktrace.self + return try IDEActivityLogSectionAttachment(identifier: identifier, + majorVersion: try parseAsInt(token: iterator.next()), + minorVersion: try parseAsInt(token: iterator.next()), + metrics: nil, + backtrace: try parseAsJson(token: iterator.next(), + type: jsonType)) + default: + throw XCLogParserError.parseError("Unexpected attachment identifier \(identifier)") + } } throw XCLogParserError.parseError("Unexpected className found parsing IDEConsoleItem \(className)") } diff --git a/Sources/XCLogParser/activityparser/IDEActivityModel.swift b/Sources/XCLogParser/activityparser/IDEActivityModel.swift index adeb83f..0826d60 100644 --- a/Sources/XCLogParser/activityparser/IDEActivityModel.swift +++ b/Sources/XCLogParser/activityparser/IDEActivityModel.swift @@ -658,16 +658,115 @@ public class IDEActivityLogSectionAttachment: Encodable { public let majorVersion: UInt64 public let minorVersion: UInt64 public let metrics: BuildOperationTaskMetrics? + public let backtrace: BuildOperationTaskBacktrace? public init( identifier: String, majorVersion: UInt64, minorVersion: UInt64, - metrics: BuildOperationTaskMetrics? + metrics: BuildOperationTaskMetrics?, + backtrace: BuildOperationTaskBacktrace? ) throws { self.identifier = identifier self.majorVersion = majorVersion self.minorVersion = minorVersion self.metrics = metrics + self.backtrace = backtrace + } + + public struct BuildOperationTaskBacktrace: Codable { + public let frames: [BuildOperationTaskBacktraceFrame] + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + self.frames = try container.decode([BuildOperationTaskBacktraceFrame].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(frames) + } + } + + public struct BuildOperationTaskBacktraceFrame: Codable { + public let category: BuildOperationTaskBacktraceCategory + public let description: String + } + + public enum BuildOperationTaskBacktraceCategory: String, Codable { + case ruleHadInvalidValue + case ruleSignatureChanged + case ruleNeverBuilt + case ruleInputRebuilt + case ruleForced + case dynamicTaskRegistration + case dynamicTaskRequest + case none + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: BuildOperationTaskBacktraceCategoryCodingKeys.self) + + if container.contains(.ruleHadInvalidValue) { + self = .ruleHadInvalidValue + } else if container.contains(.ruleSignatureChanged) { + self = .ruleSignatureChanged + } else if container.contains(.ruleNeverBuilt) { + self = .ruleNeverBuilt + } else if container.contains(.ruleInputRebuilt) { + self = .ruleInputRebuilt + } else if container.contains(.ruleForced) { + self = .ruleForced + } else if container.contains(.dynamicTaskRegistration) { + self = .dynamicTaskRegistration + } else if container.contains(.dynamicTaskRequest) { + self = .dynamicTaskRequest + } else if container.contains(.none) { + self = .none + } else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unknown task backtrace category" + ) + ) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: BuildOperationTaskBacktraceCategoryCodingKeys.self) + switch self { + case .ruleHadInvalidValue: + try container.encode(EmptyObject(), forKey: .ruleHadInvalidValue) + case .ruleSignatureChanged: + try container.encode(EmptyObject(), forKey: .ruleSignatureChanged) + case .ruleNeverBuilt: + try container.encode(EmptyObject(), forKey: .ruleNeverBuilt) + case .ruleInputRebuilt: + try container.encode(EmptyObject(), forKey: .ruleInputRebuilt) + case .ruleForced: + try container.encode(EmptyObject(), forKey: .ruleForced) + case .dynamicTaskRegistration: + try container.encode(EmptyObject(), forKey: .dynamicTaskRegistration) + case .dynamicTaskRequest: + try container.encode(EmptyObject(), forKey: .dynamicTaskRequest) + case .none: + try container.encode(EmptyObject(), forKey: .none) + } + } + } + + private enum BuildOperationTaskBacktraceCategoryCodingKeys: String, CodingKey { + case ruleHadInvalidValue + case ruleSignatureChanged + case ruleNeverBuilt + case ruleInputRebuilt + case ruleForced + case dynamicTaskRegistration + case dynamicTaskRequest + case none + } + + private struct EmptyObject: Codable { + // Empty struct for objects with no properties } } diff --git a/Sources/XCLogParser/logmanifest/LogManifest.swift b/Sources/XCLogParser/logmanifest/LogManifest.swift index 57773c6..eafff2e 100644 --- a/Sources/XCLogParser/logmanifest/LogManifest.swift +++ b/Sources/XCLogParser/logmanifest/LogManifest.swift @@ -25,7 +25,7 @@ public struct LogManifest { public init() {} - public func getWithLogOptions(_ logOptions: LogOptions) throws -> [LogManifestEntry] { + public func getWithLogOptions(_ logOptions: LogOptions) throws -> [LogManifestEntry] { let logFinder = LogFinder() let logManifestURL = try logFinder.findLogManifestWithLogOptions(logOptions) let logManifestDictionary = try getDictionaryFromURL(logManifestURL) diff --git a/Tests/XCLogParserTests/ActivityParserTests.swift b/Tests/XCLogParserTests/ActivityParserTests.swift index de1c787..b68245c 100644 --- a/Tests/XCLogParserTests/ActivityParserTests.swift +++ b/Tests/XCLogParserTests/ActivityParserTests.swift @@ -97,7 +97,13 @@ class ActivityParserTests: XCTestCase { Token.string("501796C4-6BE4-4F80-9F9D-3269617ECC17"), Token.string("localizedResultString"), Token.string("xcbuildSignature"), - Token.list(1), + Token.list(2), + Token.classNameRef("IDEFoundation.IDEActivityLogSectionAttachment"), + Token.string("com.apple.dt.ActivityLogSectionAttachment.TaskBacktrace"), + Token.int(1), + Token.int(0), + // swiftlint:disable:next line_length + Token.json(#"[{"description":"'Planning Swift module ConcurrencyExtras (arm64)' had never run","category":{"ruleNeverBuilt":{}},"identifier":{"storage":{"task":{"_0":[0,80,50,58,116,97,114,103,101,116,45,67,111,110,99,117,114,114,101,110,99,121,69,120,116,114,97,115,45,101,102,52,50,51,48,52,53,57,52,98,102,56,53,50,102,52,51,56,101,102,55,99,51,97,49,51,54,98,50,99,57,48,100,102,56,55,49,56,97,102,50,98,57,100,51,97,97,99,48,100,48,100,99,97,50,50,98,52,99,50,57,99,50,45,58,66,101,116,97,32,68,101,98,117,103,58,51,99,57,97,99,57,53,50,98,52,99,56,49,100,57,99,99,49,55,100,49,97,102,52,55,49,97,48,52,53,101,56]}}},"frameKind":{"genericTask":{}}}]"#), Token.classNameRef("IDEFoundation.IDEActivityLogSectionAttachment"), Token.string("com.apple.dt.ActivityLogSectionAttachment.TaskMetrics"), Token.int(1), @@ -339,7 +345,9 @@ class ActivityParserTests: XCTestCase { XCTAssertEqual("501796C4-6BE4-4F80-9F9D-3269617ECC17", logSection.uniqueIdentifier) XCTAssertEqual("localizedResultString", logSection.localizedResultString) XCTAssertEqual("xcbuildSignature", logSection.xcbuildSignature) - XCTAssertEqual(1, logSection.attachments.count) + XCTAssertEqual(2, logSection.attachments.count) + XCTAssertEqual(logSection.attachments[0].backtrace?.frames.first?.category, .ruleNeverBuilt) + XCTAssertEqual(logSection.attachments[1].metrics?.wcDuration, 1) XCTAssertEqual(0, logSection.unknown) }