[Pitch] Package Manager Command Plugins

I have a security-related question inspired by CVE-2021-30892:

Is a plugin command run using the system's default shell? If it is, then will the shell start with --noprofile --norc or something similar, in order to prevent arbitrary code in user's shell configuration from running?

No, the plugin is invoked directly, with the compiled executable as the first argument to Foundation's Process. There is no shell involved. The same is true for the commands that build tool plugins return.


This proposal is great and I'm very excited about it!

  • The shape and number of exposed information to the plugins sounds about right.

[from the proposal] One key tension in this proposal is between providing rich functionality for plugins to use while still presenting that functionality in a way that's general enough to be implemented in both the SwiftPM CLI and in any IDE that supports packages. To that end, this proposal provides a minimal initial API, with the intention of adding more functionality in future proposals.

  • Realistically as long as an IDE can get a list of "command + description" they'll all be happy. This will work well with either CLion or Visual Studio Code I believe; they all generally simply expose "the commands" that they have discovered in some way as a list like this:

And for VS Code we're trying to get together and work on improving existing plugin or making a new one in the SSWG, so we'll figure it out. CLion has a good history supporting build tool commands, and I'm not worried about them as long as there's some way to "dump" what commands are available.

Question: So, how are external tools going to find commands they can invoke? Is there some swift plugin list --type=command or similar?

  • Great that a plugin can invoke build or test using the provided PackageManager, this is great. The name of that type is fine too, I think we had some worries about it at some point, but it seems fine to me :+1:

  • I would really really love re-visit is allowing commands to be "top level".

I.e.: swift doc rather than swift package doc, most notably because I'd love to offer "more fancy" ways to test that do things like "only this file changed, only re-run tests affected by it" which is something sbt can do and is called

I really would love to have "deployment" and documentation tasks available as top-level...

One can imagine that swift docc (to be honest I'd argue for swift doc but let's bikeshed that once that's on the table...) it much more what people will expect from other ecosystems:

  • mvn javadoc -- java's maven does not know about javadoc at all, it's a plugin; in our world we want to document the entire package of course
  • sbt test, sbt jmh, sbt testQuick

I think most notably, we should look at go that while does not have plugins, it does have the "CLI tool" share the name with the language (as does our swift command), and there are plenty "not really package task" involved here as well:

  • go doc
  • go vet (linting, in our world this would be swift lint which again is an external tool today).
  • go generate (generate sources) etc.

I think it really matters for discoverability and making swift feel simple if such tasks are possible to invoke just as swift. I was recently dreaming of swift deploy staging where staging is an environment name you want to deploy to (and have configured the plugin to do so). All those become rather tedious if we had to say "swift package deploy staging" IMHO...

We could say that if ambiguous (or the top level name is used up by something provided by Swift already) you have to invoke the "long form" of swift package <command> but in general I'd love commands to just be available on top level.

The PluginCommandIntent sounds good to me, but I wonder -- shouldn't this be a collection of static properties rather than an enum?

I can imagine browsing plugins by "intent" and looking for a good source code formatter etc, but why should specific names be blocked on major swift versions, as I understand we could not add new enum cases within minor swift releases?

Minor: Should we qualify as MyPlugin:doc rather than MyPlugin.doc? I guess the dot seems weird to me personally... Swift doesn't really have namespacing so we don't have much to lean on here...

The permissions affect the ways in which the command plugin can access external resources such as the file system or network. By default, command plugins have only read-only access to the file system (except for temporary-files locations) and cannot access the network.

We should clarify that this is only currently supported on apple platforms via sandboxing, as I understand we don't have this implemented on Linux yet...?

Feedback on the implementation of:

Many command plugins will invoke other tools to do the actual work. A plugin can use Foundation’s Process API to invoke executables, using the PluginContext.tool(named:) API to obtain the full path of the command line tool in the local file system (even if it originally came from a binary target or is provided by the Swift toolchain, etc).

When writing plugins I found it hard to understand what name I'm expected to pass there.

It would be nice if the error when crashing with an unknown name provided the available tools and where they came from.

It seems we want to call this:

var packageManager: PackageManagerServiceProvider { get }

just PackageManager instead? it seems so in the listing below where we discuss struct PackageManager.

I love the ways to interact with it -- great that it's an async function to build or test. This would allow me to implement a "quick test" (only run tests which failed the last time) thing that other package managers offer, very excited for this.

The TestResult seems to be missing information that I'd like to see in the proposal?

public struct TestResult {
   /// Path of the code coverage JSON file, if code coverage was requested.
   public var codeCoveragePath: Path?
   /// This should also contain information about the tests that were run
   /// and whether each succeeded/failed.


So if TestResult is "all the tests" how will be the individual tests reported? let tests: [????TestResult]? where each has a failed / succeeded / skipped? Would those also be able to have the time it took them to execute?

I love the way running tests is exposed, it is nice to be able to pass the filterest list there.

Overall design comment:

So this is a pretty simple plugin infrastructure; we can't have plugins produce outputs that other plugins take as inputs and have it be type-safe etc. That's fine I suppose, though every time I read these I'm left longing for a very powerful system like that (and that I'm used to from a prior life). I understand though this would be very hard to implement in compatible ways with existing build systems, so I'll drop the ball on that -- this should be good enough for all the needs we have today :+1:

I'm positive on the pitch! I'm excited to be able to write a few types of plugins I'm missing in Swift today: building docs, deployments, "test quick", and "special weird tests (that create multiple clustered nodes)" etc.

Thank you for the work on this, it's really promising!


Thanks a lot @ktoso for the extensive feedback!

Thanks, great to hear. One of the particular areas where I think it's difficult to find this balance is in the PackageManager type where SwiftPM or an IDE can vend services for plugins to "call back" into. It's easy here to make unintentional assumptions about the concepts and features vended by the environment in which the plugin is running.

One possibility is that there is some way of expressing whether a particular functionality is available, e.g. a plugin that requires access to symbol graphs can only work where those are available.

That's a good point — the intent was that tools that integrate with packages would use libSwiftPM and invoke tools that way, and there they could fairly easily find the plugins. They will also appear in package describe. But I think it's a great idea to also add some kind of list functionality, even for use from the command line.

1 Like

I can see the point, but I am concerned about possible ambiguity here. The direct subcommands of swift that are available today are a mixture of ones that conceptually operate on packages and ones that don't, and I think that's confusing. For example, swift build and swift demangle both look like peers but operate very differently (in particular one requires a package and one does not).

I would actually prefer to see us go the opposite way (and I think this was the intent at some point) to make any command that operates on packages be spelled as swift package <command>. With the introduction of a now open-ended set of verbs, it seems to me that having the Swift driver treat any unknown verb as a possible Swift Package plugin command would get confusing.

I'm pretty sure that @available will work for minor releases of SwiftPM (note these are SwiftPM version, not Swift language versions). For example in CLanguageStandard, there is:

    /// ISO C 2017.
    @available(_PackageDescription, introduced: 5.4)
    case c17

etc. So I don't think that static functions will give us anything here that enums don't. The only thing is that we couldn't have two cases with the same name but different sets of parameters, but I think that might be confusing to have anyway.

That makes sense, although we could also use a separate parameter that specifies which target (or package?) the plugin comes from.

Yes, this is a great point. That's the current case for manifests too. I think only Darwin is sandboxed at the moment.

Many command plugins will invoke other tools to do the actual work. A plugin can use Foundation’s Process API to invoke executables, using the PluginContext.tool(named:) API to obtain the full path of the command line tool in the local file system (even if it originally came from a binary target or is provided by the Swift toolchain, etc).

Thanks, that's a great point. Perhaps we should also have an iterable collection of the available tools.

Yes, I think that was just an oversight. Thanks!

Yes, you're right. This struct needs to be reworked a bit. The intent is to pass back a struct for each of the tests, grouped into the different test suites that were run. I'll update the pitch with a reworking of this part.

Thanks a lot for the detailed comments as well as this assessment. One challenge here has been to find a way to fit plugins into the existing Swift Package Manager, and to make it work in the different environments where packages are supported (with their own build systems etc).

It would be fantastic to reconsider how SwiftPM works at a more fundamental level, and I think that would open up a lot of possibilities. Having type safe information flow from one plugin to another might be possible in the current SwiftPM — I'd have to think more about that — but one more basic thing that would make a big difference would be to be able to have layered dependency graphs, e.g. to have plugins and build tools be able to have separate dependencies from the actual library or executable code that they operate on.

The goal with the current effort is to add some flexibility within the current model. But I would love to see a rethinking of SwiftPM that was built on plugins from the ground up rather than have them get added on as this and the previous plugin proposals are doing.

You mentioned that you'd worked with a plugin system that does this really well — which one was that, and do you think its approach could be made to work in SwiftPM without a fundamental restructuring?

Thanks again for all your thoughts and comments here!


CLion currently retrieves SwiftPM project structure by running swift package dump-package. It would indeed be really helpful for CLion, and probably for other IDE integrations as well, if SwiftPM had a similar command for dumping JSON with command names & descriptions.

1 Like

+1 on this, it looks really great!

Also, on a broader level, I really love these new plugin APIs that have been added recently. The idea of providing a special module which gives you access to package manager services is a really elegant solution. My experience of using other build systems (make, cmake, etc) is full of awkward DSLs to access the build system's understanding of the project so I can build some software the way I need. Having a richly-typed Swift API which lets me build parts of the package graph and access SwiftPM's understanding of the package is so much better.

One thing I would like is custom swift settings on BuildParameters. I think it would be useful to add a fuzzing plugin, which currently requires adding the following settings to your fuzzer executable targets:

  name: ...,
  dependencies: ...,
  swiftSettings: [.unsafeFlags(["-parse-as-library", "-sanitize=fuzzer,address"])]

And building with a script similar to the following to set some compiler flags:

if [ "$(uname)" == "Darwin" ]; then
  # Apple's SDK toolchains do not include fuzzer support, but swift.org toolchains do.
  xcrun -toolchain swift swift build -Xswiftc -sanitize=fuzzer,address -Xswiftc -g
  swift build -Xswiftc -sanitize=fuzzer,address -Xswiftc -g

It would be nice if this could be done with a command plugin, so I could just add the plugin dependency, and start fuzzing with swift fuzz or swift package fuzz.


I had come up with the idea of introducing a new swiftpm shortcut which packs the core functionality of SwiftPM, with subcommands like build, run, clean, reset, update as well as custom ones. This would make new users comfortable because such command interface is generally shorter and more popular, but the details should be carefully handled.

Thanks a lot for chiming in @egor.zhdan! We'll make sure the plugins are discoverable in some way like this :+1:

Thanks for this suggestion, I can really see the use of this. Would it suffice to have the same flags for all targets, as in your example with the -Xswiftc flag? It would of course be more flexible to allow custom flags for different targets, but specifying that could get quite complicated. Maybe this proposal should start out by letting you specify the same things you can on the swift build command line (which applies to all targets) and then that can be further extended in a later proposal.

Thanks @egor.zhdan and @ktoso. Plugins would indeed be included in the swift package describe output (which has a JSON form in addition to text), but that only lists plugins that are defined in the package being described.

Since the set of command plugins that is available for a package includes those defined in the package as well as in any dependencies, it might also make sense to have a separate command (as suggested here) that's more focused on the set of plugins that are available for use and where they come from. That could be derived by describing each of the packages but then every IDE would need to reimplement that logic. So this might be worth addressing in the proposal as well.

1 Like

That's an interesting idea and would help keep some clarity, though it seems beyond the scope of this proposal unless there is a strong push to make plugins be invoked directly from the swift driver. I don't think that decision necessarily has to be made as part of this proposal — we could start out by having a more verbose way to invoke the plugins and then broaden that in a future proposal (while keeping the more verbose way working of course) if there is a strong desire to do that. That seems easier than going the opposite direction.

I have updated the pitch based on a lot of the feedback here in the pitch. The main changes are to:

  • add information about the outcomes of the unit test runs to the TestResult structure
  • add the ability to pass extra flags in the BuildParameters structure
  • add a section with a suggested command syntax for listing the command plugins available to a package
  • fix various inconsistencies and mistakes in the examples

The top-of-branch of the pitch proposal has the changes, and the differences can be seen at Comparing d4ad816be262b9fa7381d35a1946b82356f65738...swiftpm-command-plugins · abertelrud/swift-evolution · GitHub


Is there a guide on how to setup a local environment to try the branch?

My use case is a new plugin provided by swift-argument-parser to used to generate a man page. To to this the plugin would need to run after the primary build and invoke the built binary with --experimental-dump-help.

1 Like

There isn't a specific guide for this feature beyond what is in CONTRIBUTING.md in the SwiftPM repository, but it is a good idea to add some more detailed steps to the implementation PR since it requires PackagePlugin changes. Because the implementation-in-progress is guarded by setting SWIFTPM_ENABLE_COMMAND_PLUGINS to 1 in the environment, I am hoping to get this into the main toolchain fairly quickly so that everyone can try it out in nightly builds.

This will be possible using this proposal, if you are fine with running the build from your plugin. In other words, this proposal is for cases where the custom command is the primary entry point. It can then do a build as a part of its implementation, but that isn't how users invoke the command plugin.

A separate kind of plugin that would be useful (but which is not this proposal) would be a kind of "build event watcher" plugin that would automatically run at some stage of the build, e.g. after an executable that uses SwiftArgumentParser is linked. Such a plugin would be activated as a side effect of doing a normal build. But that is not possible using this proposal.

The review has started at SE-0332: Package Manager Command Plugins.

Thanks a lot for all the feedback during the pitch!


To be sure, I know SwiftPM doesn’t allow us to build app targets yet, but the command could be invoked as a separate step, passing the path to the archived release build, until it becomes possible we can achieve the goal of not having to deal with multiple tech stacks and have automated, deterministic and secure processes.

I have a tool called swift-bundler that I have been using to build a macOS app with SwiftPM, and this proposal looks like it will help me to integrate swift-bundler with SwiftPM better, and improve its stability! The parts that I love about this proposal are that it provides context to the plugin (currently I have to parse package manifests manually); and that it allows plugins to request builds and such. The original extensible build tools proposal added most of what I needed, but this one brings it to the next level, and I look forward to this proposal (hopefully) getting implemented!


I had some problems with initial setup of buildTool and made a basic example to https://github.com/doozMen/SPMPluginif this helps somebody


I've used

swift package  --allow-writing-to-directory /Users/daniele/Desktop generate-documentation --target Glider --disable-indexing --output-path /Users/daniele/Desktop/GliderDoc --transform-for-static-hosting

to generate documentation for a project of mine.
The output is a folder of 14MB.
Is this expected? 14MB??
(I would upload it on Github)

Hi @danielemm, the query you have is related to the Swift-DocC plugin specifically. The size issue you're seeing is being discussed here.

1 Like