Auto linking with Mach-O binaries
Auto linking is a feature that embeds information in your binaries' at compile time which is then used at link time to automatically link your dependencies. This allows you to reduce the duplication of flags between the different phases of your (or your consumers') builds.
For example, with this Objective-C file:
#include <Foundation/Foundation.h>
int main() {
NSLog(@"Hello, World!");
return 1;
}
Compiled with:
$ clang -fmodules -c foo.m -o foo.o
You can then inspect the options added for use at link time:
$ otool -l foo.o | grep LC_LINKER_OPTION -A3
cmd LC_LINKER_OPTION
cmdsize 40
count 2
string #1 -framework
string #2 Foundation
...
Now when linking this binary you don't have to pass any extra flags to
the linker to make sure you link Foundation
:
$ ld foo.o -syslibroot `xcrun --show-sdk-path`
To compare, if you compile the binary without -fmodules
1:
$ clang -c foo.m -o foo.o
You don't get any LC_LINKER_OPTION
s. Then when linking the binary with
the same command as before, it fails with these errors:
$ ld foo.o -syslibroot `xcrun --show-sdk-path`
Undefined symbols for architecture arm64:
"_NSLog", referenced from:
_main in foo.o
"___CFConstantStringClassReference", referenced from:
CFString in foo.o
ld: symbol(s) not found for architecture arm64
To make it succeed you must explicitly link Foundation
through an
argument to your linker invocation:
$ ld foo.o -syslibroot `xcrun --show-sdk-path` -framework Foundation
Auto linking is also applied when using module maps that use the link
directive. For example with this module map file:
// module.modulemap
module foo {
link "foo"
link framework "Foundation"
}
That you include with in this source file:
@import foo;
int main() {
return 1;
}
And compile (with an include path to the module.modulemap
file):
$ clang -fmodules -c foo.m -o foo.o -I.
The produced object depends on foo
and Foundation
. This can be
useful for handwriting module map files for prebuilt libraries, and for
quite a few other cases. You can read about this file format in the
docs.
You can also see auto linking with Swift code:
print("Hello, World!")
Compiled with:
$ swiftc foo.swift -o foo.o -emit-object
You can see it requires the Swift standard libraries:
$ otool -l foo.o | grep LC_LINKER_OPTION -A3
cmd LC_LINKER_OPTION
cmdsize 24
count 1
string #1 -lswiftCore
...
For Swift this is especially useful since there are some underlying
libraries like libswiftSwiftOnoneSupport.dylib
that need to be linked,
but should be treated as implementation details that Swift developers
are never exposed to.
In general, this is more than you'll ever need to know about auto
linking. But there are some situations where you might want to force
binaries to include LC_LINKER_OPTION
s when they don't automatically.
For example, if your build system builds without -fmodules
(like bazel
and cmake by default) and for some reason you cannot enable it1, or
when you're distributing a library and don't want your consumers to have
to worry about adding extra linker flags.
There are 3 different ways you can explicitly add LC_LINKER_OPTION
s
during your builds. First you can pass a flag when compiling your
sources with clang:
$ clang -c foo.m -o foo.o -Xclang --linker-option=-lfoo
Or with swiftc:
$ swiftc foo.swift -o foo.o -emit-object -Xcc -Xclang -Xcc --linker-option=-lfoo
Specifically for libraries there's an even easier way to do this:
$ clang -c foo.m -o foo.o -Xclang --dependent-lib=foo
These work perfectly for libraries you depend on, but for frameworks you need to pass multiple flags, and because of the space between them, it doesn't seem like there is a way to pass this with the current clang flags (although it seems reasonable to add support for this). Luckily the second option supports spaces in options. Instead of passing a flag, you can add an assembly directive to one of the source files you're compiling with clang:
#include <Foundation/Foundation.h>
asm(".linker_option \"-lfoo\"");
asm(".linker_option \"-framework\", \"Foundation\"");
int main() {
NSLog(@"Hello, World!");
return 1;
}
Compiling this results in a binary that automatically links Foundation
and foo
.
To see a real world example where this was helpful, check out this
change for building a static library from a C++ library that
requires some dependencies, but doesn't build with -fmodules
.