@@ -54,8 +54,8 @@ extension Plugin {
54
54
/// Main entry point of the plugin — sets up a communication channel with
55
55
/// the plugin host and runs the main message loop.
56
56
public static func main( ) throws {
57
- // Duplicate the `stdin` file descriptor, which we will then use as an
58
- // input stream from which we receive messages from the plugin host.
57
+ // Duplicate the `stdin` file descriptor, which we will then use for
58
+ // receiving messages from the plugin host.
59
59
let inputFD = dup ( fileno ( stdin) )
60
60
guard inputFD >= 0 else {
61
61
internalError ( " Could not duplicate `stdin`: \( describe ( errno: errno) ) . " )
@@ -68,8 +68,8 @@ extension Plugin {
68
68
internalError ( " Could not close `stdin`: \( describe ( errno: errno) ) . " )
69
69
}
70
70
71
- // Duplicate the `stdout` file descriptor, which we will then use as an
72
- // output stream to which we send messages to the plugin host.
71
+ // Duplicate the `stdout` file descriptor, which we will then use for
72
+ // sending messages to the plugin host.
73
73
let outputFD = dup ( fileno ( stdout) )
74
74
guard outputFD >= 0 else {
75
75
internalError ( " Could not dup `stdout`: \( describe ( errno: errno) ) . " )
@@ -89,7 +89,7 @@ extension Plugin {
89
89
inputStream: FileHandle ( fileDescriptor: inputFD) ,
90
90
outputStream: FileHandle ( fileDescriptor: outputFD) )
91
91
92
- // Process messages from the host until the input stream is closed,
92
+ // Handle messages from the host until the input stream is closed,
93
93
// indicating that we're done.
94
94
while let message = try pluginHostConnection. waitForNextMessage ( ) {
95
95
try handleMessage ( message)
@@ -98,13 +98,11 @@ extension Plugin {
98
98
99
99
fileprivate static func handleMessage( _ message: HostToPluginMessage ) throws {
100
100
switch message {
101
- // Invokes an action defined in the input JSON. This is an interim
102
- // message to bridge to the old logic; this will be separateed out
103
- // into different messages for different plugin capabilities, etc.
104
- // This will let us avoid the double encoded JSON.
101
+
105
102
case . performAction( let wireInput) :
106
- // Decode the plugin input structure. We'll resolve this doubly
107
- // encoded JSON in an upcoming change.
103
+ // Invokes an action defined in the input JSON. This is an interim
104
+ // bridge to the old logic; the intent is to separate each action
105
+ // into its own message type with customized input payload.
108
106
let inputStruct : PluginInput
109
107
do {
110
108
inputStruct = try PluginInput ( from: wireInput)
@@ -123,12 +121,13 @@ extension Plugin {
123
121
// this is where we would set them up, most likely as properties
124
122
// of the plugin instance (similar to how SwiftArgumentParser
125
123
// allows commands to annotate arguments). It could use property
126
- // wrappers to mark up properties in the plugin.
124
+ // wrappers to mark up properties in the plugin, and a separate
125
+ // message could be used to query the plugin for its parameter
126
+ // definitions.
127
127
let plugin = self . init ( )
128
128
129
129
// Invoke the appropriate protocol method, based on the plugin
130
130
// action that SwiftPM specified.
131
- let generatedCommands : [ Command ]
132
131
switch inputStruct. pluginAction {
133
132
134
133
case . createBuildToolCommands( let target) :
@@ -138,8 +137,39 @@ extension Plugin {
138
137
throw PluginDeserializationError . malformedInputJSON ( " Plugin declared with `buildTool` capability but doesn't conform to `BuildToolPlugin` protocol " )
139
138
}
140
139
141
- // Ask the plugin to create build commands for the target.
142
- generatedCommands = try plugin. createBuildCommands ( context: context, target: target)
140
+ // Invoke the plugin to create build commands for the target.
141
+ let generatedCommands = try plugin. createBuildCommands ( context: context, target: target)
142
+
143
+ // Send each of the generated commands to the host.
144
+ for command in generatedCommands {
145
+ switch command {
146
+
147
+ case let . _buildCommand( name, exec, args, env, workdir, inputs, outputs) :
148
+ let command = PluginToHostMessage . CommandConfiguration (
149
+ displayName: name,
150
+ executable: exec. string,
151
+ arguments: args,
152
+ environment: env,
153
+ workingDirectory: workdir? . string)
154
+ let message = PluginToHostMessage . defineBuildCommand (
155
+ configuration: command,
156
+ inputFiles: inputs. map { $0. string } ,
157
+ outputFiles: outputs. map { $0. string } )
158
+ try pluginHostConnection. sendMessage ( message)
159
+
160
+ case let . _prebuildCommand( name, exec, args, env, workdir, outdir) :
161
+ let command = PluginToHostMessage . CommandConfiguration (
162
+ displayName: name,
163
+ executable: exec. string,
164
+ arguments: args,
165
+ environment: env,
166
+ workingDirectory: workdir? . string)
167
+ let message = PluginToHostMessage . definePrebuildCommand (
168
+ configuration: command,
169
+ outputFilesDirectory: outdir. string)
170
+ try pluginHostConnection. sendMessage ( message)
171
+ }
172
+ }
143
173
144
174
case . performCommand( let targets, let arguments) :
145
175
// Check that the plugin implements the appropriate protocol
@@ -148,22 +178,32 @@ extension Plugin {
148
178
throw PluginDeserializationError . malformedInputJSON ( " Plugin declared with `command` capability but doesn't conform to `CommandPlugin` protocol " )
149
179
}
150
180
151
- // Invoke the plugin.
181
+ // Invoke the plugin to perform its custom logic .
152
182
try plugin. performCommand ( context: context, targets: targets, arguments: arguments)
153
-
154
- // For command plugin there are currently no return commands
155
- // (any commands invoked by the plugin are invoked directly).
156
- generatedCommands = [ ]
157
183
}
158
184
159
- // Send back the output data (a JSON-encoded struct) to the plugin host.
160
- let outputStruct : PluginOutput
161
- do {
162
- outputStruct = try PluginOutput ( commands: generatedCommands, diagnostics: Diagnostics . emittedDiagnostics)
163
- } catch {
164
- internalError ( " Couldn’t encode output JSON: \( error) . " )
185
+ // Send any emitted diagnostics to the host.
186
+ // FIXME: We should really be doing while diagnostics are emitted.
187
+ for diagnostic in Diagnostics . emittedDiagnostics {
188
+ let severity : PluginToHostMessage . DiagnosticSeverity
189
+ switch diagnostic. severity {
190
+ case . error:
191
+ severity = . error
192
+ case . warning:
193
+ severity = . warning
194
+ case . remark:
195
+ severity = . remark
196
+ }
197
+ let message = PluginToHostMessage . emitDiagnostic (
198
+ severity: severity,
199
+ message: diagnostic. message,
200
+ file: diagnostic. file? . string,
201
+ line: diagnostic. line)
202
+ try pluginHostConnection. sendMessage ( message)
165
203
}
166
- try pluginHostConnection. sendMessage ( . pluginFinished( result: outputStruct. output) )
204
+
205
+ // Send back a message to the host indicating that we're done.
206
+ try pluginHostConnection. sendMessage ( . actionComplete( success: true ) )
167
207
168
208
default :
169
209
internalError ( " unexpected top-level message \( message) " )
@@ -186,7 +226,6 @@ extension Plugin {
186
226
/// Message channel for communicating with the plugin host.
187
227
internal fileprivate( set) var pluginHostConnection : PluginHostConnection !
188
228
189
-
190
229
/// A message that the host can send to the plugin.
191
230
enum HostToPluginMessage : Decodable {
192
231
/// The host is requesting that the plugin perform one of its declared plugin actions.
@@ -198,14 +237,34 @@ enum HostToPluginMessage: Decodable {
198
237
199
238
/// A message that the plugin can send to the host.
200
239
enum PluginToHostMessage : Encodable {
201
- /// The plugin has finished the requested action and is returning a result.
202
- case pluginFinished( result: WireOutput )
203
- }
240
+ /// The plugin emits a diagnostic.
241
+ case emitDiagnostic( severity: DiagnosticSeverity , message: String , file: String ? , line: Int ? )
242
+
243
+ enum DiagnosticSeverity : String , Encodable {
244
+ case error, warning, remark
245
+ }
246
+
247
+ /// The plugin defines a build command.
248
+ case defineBuildCommand( configuration: CommandConfiguration , inputFiles: [ String ] , outputFiles: [ String ] )
204
249
250
+ /// The plugin defines a prebuild command.
251
+ case definePrebuildCommand( configuration: CommandConfiguration , outputFilesDirectory: String )
252
+
253
+ struct CommandConfiguration : Encodable {
254
+ var displayName : String ?
255
+ var executable : String
256
+ var arguments : [ String ]
257
+ var environment : [ String : String ]
258
+ var workingDirectory : String ?
259
+ }
260
+
261
+ /// The plugin has finished the requested action.
262
+ case actionComplete( success: Bool )
263
+ }
205
264
206
265
typealias PluginHostConnection = MessageConnection < PluginToHostMessage , HostToPluginMessage >
207
266
208
- struct MessageConnection < TX, RX> where TX: Encodable , RX: Decodable {
267
+ internal struct MessageConnection < TX, RX> where TX: Encodable , RX: Decodable {
209
268
let inputStream : FileHandle
210
269
let outputStream : FileHandle
211
270
0 commit comments