Xcode 7’s New Linker Rules

The other day I received a new error from Xcode 7. I was building an iOS project for simulator, linking in a fat static library with i386 and x86_64. The error looked like this:

ld: in /Users/tk/Code/.../libtest.a(TestLib.o), building for iOS simulator,
    but linking in object file built for OSX, for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

This is a pretty strange thing for a linker to say. The iOS simulator and OSX use exactly the same architectures. How is it supposed to know the difference, and why does it matter?

I followed this up with some testing. If I compiled a simple C library like this:

$ clang -c -arch i386 -arch x86_64 TestLib.c
$ libtool -static -o libtest.a TestLib.o
$ lipo -info libtest.a
Architectures in the fat file: libtest.a are: i386 x86_64

…then that would fail to link inside an iOS simulator project.

But if I took exactly the same files and built a static library using Xcode, then that libtest.a would link fine, even though both files contained the same function for the same architectures.4

So Xcode was doing something magic and I figured it must be a bug in Xcode 7’s linker. At this point I filed a bug report with Apple, who quickly got back to me with this explanation:

“In Xcode 7, the linker enforces that all files linked together were built for the same platform. You control the platform via -mmacosx-version-min=XX or -miphoneos-version-min=XX.”

As far as I can tell Xcode 7 makes sure that every single object file is marked with extra flags for both the platform (OSX, iOS, tvOS, watchOS) and the minimum version of the platform that it’s designed for. I’m not sure why. Perhaps there are technical reasons, but I can certainly imagine that with so many platforms flying about this could be a source of errors.

I tried recompiling my static library using clang -miphoneos-version-min=7.0 and sure it enough it worked. Makes sense. (Though confusingly, this particular option doesn’t appear in clang --help.)

They went on to explain that you can see these flags in .o or .a files by using the otool command. I tried it out and it came out like this:

# A static library marked as iOS
$ otool -lv libtest.a | grep LC_VERSION
    cmd LC_VERSION_MIN_IPHONEOS
    
# A static library marked as OSX
$ otool -lv libtest.a | grep LC_VERSION
    cmd LC_VERSION_MIN_MACOSX

Bafflingly, I haven’t been able to find any Apple documentation about these clang flags and their new significance. I decided I’d better write this up as it might save some people some time.


3 Comments

Chris
16 Sep 2019

Hello there

Your post just helped me out an awful lot!

I’ve got an Xcode 7 Swift project that includes auxiliary executables in its resources folder which it calls with NSTask to do work. Some of those executables were lacking the LC_VERSION_MIN_MACOSX load command, and even though I am not directly linking against them from my code, the archive validator processes them anyway, and validation fails for some distribution destinations like the Mac App Store (I guess they have stricter requirements) because those executables don’t have that header.

I then tried recompiling one of them, without making any change to its Makefile or other build config, on my own workstation, which has Xcode 7.3.1, and sure enough the new version of the binary had this load command in its header:


version 10.11
sdk n/a

So it seems to be Xcode 7.x policy to bake in LC_VERSION_MIN by default, although it’s not setting the Base SDK for some reason. I do not know if that’s harmful yet.

I’d like to know what minor/patchlevel version of Xcode 7 you noticed this happening in. Was this behaviour introduced in the very first release of 7, or was it specifically in 7.1 / 7.2 / 7.3?

Thanks in advance

Tom
17 Sep 2019

Hi Chris,

Going by the dates it must have been Xcode 7.0 (beta) that I first encountered this. I must have needed to fix this to run on iOS 9.

Your results are interesting and I have no insights into why 7.3.1 is doing that for you. I haven’t revisited the issue since I got it working. :) I’m not surprised that the app store validation was more strict about the checks it performs—that’s been my experience too.

Chris
18 Sep 2019

Hi Tom

I ended up recompiling the whole of the third party dependency. I found that the LC_VERSION_MIN header was correctly inserted with either:

– Xcode 7.1 GM on OS X Yosemite
– Xcode 7.1+ on OS X El Capitan

For any other lost souls wandering in this particular wilderness who stumble across this post, I’ve written up what I had to do in detail over at http://stackoverflow.com/q/39193295/1475135