Skip to content

Commit d0c576a

Browse files
authored
command plugins: Add a 'progress' diagnostic message at default verbosity (#7256)
This change adds a 'progress' message to the diagnostics which a plugin can send back to SwiftPM. This message is printed to standard error at the default verbosity level. ### Motivation: Currently, a plugin can write output to standard output or send diagnostics which SwiftPM writes to standard error. If the plugin spawns long-running operations we might want to print progress messages to let the user know that it is still running. The existing options all have compromises: * Anything the plugin prints to its standard output or standard error is echoed to SwiftPM's standard output, but we may want to keep progress information separate from other plugin outputs - the plugin might be called as part of a shell pipeline where a downstream process will consume its output, for example. * The existing diagnostic messages - remark (info), warning and error - are all suppressed at the default verbosity level. * Increasing the level with `-v` causes SwiftPM to print lots of extra logging information from the build, which swamps the plugin info messages. This commit adds a 'progress' message which SwiftPM will print to standard error at the default verbosity level, allowing progress to be shown without polluting standard output or pulling in additional build logging which might not be relevant to the user. ### Modifications: * `Diagnostics.progress` takes a message as a string and passes it to SwiftPM, which prints it to standard error. From the plugin programmer's point of view `Diagnostics.progress` is logically a diagnostics message, however it is a new type in the SwiftPM <-> plugin protocol because extending the existing diagnostics enum would require extensive changes to add a the new message and in many existing cases it would not be relevant. ### Result: A plugin can show the user that it is making progress by printing messages which SwiftPM will echoed to stderr at the default verbosity level. Example output (`diagnostics-stub` is the name of the plugin in `Package.swift`): ``` % swift-package print-diagnostics progress [diagnostics-stub] command plugin: Diagnostics.progress ```
1 parent dc514d5 commit d0c576a

File tree

7 files changed

+66
-15
lines changed

7 files changed

+66
-15
lines changed

Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Plugins/diagnostics-stub/diagnostics_stub.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ struct diagnostics_stub: CommandPlugin {
1313
}
1414

1515
// Diagnostics are collected by SwiftPM and printed to standard error, depending on the current log verbosity level.
16+
if arguments.contains("progress") {
17+
Diagnostics.progress("command plugin: Diagnostics.progress") // prefixed with [plugin_name]
18+
}
19+
1620
if arguments.contains("remark") {
1721
Diagnostics.remark("command plugin: Diagnostics.remark") // prefixed with 'info:' when printed
1822
}

Sources/Commands/Utilities/PluginDelegate.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ final class PluginDelegate: PluginInvocationDelegate {
5353
swiftTool.observabilityScope.emit(diagnostic)
5454
}
5555

56+
func pluginEmittedProgress(_ message: String) {
57+
swiftTool.outputStream.write("[\(plugin.name)] \(message)\n")
58+
swiftTool.outputStream.flush()
59+
}
60+
5661
func pluginRequestedBuildOperation(
5762
subset: PluginInvocationBuildSubset,
5863
parameters: PluginInvocationBuildParameters,

Sources/PackagePlugin/Diagnostics.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,9 @@ public struct Diagnostics {
4949
public static func remark(_ message: String, file: String? = #file, line: Int? = #line) {
5050
self.emit(.remark, message, file: file, line: line)
5151
}
52+
53+
/// Emits a progress message
54+
public static func progress(_ message: String) {
55+
try? pluginHostConnection.sendMessage(.emitProgress(message: message))
56+
}
5257
}

Sources/PackagePlugin/PluginMessages.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,10 @@ enum PluginToHostMessage: Codable {
269269
enum DiagnosticSeverity: String, Codable {
270270
case error, warning, remark
271271
}
272-
272+
273+
/// The plugin emits a progress message.
274+
case emitProgress(message: String)
275+
273276
/// The plugin defines a build command.
274277
case defineBuildCommand(configuration: CommandConfiguration, inputFiles: [URL], outputFiles: [URL])
275278

Sources/SPMBuildCore/Plugins/PluginInvocation.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,10 @@ extension PluginTarget {
240240
diagnostic = .info(message, metadata: metadata)
241241
}
242242
self.invocationDelegate.pluginEmittedDiagnostic(diagnostic)
243-
243+
244+
case .emitProgress(let message):
245+
self.invocationDelegate.pluginEmittedProgress(message)
246+
244247
case .defineBuildCommand(let config, let inputFiles, let outputFiles):
245248
if config.version != 2 {
246249
throw PluginEvaluationError.pluginUsesIncompatibleVersion(expected: 2, actual: config.version)
@@ -485,7 +488,9 @@ extension PackageGraph {
485488
dispatchPrecondition(condition: .onQueue(delegateQueue))
486489
outputData.append(contentsOf: data)
487490
}
488-
491+
492+
func pluginEmittedProgress(_ message: String) {}
493+
489494
func pluginEmittedDiagnostic(_ diagnostic: Basics.Diagnostic) {
490495
dispatchPrecondition(condition: .onQueue(delegateQueue))
491496
diagnostics.append(diagnostic)
@@ -819,6 +824,9 @@ public protocol PluginInvocationDelegate {
819824
/// Called when a plugin emits a diagnostic through the PackagePlugin APIs.
820825
func pluginEmittedDiagnostic(_: Basics.Diagnostic)
821826

827+
/// Called when a plugin emits a progress message through the PackagePlugin APIs.
828+
func pluginEmittedProgress(_: String)
829+
822830
/// Called when a plugin defines a build command through the PackagePlugin APIs.
823831
func pluginDefinedBuildCommand(displayName: String?, executable: AbsolutePath, arguments: [String], environment: [String: String], workingDirectory: AbsolutePath?, inputFiles: [AbsolutePath], outputFiles: [AbsolutePath])
824832

Tests/CommandsTests/PackageToolTests.swift

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,7 @@ final class PackageToolTests: CommandsTestCase {
18861886
// Match patterns for expected messages
18871887
let isEmpty = StringPattern.equal("")
18881888
let isOnlyPrint = StringPattern.equal("command plugin: print\n")
1889+
let containsProgress = StringPattern.contains("[diagnostics-stub] command plugin: Diagnostics.progress")
18891890
let containsRemark = StringPattern.contains("command plugin: Diagnostics.remark")
18901891
let containsWarning = StringPattern.contains("command plugin: Diagnostics.warning")
18911892
let containsError = StringPattern.contains("command plugin: Diagnostics.error")
@@ -1915,18 +1916,25 @@ final class PackageToolTests: CommandsTestCase {
19151916
XCTAssertMatch(stderr, isEmpty)
19161917
}
19171918

1918-
try runPlugin(flags: [], diagnostics: ["print", "remark"]) { stdout, stderr in
1919+
try runPlugin(flags: [], diagnostics: ["print", "progress"]) { stdout, stderr in
1920+
XCTAssertMatch(stdout, isOnlyPrint)
1921+
XCTAssertMatch(stderr, containsProgress)
1922+
}
1923+
1924+
try runPlugin(flags: [], diagnostics: ["print", "progress", "remark"]) { stdout, stderr in
19191925
XCTAssertMatch(stdout, isOnlyPrint)
1920-
XCTAssertMatch(stderr, isEmpty)
1926+
XCTAssertMatch(stderr, containsProgress)
19211927
}
19221928

1923-
try runPlugin(flags: [], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1929+
try runPlugin(flags: [], diagnostics: ["print", "progress", "remark", "warning"]) { stdout, stderr in
19241930
XCTAssertMatch(stdout, isOnlyPrint)
1931+
XCTAssertMatch(stderr, containsProgress)
19251932
XCTAssertMatch(stderr, containsWarning)
19261933
}
19271934

1928-
try runPluginWithError(flags: [], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1935+
try runPluginWithError(flags: [], diagnostics: ["print", "progress", "remark", "warning", "error"]) { stdout, stderr in
19291936
XCTAssertMatch(stdout, isOnlyPrint)
1937+
XCTAssertMatch(stderr, containsProgress)
19301938
XCTAssertMatch(stderr, containsWarning)
19311939
XCTAssertMatch(stderr, containsError)
19321940
}
@@ -1940,18 +1948,24 @@ final class PackageToolTests: CommandsTestCase {
19401948
XCTAssertMatch(stderr, isEmpty)
19411949
}
19421950

1943-
try runPlugin(flags: ["-q"], diagnostics: ["print", "remark"]) { stdout, stderr in
1951+
try runPlugin(flags: ["-q"], diagnostics: ["print", "progress"]) { stdout, stderr in
19441952
XCTAssertMatch(stdout, isOnlyPrint)
1945-
XCTAssertMatch(stderr, isEmpty)
1953+
XCTAssertMatch(stderr, containsProgress)
19461954
}
19471955

1948-
try runPlugin(flags: ["-q"], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1956+
try runPlugin(flags: ["-q"], diagnostics: ["print", "progress", "remark"]) { stdout, stderr in
19491957
XCTAssertMatch(stdout, isOnlyPrint)
1950-
XCTAssertMatch(stderr, isEmpty)
1958+
XCTAssertMatch(stderr, containsProgress)
19511959
}
19521960

1953-
try runPluginWithError(flags: ["-q"], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1961+
try runPlugin(flags: ["-q"], diagnostics: ["print", "progress", "remark", "warning"]) { stdout, stderr in
19541962
XCTAssertMatch(stdout, isOnlyPrint)
1963+
XCTAssertMatch(stderr, containsProgress)
1964+
}
1965+
1966+
try runPluginWithError(flags: ["-q"], diagnostics: ["print", "progress", "remark", "warning", "error"]) { stdout, stderr in
1967+
XCTAssertMatch(stdout, isOnlyPrint)
1968+
XCTAssertMatch(stderr, containsProgress)
19551969
XCTAssertNoMatch(stderr, containsRemark)
19561970
XCTAssertNoMatch(stderr, containsWarning)
19571971
XCTAssertMatch(stderr, containsError)
@@ -1967,19 +1981,27 @@ final class PackageToolTests: CommandsTestCase {
19671981
// At this level stderr contains extra compiler output even if the plugin does not print diagnostics
19681982
}
19691983

1970-
try runPlugin(flags: ["-v"], diagnostics: ["print", "remark"]) { stdout, stderr in
1984+
try runPlugin(flags: ["-v"], diagnostics: ["print", "progress"]) { stdout, stderr in
1985+
XCTAssertMatch(stdout, isOnlyPrint)
1986+
XCTAssertMatch(stderr, containsProgress)
1987+
}
1988+
1989+
try runPlugin(flags: ["-v"], diagnostics: ["print", "progress", "remark"]) { stdout, stderr in
19711990
XCTAssertMatch(stdout, isOnlyPrint)
1991+
XCTAssertMatch(stderr, containsProgress)
19721992
XCTAssertMatch(stderr, containsRemark)
19731993
}
19741994

1975-
try runPlugin(flags: ["-v"], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1995+
try runPlugin(flags: ["-v"], diagnostics: ["print", "progress", "remark", "warning"]) { stdout, stderr in
19761996
XCTAssertMatch(stdout, isOnlyPrint)
1997+
XCTAssertMatch(stderr, containsProgress)
19771998
XCTAssertMatch(stderr, containsRemark)
19781999
XCTAssertMatch(stderr, containsWarning)
19792000
}
19802001

1981-
try runPluginWithError(flags: ["-v"], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
2002+
try runPluginWithError(flags: ["-v"], diagnostics: ["print", "progress", "remark", "warning", "error"]) { stdout, stderr in
19822003
XCTAssertMatch(stdout, isOnlyPrint)
2004+
XCTAssertMatch(stderr, containsProgress)
19832005
XCTAssertMatch(stderr, containsRemark)
19842006
XCTAssertMatch(stderr, containsWarning)
19852007
XCTAssertMatch(stderr, containsError)

Tests/FunctionalTests/PluginTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,8 @@ class PluginTests: XCTestCase {
478478
print("[DIAG] \(diagnostic)")
479479
diagnostics.append(diagnostic)
480480
}
481+
482+
func pluginEmittedProgress(_ message: String) {}
481483
}
482484

483485
// Helper function to invoke a plugin with given input and to check its outputs.
@@ -767,6 +769,8 @@ class PluginTests: XCTestCase {
767769
dispatchPrecondition(condition: .onQueue(delegateQueue))
768770
diagnostics.append(diagnostic)
769771
}
772+
773+
func pluginEmittedProgress(_ message: String) {}
770774
}
771775

772776
// Find the relevant plugin.

0 commit comments

Comments
 (0)