Skip to content

Commit 9e02bd6

Browse files
authored
Updates to the Plugins.md documentation (#5568)
* Some refinements to the language of the plugin documentation text, and add a few words about debugging and test (currently rather primitive but there are some workarounds). * Add a brief section about the XcodeProjectPlugin module that Xcode provides to extend PackagePlugin. This section will be extended over time, and should possible be in its own document at some point.
1 parent 904052c commit 9e02bd6

File tree

1 file changed

+56
-17
lines changed

1 file changed

+56
-17
lines changed

Documentation/Plugins.md

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ This guide provides a brief overview of Swift Package Manager plugins, describes
44

55
## Overview
66

7-
Some of Swift Package Manager's functionality can be extended through _plugins_. Package plugins are written in Swift using the `PackagePlugin` API provided by the Swift Package Manager. This is somewhat similar to how the Swift Package manifest itself is implemented as a Swift script that is run as needed to produce the information SwiftPM needs.
7+
Some of Swift Package Manager's functionality can be extended through _plugins_. Package plugins are written in Swift using the `PackagePlugin` API provided by the Swift Package Manager. This is similar to how the Swift Package manifest itself is implemented as a Swift script that runs as needed in order to produce the information SwiftPM needs.
88

9-
Plugins are represented as targets of the `pluginTarget` type in the SwiftPM manifest, and a plugin in one package can be made available to other packages by defining a `plugin` product for it. Source code for plugins is normally located under the `Plugins` directory in the package, but this can be customized.
9+
A plugin is represented in the SwiftPM package manifest as a target of the `pluginTarget` type and if it should be available to other packages, there also needs to be a corresponding `pluginProduct` target. Source code for a plugin is normally located in a directory under the `Plugins` directory in the package, but this can be customized.
1010

1111
SwiftPM currently defines two extension points for plugins:
1212

13-
- custom build tool tasks that are run before or during the build
13+
- custom build tool tasks that provide commands to run before or during the build
1414
- custom commands that are run using the `swift package` command line interface
1515

1616
A plugin declares which extension point it implements by defining the plugin's _capability_. This determines the entry point through which SwiftPM will call it, and determines which actions the plugin can perform.
1717

1818
Plugins have access to a representation of the package model, and plugins that define custom commands can also invoke services provided by SwiftPM to build and test products and targets defined in the package to which the plugin is applied.
1919

20-
Every plugin runs in its own process in a sandbox that prevents network access as well as attempts to write to the file system. Custom command plugins that need to modify the package source code can specify this requirement, and if the user approves, will have write access to the package directory. Build tool plugins cannot modify the package source code.
20+
Every plugin runs as a separate process, and (on platforms that support sandboxing) it is wrapped in a sandbox that prevents network access as well as attempts to write to arbitrary locations in the file system. Custom command plugins that need to modify the package source code can specify this requirement, and if the user approves, will have write access to the package directory. Build tool plugins cannot modify the package source code. All plugins can write to a temporary directory.
2121

2222
## Using a Package Plugin
2323

24-
A package plugin is available in the package that defines it, as well as in any other package that has a direct dependency on the package that defines it.
24+
A package plugin is available to the package that defines it, and if there is a corresponding plugin product, it is also available to any other package that has a direct dependency on the package that defines it.
2525

2626
To get access to a plugin defined in another package, add a package dependency on the package that defines the plugin. This will let the package access any build tool plugins and command plugins from the dependency.
2727

@@ -49,7 +49,7 @@ let package = Package(
4949
)
5050
```
5151

52-
This will cause SwiftPM to call the plugin, passing it a simplified version of the package model for the target to which it is being applied. Any build commands defined by the plugin will be incorporated into the build graph and will run at the appropriate time during the build.
52+
This will cause SwiftPM to call the plugin, passing it a simplified version of the package model for the target to which it is being applied. Any build commands returned by the plugin will be incorporated into the build graph and will run at the appropriate time during the build.
5353

5454
### Making use of a command plugin
5555

@@ -67,28 +67,28 @@ To list the plugins that are available within the context of a package, use the
6767
❯ swift package plugin --list
6868
```
6969

70-
Command plugins that need to write to the file system will only be allowed to do so if the user passes the `--allow-writing-to-package-directory` option to the `swift package` invocation.
70+
Command plugins that need to write to the file system will cause SwiftPM to ask the user for approval if `swift package` is invoked from a console, or deny the request if it is not. Passing the `--allow-writing-to-package-directory` flag to the `swift package` invocation will allow the request without questions — this is particularly useful in a Continuous Integration environment.
7171

7272
## Writing a Plugin
7373

7474
The first step when writing a package plugin is to decide what kind of plugin you need. If your goal is to generate source files that should be part of a build, or to perform other actions at the start of every build, implement a build tool plugin. If your goal is to provide actions that users can perform at any time and that are not associated with a build, implement a command plugin.
7575

7676
### Build tool plugins
7777

78-
Build tool plugins are invoked before building a package in order to construct command invocations that should run as part of the build. There are two kinds of command invocations that a build tool plugin can construct:
78+
Build tool plugins are invoked before a package is built in order to construct command invocations to run as part of the build. There are two kinds of commands that a build tool plugin can return:
7979

8080
- prebuild commands — are run before the build starts and can generate an arbitrary number of output files with names that can't be predicted before running the command
8181
- build commands — are incorporated into the build system's dependency graph and will run at the appropriate time during the build based on the existence and timestamps of their predefined inputs and outputs
8282

83-
Build commands are preferred over prebuild commands when the paths of all of the inputs and outputs are known before the command runs, since they allow the build system to more efficiently decide when they should be run. This is actually quite common. Examples include source translation tools that generate one output file (with a predictable name) for each input file, or other cases where the plugin can control the names of the outputs without having to first run the tool. In this case the build system can run the command only when some of the outputs are missing or when the inputs have changed since the last time the command ran.
83+
Build commands are preferred over prebuild commands when the paths of all of the inputs and outputs are known before the command runs, since they allow the build system to more efficiently decide when they should be run. This is actually quite common. Examples include source translation tools that generate one output file (with a predictable name) for each input file, or other cases where the plugin can control the names of the outputs without having to first run the tool. In this case the build system can run the command only when some of the outputs are missing or when the inputs have changed since the last time the command ran. There doesn't have to be a one-to-one correspondence between inputs and outputs; a plugin is free to choose how many (if any) output files to create by examining the input target using any logic it wants to.
8484

8585
Prebuild commands should be used only when the names of the outputs are not known until the tool is run — this is the case if the _contents_ of the input files (as opposed to just their names) determines the number and names of the output files. Prebuild commands have to run before every build, and should therefore do their own caching to do as little work as possible to avoid slowing down incremental builds.
8686

87-
In either case, it is important to note that it is not the plugin itself that does all the work of the build command — rather, the plugin constructs the commands that will later need to run, and it is those commands that perform the actual work. The plugin itself is usually rather small and mostly concerned with forming the command line for the build command that does the actual work.
87+
In either case, it is important to note that it is not the plugin itself that does all the work of the build command — rather, the plugin constructs the commands that will later need to run, and it is those commands that perform the actual work. The plugin itself is usually quite small and is mostly concerned with forming the command line for the build command that does the actual work.
8888

8989
#### Declaring a build tool plugin in the package manifest
9090

91-
Like all kinds of plugins, build tool plugins are declared in the package manifest. This is done using a `pluginTarget` entry in the `targets` section of the package, and if the plugin should be visible to other packages, there needs to be a corresponding entry in the `products` section as well:
91+
Like all kinds of package plugins, build tool plugins are declared in the package manifest. This is done using a `pluginTarget` entry in the `targets` section of the package. If the plugin should be visible to other packages, there needs to be a corresponding `plugin` entry in the `products` section as well:
9292

9393
```swift
9494
// swift-tools-version: 5.6
@@ -122,11 +122,11 @@ let package = Package(
122122
)
123123
```
124124

125-
The `plugin` target declares the name and capabilities of the plugin, along with its dependencies. The capability of `.buildTool()` is what indicates this as a build tool plugin as opposed to any other kind of plugin — this also determines what entry point the plugin is expected to implement (see below).
125+
The `plugin` target declares the name and capability of the plugin, along with its dependencies. The capability of `.buildTool()` is what declares it as a build tool plugin as opposed to any other kind of plugin — this also determines what entry point the plugin is expected to implement (as described below).
126126

127-
The Swift script files that implement the logic of the plugin are expected to be in a directory named the same as the plugin under the `Plugins` subdirectory of the package, unless overridden with a `path` parameter in the `pluginTarget`.
127+
The Swift script files that implement the logic of the plugin are expected to be in a directory named the same as the plugin, located under the `Plugins` subdirectory of the package. This can be overridden with a `path` parameter in the `pluginTarget`.
128128

129-
The `plugin` product is what makes the plugin visible to other packages that have dependencies on the package that defines the plugin. The name of the plugin doesn't have to match the name of the product, but they are often the same in order to avoid confusion. If a built tool plugin is used only within the package that declares it, there is no need to declare a `plugin` product.
129+
The `plugin` product is what makes the plugin visible to other packages that have dependencies on the package that defines the plugin. The name of the plugin doesn't have to match the name of the product, but they are often the same in order to avoid confusion. The plugin product should list only the name of the plugin target it vends. If a built tool plugin is used only within the package that declares it, there is no need to declare a `plugin` product.
130130

131131
#### Build tool target dependencies
132132

@@ -170,7 +170,7 @@ struct MyPlugin: BuildToolPlugin {
170170

171171
The plugin script can import *Foundation* and other standard libraries, but in the current version of SwiftPM, it cannot import other libraries.
172172

173-
In this example, the returned command of the type `buildCommand`, so it will be incorporated into the build system's command graph and will run if any of the output files are missing or if the contents of any of the input files have changed since the last time the command ran.
173+
In this example, the returned command is of the type `buildCommand`, so it will be incorporated into the build system's command graph and will run if any of the output files are missing or if the contents of any of the input files have changed since the last time the command ran.
174174

175175
Note that build tool plugins are always applied to a target, which is passed in the parameter to the entry point. Only source module targets have source files, so a plugin that iterates over source files will commonly test that the target it was given conforms to `SourceModuleTarget`.
176176

@@ -209,11 +209,11 @@ Note that a build tool plugin can return a combination of build tool commands an
209209

210210
Any prebuild commands are run after the plugin runs but before the build starts, and any files that are in the prebuild command's declared `outputFilesDirectory` will be evaluated as if they had been source files in the target. The prebuild command should add or remove files in this directory to reflect the results of having run the command.
211211

212-
The current version of the Swift Package Manager supports only generated Swift source files as outputs, but the intent is it support any type of file that could have been included as a source file in the target.
212+
The current version of the Swift Package Manager supports generated Swift source files and resources as outputs, but it does not yet support non-Swift source files. Any generated resources are processed as if they had been declared in the manifest with the `.process()` rule. The intent is to eventually support any type of file that could have been included as a source file in the target, and to let the plugin provide greater controls over the downstream processing of generated files.
213213

214214
### Command plugins
215215

216-
Command plugins are invoked at will by the user, by invoking `swift` `package` `<command>` `<arguments>`. They are unrelated to the build graph, and often perform their work by calling out to underlying tools as subprocesses.
216+
Command plugins are invoked at will by the user, by invoking `swift` `package` `<command>` `<arguments>`. They are unrelated to the build graph, and often perform their work by invoking to command line tools as subprocesses.
217217

218218
Command plugins are declared in a similar way to build tool plugins, except that they declare a `.command()` capability and implement a different entry point in the plugin script.
219219

@@ -337,3 +337,42 @@ In the current version of Swift Package Manager, plugins can only use standard s
337337
Plugin entry points are marked `throws`, and any errors thrown from the entry point cause the plugin invocation to be marked as having failed. The thrown error is presented to the user, and should include a clear description of what went wrong.
338338

339339
Additionally, plugins can use the `Diagnostics` API in PackagePlugin to emit warnings and errors that optionally include references to file paths and line numbers in those files.
340+
341+
### Debugging and Testing
342+
343+
SwiftPM doesn't currently have any specific support for debugging and testing plugins. Many plugins act only as adapters that construct command lines for invoking the tools that do the real work — in the cases in which there is non-trivial code in a plugin, the best current approach is to factor out that code into separate source files that can be included in unit tests in the plugin package via symbolic links with relative paths.
344+
345+
### Xcode Extensions to the PackagePlugin API
346+
347+
When invoked in Apple’s Xcode IDE, plugins have access to a library module provided by Xcode called *XcodeProjectPlugin* — this module extends the *PackagePlugin* APIs to let plugins work on Xcode targets in addition to packages.
348+
349+
In order to write a plugin that works with packages in every environment and that conditionally works with Xcode projects when run in Xcode, the plugin should conditionally import the *XcodeProjectPlugin* module when it is available. For exampe:
350+
351+
```swift
352+
import PackagePlugin
353+
354+
@main
355+
struct MyCommandPlugin: CommandPlugin {
356+
/// This entry point is called when operating on a Swift package.
357+
func performCommand(context: PluginContext, arguments: [String]) throws {
358+
debugPrint(context)
359+
}
360+
}
361+
362+
#if canImport(XcodeProjectPlugin)
363+
import XcodeProjectPlugin
364+
365+
extension MyCommandPlugin: XcodeCommandPlugin {
366+
/// This entry point is called when operating on an Xcode project.
367+
func performCommand(context: XcodePluginContext, arguments: [String]) throws {
368+
debugPrint(context)
369+
}
370+
}
371+
#endif
372+
```
373+
374+
The `XcodePluginContext` input structure is similar to the regular `PluginContext` structure, except that it provides access to an Xcode project that uses Xcode naming and semantics for the project model (which is somewhat different from that of SwiftPM). Some of the underlying types, such as `FileList`, `Path`, etc are the same for `PackagePlugin` and `XcodeProjectPlugin` types.
375+
376+
If any targets are chosen in the Xcode user interface, Xcode passes their names as `--target` arguments to the plugin.
377+
378+
It is expected that other IDEs or custom environments that use SwiftPM could similarly provide modules that define new entry points and extend the functionality of the core `PackagePlugin` APIs.

0 commit comments

Comments
 (0)