Skip to content

Commit d8d4c77

Browse files
authored
Implement SE-0325 Additional Package Plugin APIs (#3758)
* Extend the plugin API by passing down more information about the package graph The original TargetBuildContext input structure was focused on build tools, which was the subject of SE-0303. In order to prepare for new kinds of plugins such as custom commands, which have much more varied purposes and need a more diverse set of input information, we generalize this structure into a PluginContext that contains information about a whole package graph. The graph — which is a directed acyclic graph and not trivially encodable into a transport format — is flattened and serialized into the plugin input JSON, and is reconstituted on the plugin side. Path deduplication is used to reduce the amount of information sent over the wire. A compatibility layer lets old plugins keep working with the old TargetBuildContext structure. This is preliminary pending approval of an upcoming pitch and evolution proposal. * Change Product and Target to protocols, and change the classes to structs. Since structs cannot be nested in protocols, `Package.Dependency` moves out to a top-level `PackageDependency`, and similarly `Target.Dependency` moves out to `TargetDependency`. Also define identifiers for Package, Product, and Target since they are now value types. We cannot make them conform to `Identifiable` and still use arrays of `Product` and `Target` due to the Self requirement of `Identifiable`. We can maybe resolve this in the future. For now we use the ID that we already have from the model that we were handed from libSwiftPM as the identifier, but the exact value doesn't matter. * Move the `builtProductsDirectory` in the package plugin context to be a private property. The original intent was that it could be used in forming paths to built executables, but this is instead done through the `tools` to paths mapping. * Bring draft implementation in line with proposal: - add Origin to Package struct - add SourceModuleTarget protocol, and separate SwiftSourceModuleTarget and ClangSourceModuleTarget - fill in BinaryArtifactTarget and SystemLibraryTarget - add ToolsVersion to Package struct * Add BuildEnvironment to plugin evaluation. * Adjust to changes to binary targets to have a `unknown` artifact type * Add support for `pkgConfig` for system library targets.
1 parent 32fb825 commit d8d4c77

File tree

20 files changed

+1769
-339
lines changed

20 files changed

+1769
-339
lines changed

Fixtures/Miscellaneous/Plugins/ContrivedTestPlugin/Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,14 @@ let package = Package(
3232
"MySourceGenBuildTool",
3333
]
3434
),
35+
// Sample system library target for testing.
36+
.systemLibrary(
37+
name: "libpcre",
38+
path: "Sources/libpcre",
39+
pkgConfig: "libpcre",
40+
providers: [
41+
.apt(["libpcre-dev"])
42+
]
43+
)
3544
]
3645
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module readline [system] {
2+
header "sdk_libpcre.h"
3+
link "libpcre"
4+
export *
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include <libpcre/libpcre.h>

Sources/Commands/SwiftTool.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,7 @@ public class SwiftTool {
766766
let result = try graph.invokePlugins(
767767
outputDir: outputDir,
768768
builtToolsDir: builtToolsDir,
769+
buildEnvironment: buildEnvironment,
769770
pluginScriptRunner: pluginScriptRunner,
770771
observabilityScope: self.observabilityScope,
771772
fileSystem: localFileSystem

Sources/PackagePlugin/CMakeLists.txt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(PackagePlugin
10-
PublicAPI/Command.swift
11-
PublicAPI/Context.swift
12-
PublicAPI/Diagnostics.swift
13-
PublicAPI/FileList.swift
14-
PublicAPI/Path.swift
15-
PublicAPI/Protocols.swift
16-
Implementation.swift)
10+
Command.swift
11+
Context.swift
12+
Diagnostics.swift
13+
Errors.swift
14+
PackageModel.swift
15+
Path.swift
16+
Plugin.swift
17+
PluginInput.swift
18+
PluginOutput.swift
19+
Protocols.swift)
1720

1821
target_compile_options(PackagePlugin PUBLIC
1922
$<$<COMPILE_LANGUAGE:Swift>:-package-description-version$<SEMICOLON>999.0>)

Sources/PackagePlugin/PublicAPI/Context.swift renamed to Sources/PackagePlugin/Context.swift

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,64 @@
66

77
See http://swift.org/LICENSE.txt for license information
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9-
*/
9+
*/
10+
11+
/// Provides information about the package for which the plugin is invoked,
12+
/// as well as contextual information based on the plugin's stated intent
13+
/// and requirements.
14+
public struct PluginContext {
15+
/// Information about the package to which the plugin is being applied.
16+
public let package: Package
17+
18+
/// The path of a writable directory into which the plugin or the build
19+
/// commands it constructs can write anything it wants. This could include
20+
/// any generated source files that should be processed further, and it
21+
/// could include any caches used by the build tool or the plugin itself.
22+
/// The plugin is in complete control of what is written under this di-
23+
/// rectory, and the contents are preserved between builds.
24+
///
25+
/// A plugin would usually create a separate subdirectory of this directory
26+
/// for each command it creates, and the command would be configured to
27+
/// write its outputs to that directory. The plugin may also create other
28+
/// directories for cache files and other file system content that either
29+
/// it or the command will need.
30+
public let pluginWorkDirectory: Path
31+
32+
/// The path of the directory into which built products associated with
33+
/// targets in the graph are written. This is a private implementation
34+
/// detail.
35+
let builtProductsDirectory: Path
36+
37+
/// Looks up and returns the path of a named command line executable tool.
38+
/// The executable must be provided by an executable target or a binary
39+
/// target on which the package plugin target depends. This function throws
40+
/// an error if the tool cannot be found. The lookup is case sensitive.
41+
public func tool(named name: String) throws -> Tool {
42+
if let path = self.toolNamesToPaths[name] { return Tool(name: name, path: path) }
43+
throw PluginContextError.toolNotFound(name: name)
44+
}
45+
46+
/// A mapping from tool names to their definitions. Not directly available
47+
/// to the plugin, but used by the `tool(named:)` API.
48+
let toolNamesToPaths: [String: Path]
49+
50+
/// Information about a particular tool that is available to a plugin.
51+
public struct Tool {
52+
/// Name of the tool (suitable for display purposes).
53+
public let name: String
54+
55+
/// Full path of the built or provided tool in the file system.
56+
public let path: Path
57+
}
58+
}
59+
60+
1061

1162
/// Provides information about the target being built, as well as contextual
1263
/// information such as the paths of the directories to which commands should
1364
/// be configured to write their outputs. This information should be used as
1465
/// part of generating the commands to be run during the build.
15-
public final class TargetBuildContext: Decodable {
16-
66+
public struct TargetBuildContext {
1767
/// The name of the target being built, as specified in the manifest.
1868
public let targetName: String
1969

@@ -83,48 +133,21 @@ public final class TargetBuildContext: Decodable {
83133
/// The executable must be provided by an executable target or a binary
84134
/// target on which the package plugin target depends. This function throws
85135
/// an error if the tool cannot be found. The lookup is case sensitive.
86-
public func tool(named name: String, line: UInt = #line) throws -> ToolInfo {
87-
if let tool = self.tools[name] { return tool }
88-
throw TargetBuildContextError.toolNotFound(name: name, line: line)
136+
public func tool(named name: String) throws -> ToolInfo {
137+
if let path = self.toolNamesToPaths[name] { return ToolInfo(name: name, path: path) }
138+
throw PluginContextError.toolNotFound(name: name)
89139
}
90140

91141
/// A mapping from tool names to their definitions. Not directly available
92-
/// to the plugin but used by the `tool(named:)` API.
93-
private let tools: [String: ToolInfo]
142+
/// to the plugin, but used by the `tool(named:)` API.
143+
let toolNamesToPaths: [String: Path]
94144

95145
/// Information about a particular tool that is available to a plugin.
96-
public struct ToolInfo: Codable {
97-
98-
/// Name of the tool, suitable for display purposes.
146+
public struct ToolInfo {
147+
/// Name of the tool (suitable for display purposes).
99148
public let name: String
100149

101-
/// Path of the built or provided tool in the file system.
150+
/// Full path of the built or provided tool in the file system.
102151
public let path: Path
103152
}
104-
105-
/// Internal
106-
internal let pluginAction: PluginAction
107-
108-
internal enum PluginAction: String, Decodable {
109-
case createBuildToolCommands
110-
}
111-
}
112-
113-
public enum TargetBuildContextError: Error {
114-
115-
/// Could not find a tool with the given name. This could be either because
116-
/// it doesn't exist, or because the plugin doesn't have a dependency on it.
117-
case toolNotFound(name: String, line: UInt)
118-
}
119-
120-
extension TargetBuildContextError: CustomStringConvertible {
121-
122-
public var description: String {
123-
switch self {
124-
case .toolNotFound(let name, let line):
125-
// FIXME: How to convey this line number to where it gets shown for errors at top level.
126-
// Need to customize "Fatal error: Error raised at top level: plugin doesn’t have access to any tool named ‘MySourceGenBuildTools’ [5]: file Swift/ErrorType.swift, line 200"
127-
return "plugin doesn’t have access to any tool named ‘\(name)’ [line \(line)]"
128-
}
129-
}
130153
}

Sources/PackagePlugin/PublicAPI/Diagnostics.swift renamed to Sources/PackagePlugin/Diagnostics.swift

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,41 @@
1212
/// plugin. After emitting one or more errors, the plugin should return a
1313
/// non-zero exit code.
1414
public struct Diagnostics {
15+
// Internal variable collecting the diagnostics that have been emitted.
16+
static var emittedDiagnostics: [Diagnostic] = []
1517

16-
// This prevents a Diagnostics from being instantiated by the script.
18+
// This prevents a Diagnostics struct from being instantiated by the script.
1719
internal init() {}
18-
20+
1921
/// Severity of the diagnostic.
2022
public enum Severity: String, Encodable {
2123
case error, warning, remark
2224
}
23-
25+
2426
/// Emits an error with a specified severity and message, and optional file path and line number.
2527
public static func emit(_ severity: Severity, _ message: String, file: Path? = #file, line: Int? = #line) {
26-
output.diagnostics.append(Diagnostic(severity: severity, message: message, file: file, line: line))
28+
self.emittedDiagnostics.append(Diagnostic(severity: severity, message: message, file: file, line: line))
2729
}
2830

2931
/// Emits an error with the specified message, and optional file path and line number.
30-
public static func error(_ message: String, file: Path? = nil, line: Int? = nil) {
31-
output.diagnostics.append(Diagnostic(severity: .error, message: message, file: file, line: line))
32+
public static func error(_ message: String, file: Path? = #file, line: Int? = #line) {
33+
self.emit(.error, message, file: file, line: line)
3234
}
3335

3436
/// Emits a warning with the specified message, and optional file path and line number.
35-
public static func warning(_ message: String, file: Path? = nil, line: Int? = nil) {
36-
output.diagnostics.append(Diagnostic(severity: .warning, message: message, file: file, line: line))
37+
public static func warning(_ message: String, file: Path? = #file, line: Int? = #line) {
38+
self.emit(.warning, message, file: file, line: line)
3739
}
3840

3941
/// Emits a remark with the specified message, and optional file path and line number.
40-
public static func remark(_ message: String, file: Path? = nil, line: Int? = nil) {
41-
output.diagnostics.append(Diagnostic(severity: .remark, message: message, file: file, line: line))
42+
public static func remark(_ message: String, file: Path? = #file, line: Int? = #line) {
43+
self.emit(.remark, message, file: file, line: line)
4244
}
4345
}
46+
47+
struct Diagnostic {
48+
var severity: Diagnostics.Severity
49+
var message: String
50+
var file: Path?
51+
var line: Int?
52+
}

Sources/PackagePlugin/Errors.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
public enum PluginContextError: Error {
12+
/// Could not find a tool with the given name. This could be either because
13+
/// it doesn't exist, or because the plugin doesn't have a dependency on it.
14+
case toolNotFound(name: String)
15+
}
16+
17+
extension PluginContextError: CustomStringConvertible {
18+
public var description: String {
19+
switch self {
20+
case .toolNotFound(let name):
21+
return "Plugin does not have access to a tool named ‘\(name)"
22+
}
23+
}
24+
}
25+
26+
public enum PluginDeserializationError: Error {
27+
/// The input JSON is malformed in some way; the message provides more details.
28+
case malformedInputJSON(_ message: String)
29+
}
30+
31+
extension PluginDeserializationError: CustomStringConvertible {
32+
public var description: String {
33+
switch self {
34+
case .malformedInputJSON(let message):
35+
return "Malformed input JSON: \(message)"
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)