@@ -94,73 +94,144 @@ extension Plugin {
94
94
// Open input and output handles for read from and writing to the host.
95
95
let inputHandle = FileHandle ( fileDescriptor: inputFD)
96
96
let outputHandle = FileHandle ( fileDescriptor: outputFD)
97
-
98
- // Read the input data (a JSON-encoded struct) from the host. It has
99
- // all the input context for the plugin invocation.
100
- guard let inputData = try inputHandle. readToEnd ( ) else {
101
- internalError ( " Couldn’t read input JSON. " )
102
- }
103
- let inputStruct : PluginInput
104
- do {
105
- inputStruct = try PluginInput ( from: inputData)
106
- } catch {
107
- internalError ( " Couldn’t decode input JSON: \( error) . " )
108
- }
109
-
110
- // Construct a PluginContext from the deserialized input.
111
- let context = PluginContext (
112
- package : inputStruct. package ,
113
- pluginWorkDirectory: inputStruct. pluginWorkDirectory,
114
- builtProductsDirectory: inputStruct. builtProductsDirectory,
115
- toolNamesToPaths: inputStruct. toolNamesToPaths)
116
-
117
- // Instantiate the plugin. For now there are no parameters, but this is
118
- // where we would set them up, most likely as properties of the plugin
119
- // instance (in a manner similar to SwiftArgumentParser). This would
120
- // use property wrappers to mark up properties in the plugin.
121
- let plugin = self . init ( )
122
97
123
- // Invoke the appropriate protocol method, based on the plugin action
124
- // that SwiftPM specified.
125
- let generatedCommands : [ Command ]
126
- switch inputStruct. pluginAction {
98
+ // Read and process messages from the plugin host. At present there is
99
+ // only one action message per plugin invocation followed by a message
100
+ // to exit, but this can be extended in the future.
101
+ while let message = try inputHandle. readPluginMessage ( ) {
102
+ switch message {
103
+ // Invokes an action defined in the input JSON. This is an interim
104
+ // message to bridge to the old logic; this will be separateed out
105
+ // into different messages for different plugin capabilities, etc.
106
+ // This will let us avoid the double encoded JSON.
107
+ case . performAction( let wireInput) :
108
+ // Decode the plugin input structure. We'll resolve this doubly
109
+ // encoded JSON in an upcoming change.
110
+ let inputStruct : PluginInput
111
+ do {
112
+ inputStruct = try PluginInput ( from: wireInput)
113
+ } catch {
114
+ internalError ( " Couldn’t decode input JSON: \( error) . " )
115
+ }
116
+
117
+ // Construct a PluginContext from the deserialized input.
118
+ let context = PluginContext (
119
+ package : inputStruct. package ,
120
+ pluginWorkDirectory: inputStruct. pluginWorkDirectory,
121
+ builtProductsDirectory: inputStruct. builtProductsDirectory,
122
+ toolNamesToPaths: inputStruct. toolNamesToPaths)
123
+
124
+ // Instantiate the plugin. For now there are no parameters, but
125
+ // this is where we would set them up, most likely as properties
126
+ // of the plugin instance (similar to how SwiftArgumentParser
127
+ // allows commands to annotate arguments). It could use property
128
+ // wrappers to mark up properties in the plugin.
129
+ let plugin = self . init ( )
130
+
131
+ // Invoke the appropriate protocol method, based on the plugin
132
+ // action that SwiftPM specified.
133
+ let generatedCommands : [ Command ]
134
+ switch inputStruct. pluginAction {
135
+
136
+ case . createBuildToolCommands( let target) :
137
+ // Check that the plugin implements the appropriate protocol
138
+ // for its declared capability.
139
+ guard let plugin = plugin as? BuildToolPlugin else {
140
+ throw PluginDeserializationError . malformedInputJSON ( " Plugin declared with `buildTool` capability but doesn't conform to `BuildToolPlugin` protocol " )
141
+ }
142
+
143
+ // Ask the plugin to create build commands for the target.
144
+ generatedCommands = try plugin. createBuildCommands ( context: context, target: target)
145
+
146
+ case . performCommand( let targets, let arguments) :
147
+ // Check that the plugin implements the appropriate protocol
148
+ // for its declared capability.
149
+ guard let plugin = plugin as? CommandPlugin else {
150
+ throw PluginDeserializationError . malformedInputJSON ( " Plugin declared with `command` capability but doesn't conform to `CommandPlugin` protocol " )
151
+ }
152
+
153
+ // Invoke the plugin.
154
+ try plugin. performCommand ( context: context, targets: targets, arguments: arguments)
155
+
156
+ // For command plugin there are currently no return commands
157
+ // (any commands invoked by the plugin are invoked directly).
158
+ generatedCommands = [ ]
159
+ }
160
+
161
+ // Send back the output data (a JSON-encoded struct) to the plugin host.
162
+ let outputStruct : PluginOutput
163
+ do {
164
+ outputStruct = try PluginOutput ( commands: generatedCommands, diagnostics: Diagnostics . emittedDiagnostics)
165
+ } catch {
166
+ internalError ( " Couldn’t encode output JSON: \( error) . " )
167
+ }
168
+ try outputHandle. writePluginMessage ( . provideResult( output: outputStruct. output) )
127
169
128
- case . createBuildToolCommands( let target) :
129
- // Check that the plugin implements the appropriate protocol for its
130
- // declared capability.
131
- guard let plugin = plugin as? BuildToolPlugin else {
132
- throw PluginDeserializationError . malformedInputJSON ( " Plugin declared with `buildTool` capability but doesn't conform to `BuildToolPlugin` protocol " )
133
- }
134
-
135
- // Ask the plugin to create build commands for the input target.
136
- generatedCommands = try plugin. createBuildCommands ( context: context, target: target)
137
-
138
- case . performCommand( let targets, let arguments) :
139
- // Check that the plugin implements the appropriate protocol for its
140
- // declared capability.
141
- guard let plugin = plugin as? CommandPlugin else {
142
- throw PluginDeserializationError . malformedInputJSON ( " Plugin declared with `command` capability but doesn't conform to `CommandPlugin` protocol " )
143
- }
144
-
145
- // Invoke the plugin.
146
- try plugin. performCommand ( context: context, targets: targets, arguments: arguments)
170
+ // Exits the plugin logic.
171
+ case . quit:
172
+ exit ( 0 )
147
173
148
- // For command plugin there are currently no return commands (any
149
- // commands invoked by the plugin are invoked directly).
150
- generatedCommands = [ ]
151
- }
152
-
153
- // Send back the output data (a JSON-encoded struct) to the plugin host.
154
- let outputStruct : PluginOutput
155
- do {
156
- outputStruct = try PluginOutput ( commands: generatedCommands, diagnostics: Diagnostics . emittedDiagnostics)
157
- } catch {
158
- internalError ( " Couldn’t encode output JSON: \( error) . " )
174
+ // Ignore other messages
175
+ default :
176
+ continue
177
+ }
159
178
}
160
- try outputHandle. write ( contentsOf: outputStruct. outputData)
161
179
}
162
180
163
181
public static func main( ) throws {
164
182
try self . main ( CommandLine . arguments)
165
183
}
166
184
}
185
+
186
+
187
+ /// A message that the host can send to the plugin.
188
+ enum HostToPluginMessage : Decodable {
189
+ case performAction( input: WireInput )
190
+ case quit
191
+ }
192
+
193
+ /// A message that the plugin can send to the host.
194
+ enum PluginToHostMessage : Encodable {
195
+ case provideResult( output: WireOutput )
196
+ }
197
+
198
+ fileprivate extension FileHandle {
199
+
200
+ func writePluginMessage( _ message: PluginToHostMessage ) throws {
201
+ // Encode the message as JSON.
202
+ let payload = try JSONEncoder ( ) . encode ( message)
203
+
204
+ // Form the header (a 12-digit length field in base-ten ASCII).
205
+ try self . write ( contentsOf: Data ( String ( format: " %012u " , payload. count) . utf8) )
206
+
207
+ // The data is the header followed by the payload.
208
+ try self . write ( contentsOf: payload)
209
+ }
210
+
211
+ func readPluginMessage( ) throws -> HostToPluginMessage ? {
212
+ // Read the header (a 12-digit length field in base-ten ASCII).
213
+ guard let header = try self . read ( upToCount: 12 ) else { return nil }
214
+ guard header. count == 12 else {
215
+ throw PluginMessageError . truncatedHeader
216
+ }
217
+
218
+ // Decode the count.
219
+ guard let count = Int ( String ( decoding: header, as: UTF8 . self) ) , count >= 2 else {
220
+ throw PluginMessageError . invalidPayloadSize
221
+ }
222
+
223
+ // Read the JSON payload.
224
+ guard let payload = try self . read ( upToCount: count) , payload. count == count else {
225
+ throw PluginMessageError . truncatedPayload
226
+ }
227
+
228
+ // Decode and return the message.
229
+ return try JSONDecoder ( ) . decode ( HostToPluginMessage . self, from: payload)
230
+ }
231
+
232
+ enum PluginMessageError : Swift . Error {
233
+ case truncatedHeader
234
+ case invalidPayloadSize
235
+ case truncatedPayload
236
+ }
237
+ }
0 commit comments