Skip to content

Commit 2389346

Browse files
committed
command plugins: Add a 'progress' diagnostic message at default verbosity
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.
1 parent 95397a9 commit 2389346

File tree

7 files changed

+67
-15
lines changed

7 files changed

+67
-15
lines changed

Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Plugins/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
@@ -1882,6 +1882,7 @@ final class PackageToolTests: CommandsTestCase {
18821882
// Match patterns for expected messages
18831883
let isEmpty = StringPattern.equal("")
18841884
let isOnlyPrint = StringPattern.equal("command plugin: print\n")
1885+
let containsProgress = StringPattern.contains("[diagnostics-stub] command plugin: Diagnostics.progress")
18851886
let containsRemark = StringPattern.contains("command plugin: Diagnostics.remark")
18861887
let containsWarning = StringPattern.contains("command plugin: Diagnostics.warning")
18871888
let containsError = StringPattern.contains("command plugin: Diagnostics.error")
@@ -1911,18 +1912,25 @@ final class PackageToolTests: CommandsTestCase {
19111912
XCTAssertMatch(stderr, isEmpty)
19121913
}
19131914

1914-
try runPlugin(flags: [], diagnostics: ["print", "remark"]) { stdout, stderr in
1915+
try runPlugin(flags: [], diagnostics: ["print", "progress"]) { stdout, stderr in
1916+
XCTAssertMatch(stdout, isOnlyPrint)
1917+
XCTAssertMatch(stderr, containsProgress)
1918+
}
1919+
1920+
try runPlugin(flags: [], diagnostics: ["print", "progress", "remark"]) { stdout, stderr in
19151921
XCTAssertMatch(stdout, isOnlyPrint)
1916-
XCTAssertMatch(stderr, isEmpty)
1922+
XCTAssertMatch(stderr, containsProgress)
19171923
}
19181924

1919-
try runPlugin(flags: [], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1925+
try runPlugin(flags: [], diagnostics: ["print", "progress", "remark", "warning"]) { stdout, stderr in
19201926
XCTAssertMatch(stdout, isOnlyPrint)
1927+
XCTAssertMatch(stderr, containsProgress)
19211928
XCTAssertMatch(stderr, containsWarning)
19221929
}
19231930

1924-
try runPluginWithError(flags: [], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1931+
try runPluginWithError(flags: [], diagnostics: ["print", "progress", "remark", "warning", "error"]) { stdout, stderr in
19251932
XCTAssertMatch(stdout, isOnlyPrint)
1933+
XCTAssertMatch(stderr, containsProgress)
19261934
XCTAssertMatch(stderr, containsWarning)
19271935
XCTAssertMatch(stderr, containsError)
19281936
}
@@ -1936,18 +1944,24 @@ final class PackageToolTests: CommandsTestCase {
19361944
XCTAssertMatch(stderr, isEmpty)
19371945
}
19381946

1939-
try runPlugin(flags: ["-q"], diagnostics: ["print", "remark"]) { stdout, stderr in
1947+
try runPlugin(flags: ["-q"], diagnostics: ["print", "progress"]) { stdout, stderr in
19401948
XCTAssertMatch(stdout, isOnlyPrint)
1941-
XCTAssertMatch(stderr, isEmpty)
1949+
XCTAssertMatch(stderr, containsProgress)
19421950
}
19431951

1944-
try runPlugin(flags: ["-q"], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1952+
try runPlugin(flags: ["-q"], diagnostics: ["print", "progress", "remark"]) { stdout, stderr in
19451953
XCTAssertMatch(stdout, isOnlyPrint)
1946-
XCTAssertMatch(stderr, isEmpty)
1954+
XCTAssertMatch(stderr, containsProgress)
19471955
}
19481956

1949-
try runPluginWithError(flags: ["-q"], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1957+
try runPlugin(flags: ["-q"], diagnostics: ["print", "progress", "remark", "warning"]) { stdout, stderr in
19501958
XCTAssertMatch(stdout, isOnlyPrint)
1959+
XCTAssertMatch(stderr, containsProgress)
1960+
}
1961+
1962+
try runPluginWithError(flags: ["-q"], diagnostics: ["print", "progress", "remark", "warning", "error"]) { stdout, stderr in
1963+
XCTAssertMatch(stdout, isOnlyPrint)
1964+
XCTAssertMatch(stderr, containsProgress)
19511965
XCTAssertNoMatch(stderr, containsRemark)
19521966
XCTAssertNoMatch(stderr, containsWarning)
19531967
XCTAssertMatch(stderr, containsError)
@@ -1963,19 +1977,27 @@ final class PackageToolTests: CommandsTestCase {
19631977
// At this level stderr contains extra compiler output even if the plugin does not print diagnostics
19641978
}
19651979

1966-
try runPlugin(flags: ["-v"], diagnostics: ["print", "remark"]) { stdout, stderr in
1980+
try runPlugin(flags: ["-v"], diagnostics: ["print", "progress"]) { stdout, stderr in
1981+
XCTAssertMatch(stdout, isOnlyPrint)
1982+
XCTAssertMatch(stderr, containsProgress)
1983+
}
1984+
1985+
try runPlugin(flags: ["-v"], diagnostics: ["print", "progress", "remark"]) { stdout, stderr in
19671986
XCTAssertMatch(stdout, isOnlyPrint)
1987+
XCTAssertMatch(stderr, containsProgress)
19681988
XCTAssertMatch(stderr, containsRemark)
19691989
}
19701990

1971-
try runPlugin(flags: ["-v"], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1991+
try runPlugin(flags: ["-v"], diagnostics: ["print", "progress", "remark", "warning"]) { stdout, stderr in
19721992
XCTAssertMatch(stdout, isOnlyPrint)
1993+
XCTAssertMatch(stderr, containsProgress)
19731994
XCTAssertMatch(stderr, containsRemark)
19741995
XCTAssertMatch(stderr, containsWarning)
19751996
}
19761997

1977-
try runPluginWithError(flags: ["-v"], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1998+
try runPluginWithError(flags: ["-v"], diagnostics: ["print", "progress", "remark", "warning", "error"]) { stdout, stderr in
19781999
XCTAssertMatch(stdout, isOnlyPrint)
2000+
XCTAssertMatch(stderr, containsProgress)
19792001
XCTAssertMatch(stderr, containsRemark)
19802002
XCTAssertMatch(stderr, containsWarning)
19812003
XCTAssertMatch(stderr, containsError)

Tests/FunctionalTests/PluginTests.swift

Lines changed: 5 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,9 @@ class PluginTests: XCTestCase {
767769
dispatchPrecondition(condition: .onQueue(delegateQueue))
768770
diagnostics.append(diagnostic)
769771
}
772+
773+
func pluginEmittedProgress(_ message: String) {}
774+
770775
}
771776

772777
// Find the relevant plugin.

0 commit comments

Comments
 (0)