I ran into a vexing build failure with one of my iOS integration builds. The vast majority of everything in my complex project, consisting of dozens of dependencies, has built fine, but at link time things blow up because one of the dependencies is not of the expected architecture.
Undefined symbols for architecture armv7: "_RSIsEmpty", referenced from: -[RSFormattingMacro emptyMarkupPlaceholders] in RSFormattingMacro.o ld: symbol(s) not found for architecture armv7
Ugh, huh, wha? How is this happening? It turns out the library in which “RSIsEmpty” resides, RSFoundation, is being linked to, but it’s opting for an OS X version of the library instead of an iOS one. Because this is an integration build that performs a number of related builds for both Mac and iOS, it makes some sense that I would have both an iOS and OS X build result in the build folder. But even if I have built copies for either architecture, why is it opting for the OS X version on an iOS build?
I decided to blow away all the built versions of RSFoundation and try the build again. This time, I got an even more perplexing failure:
clang: error: no such file or directory: '[...]/build-Integration/Release/RSFoundation.framework/RSFoundation'
I’m scratching my head. Did it somehow get removed as a dependency? But then I notice something subtle. It’s looking for the framework in the “Release” build folder, but for my iOS build it should be looking in “Release-iphoneos”. What the heck is going on?
In spite of the sole build target in this scheme being an iOS app, Xcode (actually xcodebuild) is opting to build this scheme with a default destination of OS X. I don’t know of a direct way to get xcodebuild to list the available destinations for a scheme, but I know that if you pass a bogus destination platform, it will do the honor of listing its impressions:
xcodebuild -destination "platform=xx" -scheme MyIPhoneApp The requested device could not be found because no platform could be found for the requested platform name. Available destinations for the "MyIPhoneApp" scheme: { platform:OS X, arch:x86_64 } { platform:iOS Simulator, id:6DF04FFC-1C8A-4745-8F8C-7369E9CBF8DB, OS:9.3, name:iPad 2 } [... every other iOS simulator on my Mac ...]
Aha! So it thinks my iPhone app is suitable for OS X. But why? After a great deal of experimentation and poking around, I discovered the root of the problem is summarized by this true statement:
If any target in a Scheme’s dependency tree targets OS X, then the scheme itself will also be considered to target OS X.
In retrospect, this explains the problem perfectly. I had recently added a subproject to my dependency tree that builds both an iOS and a Mac version of a library. That’s fine: it works pretty well these days to allow targets for differing platforms to coexist in the same project file. But each of these targets also shares a dependency on a single, legitimate OS X target: a helper tool used in the process of generating that project’s own source files.
This must be at least a relatively common scenario for iOS builds of certain complexity. Because the platform on which all iOS builds run is OS X, any helper tools that are compiled and used in the process of generating sources or otherwise processing build materials, must be built for OS X.
The workaround to my specific problem is to specify a platform explicitly on the command line. I can’t assume that because the scheme targets iOS, it will necessarily default to an iOS platform target. I consider this a bug, not only because it led to this difficult to diagnose build error, but because it has other ramifications such as the presentation in Xcode’s scheme popup for these projects a useless, distracting “My Mac” target which should never be selected for the schemes in question.
I filed this as Radar #24247701: A scheme whose target has dependencies on another platform shouldn’t “support” that platform.