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-lx and its purpose is summarised neatly in the ld(1) manpage on my Mac:

        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.

  1. Have some Rust code cross-compiled to iOS static libraries (.a files).
  2. Define a Framework target in Xcode and supply Other Linker Flags of the form -lmylib1 -lmylib2 etc. to pull in that Rust code.
  3. 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:


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.