Some inspiration for dynamically finding plug-in types:
For anyone interested in seeing an existing implementation of a Swift plugin system, my project Delta Client has one. Hereās the plugin api and hereās an example plugin. As you would see if you read some of that code, a nicer API for dynamic loading would definitely help! The plugin side of things isnāt too bad, plugin loading is the tricky part.
Hereās an overview of the approach I took:
- Plugins declare a cdecl function called buildPlugin that returns a pointer to a PluginBuilder instance
- The plugin builder holds the metatype of the plugin in a property and has a method that just calls the initialiser of the metatype to create a new instance of the plugin
- The plugin has to be transported that way because generics donāt really work across the boundary between the pluginās dylib and the main app. I tried a few other methods and none of them worked but I canāt remember exactly what the methods were or why they failed.
- Plugins just conform to a basic protocol that acts as the interface between the app and the plugin
Even just getting all of the dynamic linking working correctly with SwiftPM (the entire project including the app is SwiftPM) was a pain, I ended up having to make sub packages and reexport them from the root package. Maybe making that easier could be another topic of discussion at some point.
For further reading I suggest this article which is about making a plugin system in Swift. It is where I got my initial implementation from. I just cleaned it up and tweaked it bit. Itās the cleanest method Iāve found.
Ok, I've put together a standalone basic dynamic plugin API based on the approach of @stackotter and other related posts and blogs I've scrapped up while googling which works on macOS 12+ and Linux without any Foundation or Obj-C dependencies (uses actors though so only recent OS versions).
It's not doing the fancy type filtering, but provides a simple approach of supporting multiple types of plugins by specialising the factory based on the factory type used to create instances. It's also possible of course (and recommended really) to have different types of plugins in different locations.
Example:
let pluginManager = try await PluginManager<PluginExampleAPIFactory>(withPath: validPath)
for (_, plugin) in await pluginManager.plugins {
var myInstance = plugin.factory.create()
myInstance.setPluginManagerExampleAPI(PluginManagerExampleAPIProvider())
print(myInstance.callFunctionThatUsesHostingApplicationAPI())
}
I have the idea to use SPM:s diagnose-api-breaking-changes during compile time to generate a semver automatically in the future for both hosting API and plugin API to have more robust compatibility checking. For the time being, the onus is on the user to make sure compatible plugins are deployed for a given hosting application.
It's early days and no formal version is cut yet and API is subject to change, but if someone wants to play with it I put it out there - the main package for hosting apps can be found here: GitHub - hassila/swift-plugin-manager: Dynamic loading of Swift plug-ins for extensible architectures
The unit tests are required to be run from the command line as I didn't figure out how to find whether it was run from Xcode or where the output directory is there.
To download and run the sample application:
mkdir plugin-test
cd plugin-test
git clone https://github.com/hassila/swift-plugin-manager-example
git clone https://github.com/hassila/swift-plugin-example
cd swift-plugin-example
swift build
cd ../swift-plugin-manager-example
swift run
To download main package and run test suite on both macOS and Linux:
git clone https://github.com/hassila/swift-plugin-manager
cd swift-plugin-manager/
swift test
...
Hope it can be useful for someone, but for me its just a basic starting point for plugin support without Foundation / ObjC.
Merry Christmas to everyone!
Joakim