Use a dynamic library in a swift package on Linux

On Apple platforms, it's very easy to use a dynamic library in a swift package. All you have to do is create a .xcframework containing the .dylib file and the header files and include it in the package using a binaryTarget.
On Linux however, there isn't such an easy solution, AFAIK. I could create a target containing the header files and a module.modulemap, but how can I link the .so file into that?

You have to use systemLibrary target. Look at this example GitHub - mikolasstuchlik/GameTest: Playground for my exploration of SDL2 from Swift which uses various SDL libraries. Should work on macOS and Linux.

Hmm. The problem is that my library isn't installed on the system, but rather included in the package (actually there is some rust code in the package that is built into a dynamic library by a SPM build plugin). Is it somehow possible to use this setup together with a systemLibrary target?

SR 0305 has done some work towards proper binary library targets but the actual 'type = library' part isn't designed.

The unpleasant hack I've done is to inject compile/link flags in the Package.swift using unsafeflags to pick up the .so / .dylib / .dll and its headers -- I preferred this to passing on instructions to users to pass -Xswiftc etc. flags. (example without any kind of recommendation!)

There are many options. In that case, I usually use CXXSettings/LinkerSettings unsafeFlags, but it's not recommended for distribution. Or you may include the header files in your project "paste" the .so to any rpath destination.
Or you may use the same solution as SourceKitten - dlopen and dlsym.

Interesting. Normally I follow evolution proposals quite closely but this somehow went completely over my head. It's a pity that my use case isn't supported yet.

Yeah, that's probably what I will have to do for the time being. Thanks for the example, that will help me quite a bit.

In what way is that approach not recommended for distribution? Because it uses 'unsafe' flags?

unsafeFlags are ignored if the package is used as dependency. The only exception is, if the dependency is "checked out" as a specific commit or branch.

Ah ok. That should hopefully not be a problem for my use case. Thanks for your help!

1 Like

Just to clarify: this is an intentional design decision. Building a .so file for Linux that is reasonably portable across the range of distributions that Swift supports is not straightforward. SwiftPM needs additional tooling and clarification to instruct SwiftPM developers on how they can do this in an appropriate way as part of adding support for this kind of binary distribution, and so far no-one has spent the time to flesh this out.

Perhaps there could be room for a more simplified use case for those of us who control the deployment environment. E.g. we will deploy on a specific Linux version and will mandate both OS and toolchain versions for our customers (enterprise software deployment scenario) - we'll definitely want to dynamically link (and use library evolution) for our deployments and doesn't really care that the .so wouldn't be useable on any other OS/toolchain really.

I opened:

for that.

Yeah, I understand that. However, in my case the .so is built by a build tool plugin, so it will always work on the target platform.

Such a change normally has to go through swift evolution, so the probability of getting support for this into the language would be higher if someone pitched it here on the forums. Unfortunately, I don't have the time to do that at the moment.

The concern I have there is that it's quite hard to limit this to only the enterprise scenario, so you have to deal with the question of how to communicate to adopters not to use the feature, as well as to provide tooling that explains why things went wrong. I worry about how best to manage that. Frankly, I suspect it'll be easier to just do it right.

If we know how to do it right, I'd agree - I'm just worried that this will be postponed for a very long time while trying to figure that out blocking adoption in the meantime.

I do agree there's a challenge with the question of communication, but with a simple model it is easier (and perhaps the platform tuple could be complemented with a more specific exact platform we are using) to just say 'the platforms must match exactly, otherwise it will not work'. I'd want to try to help to push it forward as we do have a need for the 'simple case' and there does not seem to be palatable workarounds for it at the moment, I'll try to google previous discussions and solutions for how to 'do it right'.

I think the issue there is that "the platforms must match exactly" is extremely difficult to express. This implies the same distort, with the same set of installed dependent packages, at the same or later version. I honestly don't know how we'd express that.

My note about "the right way" is at Binary dependencies on Linux.

2 Likes