Skip to content

Commit 7cf0098

Browse files
authored
Merge pull request #3014 from apple/xcbuild-output-parsing-robustness
Improve robustness of XCBuild output parser
2 parents 4f70efb + 15eb623 commit 7cf0098

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

Sources/XCBuildSupport/XCBuildDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension XCBuildDelegate: XCBuildOutputParserDelegate {
5050
case .taskStarted(let info):
5151
queue.async {
5252
self.didEmitProgressOutput = true
53-
let text = self.isVerbose ? info.executionDescription + "\n" + info.commandLineDisplayString : info.executionDescription
53+
let text = self.isVerbose ? [info.executionDescription, info.commandLineDisplayString].compactMap { $0 }.joined(separator: "\n") : info.executionDescription
5454
self.progressAnimation.update(step: self.percentComplete, total: 100, text: text)
5555
}
5656
case .taskOutput(let info):

Sources/XCBuildSupport/XCBuildOutputParser.swift

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -62,25 +62,25 @@ public enum XCBuildMessage {
6262
}
6363

6464
public struct TaskUpToDateInfo {
65-
let targetID: Int
66-
let targetSignature: String
65+
let targetID: Int?
66+
let taskSignature: String
6767
let parentTaskID: Int?
6868
}
6969

7070
public struct TaskStartedInfo {
7171
let taskID: Int
72-
let targetID: Int
73-
let targetSignature: String
72+
let targetID: Int?
73+
let taskSignature: String
7474
let parentTaskID: Int?
7575
let ruleInfo: String
7676
let interestingPath: AbsolutePath?
77-
let commandLineDisplayString: String
77+
let commandLineDisplayString: String?
7878
let executionDescription: String
7979
}
8080

8181
public struct TaskDiagnosticInfo {
8282
let taskID: Int
83-
let targetID: Int
83+
let targetID: Int?
8484
let message: String
8585
}
8686

@@ -211,8 +211,8 @@ extension XCBuildMessage.DidUpdateProgressInfo: Decodable, Equatable {
211211
public init(from decoder: Decoder) throws {
212212
let container = try decoder.container(keyedBy: CodingKeys.self)
213213
message = try container.decode(String.self, forKey: .message)
214-
percentComplete = try Double(container.decode(String.self, forKey: .percentComplete))!
215-
showInLog = try Bool(container.decode(String.self, forKey: .showInLog))!
214+
percentComplete = try container.decodeDoubleOrString(forKey: .percentComplete)
215+
showInLog = try container.decodeBoolOrString(forKey: .showInLog)
216216
}
217217
}
218218

@@ -227,7 +227,7 @@ extension XCBuildMessage.TargetStartedInfo: Decodable, Equatable {
227227

228228
public init(from decoder: Decoder) throws {
229229
let container = try decoder.container(keyedBy: CodingKeys.self)
230-
targetID = try Int(container.decode(String.self, forKey: .targetID))!
230+
targetID = try container.decodeIntOrString(forKey: .targetID)
231231
targetGUID = try container.decode(PIF.GUID.self, forKey: .targetGUID)
232232
targetName = try container.decode(String.self, forKey: .targetName)
233233
type = try container.decode(Kind.self, forKey: .type)
@@ -241,31 +241,30 @@ extension XCBuildMessage.TargetCompleteInfo: Decodable, Equatable {
241241

242242
public init(from decoder: Decoder) throws {
243243
let container = try decoder.container(keyedBy: CodingKeys.self)
244-
targetID = try Int(container.decode(String.self, forKey: .targetID))!
244+
targetID = try container.decodeIntOrString(forKey: .targetID)
245245
}
246246
}
247247

248248
extension XCBuildMessage.TaskUpToDateInfo: Decodable, Equatable {
249249
enum CodingKeys: String, CodingKey {
250250
case targetID
251-
case targetSignature = "signature"
251+
case taskSignature = "signature"
252252
case parentTaskID = "parentID"
253253
}
254254

255255
public init(from decoder: Decoder) throws {
256256
let container = try decoder.container(keyedBy: CodingKeys.self)
257-
targetID = try Int(container.decode(String.self, forKey: .targetID))!
258-
targetSignature = try container.decode(String.self, forKey: .targetSignature)
259-
let parentTaskIDString = try container.decode(String.self, forKey: .parentTaskID)
260-
parentTaskID = !parentTaskIDString.isEmpty ? Int(parentTaskIDString)! : nil
257+
targetID = try container.decodeIntOrStringIfPresent(forKey: .targetID)
258+
taskSignature = try container.decode(String.self, forKey: .taskSignature)
259+
parentTaskID = try container.decodeIntOrStringIfPresent(forKey: .parentTaskID)
261260
}
262261
}
263262

264263
extension XCBuildMessage.TaskStartedInfo: Decodable, Equatable {
265264
enum CodingKeys: String, CodingKey {
266265
case taskID = "id"
267266
case targetID
268-
case targetSignature = "signature"
267+
case taskSignature = "signature"
269268
case parentTaskID = "parentID"
270269
case ruleInfo
271270
case interestingPath
@@ -275,14 +274,12 @@ extension XCBuildMessage.TaskStartedInfo: Decodable, Equatable {
275274

276275
public init(from decoder: Decoder) throws {
277276
let container = try decoder.container(keyedBy: CodingKeys.self)
278-
taskID = try Int(container.decode(String.self, forKey: .taskID))!
279-
targetID = try Int(container.decode(String.self, forKey: .targetID))!
280-
targetSignature = try container.decode(String.self, forKey: .targetSignature)
281-
let parentTaskIDString = try container.decode(String.self, forKey: .parentTaskID)
282-
parentTaskID = !parentTaskIDString.isEmpty ? Int(parentTaskIDString)! : nil
277+
taskID = try container.decodeIntOrString(forKey: .taskID)
278+
targetID = try container.decodeIntOrStringIfPresent(forKey: .targetID)
279+
taskSignature = try container.decode(String.self, forKey: .taskSignature)
280+
parentTaskID = try container.decodeIntOrStringIfPresent(forKey: .parentTaskID)
283281
ruleInfo = try container.decode(String.self, forKey: .ruleInfo)
284-
let interestingPathString = try container.decode(String.self, forKey: .interestingPath)
285-
interestingPath = !interestingPathString.isEmpty ? AbsolutePath(interestingPathString) : nil
282+
interestingPath = try container.decodeIfPresent(AbsolutePath.self, forKey: .interestingPath)
286283
commandLineDisplayString = try container.decode(String.self, forKey: .commandLineDisplayString)
287284
executionDescription = try container.decode(String.self, forKey: .executionDescription)
288285
}
@@ -296,7 +293,7 @@ extension XCBuildMessage.TaskOutputInfo: Decodable, Equatable {
296293

297294
public init(from decoder: Decoder) throws {
298295
let container = try decoder.container(keyedBy: CodingKeys.self)
299-
taskID = try Int(container.decode(String.self, forKey: .taskID))!
296+
taskID = try container.decodeIntOrString(forKey: .taskID)
300297
data = try container.decode(String.self, forKey: .data)
301298
}
302299
}
@@ -311,7 +308,7 @@ extension XCBuildMessage.TaskCompleteInfo: Decodable, Equatable {
311308

312309
public init(from decoder: Decoder) throws {
313310
let container = try decoder.container(keyedBy: CodingKeys.self)
314-
taskID = try Int(container.decode(String.self, forKey: .taskID))!
311+
taskID = try container.decodeIntOrString(forKey: .taskID)
315312
result = try container.decode(Result.self, forKey: .result)
316313
signalled = try container.decode(Bool.self, forKey: .signalled)
317314
}
@@ -361,3 +358,55 @@ extension XCBuildMessage: Decodable, Equatable {
361358
}
362359
}
363360
}
361+
362+
fileprivate extension KeyedDecodingContainer {
363+
func decodeBoolOrString(forKey key: Key) throws -> Bool {
364+
do {
365+
return try decode(Bool.self, forKey: key)
366+
} catch {
367+
let string = try decode(String.self, forKey: key)
368+
guard let value = Bool(string) else {
369+
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Could not parse '\(string)' as Bool for key \(key)")
370+
}
371+
return value
372+
}
373+
}
374+
375+
func decodeDoubleOrString(forKey key: Key) throws -> Double {
376+
do {
377+
return try decode(Double.self, forKey: key)
378+
} catch {
379+
let string = try decode(String.self, forKey: key)
380+
guard let value = Double(string) else {
381+
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Could not parse '\(string)' as Double for key \(key)")
382+
}
383+
return value
384+
}
385+
}
386+
387+
func decodeIntOrString(forKey key: Key) throws -> Int {
388+
do {
389+
return try decode(Int.self, forKey: key)
390+
} catch {
391+
let string = try decode(String.self, forKey: key)
392+
guard let value = Int(string) else {
393+
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Could not parse '\(string)' as Int for key \(key)")
394+
}
395+
return value
396+
}
397+
}
398+
399+
func decodeIntOrStringIfPresent(forKey key: Key) throws -> Int? {
400+
do {
401+
return try decodeIfPresent(Int.self, forKey: key)
402+
} catch {
403+
guard let string = try decodeIfPresent(String.self, forKey: key), !string.isEmpty else {
404+
return nil
405+
}
406+
guard let value = Int(string) else {
407+
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Could not parse '\(string)' as Int for key \(key)")
408+
}
409+
return value
410+
}
411+
}
412+
}

0 commit comments

Comments
 (0)