Skip to content

Commit dc27f4b

Browse files
hartbitaciidgh
authored andcommitted
Improve build animation
Improved the build animation by: * Printing `[finished_count/scanned_count]` instead of `[finished_count/started_count]` to get a higher estimate quicker of the total number of tasks * Printing only when `finished_count` changes, and not when the second value changes, to avoid initial `[0/1]` updates. * Reworking the progress text to show `Compiling MODULE_NAME FILE_NAME`
1 parent af00e15 commit dc27f4b

File tree

4 files changed

+121
-109
lines changed

4 files changed

+121
-109
lines changed

Sources/Build/BuildDelegate.swift

Lines changed: 98 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
198198

199199
/// Swift parsers keyed by llbuild command name.
200200
private var swiftParsers: [String: SwiftCompilerOutputParser] = [:]
201+
/// Target name keyed by llbuild command name.
202+
private let targetNames: [String: String]
201203

202204
public init(
203205
plan: BuildPlan,
@@ -212,9 +214,15 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
212214
self.progressAnimation = progressAnimation
213215

214216
let buildConfig = plan.buildParameters.configuration.dirname
217+
218+
targetNames = Dictionary(uniqueKeysWithValues: plan.targetMap.map({ (target, description) in
219+
return (target.getCommandName(config: buildConfig), target.name)
220+
}))
221+
215222
swiftParsers = Dictionary(uniqueKeysWithValues: plan.targetMap.compactMap({ (target, description) in
216223
guard case .swift = description else { return nil }
217-
return (target.getCommandName(config: buildConfig), SwiftCompilerOutputParser(delegate: self))
224+
let parser = SwiftCompilerOutputParser(targetName: target.name, delegate: self)
225+
return (target.getCommandName(config: buildConfig), parser)
218226
}))
219227
}
220228

@@ -235,6 +243,14 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
235243
}
236244

237245
public func commandStatusChanged(_ command: SPMLLBuild.Command, kind: CommandStatusKind) {
246+
guard !isVerbose else { return }
247+
guard command.shouldShowStatus else { return }
248+
guard !swiftParsers.keys.contains(command.name) else { return }
249+
250+
queue.async {
251+
self.taskTracker.commandStatusChanged(command, kind: kind)
252+
self.updateProgress()
253+
}
238254
}
239255

240256
public func commandPreparing(_ command: SPMLLBuild.Command) {
@@ -243,13 +259,10 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
243259
public func commandStarted(_ command: SPMLLBuild.Command) {
244260
guard command.shouldShowStatus else { return }
245261

246-
queue.sync {
247-
if isVerbose {
248-
outputStream <<< command.verboseDescription <<< "\n"
249-
outputStream.flush()
250-
} else if !swiftParsers.keys.contains(command.name) {
251-
taskTracker.commandStarted(command)
252-
updateProgress()
262+
queue.async {
263+
if self.isVerbose {
264+
self.outputStream <<< command.verboseDescription <<< "\n"
265+
self.outputStream.flush()
253266
}
254267
}
255268
}
@@ -259,13 +272,14 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
259272
}
260273

261274
public func commandFinished(_ command: SPMLLBuild.Command, result: CommandResult) {
275+
guard !isVerbose else { return }
262276
guard command.shouldShowStatus else { return }
263277
guard !swiftParsers.keys.contains(command.name) else { return }
264-
guard !isVerbose else { return }
265278

266-
queue.sync {
267-
taskTracker.commandFinished(command, result: result)
268-
updateProgress()
279+
queue.async {
280+
let targetName = self.targetNames[command.name]
281+
self.taskTracker.commandFinished(command, result: result, targetName: targetName)
282+
self.updateProgress()
269283
}
270284
}
271285

@@ -306,9 +320,11 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
306320
if let swiftParser = swiftParsers[command.name] {
307321
swiftParser.parse(bytes: data)
308322
} else {
309-
progressAnimation.clear()
310-
outputStream <<< data
311-
outputStream.flush()
323+
queue.async {
324+
self.progressAnimation.clear()
325+
self.outputStream <<< data
326+
self.outputStream.flush()
327+
}
312328
}
313329
}
314330

@@ -327,37 +343,37 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
327343
return false
328344
}
329345

330-
func swiftCompilerDidOutputMessage(_ message: SwiftCompilerMessage) {
331-
queue.sync {
332-
if isVerbose {
346+
func swiftCompilerOutputParser(_ parser: SwiftCompilerOutputParser, didParse message: SwiftCompilerMessage) {
347+
queue.async {
348+
if self.isVerbose {
333349
if let text = message.verboseProgressText {
334-
outputStream <<< text <<< "\n"
335-
outputStream.flush()
350+
self.outputStream <<< text <<< "\n"
351+
self.outputStream.flush()
336352
}
337353
} else {
338-
taskTracker.swiftCompilerDidOuputMessage(message)
339-
updateProgress()
354+
self.taskTracker.swiftCompilerDidOuputMessage(message, targetName: parser.targetName)
355+
self.updateProgress()
340356
}
341357

342358
if let output = message.standardOutput {
343-
if !isVerbose {
344-
progressAnimation.clear()
359+
if !self.isVerbose {
360+
self.progressAnimation.clear()
345361
}
346362

347-
outputStream <<< output
348-
outputStream.flush()
363+
self.outputStream <<< output
364+
self.outputStream.flush()
349365
}
350366
}
351367
}
352368

353-
func swiftCompilerOutputParserDidFail(withError error: Error) {
369+
func swiftCompilerOutputParser(_ parser: SwiftCompilerOutputParser, didFailWith error: Error) {
354370
let message = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription
355371
diagnostics.emit(data: SwiftCompilerOutputParsingError(message: message))
356372
onCommmandFailure?()
357373
}
358374

359375
private func updateProgress() {
360-
if let progressText = taskTracker.latestRunningText {
376+
if let progressText = taskTracker.latestFinishedText {
361377
progressAnimation.update(
362378
step: taskTracker.finishedCount,
363379
total: taskTracker.totalCount,
@@ -368,103 +384,101 @@ public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParser
368384

369385
/// Tracks tasks based on command status and swift compiler output.
370386
fileprivate struct CommandTaskTracker {
371-
private struct Task {
372-
let identifier: String
373-
let text: String
374-
}
375-
376-
private var tasks: [Task] = []
377-
private(set) var finishedCount = 0
378387
private(set) var totalCount = 0
388+
private(set) var finishedCount = 0
389+
private var swiftTaskProgressTexts: [Int: String] = [:]
379390

380391
/// The last task text before the task list was emptied.
381-
private var lastText: String?
382-
383-
var latestRunningText: String? {
384-
return tasks.last?.text ?? lastText
385-
}
386-
387-
mutating func commandStarted(_ command: SPMLLBuild.Command) {
388-
addTask(identifier: command.name, text: command.description)
389-
totalCount += 1
392+
private(set) var latestFinishedText: String?
393+
394+
mutating func commandStatusChanged(_ command: SPMLLBuild.Command, kind: CommandStatusKind) {
395+
switch kind {
396+
case .isScanning:
397+
totalCount += 1
398+
break
399+
case .isUpToDate:
400+
totalCount -= 1
401+
break
402+
case .isComplete:
403+
break
404+
}
390405
}
391406

392-
mutating func commandFinished(_ command: SPMLLBuild.Command, result: CommandResult) {
393-
removeTask(identifier: command.name)
407+
mutating func commandFinished(_ command: SPMLLBuild.Command, result: CommandResult, targetName: String?) {
408+
latestFinishedText = progressText(of: command, targetName: targetName)
394409

395410
switch result {
396-
case .succeeded:
411+
case .succeeded, .skipped:
397412
finishedCount += 1
398-
case .cancelled, .failed, .skipped:
413+
case .cancelled, .failed:
399414
break
400415
}
401416
}
402417

403-
mutating func swiftCompilerDidOuputMessage(_ message: SwiftCompilerMessage) {
418+
mutating func swiftCompilerDidOuputMessage(_ message: SwiftCompilerMessage, targetName: String) {
404419
switch message.kind {
405420
case .began(let info):
406-
if let text = message.progressText {
407-
addTask(identifier: info.pid.description, text: text)
421+
if let text = progressText(of: message, targetName: targetName) {
422+
swiftTaskProgressTexts[info.pid] = text
408423
}
409424

410425
totalCount += 1
411426
case .finished(let info):
412-
removeTask(identifier: info.pid.description)
427+
if let progressText = swiftTaskProgressTexts[info.pid] {
428+
latestFinishedText = progressText
429+
swiftTaskProgressTexts[info.pid] = nil
430+
}
431+
413432
finishedCount += 1
414-
case .signalled(let info):
415-
removeTask(identifier: info.pid.description)
416-
case .skipped:
433+
case .signalled, .skipped:
417434
break
418435
}
419436
}
420-
421-
private mutating func addTask(identifier: String, text: String) {
422-
tasks.append(Task(identifier: identifier, text: text))
423-
}
424-
425-
private mutating func removeTask(identifier: String) {
426-
if let index = tasks.index(where: { $0.identifier == identifier }) {
427-
if tasks.count == 1 {
428-
lastText = tasks[0].text
429-
}
430437

431-
tasks.remove(at: index)
438+
private func progressText(of command: SPMLLBuild.Command, targetName: String?) -> String {
439+
// Transforms descriptions like "Linking ./.build/x86_64-apple-macosx/debug/foo" into "Linking foo".
440+
if let firstSpaceIndex = command.description.firstIndex(of: " "),
441+
let lastDirectorySeperatorIndex = command.description.lastIndex(of: "/")
442+
{
443+
let action = command.description[..<firstSpaceIndex]
444+
let fileNameStartIndex = command.description.index(after: lastDirectorySeperatorIndex)
445+
let fileName = command.description[fileNameStartIndex...]
446+
447+
if let targetName = targetName {
448+
return "\(action) \(targetName) \(fileName)"
449+
} else {
450+
return "\(action) \(fileName)"
451+
}
452+
} else {
453+
return command.description
432454
}
433455
}
434-
}
435456

436-
extension SwiftCompilerMessage {
437-
fileprivate var progressText: String? {
438-
if case .began(let info) = kind {
439-
switch name {
457+
private func progressText(of message: SwiftCompilerMessage, targetName: String) -> String? {
458+
if case .began(let info) = message.kind {
459+
switch message.name {
440460
case "compile":
441461
if let sourceFile = info.inputs.first {
442-
return generateProgressText(prefix: "Compiling", file: sourceFile)
462+
return "Compiling \(targetName) \(AbsolutePath(sourceFile).components.last!)"
443463
}
444464
case "link":
445-
if let imageFile = info.outputs.first(where: { $0.type == "image" })?.path {
446-
return generateProgressText(prefix: "Linking", file: imageFile)
447-
}
465+
return "Linking \(targetName)"
448466
case "merge-module":
449-
if let moduleFile = info.outputs.first(where: { $0.type == "swiftmodule" })?.path {
450-
return generateProgressText(prefix: "Merging module", file: moduleFile)
451-
}
467+
return "Merging module \(targetName)"
452468
case "generate-dsym":
453-
if let dSYMFile = info.outputs.first(where: { $0.type == "dSYM" })?.path {
454-
return generateProgressText(prefix: "Generating dSYM", file: dSYMFile)
455-
}
469+
return "Generating \(targetName) dSYM"
456470
case "generate-pch":
457-
if let pchFile = info.outputs.first(where: { $0.type == "pch" })?.path {
458-
return generateProgressText(prefix: "Generating PCH", file: pchFile)
459-
}
471+
return "Generating \(targetName) PCH"
460472
default:
461473
break
462474
}
463475
}
464476

465477
return nil
466478
}
479+
}
467480

481+
extension SwiftCompilerMessage {
468482
fileprivate var verboseProgressText: String? {
469483
if case .began(let info) = kind {
470484
return ([info.commandExecutable] + info.commandArguments).joined(separator: " ")
@@ -482,10 +496,4 @@ extension SwiftCompilerMessage {
482496
return nil
483497
}
484498
}
485-
486-
private func generateProgressText(prefix: String, file: String) -> String {
487-
// FIXME: Eliminate cwd from here.
488-
let relativePath = AbsolutePath(file).relative(to: localFileSystem.currentWorkingDirectory!)
489-
return "\(prefix) \(relativePath)"
490-
}
491499
}

Sources/Build/SwiftCompilerOutputParser.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ struct SwiftCompilerMessage {
5050
/// Protocol for the parser delegate to get notified of parsing events.
5151
protocol SwiftCompilerOutputParserDelegate: class {
5252
/// Called for each message parsed.
53-
func swiftCompilerDidOutputMessage(_ message: SwiftCompilerMessage)
53+
func swiftCompilerOutputParser(_ parser: SwiftCompilerOutputParser, didParse message: SwiftCompilerMessage)
5454

5555
/// Called on an un-expected parsing error. No more events will be received after that.
56-
func swiftCompilerOutputParserDidFail(withError error: Error)
56+
func swiftCompilerOutputParser(_ parser: SwiftCompilerOutputParser, didFailWith error: Error)
5757
}
5858

5959
/// Parser for the Swift compiler JSON output mode.
@@ -66,6 +66,9 @@ final class SwiftCompilerOutputParser {
6666
case parsingNewlineAfterMessage
6767
}
6868

69+
/// Name of the target the compiler is compiling.
70+
public let targetName: String
71+
6972
/// Delegate to notify of parsing events.
7073
public weak var delegate: SwiftCompilerOutputParserDelegate?
7174

@@ -82,7 +85,8 @@ final class SwiftCompilerOutputParser {
8285
private let decoder: JSONDecoder
8386

8487
/// Initializes the parser with a delegate to notify of parsing events.
85-
init(delegate: SwiftCompilerOutputParserDelegate) {
88+
init(targetName: String, delegate: SwiftCompilerOutputParserDelegate) {
89+
self.targetName = targetName
8690
self.delegate = delegate
8791
let decoder = JSONDecoder()
8892
decoder.keyDecodingStrategy = .convertFromSnakeCase
@@ -99,7 +103,7 @@ final class SwiftCompilerOutputParser {
99103
try parseImpl(bytes: bytes)
100104
} catch {
101105
hasFailed = true
102-
delegate?.swiftCompilerOutputParserDidFail(withError: error)
106+
delegate?.swiftCompilerOutputParser(self, didFailWith: error)
103107
}
104108
}
105109
}
@@ -135,7 +139,7 @@ private extension SwiftCompilerOutputParser {
135139
buffer.append(contentsOf: bytes.prefix(remainingBytes))
136140

137141
let message = try parseMessage()
138-
delegate?.swiftCompilerDidOutputMessage(message)
142+
delegate?.swiftCompilerOutputParser(self, didParse: message)
139143

140144
if case .signalled = message.kind {
141145
hasFailed = true

0 commit comments

Comments
 (0)