15
15
*/
16
16
17
17
@_implementationOnly import Foundation
18
+ #if os(Windows)
19
+ @_implementationOnly import ucrt // for stdio functions
20
+ #endif
18
21
19
22
// The way in which SwiftPM communicates with the plugin is an implementation
20
23
// detail, but the way it currently works is that the plugin is compiled (in
25
28
// likely change so that it is instead passed on `stdin` of the process that
26
29
// runs the plugin, since that avoids any command line length limitations.
27
30
//
28
- // Any generated commands and diagnostics are emitted on `stdout` after a zero
29
- // byte; this allows regular output, such as print statements for debugging,
30
- // to be emitted to SwiftPM verbatim. SwiftPM tries to interpret any stdout
31
- // contents after the last zero byte as a JSON encoded output struct in UTF-8
32
- // encoding; any failure to decode it is considered a protocol failure.
31
+ // An output structure containing any generated commands and diagnostics is
32
+ // passed back to SwiftPM on `stdout`. All freeform output from the plugin
33
+ // is redirected to `stderr`, which SwiftPM shows to the user without inter-
34
+ // preting it in any way.
33
35
//
34
36
// The exit code of the compiled plugin determines success or failure (though
35
37
// failure to decode the output is also considered a failure to run the ex-
38
40
extension Plugin {
39
41
40
42
public static func main( _ arguments: [ String ] ) throws {
43
+
44
+ // Use the initial `stdout` for returning JSON, and redirect `stdout`
45
+ // to `stderr` for capturing freeform text.
46
+ let jsonOut = fdopen ( dup ( fileno ( stdout) ) , " w " )
47
+ dup2 ( fileno ( stderr) , fileno ( stdout) )
48
+
49
+ // Close `stdin` to avoid blocking if the plugin tries to read input.
50
+ close ( fileno ( stdin) )
51
+
52
+ // Private function for reporting internal errors and halting execution.
53
+ func internalError( _ message: String ) -> Never {
54
+ Diagnostics . error ( " Internal Error: \( message) " )
55
+ fputs ( " Internal Error: \( message) " , stderr)
56
+ exit ( 1 )
57
+ }
58
+
41
59
// Look for the input JSON as the last argument of the invocation.
42
60
guard let inputData = ProcessInfo . processInfo. arguments. last? . data ( using: . utf8) else {
43
- fputs ( " Expected last argument to contain JSON input data in UTF-8 encoding, but didn't find it. " , stderr)
44
- Diagnostics . error ( " Expected last argument to contain JSON input data in UTF-8 encoding, but didn't find it. " )
45
- exit ( 1 )
61
+ internalError ( " Expected last argument to contain JSON input data in UTF-8 encoding, but didn't find it. " )
46
62
}
47
-
63
+
48
64
// Deserialize the input JSON.
49
- let input = try PluginInput ( from: inputData)
65
+ let input : PluginInput
66
+ do {
67
+ input = try PluginInput ( from: inputData)
68
+ } catch {
69
+ internalError ( " Couldn’t decode input JSON: \( error) . " )
70
+ }
50
71
51
72
// Construct a PluginContext from the deserialized input.
52
73
let context = PluginContext (
@@ -57,12 +78,13 @@ extension Plugin {
57
78
58
79
// Instantiate the plugin. For now there are no parameters, but this is
59
80
// where we would set them up, most likely as properties of the plugin
60
- // instance (in a manner similar to SwiftArgumentParser).
81
+ // instance (in a manner similar to SwiftArgumentParser). This would
82
+ // use property wrappers to mark up properties in the plugin.
61
83
let plugin = self . init ( )
62
84
63
85
// Invoke the appropriate protocol method, based on the plugin action
64
86
// that SwiftPM specified.
65
- let commands : [ Command ]
87
+ let generatedCommands : [ Command ]
66
88
switch input. pluginAction {
67
89
68
90
case . createBuildToolCommands( let target) :
@@ -73,7 +95,7 @@ extension Plugin {
73
95
}
74
96
75
97
// Ask the plugin to create build commands for the input target.
76
- commands = try plugin. createBuildCommands ( context: context, target: target)
98
+ generatedCommands = try plugin. createBuildCommands ( context: context, target: target)
77
99
78
100
case . performCommand( let targets, let arguments) :
79
101
// Check that the plugin implements the appropriate protocol for its
@@ -87,15 +109,21 @@ extension Plugin {
87
109
88
110
// For command plugin there are currently no return commands (any
89
111
// commands invoked by the plugin are invoked directly).
90
- commands = [ ]
112
+ generatedCommands = [ ]
91
113
}
92
114
93
- // Construct the output structure to send to SwiftPM.
94
- let output = try PluginOutput ( commands: commands, diagnostics: Diagnostics . emittedDiagnostics)
115
+ // Construct the output structure to send back to SwiftPM.
116
+ let output : PluginOutput
117
+ do {
118
+ output = try PluginOutput ( commands: generatedCommands, diagnostics: Diagnostics . emittedDiagnostics)
119
+ } catch {
120
+ internalError ( " Couldn’t encode output JSON: \( error) . " )
121
+ }
95
122
96
123
// On stdout, write a zero byte followed by the JSON data — this is what libSwiftPM expects to see. Anything before the last zero byte is treated as freeform output from the plugin (such as debug output from `print` statements). Since `FileHandle.write()` doesn't obey buffering we first have to flush any existing output.
97
- fputc ( 0 , stdout)
98
- fwrite ( [ UInt8] ( output. outputData) , 1 , output. outputData. count, stdout)
124
+ if fwrite ( [ UInt8] ( output. outputData) , 1 , output. outputData. count, jsonOut) != output. outputData. count {
125
+ internalError ( " Couldn’t write output JSON: \( strerror ( errno) . map { String ( cString: $0) } ?? String ( describing: errno) ) . " )
126
+ }
99
127
}
100
128
101
129
public static func main( ) throws {
0 commit comments