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