Skip to content

Commit 18f7b44

Browse files
authored
Refactor and clean up plugin communication and wire messages (#4155)
Refactor the plugin machinery for better maintainability, consolidation of wire protocol struct definitions, and groundwork for extending the plugin functionality (such as adding new capabilities or new plugin messages such as dynamic querying of capabilities). Changes: - factor out the Codable declarations of enums and struct for the plugin wire protocol into a PluginMessage file that can be shared between the plugin and plugin host; this gets us compile-time checking so that the host and plugin are always in sync - separate out the functionality of DefaultPluginScriptRunner to only handle the mechanics to talking to the plugin and not be concerned with what the messages are (this is factored back down into PluginInvocation) - hoist the different kinds of plugin action messages up to top-level messages, allowing each to have a separate set of parameters - factor the plugin input context serializer and deserializer out into their own source files and better separating from the semantics - better separation of the structures for the plugin request structures (for building, testing, etc) to not reference any part of the plugin API; this will allow separate representations when/if necessary - pull the package parameter into the plugin action, to support actions that don't operate on packages (such as querying the plugin for information, etc) No functional changes.
1 parent e9672e0 commit 18f7b44

14 files changed

+1479
-1217
lines changed

Sources/Commands/SwiftPackageTool.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,8 +1064,7 @@ extension SwiftPackageTool {
10641064
// Run the command plugin.
10651065
let buildEnvironment = try swiftTool.buildParameters().buildEnvironment
10661066
let _ = try tsc_await { plugin.invoke(
1067-
action: .performCommand(arguments: arguments),
1068-
package: package,
1067+
action: .performCommand(package: package, arguments: arguments),
10691068
buildEnvironment: buildEnvironment,
10701069
scriptRunner: pluginScriptRunner,
10711070
workingDirectory: swiftTool.originalWorkingDirectory,

Sources/PackagePlugin/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ add_library(PackagePlugin
1616
PackageModel.swift
1717
Path.swift
1818
Plugin.swift
19-
PluginInput.swift
19+
PluginContextDeserializer.swift
20+
PluginMessages.swift
2021
Protocols.swift
2122
Utilities.swift)
2223

Sources/PackagePlugin/PackageManagerProxy.swift

Lines changed: 195 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See http://swift.org/LICENSE.txt for license information
@@ -29,14 +29,14 @@ public struct PackageManager {
2929
) throws -> BuildResult {
3030
// Ask the plugin host to build the specified products and targets, and wait for a response.
3131
// FIXME: We'll want to make this asynchronous when there is back deployment support for it.
32-
return try sendMessageAndWaitForReply(.buildOperationRequest(subset: subset, parameters: parameters)) {
32+
return try sendMessageAndWaitForReply(.buildOperationRequest(subset: .init(subset), parameters: .init(parameters))) {
3333
guard case .buildOperationResponse(let result) = $0 else { return nil }
34-
return result
34+
return .init(result)
3535
}
3636
}
3737

3838
/// Specifies a subset of products and targets of a package to build.
39-
public enum BuildSubset: Encodable {
39+
public enum BuildSubset {
4040
/// Represents the subset consisting of all products and of either all
4141
/// targets or (if `includingTests` is false) just non-test targets.
4242
case all(includingTests: Bool)
@@ -49,7 +49,7 @@ public struct PackageManager {
4949
}
5050

5151
/// Parameters and options to apply during a build.
52-
public struct BuildParameters: Encodable {
52+
public struct BuildParameters {
5353
/// Whether to build for debug or release.
5454
public var configuration: BuildConfiguration
5555

@@ -76,17 +76,17 @@ public struct PackageManager {
7676

7777
/// Represents an overall purpose of the build, which affects such things
7878
/// asoptimization and generation of debug symbols.
79-
public enum BuildConfiguration: String, Encodable {
79+
public enum BuildConfiguration: String {
8080
case debug, release
8181
}
8282

8383
/// Represents the amount of detail in a build log.
84-
public enum BuildLogVerbosity: String, Encodable {
84+
public enum BuildLogVerbosity: String {
8585
case concise, verbose, debug
8686
}
8787

8888
/// Represents the results of running a build.
89-
public struct BuildResult: Decodable {
89+
public struct BuildResult {
9090
/// Whether the build succeeded or failed.
9191
public var succeeded: Bool
9292

@@ -98,7 +98,7 @@ public struct PackageManager {
9898
public var builtArtifacts: [BuiltArtifact]
9999

100100
/// Represents a single artifact produced during a build.
101-
public struct BuiltArtifact: Decodable {
101+
public struct BuiltArtifact {
102102
/// Full path of the built artifact in the local file system.
103103
public var path: Path
104104

@@ -108,7 +108,7 @@ public struct PackageManager {
108108
/// Represents the kind of artifact that was built. The specific file
109109
/// formats may vary from platform to platform — for example, on macOS
110110
/// a dynamic library may in fact be built as a framework.
111-
public enum Kind: String, Decodable {
111+
public enum Kind: String {
112112
case executable, dynamicLibrary, staticLibrary
113113
}
114114
}
@@ -129,14 +129,14 @@ public struct PackageManager {
129129
) throws -> TestResult {
130130
// Ask the plugin host to run the specified tests, and wait for a response.
131131
// FIXME: We'll want to make this asynchronous when there is back deployment support for it.
132-
return try sendMessageAndWaitForReply(.testOperationRequest(subset: subset, parameters: parameters)) {
132+
return try sendMessageAndWaitForReply(.testOperationRequest(subset: .init(subset), parameters: .init(parameters))) {
133133
guard case .testOperationResponse(let result) = $0 else { return nil }
134-
return result
134+
return .init(result)
135135
}
136136
}
137137

138138
/// Specifies what tests in a package to run.
139-
public enum TestSubset: Encodable {
139+
public enum TestSubset {
140140
/// Represents all tests in the package.
141141
case all
142142

@@ -147,7 +147,7 @@ public struct PackageManager {
147147
}
148148

149149
/// Parameters that control how the tests are run.
150-
public struct TestParameters: Encodable {
150+
public struct TestParameters {
151151
/// Whether to collect code coverage information while running the tests.
152152
public var enableCodeCoverage: Bool
153153

@@ -157,7 +157,7 @@ public struct PackageManager {
157157
}
158158

159159
/// Represents the result of running unit tests.
160-
public struct TestResult: Decodable {
160+
public struct TestResult {
161161
/// Whether the test run succeeded or failed.
162162
public var succeeded: Bool
163163

@@ -171,24 +171,24 @@ public struct PackageManager {
171171

172172
/// Represents the results of running some or all of the tests in a
173173
/// single test target.
174-
public struct TestTarget: Decodable {
174+
public struct TestTarget {
175175
public var name: String
176176
public var testCases: [TestCase]
177177

178178
/// Represents the results of running some or all of the tests in
179179
/// a single test case.
180-
public struct TestCase: Decodable {
180+
public struct TestCase {
181181
public var name: String
182182
public var tests: [Test]
183183

184184
/// Represents the results of running a single test.
185-
public struct Test: Decodable {
185+
public struct Test {
186186
public var name: String
187187
public var result: Result
188188
public var duration: Double
189189

190190
/// Represents the result of running a single test.
191-
public enum Result: String, Decodable {
191+
public enum Result: String {
192192
case succeeded, skipped, failed
193193
}
194194
}
@@ -206,19 +206,19 @@ public struct PackageManager {
206206
) throws -> SymbolGraphResult {
207207
// Ask the plugin host for symbol graph information for the target, and wait for a response.
208208
// FIXME: We'll want to make this asynchronous when there is back deployment support for it.
209-
return try sendMessageAndWaitForReply(.symbolGraphRequest(targetName: target.name, options: options)) {
209+
return try sendMessageAndWaitForReply(.symbolGraphRequest(targetName: target.name, options: .init(options))) {
210210
guard case .symbolGraphResponse(let result) = $0 else { return nil }
211-
return result
211+
return .init(result)
212212
}
213213
}
214214

215215
/// Represents options for symbol graph generation.
216-
public struct SymbolGraphOptions: Encodable {
216+
public struct SymbolGraphOptions {
217217
/// The symbol graph will include symbols at this access level and higher.
218218
public var minimumAccessLevel: AccessLevel
219219

220220
/// Represents a Swift access level.
221-
public enum AccessLevel: String, CaseIterable, Encodable {
221+
public enum AccessLevel: String, CaseIterable {
222222
case `private`, `fileprivate`, `internal`, `public`, `open`
223223
}
224224

@@ -236,13 +236,15 @@ public struct PackageManager {
236236
}
237237

238238
/// Represents the result of symbol graph generation.
239-
public struct SymbolGraphResult: Decodable {
239+
public struct SymbolGraphResult {
240240
/// The directory that contains the symbol graph files for the target.
241241
public var directoryPath: Path
242242
}
243-
243+
}
244+
245+
fileprivate extension PackageManager {
244246
/// Private helper function that sends a message to the host and waits for a reply. The reply handler should return nil for any reply message it doesn't recognize.
245-
fileprivate func sendMessageAndWaitForReply<T>(_ message: PluginToHostMessage, replyHandler: (HostToPluginMessage) -> T?) throws -> T {
247+
func sendMessageAndWaitForReply<T>(_ message: PluginToHostMessage, replyHandler: (HostToPluginMessage) -> T?) throws -> T {
246248
try pluginHostConnection.sendMessage(message)
247249
guard let reply = try pluginHostConnection.waitForNextMessage() else {
248250
throw PackageManagerProxyError.unspecified("internal error: unexpected lack of response message")
@@ -264,3 +266,170 @@ public enum PackageManagerProxyError: Error {
264266
/// An unspecified other kind of error from the Package Manager proxy.
265267
case unspecified(_ message: String)
266268
}
269+
270+
fileprivate extension PluginToHostMessage.BuildSubset {
271+
init(_ subset: PackageManager.BuildSubset) {
272+
switch subset {
273+
case .all(let includingTests):
274+
self = .all(includingTests: includingTests)
275+
case .product(let name):
276+
self = .product(name)
277+
case .target(let name):
278+
self = .target(name)
279+
}
280+
}
281+
}
282+
283+
fileprivate extension PluginToHostMessage.BuildParameters {
284+
init(_ parameters: PackageManager.BuildParameters) {
285+
self.configuration = .init(parameters.configuration)
286+
self.logging = .init(parameters.logging)
287+
self.otherCFlags = parameters.otherCFlags
288+
self.otherCxxFlags = parameters.otherCxxFlags
289+
self.otherSwiftcFlags = parameters.otherSwiftcFlags
290+
self.otherLinkerFlags = parameters.otherLinkerFlags
291+
}
292+
}
293+
294+
fileprivate extension PluginToHostMessage.BuildParameters.Configuration {
295+
init(_ configuration: PackageManager.BuildConfiguration) {
296+
switch configuration {
297+
case .debug:
298+
self = .debug
299+
case .release:
300+
self = .release
301+
}
302+
}
303+
}
304+
305+
fileprivate extension PluginToHostMessage.BuildParameters.LogVerbosity {
306+
init(_ verbosity: PackageManager.BuildLogVerbosity) {
307+
switch verbosity {
308+
case .concise:
309+
self = .concise
310+
case .verbose:
311+
self = .verbose
312+
case .debug:
313+
self = .debug
314+
}
315+
}
316+
}
317+
318+
fileprivate extension PackageManager.BuildResult {
319+
init(_ result: HostToPluginMessage.BuildResult) {
320+
self.succeeded = result.succeeded
321+
self.logText = result.logText
322+
self.builtArtifacts = result.builtArtifacts.map { .init($0) }
323+
}
324+
}
325+
326+
fileprivate extension PackageManager.BuildResult.BuiltArtifact {
327+
init(_ artifact: HostToPluginMessage.BuildResult.BuiltArtifact) {
328+
self.path = .init(artifact.path)
329+
self.kind = .init(artifact.kind)
330+
}
331+
}
332+
333+
fileprivate extension PackageManager.BuildResult.BuiltArtifact.Kind {
334+
init(_ kind: HostToPluginMessage.BuildResult.BuiltArtifact.Kind) {
335+
switch kind {
336+
case .executable:
337+
self = .executable
338+
case .dynamicLibrary:
339+
self = .dynamicLibrary
340+
case .staticLibrary:
341+
self = .staticLibrary
342+
}
343+
}
344+
}
345+
346+
fileprivate extension PluginToHostMessage.TestSubset {
347+
init(_ subset: PackageManager.TestSubset) {
348+
switch subset {
349+
case .all:
350+
self = .all
351+
case .filtered(let regexes):
352+
self = .filtered(regexes)
353+
}
354+
}
355+
}
356+
357+
fileprivate extension PluginToHostMessage.TestParameters {
358+
init(_ parameters: PackageManager.TestParameters) {
359+
self.enableCodeCoverage = parameters.enableCodeCoverage
360+
}
361+
}
362+
363+
fileprivate extension PackageManager.TestResult {
364+
init(_ result: HostToPluginMessage.TestResult) {
365+
self.succeeded = result.succeeded
366+
self.testTargets = result.testTargets.map{ .init($0) }
367+
self.codeCoverageDataFile = result.codeCoverageDataFile.map{ .init($0) }
368+
}
369+
}
370+
371+
fileprivate extension PackageManager.TestResult.TestTarget {
372+
init(_ testTarget: HostToPluginMessage.TestResult.TestTarget) {
373+
self.name = testTarget.name
374+
self.testCases = testTarget.testCases.map{ .init($0) }
375+
}
376+
}
377+
378+
fileprivate extension PackageManager.TestResult.TestTarget.TestCase {
379+
init(_ testCase: HostToPluginMessage.TestResult.TestTarget.TestCase) {
380+
self.name = testCase.name
381+
self.tests = testCase.tests.map{ .init($0) }
382+
}
383+
}
384+
385+
fileprivate extension PackageManager.TestResult.TestTarget.TestCase.Test {
386+
init(_ test: HostToPluginMessage.TestResult.TestTarget.TestCase.Test) {
387+
self.name = test.name
388+
self.result = .init(test.result)
389+
self.duration = test.duration
390+
}
391+
}
392+
393+
fileprivate extension PackageManager.TestResult.TestTarget.TestCase.Test.Result {
394+
init(_ result: HostToPluginMessage.TestResult.TestTarget.TestCase.Test.Result) {
395+
switch result {
396+
case .succeeded:
397+
self = .succeeded
398+
case .skipped:
399+
self = .skipped
400+
case .failed:
401+
self = .failed
402+
}
403+
}
404+
}
405+
406+
fileprivate extension PluginToHostMessage.SymbolGraphOptions {
407+
init(_ options: PackageManager.SymbolGraphOptions) {
408+
self.minimumAccessLevel = .init(options.minimumAccessLevel)
409+
self.includeSynthesized = options.includeSynthesized
410+
self.includeSPI = options.includeSPI
411+
}
412+
}
413+
414+
fileprivate extension PluginToHostMessage.SymbolGraphOptions.AccessLevel {
415+
init(_ accessLevel: PackageManager.SymbolGraphOptions.AccessLevel) {
416+
switch accessLevel {
417+
case .private:
418+
self = .private
419+
case .fileprivate:
420+
self = .fileprivate
421+
case .internal:
422+
self = .internal
423+
case .public:
424+
self = .public
425+
case .open:
426+
self = .open
427+
}
428+
}
429+
}
430+
431+
fileprivate extension PackageManager.SymbolGraphResult {
432+
init(_ result: HostToPluginMessage.SymbolGraphResult) {
433+
self.directoryPath = .init(result.directoryPath)
434+
}
435+
}

0 commit comments

Comments
 (0)