Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
17 changes: 13 additions & 4 deletions Sources/SWBBuildService/BuildOperationMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ final class ActiveBuild: ActiveBuildOperation {

func updateProgress(statusMessage: String, showInLog: Bool) {
if activeBuild.shouldSendStatusUpdate(showInLog: showInLog) {
activeBuild.request.send(BuildOperationProgressUpdated(statusMessage: statusMessage, percentComplete: -1, showInLog: showInLog))
activeBuild.request.send(BuildOperationProgressUpdated(statusMessage: statusMessage, percentComplete: -1, showInLog: showInLog, condensedStatusMessage: statusMessage))
}
}

Expand Down Expand Up @@ -1227,13 +1227,22 @@ final class OperationDelegate: BuildOperationDelegate {
// If we haven't started, show a custom message (to prevent a "Building 0" message).
if stats.numCommandsStarted == 0 {
if messageShortening != .full || workspaceContext.userPreferences.enableDebugActivityLogs {
request.send(BuildOperationProgressUpdated(targetName: targetName, statusMessage: "Scanning build tasks", percentComplete: percentComplete, showInLog: false))
let status = "Scanning build tasks"
request.send(BuildOperationProgressUpdated(targetName: targetName, statusMessage: status, percentComplete: percentComplete, showInLog: false, numCommandsStarted: stats.numCommandsStarted, numPossibleMaxExecutedCommands: stats.numPossibleMaxExecutedCommands, condensedStatusMessage: status))
}
} else {
let statusMessage = messageShortening > .legacy
? activityMessageFractionString(stats.numCommandsStarted, over: stats.numPossibleMaxExecutedCommands)
: "Building \(stats.numCommandsStarted) of \(stats.numPossibleMaxExecutedCommands) tasks"
request.send(BuildOperationProgressUpdated(targetName: targetName, statusMessage: statusMessage, percentComplete: percentComplete, showInLog: false))
request.send(BuildOperationProgressUpdated(
targetName: targetName,
statusMessage: statusMessage,
percentComplete: percentComplete,
showInLog: false,
numCommandsStarted: stats.numCommandsStarted,
numPossibleMaxExecutedCommands: stats.numPossibleMaxExecutedCommands,
condensedStatusMessage: "Building tasks"
))
}
}

Expand Down Expand Up @@ -1443,7 +1452,7 @@ final class OperationDelegate: BuildOperationDelegate {
func updateBuildProgress(statusMessage: String, showInLog: Bool) {
guard !skipCommandLevelInformation else { return }

request.send(BuildOperationProgressUpdated(statusMessage: statusMessage, percentComplete: -1, showInLog: showInLog))
request.send(BuildOperationProgressUpdated(statusMessage: statusMessage, percentComplete: -1, showInLog: showInLog, condensedStatusMessage: statusMessage))
}

func recordBuildBacktraceFrame(identifier: BuildOperationBacktraceFrameEmitted.Identifier, previousFrameIdentifier: BuildOperationBacktraceFrameEmitted.Identifier?, category: BuildOperationBacktraceFrameEmitted.Category, kind: BuildOperationBacktraceFrameEmitted.Kind, description: String) {
Expand Down
27 changes: 25 additions & 2 deletions Sources/SWBProtocol/BuildOperationMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -809,19 +809,42 @@ public struct BuildOperationProgressUpdated: Message, Equatable {
/// Whether or not to create a corresponding entry in the build log.
public let showInLog: Bool

public init(targetName: String? = nil, statusMessage: String, percentComplete: Double, showInLog: Bool) {
/// The number of commands that have been started.
public let numCommandsStarted: Int?

/// The number of build actions to complete.
public let numPossibleMaxExecutedCommands: Int?

/// A condensed status message.
public let condensedStatusMessage: String?

public init(
targetName: String? = nil,
statusMessage: String,
percentComplete: Double,
showInLog: Bool,
numCommandsStarted: Int? = nil,
numPossibleMaxExecutedCommands: Int? = nil,
condensedStatusMessage: String? = nil
) {
self.targetName = targetName
self.statusMessage = statusMessage
self.percentComplete = percentComplete
self.showInLog = showInLog
self.numCommandsStarted = numCommandsStarted
self.numPossibleMaxExecutedCommands = numPossibleMaxExecutedCommands
self.condensedStatusMessage = condensedStatusMessage
}

public init(from deserializer: any Deserializer) throws {
try deserializer.beginAggregate(4)
let count = try deserializer.beginAggregate(4...7)
self.targetName = try deserializer.deserialize()
self.statusMessage = try deserializer.deserialize()
self.percentComplete = try deserializer.deserialize()
self.showInLog = try deserializer.deserialize()
self.numCommandsStarted = count >= 5 ? try deserializer.deserialize() : nil
self.numPossibleMaxExecutedCommands = count >= 6 ? try deserializer.deserialize() : nil
self.condensedStatusMessage = count >= 7 ? try deserializer.deserialize() : nil
}

public func serialize<T: Serializer>(to serializer: T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,18 +397,27 @@ public enum SwiftBuildMessage {
public let percentComplete: Double
public let showInLog: Bool
public let targetName: String?
public let numCommands: Int?
public let numCommandsExpected: Int?
public let condensedStatusMessage: String?

@_spi(Testing)
public init(
message: String,
percentComplete: Double,
showInLog: Bool,
targetName: String? = nil
targetName: String? = nil,
numCommands: Int? = nil,
numCommandsExpected: Int? = nil,
condensedStatusMessage: String? = nil
) {
self.message = message
self.percentComplete = percentComplete
self.showInLog = showInLog
self.targetName = targetName
self.numCommands = numCommands
self.numCommandsExpected = numCommandsExpected
self.condensedStatusMessage = condensedStatusMessage
}
}

Expand Down Expand Up @@ -937,6 +946,9 @@ extension SwiftBuildMessage.DidUpdateProgressInfo: Codable, Equatable, Sendable
case percentComplete
case showInLog
case targetName
case numCommands
case numCommandsExpected
case condensedStatusMessage
}

public init(from decoder: any Decoder) throws {
Expand All @@ -945,6 +957,9 @@ extension SwiftBuildMessage.DidUpdateProgressInfo: Codable, Equatable, Sendable
percentComplete = try container.decodeDoubleOrString(forKey: .percentComplete)
showInLog = try container.decodeBoolOrString(forKey: .showInLog)
targetName = try container.decodeIfPresent(String.self, forKey: .targetName)
numCommands = try container.decodeIfPresent(Int.self, forKey: .numCommands)
numCommandsExpected = try container.decodeIfPresent(Int.self, forKey: .numCommandsExpected)
condensedStatusMessage = try container.decodeIfPresent(String.self, forKey: .condensedStatusMessage)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftBuild/SWBuildMessage+Protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ extension SwiftBuildMessage {
}

init(_ message: BuildOperationProgressUpdated) {
self = .didUpdateProgress(.init(message: message.statusMessage, percentComplete: message.percentComplete, showInLog: message.showInLog, targetName: message.targetName))
self = .didUpdateProgress(.init(message: message.statusMessage, percentComplete: message.percentComplete, showInLog: message.showInLog, targetName: message.targetName, numCommands: message.numCommandsStarted, numCommandsExpected: message.numPossibleMaxExecutedCommands))
}

init(_ message: BuildOperationTargetUpToDate) {
Expand Down
43 changes: 43 additions & 0 deletions Tests/SWBProtocolTests/MessageSerializationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,49 @@ import Testing
#expect(BuildOperationDiagnosticEmitted.Kind.error.description == "error")
}

@Test func buildOperationProgressUpdatedBackwardCompatibility() throws {
// The serializer intentionally writes only the original 4 fields so that old
// IPC clients continue to work unchanged.
// Verify that a 4-field wire message is accepted and the new optional fields
// come back as nil, and that a hypothetical future 7-field message is also
// accepted.
func makeFormat(fieldCount: Int) -> ByteString {
let serializer = MsgPackSerializer()
serializer.serializeAggregate(fieldCount) {
serializer.serialize(Optional<String>.some("MyTarget"))
serializer.serialize("a very fun status")
serializer.serialize(50.0 as Double)
serializer.serialize(true)
if fieldCount >= 5 { serializer.serialize(Optional<Int>.some(7)) }
if fieldCount >= 6 { serializer.serialize(Optional<Int>.some(12)) }
if fieldCount >= 7 { serializer.serialize(Optional<String>.some("Compiling Foo.swift")) }
}
return serializer.byteString
}

// 4-field format (current serializer output); new optional fields must all be nil
let legacyProgressMessage = try MsgPackDeserializer(makeFormat(fieldCount: 4)).deserialize() as BuildOperationProgressUpdated
#expect(legacyProgressMessage.targetName == "MyTarget")
#expect(legacyProgressMessage.statusMessage == "a very fun status")
#expect(legacyProgressMessage.percentComplete == 50.0)
#expect(legacyProgressMessage.showInLog == true)
#expect(legacyProgressMessage.numCommandsStarted == nil)
#expect(legacyProgressMessage.numPossibleMaxExecutedCommands == nil)
#expect(legacyProgressMessage.condensedStatusMessage == nil)

// 7-field format (extended sender); all fields present
let newProgressMessage = try MsgPackDeserializer(makeFormat(fieldCount: 7)).deserialize() as BuildOperationProgressUpdated
// Assure that legacy properties are still populated after deserializing
#expect(newProgressMessage.targetName == "MyTarget")
#expect(newProgressMessage.statusMessage == "a very fun status")
#expect(newProgressMessage.percentComplete == 50.0)
#expect(newProgressMessage.showInLog == true)
// Assure that new properties are being populated
#expect(newProgressMessage.numCommandsStarted == 7)
#expect(newProgressMessage.numPossibleMaxExecutedCommands == 12)
#expect(newProgressMessage.condensedStatusMessage == "Compiling Foo.swift")
}

@Test func IPCMessageSerialization() {
#expect(throws: (any Error).self) { try MsgPackDeserializer(ByteString([])).deserialize() as IPCMessage }
let serializer = MsgPackSerializer()
Expand Down
Loading