Skip to content

Commit 23e8073

Browse files
committed
Merge branch 'master' of ssh://github.com/AvdLee/XCLogParser
2 parents cf2ba21 + 0fbdb19 commit 23e8073

10 files changed

Lines changed: 192 additions & 38 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ jobs:
1313
args: --strict
1414

1515
macOS:
16-
runs-on: macos-13
16+
runs-on: macos-26
1717
env:
18-
XCODE_VERSION: ${{ '14.1' }}
18+
XCODE_VERSION: ${{ '16.4' }}
1919
steps:
2020
- name: Select Xcode
2121
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"

.github/workflows/release.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,31 @@ name: release_binaries
22
on:
33
release:
44
types: created
5+
workflow_dispatch:
6+
inputs:
7+
tag:
8+
description: 'Release tag (e.g., v0.2.40)'
9+
required: true
10+
type: string
511

612
jobs:
713
macOS:
814
name: Add macOS binaries to release
9-
runs-on: macos-13
15+
runs-on: macos-26
1016
env:
11-
XCODE_VERSION: ${{ '14.1' }}
17+
XCODE_VERSION: ${{ '16.4' }}
1218
steps:
1319
- name: Select Xcode
1420
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
1521
- name: Checkout
1622
uses: actions/checkout@v1
1723
- name: Set tag name
18-
run: echo "TAG_NAME=$(echo $GITHUB_REF | cut -c 11-)" >> $GITHUB_ENV
24+
run: |
25+
if [ -n "${{ inputs.tag }}" ]; then
26+
echo "TAG_NAME=${{ inputs.tag }}" >> $GITHUB_ENV
27+
else
28+
echo "TAG_NAME=$(echo $GITHUB_REF | cut -c 11-)" >> $GITHUB_ENV
29+
fi
1930
- name: Build x86_64-apple-macosx
2031
run: rake 'build[release, x86_64-apple-macosx]'
2132
- name: Zip x86_64-apple-macosx release
@@ -38,7 +49,7 @@ jobs:
3849
repo_token: ${{ secrets.GITHUB_TOKEN }}
3950
file: releases/*
4051
file_glob: true
41-
tag: ${{ github.ref }}
52+
tag: ${{ inputs.tag || github.ref }}
4253
overwrite: true
4354

4455
linux:
@@ -54,7 +65,12 @@ jobs:
5465
- name: Build
5566
run: LANG=en_US.UTF-8 LC_CTYPE=UTF-8 rake build[release]
5667
- name: Set tag name
57-
run: echo "TAG_NAME=$(echo $GITHUB_REF | cut -c 11-)" >> $GITHUB_ENV
68+
run: |
69+
if [ -n "${{ inputs.tag }}" ]; then
70+
echo "TAG_NAME=${{ inputs.tag }}" >> $GITHUB_ENV
71+
else
72+
echo "TAG_NAME=$(echo $GITHUB_REF | cut -c 11-)" >> $GITHUB_ENV
73+
fi
5874
- name: Zip release
5975
uses: montudor/action-zip@v0.1.0
6076
with:
@@ -67,5 +83,5 @@ jobs:
6783
repo_token: ${{ secrets.GITHUB_TOKEN }}
6884
file: releases/*
6985
file_glob: true
70-
tag: ${{ github.ref }}
86+
tag: ${{ inputs.tag || github.ref }}
7187
overwrite: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ DerivedData/
1414

1515
# Xcode 11
1616
.swiftpm/
17+
18+
.claude

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
FROM swift:5.5
1+
FROM swift:6.0
22
RUN apt-get update && apt-get install -y zlib1g-dev ruby
33
CMD cd xclogparser && swift build

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,8 +513,8 @@ xclogparser parse --file path/to/log.xcactivitylog --reporter html --output buil
513513
514514
| Environment | Version |
515515
| ----------- |-------------|
516-
| 🛠 Xcode | 13.0 |
517-
| 🐦 Language | Swift 5.5 |
516+
| 🛠 Xcode | 16.4 |
517+
| 🐦 Language | Swift 6.0 |
518518
519519
## Status
520520

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ end
5858

5959
desc 'Create a release zip'
6060
task :archive do
61-
Rake::Task["build"].invoke('release', ['macos'], 'true')
61+
Rake::Task["build"].invoke('release', nil, ['macos'], 'true')
6262
end
6363

6464
desc 'Generates a Swift class with the file content from Resources'

Sources/XCLogParser/activityparser/ActivityParser.swift

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -96,28 +96,63 @@ public class ActivityParser {
9696
additionalDescription: try parseAsString(token: iterator.next()))
9797
}
9898

99-
public func parseIDEActivityLogSection(iterator: inout IndexingIterator<[Token]>)
100-
throws -> IDEActivityLogSection {
101-
return IDEActivityLogSection(sectionType: Int8(try parseAsInt(token: iterator.next())),
102-
domainType: try parseAsString(token: iterator.next()),
103-
title: try parseAsString(token: iterator.next()),
104-
signature: try parseAsString(token: iterator.next()),
105-
timeStartedRecording: try parseAsDouble(token: iterator.next()),
106-
timeStoppedRecording: try parseAsDouble(token: iterator.next()),
107-
subSections: try parseIDEActivityLogSections(iterator: &iterator),
108-
text: try parseAsString(token: iterator.next()),
109-
messages: try parseMessages(iterator: &iterator),
110-
wasCancelled: try parseBoolean(token: iterator.next()),
111-
isQuiet: try parseBoolean(token: iterator.next()),
112-
wasFetchedFromCache: try parseBoolean(token: iterator.next()),
113-
subtitle: try parseAsString(token: iterator.next()),
114-
location: try parseDocumentLocation(iterator: &iterator),
115-
commandDetailDesc: try parseAsString(token: iterator.next()),
116-
uniqueIdentifier: try parseAsString(token: iterator.next()),
117-
localizedResultString: try parseAsString(token: iterator.next()),
118-
xcbuildSignature: try parseAsString(token: iterator.next()),
119-
attachments: try parseIDEActivityLogSectionAttachments(iterator: &iterator),
120-
unknown: isCommandLineLog ? Int(try parseAsInt(token: iterator.next())) : 0)
99+
// swiftlint:disable:next function_body_length
100+
public func parseIDEActivityLogSection(iterator: inout IndexingIterator<[Token]>) throws -> IDEActivityLogSection {
101+
let sectionType = Int8(try parseAsInt(token: iterator.next()))
102+
let domainType = try parseAsString(token: iterator.next())
103+
let title = try parseAsString(token: iterator.next())
104+
let signature = try parseAsString(token: iterator.next())
105+
let timeStartedRecording = try parseAsDouble(token: iterator.next())
106+
let timeStoppedRecording = try parseAsDouble(token: iterator.next())
107+
let subSections = try parseIDEActivityLogSections(iterator: &iterator)
108+
let text = try parseAsString(token: iterator.next())
109+
let messages = try parseMessages(iterator: &iterator)
110+
let wasCancelled = try parseBoolean(token: iterator.next())
111+
let isQuiet = try parseBoolean(token: iterator.next())
112+
let wasFetchedFromCache = try parseBoolean(token: iterator.next())
113+
let nextToken = iterator.next()
114+
var unknown: Int?
115+
let subtitle: String
116+
// On Xcode 26.2+, the unknown integer appears before subtitle
117+
switch nextToken {
118+
case let .some(.int(integer)):
119+
unknown = Int(integer)
120+
subtitle = try String(parseAsString(token: iterator.next()))
121+
default:
122+
subtitle = String(try parseAsString(token: nextToken))
123+
}
124+
let location = try parseDocumentLocation(iterator: &iterator)
125+
let commandDetailDesc = try parseAsString(token: iterator.next())
126+
let uniqueIdentifier = try parseAsString(token: iterator.next())
127+
let localizedResultString = try parseAsString(token: iterator.next())
128+
let xcbuildSignature = try parseAsString(token: iterator.next())
129+
let attachments = try parseIDEActivityLogSectionAttachments(iterator: &iterator)
130+
if unknown == nil {
131+
unknown = isCommandLineLog ? Int(try parseAsInt(token: iterator.next())) : 0
132+
}
133+
134+
return IDEActivityLogSection(
135+
sectionType: sectionType,
136+
domainType: domainType,
137+
title: title,
138+
signature: signature,
139+
timeStartedRecording: timeStartedRecording,
140+
timeStoppedRecording: timeStoppedRecording,
141+
subSections: subSections,
142+
text: text,
143+
messages: messages,
144+
wasCancelled: wasCancelled,
145+
isQuiet: isQuiet,
146+
wasFetchedFromCache: wasFetchedFromCache,
147+
subtitle: subtitle,
148+
location: location,
149+
commandDetailDesc: commandDetailDesc,
150+
uniqueIdentifier: uniqueIdentifier,
151+
localizedResultString: localizedResultString,
152+
xcbuildSignature: xcbuildSignature,
153+
attachments: attachments,
154+
unknown: unknown ?? 0
155+
)
121156
}
122157

123158
public func parseIDEActivityLogUnitTestSection(iterator: inout IndexingIterator<[Token]>)
@@ -389,15 +424,28 @@ public class ActivityParser {
389424
minorVersion: try parseAsInt(token: iterator.next()),
390425
metrics: try parseAsJson(token: iterator.next(),
391426
type: jsonType),
427+
buildOperationMetrics: nil,
392428
backtrace: nil)
393429
case .some("TaskBacktrace"):
394430
let jsonType = IDEActivityLogSectionAttachment.BuildOperationTaskBacktrace.self
395431
return try IDEActivityLogSectionAttachment(identifier: identifier,
396432
majorVersion: try parseAsInt(token: iterator.next()),
397433
minorVersion: try parseAsInt(token: iterator.next()),
398434
metrics: nil,
435+
buildOperationMetrics: nil,
399436
backtrace: try parseAsJson(token: iterator.next(),
400437
type: jsonType))
438+
case .some("BuildOperationMetrics"):
439+
let jsonType = IDEActivityLogSectionAttachment.BuildOperationMetrics.self
440+
return try IDEActivityLogSectionAttachment(identifier: identifier,
441+
majorVersion: try parseAsInt(token: iterator.next()),
442+
minorVersion: try parseAsInt(token: iterator.next()),
443+
metrics: nil,
444+
buildOperationMetrics: try parseAsJson(
445+
token: iterator.next(),
446+
type: jsonType
447+
),
448+
backtrace: nil)
401449
default:
402450
throw XCLogParserError.parseError("Unexpected attachment identifier \(identifier)")
403451
}

Sources/XCLogParser/activityparser/IDEActivityModel.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,19 +658,22 @@ public class IDEActivityLogSectionAttachment: Encodable {
658658
public let majorVersion: UInt64
659659
public let minorVersion: UInt64
660660
public let metrics: BuildOperationTaskMetrics?
661+
public let buildOperationMetrics: BuildOperationMetrics?
661662
public let backtrace: BuildOperationTaskBacktrace?
662663

663664
public init(
664665
identifier: String,
665666
majorVersion: UInt64,
666667
minorVersion: UInt64,
667668
metrics: BuildOperationTaskMetrics?,
669+
buildOperationMetrics: BuildOperationMetrics?,
668670
backtrace: BuildOperationTaskBacktrace?
669671
) throws {
670672
self.identifier = identifier
671673
self.majorVersion = majorVersion
672674
self.minorVersion = minorVersion
673675
self.metrics = metrics
676+
self.buildOperationMetrics = buildOperationMetrics
674677
self.backtrace = backtrace
675678
}
676679

@@ -769,4 +772,23 @@ public class IDEActivityLogSectionAttachment: Encodable {
769772
private struct EmptyObject: Codable {
770773
// Empty struct for objects with no properties
771774
}
775+
776+
public struct BuildOperationMetrics: Codable {
777+
public let clangCacheHits: Int
778+
public let clangCacheMisses: Int
779+
public let swiftCacheHits: Int
780+
public let swiftCacheMisses: Int
781+
782+
public init(
783+
clangCacheHits: Int,
784+
clangCacheMisses: Int,
785+
swiftCacheHits: Int,
786+
swiftCacheMisses: Int
787+
) {
788+
self.clangCacheHits = clangCacheHits
789+
self.clangCacheMisses = clangCacheMisses
790+
self.swiftCacheHits = swiftCacheHits
791+
self.swiftCacheMisses = swiftCacheMisses
792+
}
793+
}
772794
}

Sources/XCLogParser/commands/Version.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ import Foundation
2121

2222
public struct Version {
2323

24-
public static let current = "0.2.43"
24+
public static let current = "0.2.45"
2525

2626
}

Tests/XCLogParserTests/ActivityParserTests.swift

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,20 @@ class ActivityParserTests: XCTestCase {
9797
Token.string("501796C4-6BE4-4F80-9F9D-3269617ECC17"),
9898
Token.string("localizedResultString"),
9999
Token.string("xcbuildSignature"),
100-
Token.list(2),
100+
Token.list(3),
101101
Token.classNameRef("IDEFoundation.IDEActivityLogSectionAttachment"),
102102
Token.string("com.apple.dt.ActivityLogSectionAttachment.TaskBacktrace"),
103103
Token.int(1),
104104
Token.int(0),
105105
// swiftlint:disable:next line_length
106106
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":{}}}]"#),
107107
Token.classNameRef("IDEFoundation.IDEActivityLogSectionAttachment"),
108+
Token.string("com.apple.dt.ActivityLogSectionAttachment.BuildOperationMetrics"),
109+
Token.int(1),
110+
Token.int(0),
111+
// swiftlint:disable:next line_length
112+
Token.json(#"{"clangCacheHits":0,"clangCacheMisses":2,"swiftCacheHits":0,"swiftCacheMisses":8}"#),
113+
Token.classNameRef("IDEFoundation.IDEActivityLogSectionAttachment"),
108114
Token.string("com.apple.dt.ActivityLogSectionAttachment.TaskMetrics"),
109115
Token.int(1),
110116
Token.int(0),
@@ -142,6 +148,36 @@ class ActivityParserTests: XCTestCase {
142148
return startTokens + logMessageTokens + endTokens
143149
}()
144150

151+
// Xcode 26.2 format: unknown integer appears before subtitle
152+
lazy var IDEActivityLogSectionTokensXcode262: [Token] = {
153+
let startTokens = [Token.int(2),
154+
Token.string("com.apple.dt.IDE.BuildLogSection"),
155+
Token.string("Prepare build"),
156+
Token.string("Prepare build"),
157+
Token.double(575479851.278759),
158+
Token.double(575479851.778325),
159+
Token.null,
160+
Token.string("note: Using legacy build system"),
161+
Token.list(1),
162+
Token.className("IDEActivityLogMessage"),
163+
Token.classNameRef("IDEActivityLogMessage"),
164+
]
165+
let logMessageTokens = IDEActivityLogMessageTokens
166+
let endTokens = [Token.int(1),
167+
Token.int(0),
168+
Token.int(1),
169+
Token.int(42), // unknown integer before subtitle (Xcode 26.2+)
170+
Token.string("subtitle"),
171+
Token.null,
172+
Token.string("commandDetailDesc"),
173+
Token.string("501796C4-6BE4-4F80-9F9D-3269617ECC17"),
174+
Token.string("localizedResultString"),
175+
Token.string("xcbuildSignature"),
176+
Token.list(0), // attachments
177+
]
178+
return startTokens + logMessageTokens + endTokens
179+
}()
180+
145181
let IDEConsoleItemTokens: [Token] = [
146182
Token.className("IDEConsoleItem"),
147183
Token.classNameRef("IDEConsoleItem"),
@@ -345,9 +381,11 @@ class ActivityParserTests: XCTestCase {
345381
XCTAssertEqual("501796C4-6BE4-4F80-9F9D-3269617ECC17", logSection.uniqueIdentifier)
346382
XCTAssertEqual("localizedResultString", logSection.localizedResultString)
347383
XCTAssertEqual("xcbuildSignature", logSection.xcbuildSignature)
348-
XCTAssertEqual(2, logSection.attachments.count)
384+
XCTAssertEqual(3, logSection.attachments.count)
349385
XCTAssertEqual(logSection.attachments[0].backtrace?.frames.first?.category, .ruleNeverBuilt)
350-
XCTAssertEqual(logSection.attachments[1].metrics?.wcDuration, 1)
386+
print(logSection.attachments)
387+
XCTAssertEqual(logSection.attachments[1].buildOperationMetrics?.clangCacheMisses, 2)
388+
XCTAssertEqual(logSection.attachments[2].metrics?.wcDuration, 1)
351389
XCTAssertEqual(0, logSection.unknown)
352390
}
353391

@@ -379,6 +417,34 @@ class ActivityParserTests: XCTestCase {
379417
XCTAssertEqual(0, logSection.unknown)
380418
}
381419

420+
func testParseIDEActivityLogSectionXcode262() throws {
421+
parser.logVersion = 12
422+
let tokens = IDEActivityLogSectionTokensXcode262
423+
var iterator = tokens.makeIterator()
424+
let logSection = try parser.parseIDEActivityLogSection(iterator: &iterator)
425+
XCTAssertEqual(2, logSection.sectionType)
426+
XCTAssertEqual("com.apple.dt.IDE.BuildLogSection", logSection.domainType)
427+
XCTAssertEqual("Prepare build", logSection.title)
428+
XCTAssertEqual("Prepare build", logSection.signature)
429+
XCTAssertEqual(575479851.278759, logSection.timeStartedRecording)
430+
XCTAssertEqual(575479851.778325, logSection.timeStoppedRecording)
431+
XCTAssertEqual(0, logSection.subSections.count)
432+
XCTAssertEqual("note: Using legacy build system", logSection.text)
433+
XCTAssertEqual(1, logSection.messages.count)
434+
XCTAssertTrue(logSection.wasCancelled)
435+
XCTAssertFalse(logSection.isQuiet)
436+
XCTAssertTrue(logSection.wasFetchedFromCache)
437+
XCTAssertEqual("subtitle", logSection.subtitle)
438+
XCTAssertEqual("", logSection.location.documentURLString)
439+
XCTAssertEqual(0, logSection.location.timestamp)
440+
XCTAssertEqual("commandDetailDesc", logSection.commandDetailDesc)
441+
XCTAssertEqual("501796C4-6BE4-4F80-9F9D-3269617ECC17", logSection.uniqueIdentifier)
442+
XCTAssertEqual("localizedResultString", logSection.localizedResultString)
443+
XCTAssertEqual("xcbuildSignature", logSection.xcbuildSignature)
444+
XCTAssertEqual(0, logSection.attachments.count)
445+
XCTAssertEqual(42, logSection.unknown)
446+
}
447+
382448
func testParseActivityLog() throws {
383449
let activityLog = try parser.parseIDEActiviyLogFromTokens(IDEActivityLogTokens)
384450
XCTAssertEqual(10, activityLog.version)

0 commit comments

Comments
 (0)