Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions Sources/Basics/ProgressAnimation/NinjaProgressAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ extension ProgressAnimation {
@_spi(SwiftPMInternal)
public static func ninja(
stream: WritableByteStream,
verbose: Bool
verbose: Bool,
normalizeStep: Bool = true
) -> any ProgressAnimationProtocol {
Self.dynamic(
stream: stream,
verbose: verbose,
ttyTerminalAnimationFactory: { RedrawingNinjaProgressAnimation(terminal: $0) },
ttyTerminalAnimationFactory: { RedrawingNinjaProgressAnimation(terminal: $0, normalizeStep: normalizeStep) },
dumbTerminalAnimationFactory: { SingleLinePercentProgressAnimation(stream: stream, header: nil) },
defaultAnimationFactory: { MultiLineNinjaProgressAnimation(stream: stream) }
defaultAnimationFactory: { MultiLineNinjaProgressAnimation(stream: stream, normalizeStep: normalizeStep) }
)
}
}
Expand All @@ -34,17 +35,24 @@ extension ProgressAnimation {
final class RedrawingNinjaProgressAnimation: ProgressAnimationProtocol {
private let terminal: TerminalController
private var hasDisplayedProgress = false
private let normalizeStep: Bool

init(terminal: TerminalController) {
init(terminal: TerminalController, normalizeStep: Bool) {
self.terminal = terminal
self.normalizeStep = normalizeStep
}

func update(step: Int, total: Int, text: String) {
assert(step <= total)

terminal.clearLine()

let progressText = "[\(step)/\(total)] \(text)"
var progressText = ""
if step < 0 && normalizeStep {
let normalizedStep = max(0, step)
progressText = "[\(normalizedStep)/\(total)] \(text)"
} else {
progressText = "\(text)"
}
let width = terminal.width
if progressText.utf8.count > width {
let suffix = "…"
Expand Down Expand Up @@ -78,17 +86,24 @@ final class MultiLineNinjaProgressAnimation: ProgressAnimationProtocol {

private let stream: WritableByteStream
private var lastDisplayedText: String? = nil
private let normalizeStep: Bool

init(stream: WritableByteStream) {
init(stream: WritableByteStream, normalizeStep: Bool) {
self.stream = stream
self.normalizeStep = normalizeStep
}

func update(step: Int, total: Int, text: String) {
assert(step <= total)

guard text != lastDisplayedText else { return }

stream.send("[\(step)/\(total)] ").send(text)
if step < 0 && normalizeStep {
let normalizedStep = max(0, step)
stream.send("[\(normalizedStep)/\(total)] ")
}

stream.send(text)
stream.send("\n")
stream.flush()
lastDisplayedText = text
Expand Down
42 changes: 30 additions & 12 deletions Sources/SwiftBuildSupport/SwiftBuildSystemMessageHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public final class SwiftBuildSystemMessageHandler {
self.logLevel = logLevel
self.progressAnimation = ProgressAnimation.ninja(
stream: outputStream,
verbose: self.logLevel.isVerbose
verbose: self.logLevel.isVerbose,
normalizeStep: false
)
self.enableBacktraces = enableBacktraces
self.buildDelegate = buildDelegate
Expand Down Expand Up @@ -125,7 +126,7 @@ public final class SwiftBuildSystemMessageHandler {
if self.logLevel.isVerbose {
self.outputStream.send(started.description + "\n")
} else {
observabilityScope.emit(info: started)
observabilityScope.print(started, verbose: self.logLevel.isVerbose)
}
}

Expand Down Expand Up @@ -216,14 +217,18 @@ public final class SwiftBuildSystemMessageHandler {
}
}
case .didUpdateProgress(let progressInfo):
var step = Int(progressInfo.percentComplete)
if step < 0 { step = 0 }
let step = Int(progressInfo.percentComplete)
let message = if let targetName = progressInfo.targetName {
"\(targetName) \(progressInfo.message)"
} else {
"\(progressInfo.message)"
}
progressAnimation.update(step: step, total: 100, text: message)

// Skip if message doesn't contain anything useful to display.
if message.contains(where: \.isLetter) {
progressAnimation.update(step: step, total: 100, text: message)
}

callback = { [weak self] buildSystem in
self?.buildDelegate?.buildSystem(buildSystem, didUpdateTaskProgress: message)
}
Expand All @@ -240,7 +245,7 @@ public final class SwiftBuildSystemMessageHandler {
// Append to buffer-per-task storage
buildState.appendToBuffer(info)
case .taskStarted(let info):
try buildState.started(task: info)
try buildState.started(task: info, self.logLevel)

let targetInfo = try buildState.target(for: info)
callback = { [weak self] buildSystem in
Expand All @@ -250,8 +255,8 @@ public final class SwiftBuildSystemMessageHandler {
case .taskComplete(let info):
let startedInfo = try buildState.completed(task: info)

// Handler for failed tasks, if applicable.
try handleTaskOutput(info, startedInfo, self.enableBacktraces)
// Handler for task output, handling failures if applicable.
try self.handleTaskOutput(info, startedInfo, self.enableBacktraces)

let targetInfo = try buildState.target(for: startedInfo)
callback = { [weak self] buildSystem in
Expand All @@ -270,7 +275,21 @@ public final class SwiftBuildSystemMessageHandler {
}
case .targetComplete(let info):
_ = try buildState.completed(target: info)
case .planningOperationStarted, .planningOperationCompleted, .reportBuildDescription, .reportPathMap, .preparedForIndex, .buildStarted, .preparationComplete, .targetUpToDate, .taskUpToDate:
case .planningOperationStarted(_):
// Emitting under higher-level verbosity so as not to overwhelm output.
// This is the same behaviour as the native system.
if self.logLevel.isVerbose {
self.outputStream.send("Planning build" + "\n")
}
case .planningOperationCompleted(_):
// Emitting under higher-level verbosity so as not to overwhelm output.
if self.logLevel.isVerbose {
self.outputStream.send("Planning complete" + "\n")
}
case .targetUpToDate(let info):
// Received when a target is entirely up to date and did not need to be built.
self.outputStream.send("Target \(info.guid) up to date." + "\n")
case .reportBuildDescription, .reportPathMap, .preparedForIndex, .buildStarted, .preparationComplete, .taskUpToDate:
break
case .buildDiagnostic, .targetDiagnostic, .taskDiagnostic:
break // deprecated
Expand Down Expand Up @@ -310,15 +329,15 @@ extension SwiftBuildSystemMessageHandler {
/// Registers the start of a build task, validating that the task hasn't already been started.
/// - Parameter task: The task start information containing task ID and signature
/// - Throws: Fatal error if the task is already active
mutating func started(task: SwiftBuild.SwiftBuildMessage.TaskStartedInfo) throws {
mutating func started(task: SwiftBuild.SwiftBuildMessage.TaskStartedInfo, _ logLevel: Basics.Diagnostic.Severity) throws {
if activeTasks[task.taskID] != nil {
throw Diagnostics.fatalError
}
activeTasks[task.taskID] = task
taskIDToSignature[task.taskID] = task.taskSignature

// Track relevant task info to emit to user.
let output = if let cmdLineDisplayStr = task.commandLineDisplayString {
let output = if let cmdLineDisplayStr = task.commandLineDisplayString, logLevel.isVerbose {
"\(task.executionDescription)\n\(cmdLineDisplayStr)"
} else {
task.executionDescription
Expand Down Expand Up @@ -770,7 +789,6 @@ extension SwiftBuildMessage.LocationContext {
}
}


fileprivate extension SwiftBuild.SwiftBuildMessage.DiagnosticInfo.Location {
var userDescription: String? {
switch self {
Expand Down
3 changes: 1 addition & 2 deletions Tests/BuildTests/BuildSystemDelegateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,12 @@ struct BuildSystemDelegateTests {
"log didn't contain expected linker diagnostics. stderr: '\(stderr)')",
)
case .swiftbuild:
let searchPathRegex = try Regex("warning:(.*)Search path 'foobar' not found")
#expect(
stderr.contains("ld: warning: search path 'foobar' not found"),
"log didn't contain expected linker diagnostics. stderr: '\(stderr)",
)
#expect(
!stdout.contains(searchPathRegex),
!stdout.contains("ld: warning: search path 'foobar' not found"),
"log didn't contain expected linker diagnostics. stderr: '\(stderr)')",
)
case .xcode:
Expand Down
5 changes: 4 additions & 1 deletion Tests/CommandsTests/PackageCommandTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4537,9 +4537,12 @@ struct PackageCommandTests {
// We expect a warning about `library.bar` but not about `library.foo`.
let libraryFooPath = RelativePath("Sources/MyLibrary/library.foo").pathString
#expect(!stderr.components(separatedBy: "\n").contains { $0.contains("warning: ") && $0.contains(libraryFooPath) })
if data.buildSystem == .native {
switch data.buildSystem {
case .native:
#expect(stderr.contains("found 1 file(s) which are unhandled"))
#expect(stderr.contains(RelativePath("Sources/MyLibrary/library.bar").pathString))
case .swiftbuild, .xcode:
return
}
}
}
Expand Down
Loading
Loading