I am working on a MacOS app which is made with Swift Package Manager (using swift-bundler) and the app has a plugin system. For the plugin system to function correctly, any code that plugins need to access must be in a dynamic library (otherwise if both the app and the plugin statically link to the common code, features such as type casting don't work because the app and the plugin have separate copies of the types).
Currently, my project is structured as follows to ensure that the common code is dynamically linked to by the app and plugins:
- Package.swift (the root package manifest) - Sources - Client (the app executable target) - ... (a bunch of swift files) - Core (a nested package containing the common code) - Package.swift (defines a product with one target, the product is a dynamic library) - Sources - ... (swift files containing common code) - Exporter (a target that reexports the product from ../Core so that plugins can access it) - Exports.swift (the file that reexports the product)
This is a simplified version of the actual project structure, see the Delta Client repository if you want to take a look at the actual structure.
As you can see, just to dynamically link between the Client target and the Core target I had to create a nested package and add a target to reexport the nested package's product so that consumers of the root package can access it. This approach has the disadvantages that it hides a bunch of dependencies of the app inside the nested package, it makes adding new targets to the
Core package tedious, and it complicates the project structure a lot for new contributors. The reason that the
Core package needs to be split up into multiple smaller targets (which I haven't done yet), is to help improve release build times (because at the moment, a tiny change to the
Core target means recompiling hundreds of swift files which takes quite a while).
Before creating this post I searched for past evolution proposals related to monorepo Swift projects and I found that almost all of the relevant PRs and proposals evolved into the Swift Package Registry proposal and specification.
Is there a roadmap for Swift Package Registry support in SwiftPM? And if so, does someone who knows more about it think that it would be relevant to this use case of creating monorepo Swift projects with multiple 'sub-packages' that other packages can easily depend on (without reexporting).
There are two main other solutions that I can think of for this problem, one more favourable than the other
SwiftPM could add a mechanism to configure the linking between targets within the same package.
The main con of this approach is that if the common code was split into quite a few targets, there would be dynamic linking between all of the subsystems which I have a feeling could introduce some performance overhead in certain situations and would prevent quite a few whole module optimisations happening across target boundaries.
SwiftPM could add a feature similar to Rust workspaces, allowing better support for Swift monorepo projects. This would alleviate the issues with Solution 1 of introducing more dynamic linking targets and affecting optimisations within the common code even when split into smaller targets.
The 3 main options I have considered are:
- Allowing dynamic linking between same-package targets (not ideal)
- Supporting multi-package monorepo Swift projects (similar to Rust workspaces)
- Waiting for Swift Package Registry support if it is relevant to this use case
I am particular interested in option 3 because it would be quite elegant, but after skim reading the proposal and specification I still can't tell whether it would be relevant for this use case. Also, it would be nice to have a solution that doesn't involve a third party package registry (although that may be unreasonable). Is there a roadmap for when support for Swift Package Registries will be added?