From 333f943d94644e06589501d7828c2bd9940bc18f Mon Sep 17 00:00:00 2001 From: Antoine van der Lee <4329185+AvdLee@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:33:39 +0200 Subject: [PATCH 1/7] Add build statistics, exact build duration --- .../XCLogParser/logmanifest/LogManifest.swift | 24 +++++++++++--- .../logmanifest/LogManifestModel.swift | 28 ++++++++++++++--- Tests/XCLogParserTests/LogManifestTests.swift | 31 ++++++++++++++----- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/Sources/XCLogParser/logmanifest/LogManifest.swift b/Sources/XCLogParser/logmanifest/LogManifest.swift index 57773c6..12fdc6a 100644 --- a/Sources/XCLogParser/logmanifest/LogManifest.swift +++ b/Sources/XCLogParser/logmanifest/LogManifest.swift @@ -46,16 +46,28 @@ public struct LogManifest { let timeStartedRecording = entry.value["timeStartedRecording"] as? Double, let timeStoppedRecording = entry.value["timeStoppedRecording"] as? Double, let className = entry.value["className"] as? String, - let type = LogManifestEntryType.buildFromClassName(className) + let type = LogManifestEntryType.buildFromClassName(className), + let primaryObservable = entry.value["primaryObservable"] as? [String: Any], + let totalNumberOfAnalyzerIssues = primaryObservable["totalNumberOfAnalyzerIssues"] as? Int, + let totalNumberOfErrors = primaryObservable["totalNumberOfErrors"] as? Int, + let totalNumberOfWarnings = primaryObservable["totalNumberOfWarnings"] as? Int, + let totalNumberOfTestFailures = primaryObservable["totalNumberOfTestFailures"] as? Int, + let highLevelStatus = primaryObservable["highLevelStatus"] as? String else { throw LogError.invalidLogManifest("The file at \(path) is not a valid " + "LogManifest file.") } let startDate = Date(timeIntervalSinceReferenceDate: timeStartedRecording) let endDate = Date(timeIntervalSinceReferenceDate: timeStoppedRecording) - let timestampStart = Int(startDate.timeIntervalSince1970.rounded()) - let timestampEnd = Int(endDate.timeIntervalSince1970.rounded()) - + let timestampStart = startDate.timeIntervalSince1970 + let timestampEnd = endDate.timeIntervalSince1970 + let statistics = LogManifestEntryStatistics( + totalNumberOfErrors: totalNumberOfErrors, + totalNumberOfAnalyzerIssues: totalNumberOfAnalyzerIssues, + highLevelStatus: highLevelStatus, + totalNumberOfTestFailures: totalNumberOfTestFailures, + totalNumberOfWarnings: totalNumberOfWarnings + ) return LogManifestEntry(uniqueIdentifier: uniqueIdentifier, title: title, scheme: scheme, @@ -63,7 +75,9 @@ public struct LogManifest { timestampStart: timestampStart, timestampEnd: timestampEnd, duration: timestampEnd - timestampStart, - type: type) + type: type, + statistics: statistics + ) }.sorted(by: { lhs, rhs -> Bool in return lhs.timestampStart > rhs.timestampStart }) diff --git a/Sources/XCLogParser/logmanifest/LogManifestModel.swift b/Sources/XCLogParser/logmanifest/LogManifestModel.swift index 30194cf..a3ec1a0 100644 --- a/Sources/XCLogParser/logmanifest/LogManifestModel.swift +++ b/Sources/XCLogParser/logmanifest/LogManifestModel.swift @@ -42,13 +42,14 @@ public struct LogManifestEntry: Encodable { public let title: String public let scheme: String public let fileName: String - public let timestampStart: Int - public let timestampEnd: Int - public let duration: Int + public let timestampStart: TimeInterval + public let timestampEnd: TimeInterval + public let duration: Double public let type: LogManifestEntryType - + public let statistics: LogManifestEntryStatistics + public init(uniqueIdentifier: String, title: String, scheme: String, fileName: String, - timestampStart: Int, timestampEnd: Int, duration: Int, type: LogManifestEntryType) { + timestampStart: TimeInterval, timestampEnd: TimeInterval, duration: Double, type: LogManifestEntryType, statistics: LogManifestEntryStatistics) { self.uniqueIdentifier = uniqueIdentifier self.title = title self.scheme = scheme @@ -57,6 +58,23 @@ public struct LogManifestEntry: Encodable { self.timestampEnd = timestampEnd self.duration = duration self.type = type + self.statistics = statistics } } + +public struct LogManifestEntryStatistics: Encodable { + public let totalNumberOfErrors: Int + public let totalNumberOfAnalyzerIssues: Int + public let highLevelStatus: String + public let totalNumberOfTestFailures: Int + public let totalNumberOfWarnings: Int + + public init(totalNumberOfErrors: Int, totalNumberOfAnalyzerIssues: Int, highLevelStatus: String, totalNumberOfTestFailures: Int, totalNumberOfWarnings: Int) { + self.totalNumberOfErrors = totalNumberOfErrors + self.totalNumberOfAnalyzerIssues = totalNumberOfAnalyzerIssues + self.highLevelStatus = highLevelStatus + self.totalNumberOfTestFailures = totalNumberOfTestFailures + self.totalNumberOfWarnings = totalNumberOfWarnings + } +} diff --git a/Tests/XCLogParserTests/LogManifestTests.swift b/Tests/XCLogParserTests/LogManifestTests.swift index bbccf04..21c5d8d 100644 --- a/Tests/XCLogParserTests/LogManifestTests.swift +++ b/Tests/XCLogParserTests/LogManifestTests.swift @@ -45,6 +45,14 @@ class LogManifestTests: XCTestCase { highLevelStatus W +totalNumberOfAnalyzerIssues +0 +totalNumberOfErrors +0 +totalNumberOfTestFailures +0 +totalNumberOfWarnings +2 schemeIdentifier-containerName MyApp @@ -95,7 +103,13 @@ class LogManifestTests: XCTestCase { "documentTypeString": "<nil>", "domainType": "Xcode.IDEActivityLogDomainType.BuildLog", "fileName": "599BC5A8-5E6A-4C16-A71E-A8D6301BAC07.xcactivitylog", - "highLevelStatus": "E", + "primaryObservable": [ + "highLevelStatus": "E", + "totalNumberOfErrors": 1, + "totalNumberOfAnalyzerIssues": 0, + "totalNumberOfTestFailures": 0, + "totalNumberOfWarnings": 2 + ], "schemeIdentifier-containerName": "MyApp project", "schemeIdentifier-schemeName": "MyApp", "schemeIdentifier-sharedScheme": 1, @@ -109,7 +123,13 @@ class LogManifestTests: XCTestCase { "documentTypeString": "<nil>", "domainType": "Xcode.IDEActivityLogDomainType.BuildLog", "fileName": "D1FEAFFA-2E88-4221-9CD2-AB607529381D.xcactivitylog", - "highLevelStatus": "E", + "primaryObservable": [ + "highLevelStatus": "E", + "totalNumberOfErrors": 1, + "totalNumberOfAnalyzerIssues": 0, + "totalNumberOfTestFailures": 0, + "totalNumberOfWarnings": 2 + ], "schemeIdentifier-containerName": "MyApp project", "schemeIdentifier-schemeName": "MyApp", "schemeIdentifier-sharedScheme": 1, @@ -128,13 +148,10 @@ class LogManifestTests: XCTestCase { return } + let startDate = Date(timeIntervalSinceReferenceDate: firstStartedRecording) let endDate = Date(timeIntervalSinceReferenceDate: firstStoppedRecording) - let calendar = Calendar.current - guard let expectedDuration = calendar.dateComponents([.second], from: startDate, to: endDate).second else { - XCTFail("Error creating an expected duration field") - return - } + let expectedDuration = endDate.timeIntervalSince1970 - startDate.timeIntervalSince1970 XCTAssertEqual(expectedDuration, latestLog.duration) } From b30e539d4abafd53b459b53984393a793b1e4860 Mon Sep 17 00:00:00 2001 From: Jose Ricardo Correia Miranda Ramos Date: Tue, 11 Mar 2025 15:15:14 +0000 Subject: [PATCH 2/7] Replace ascii file reading for Swift6 compability --- Sources/XCLogParser/loglocation/LogLoader.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Sources/XCLogParser/loglocation/LogLoader.swift b/Sources/XCLogParser/loglocation/LogLoader.swift index 4a39b88..85cc1f2 100644 --- a/Sources/XCLogParser/loglocation/LogLoader.swift +++ b/Sources/XCLogParser/loglocation/LogLoader.swift @@ -26,7 +26,17 @@ public struct LogLoader { do { let data = try Data(contentsOf: url) let unzipped = try data.gunzipped() - guard let contents = String(data: unzipped, encoding: .ascii) else { + let string: String? = unzipped.withUnsafeBytes { pointer in + guard let charPointer = pointer + .assumingMemoryBound(to: CChar.self) + .baseAddress + else { + return nil + } + + return String(cString: charPointer, encoding: .ascii) + } + guard let contents = string else { throw LogError.readingFile(url.path) } return contents @@ -36,3 +46,4 @@ public struct LogLoader { } } + From 485d369108ea4becc60da0123790e4f16c39a916 Mon Sep 17 00:00:00 2001 From: Antoine van der Lee <4329185+AvdLee@users.noreply.github.com> Date: Thu, 22 May 2025 14:47:36 +0200 Subject: [PATCH 3/7] Fix index out of bounds --- Sources/XCLogParser/lexer/Scanner.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/XCLogParser/lexer/Scanner.swift b/Sources/XCLogParser/lexer/Scanner.swift index fc78f1b..ef42dcd 100644 --- a/Sources/XCLogParser/lexer/Scanner.swift +++ b/Sources/XCLogParser/lexer/Scanner.swift @@ -37,9 +37,12 @@ final class Scanner { func scan(count: Int) -> String? { let start = String.Index(compilerSafeOffset: self.offset, in: self.string) - let end = String.Index(compilerSafeOffset: self.offset + count, in: self.string) + let endOffset = self.offset + count - var result = self.string.substring(with: (start.. Date: Tue, 15 Jul 2025 16:05:16 +0200 Subject: [PATCH 4/7] Add Sendable conformance --- Sources/XCLogParser/logmanifest/LogManifest.swift | 2 +- Sources/XCLogParser/logmanifest/LogManifestModel.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/XCLogParser/logmanifest/LogManifest.swift b/Sources/XCLogParser/logmanifest/LogManifest.swift index 12fdc6a..9bcaa3d 100644 --- a/Sources/XCLogParser/logmanifest/LogManifest.swift +++ b/Sources/XCLogParser/logmanifest/LogManifest.swift @@ -21,7 +21,7 @@ import Foundation /// Parses a LogManifest.plist file. /// That file has a list of the existing Xcode Logs inside a Derived Data's project directory -public struct LogManifest { +public struct LogManifest: Sendable { public init() {} diff --git a/Sources/XCLogParser/logmanifest/LogManifestModel.swift b/Sources/XCLogParser/logmanifest/LogManifestModel.swift index a3ec1a0..c79ac38 100644 --- a/Sources/XCLogParser/logmanifest/LogManifestModel.swift +++ b/Sources/XCLogParser/logmanifest/LogManifestModel.swift @@ -19,7 +19,7 @@ import Foundation -public enum LogManifestEntryType: String, Encodable { +public enum LogManifestEntryType: String, Encodable, Sendable { case xcode case xcodebuild @@ -37,7 +37,7 @@ public enum LogManifestEntryType: String, Encodable { } } -public struct LogManifestEntry: Encodable { +public struct LogManifestEntry: Encodable, Sendable { public let uniqueIdentifier: String public let title: String public let scheme: String @@ -63,7 +63,7 @@ public struct LogManifestEntry: Encodable { } -public struct LogManifestEntryStatistics: Encodable { +public struct LogManifestEntryStatistics: Encodable, Sendable { public let totalNumberOfErrors: Int public let totalNumberOfAnalyzerIssues: Int public let highLevelStatus: String From 6b5d77a7d55a4671f626fe769f9ed8d20cc479e7 Mon Sep 17 00:00:00 2001 From: Antoine van der Lee <4329185+AvdLee@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:48:19 +0200 Subject: [PATCH 5/7] Fix Swiftlint warnings --- Sources/XCLogParser/logmanifest/LogManifest.swift | 1 + Sources/XCLogParser/logmanifest/LogManifestModel.swift | 4 ++-- Tests/XCLogParserTests/LogManifestTests.swift | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/XCLogParser/logmanifest/LogManifest.swift b/Sources/XCLogParser/logmanifest/LogManifest.swift index ae46912..7b34670 100644 --- a/Sources/XCLogParser/logmanifest/LogManifest.swift +++ b/Sources/XCLogParser/logmanifest/LogManifest.swift @@ -32,6 +32,7 @@ public struct LogManifest { return try parse(dictionary: logManifestDictionary, atPath: logManifestURL.path) } + // swiftlint:disable function_body_length public func parse(dictionary: NSDictionary, atPath path: String) throws -> [LogManifestEntry] { guard let logs = dictionary["logs"] as? [String: [String: Any]] else { throw LogError.invalidLogManifest("The file at \(path) is not a valid " + diff --git a/Sources/XCLogParser/logmanifest/LogManifestModel.swift b/Sources/XCLogParser/logmanifest/LogManifestModel.swift index a3ec1a0..cb82dc5 100644 --- a/Sources/XCLogParser/logmanifest/LogManifestModel.swift +++ b/Sources/XCLogParser/logmanifest/LogManifestModel.swift @@ -47,7 +47,7 @@ public struct LogManifestEntry: Encodable { public let duration: Double public let type: LogManifestEntryType public let statistics: LogManifestEntryStatistics - + public init(uniqueIdentifier: String, title: String, scheme: String, fileName: String, timestampStart: TimeInterval, timestampEnd: TimeInterval, duration: Double, type: LogManifestEntryType, statistics: LogManifestEntryStatistics) { self.uniqueIdentifier = uniqueIdentifier @@ -69,7 +69,7 @@ public struct LogManifestEntryStatistics: Encodable { public let highLevelStatus: String public let totalNumberOfTestFailures: Int public let totalNumberOfWarnings: Int - + public init(totalNumberOfErrors: Int, totalNumberOfAnalyzerIssues: Int, highLevelStatus: String, totalNumberOfTestFailures: Int, totalNumberOfWarnings: Int) { self.totalNumberOfErrors = totalNumberOfErrors self.totalNumberOfAnalyzerIssues = totalNumberOfAnalyzerIssues diff --git a/Tests/XCLogParserTests/LogManifestTests.swift b/Tests/XCLogParserTests/LogManifestTests.swift index 21c5d8d..710a55e 100644 --- a/Tests/XCLogParserTests/LogManifestTests.swift +++ b/Tests/XCLogParserTests/LogManifestTests.swift @@ -148,7 +148,6 @@ class LogManifestTests: XCTestCase { return } - let startDate = Date(timeIntervalSinceReferenceDate: firstStartedRecording) let endDate = Date(timeIntervalSinceReferenceDate: firstStoppedRecording) let expectedDuration = endDate.timeIntervalSince1970 - startDate.timeIntervalSince1970 From f4a35c4e8af69b9bfd1ad32f2073e0fda24241a0 Mon Sep 17 00:00:00 2001 From: Antoine van der Lee <4329185+AvdLee@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:59:24 +0200 Subject: [PATCH 6/7] Fix SwiftLint --- .../XCLogParser/loglocation/LogLoader.swift | 1 - .../logmanifest/LogManifestModel.swift | 21 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Sources/XCLogParser/loglocation/LogLoader.swift b/Sources/XCLogParser/loglocation/LogLoader.swift index 85cc1f2..4465c57 100644 --- a/Sources/XCLogParser/loglocation/LogLoader.swift +++ b/Sources/XCLogParser/loglocation/LogLoader.swift @@ -46,4 +46,3 @@ public struct LogLoader { } } - diff --git a/Sources/XCLogParser/logmanifest/LogManifestModel.swift b/Sources/XCLogParser/logmanifest/LogManifestModel.swift index 5dc26ea..10d3082 100644 --- a/Sources/XCLogParser/logmanifest/LogManifestModel.swift +++ b/Sources/XCLogParser/logmanifest/LogManifestModel.swift @@ -48,8 +48,17 @@ public struct LogManifestEntry: Encodable, Sendable { public let type: LogManifestEntryType public let statistics: LogManifestEntryStatistics - public init(uniqueIdentifier: String, title: String, scheme: String, fileName: String, - timestampStart: TimeInterval, timestampEnd: TimeInterval, duration: Double, type: LogManifestEntryType, statistics: LogManifestEntryStatistics) { + public init( + uniqueIdentifier: String, + title: String, + scheme: String, + fileName: String, + timestampStart: TimeInterval, + timestampEnd: TimeInterval, + duration: Double, + type: LogManifestEntryType, + statistics: LogManifestEntryStatistics + ) { self.uniqueIdentifier = uniqueIdentifier self.title = title self.scheme = scheme @@ -70,7 +79,13 @@ public struct LogManifestEntryStatistics: Encodable, Sendable { public let totalNumberOfTestFailures: Int public let totalNumberOfWarnings: Int - public init(totalNumberOfErrors: Int, totalNumberOfAnalyzerIssues: Int, highLevelStatus: String, totalNumberOfTestFailures: Int, totalNumberOfWarnings: Int) { + public init( + totalNumberOfErrors: Int, + totalNumberOfAnalyzerIssues: Int, + highLevelStatus: String, + totalNumberOfTestFailures: Int, + totalNumberOfWarnings: Int + ) { self.totalNumberOfErrors = totalNumberOfErrors self.totalNumberOfAnalyzerIssues = totalNumberOfAnalyzerIssues self.highLevelStatus = highLevelStatus From cf2ba2188bd95cea9fd1cc241bc70fe8429ebf24 Mon Sep 17 00:00:00 2001 From: Antoine van der Lee <4329185+AvdLee@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:38:21 +0100 Subject: [PATCH 7/7] Make statistics optional --- .../XCLogParser/logmanifest/LogManifest.swift | 35 +++++++++++-------- .../logmanifest/LogManifestModel.swift | 4 +-- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Sources/XCLogParser/logmanifest/LogManifest.swift b/Sources/XCLogParser/logmanifest/LogManifest.swift index efc998a..fa4e7fb 100644 --- a/Sources/XCLogParser/logmanifest/LogManifest.swift +++ b/Sources/XCLogParser/logmanifest/LogManifest.swift @@ -47,13 +47,7 @@ public struct LogManifest: Sendable { let timeStartedRecording = entry.value["timeStartedRecording"] as? Double, let timeStoppedRecording = entry.value["timeStoppedRecording"] as? Double, let className = entry.value["className"] as? String, - let type = LogManifestEntryType.buildFromClassName(className), - let primaryObservable = entry.value["primaryObservable"] as? [String: Any], - let totalNumberOfAnalyzerIssues = primaryObservable["totalNumberOfAnalyzerIssues"] as? Int, - let totalNumberOfErrors = primaryObservable["totalNumberOfErrors"] as? Int, - let totalNumberOfWarnings = primaryObservable["totalNumberOfWarnings"] as? Int, - let totalNumberOfTestFailures = primaryObservable["totalNumberOfTestFailures"] as? Int, - let highLevelStatus = primaryObservable["highLevelStatus"] as? String + let type = LogManifestEntryType.buildFromClassName(className) else { throw LogError.invalidLogManifest("The file at \(path) is not a valid " + "LogManifest file.") @@ -62,13 +56,26 @@ public struct LogManifest: Sendable { let endDate = Date(timeIntervalSinceReferenceDate: timeStoppedRecording) let timestampStart = startDate.timeIntervalSince1970 let timestampEnd = endDate.timeIntervalSince1970 - let statistics = LogManifestEntryStatistics( - totalNumberOfErrors: totalNumberOfErrors, - totalNumberOfAnalyzerIssues: totalNumberOfAnalyzerIssues, - highLevelStatus: highLevelStatus, - totalNumberOfTestFailures: totalNumberOfTestFailures, - totalNumberOfWarnings: totalNumberOfWarnings - ) + + // Optionally extract statistics if available + let statistics: LogManifestEntryStatistics? = { + guard let primaryObservable = entry.value["primaryObservable"] as? [String: Any], + let totalNumberOfAnalyzerIssues = primaryObservable["totalNumberOfAnalyzerIssues"] as? Int, + let totalNumberOfErrors = primaryObservable["totalNumberOfErrors"] as? Int, + let totalNumberOfWarnings = primaryObservable["totalNumberOfWarnings"] as? Int, + let totalNumberOfTestFailures = primaryObservable["totalNumberOfTestFailures"] as? Int, + let highLevelStatus = primaryObservable["highLevelStatus"] as? String + else { + return nil + } + return LogManifestEntryStatistics( + totalNumberOfErrors: totalNumberOfErrors, + totalNumberOfAnalyzerIssues: totalNumberOfAnalyzerIssues, + highLevelStatus: highLevelStatus, + totalNumberOfTestFailures: totalNumberOfTestFailures, + totalNumberOfWarnings: totalNumberOfWarnings + ) + }() return LogManifestEntry(uniqueIdentifier: uniqueIdentifier, title: title, scheme: scheme, diff --git a/Sources/XCLogParser/logmanifest/LogManifestModel.swift b/Sources/XCLogParser/logmanifest/LogManifestModel.swift index 10d3082..c24f78d 100644 --- a/Sources/XCLogParser/logmanifest/LogManifestModel.swift +++ b/Sources/XCLogParser/logmanifest/LogManifestModel.swift @@ -46,7 +46,7 @@ public struct LogManifestEntry: Encodable, Sendable { public let timestampEnd: TimeInterval public let duration: Double public let type: LogManifestEntryType - public let statistics: LogManifestEntryStatistics + public let statistics: LogManifestEntryStatistics? public init( uniqueIdentifier: String, @@ -57,7 +57,7 @@ public struct LogManifestEntry: Encodable, Sendable { timestampEnd: TimeInterval, duration: Double, type: LogManifestEntryType, - statistics: LogManifestEntryStatistics + statistics: LogManifestEntryStatistics? ) { self.uniqueIdentifier = uniqueIdentifier self.title = title