I'm working on a Qt plugin for macOS. I want it to be as easy to integrate and use as possible, both for developers and end users. To that end, I want to provide compiled versions of it for different versions of Qt.
However, not everyone links to Qt's libraries the same way. Often you see an app linking to Qt as an rpath-relative framework like this:
@rpath/QtCore.framework/Versions/5/QtCore
but I'm currently dealing with an app that is linking to it as a dylib like so:
@rpath/libQt5Core.5.dylib
Is there any way to build my plugin so that it'll work with either way of linking to Qt's libraries? Or does this simply require separate builds?
You can kind of get there.
The first problem to overcome is the two-level namespace. By default, Mach-Os record not only what symbols they import, but also what library it should be found in. As far as I'm aware, there is no option to specify a list of libraries that a symbol can be found in, only the option to search all libraries that have been loaded into the process. This can be turned off for all imports with the -flat_namespace
linker option (-Wl,-flat_namespace
), or only for imports that aren't found in any of the libraries specified on the command line during compilation, with -undefined dynamic_lookup
(-Wl,-undefined,dynamic_lookup
).
The former has the advantage of making sure all imports are found in a library, but has the disadvantage of forcing all symbols into the flat namespace. The latter is the opposite, maintaining the library association for all symbols found, but quietly putting all symbols into the flat namespace that weren't found.
The second problem to overcome is linking to multiple versions of the same library. Of course, with flat namespace dynamic lookups you may not even need to load the library yourself at all, if the process you're loading into already has the library loaded. But you can weakly link against multiple library paths anyway, just to be safe, and if you choose the -flat_namespace
option, then you'll actually need something to link against.
Apple's TBD files come in handy here. If you create a file named libQT.tbd
with the following contents:
--- !tapi-tbd-v2
archs: [ i386, x86_64, x86_64h, arm64, arm64e ]
platform: macosx
install-name: "@rpath/libQt5Core.5.dylib"
exports:
- archs: [ i386, x86_64, x86_64h, arm64, arm64e ]
symbols: [ ]
...
Then you can link to it with -weak-lQT
. You can then just copy the file a bunch of times and edit in the different possible install names. This allows you to have different file names at link-time (e.g. libQT1.tbd
, libQT2.tbd
, etc.) even if the last segment of the path is the same (e.g. @rpath/libQt5Core.5.dylib
vs @rpath/lib/libQt5Core.5.dylib
).
And if you do use the -flat_namespace
option, then you'll want to dump all exported symbols from a real Qt5Core dylib and put them in one of the TBD files. You do not want to do this if you're using -undefined dynamic_lookup
though, as that would mark the imports as coming from one specific library.