Linking static libraries with hidden visibility via Xcode
Apple seems to have brought in a useful new linker flag, of which I’ve found very little discussion online so far. The flag is -hidden-l
x and its purpose is summarised neatly in the ld(1) manpage on my Mac:
-hidden-lx
This is the same as the -lx for locating a static library, but treats
all global symbols from the static library as if they are visibility
hidden. Useful when building a dynamic library that uses a static
library but does not want to export anything from that static library.
I was in a bit of a pickle recently because of Xcode’s default behaviour of doing exactly the opposite.
- Have some Rust code cross-compiled to iOS static libraries (
.a
files). - Define a Framework target in Xcode and supply Other Linker Flags of the form
-lmylib1 -lmylib2
etc. to pull in that Rust code. - Define some nice ObjC wrappers around the Rust code, along with its C dependencies vendored via
*-sys
crates. The user shouldn’t have any need to directly use the underlying FFI/C functions.
The result: the framework not only contains the ObjC types, but also exports all of the Rust symbols and those of its dependencies! This is a great way to get symbol collisions in things like sqlite.
Traditionally (with GCC) you would use something like -Wl,--exclude-libs,libmylib.a
but Apple’s linker doesn’t have that flag. Even more traditionally you might analyse your binary, calculate a list of wanted/unwanted symbols and do a processing pass with strip
. By comparison, this “hidden-l” flag is much more convenient.
After some fiddling around I worked out how to apply this flag in Xcode. If the static library you want to link for internal consumption is called libmylib.a
, you would go to Other Linker Flags and replace the -l
with an entry in this format:
-Wl,-hidden-lmylib
Like -l
, the library name is sandwiched up against the flag. It is also necessary to use the -Wl
prefix to pass this directly through to the linker. Although this looks like a direct substitute for -l
, the clang frontend doesn’t understand it and it will give you an error like clang: error: unknown argument: 'hidden-l'
.
Easy peasy, no more annoying symbols.