Chris Downie and Sam Landfried
Manual Swift: Understanding the Swift/Objective-C Build Pipeline walked you through the high-level view of how Objective-C gets access to Swift code:
Today, we’re going to see what that looks like in practice by examining Xcode build logs.
The last post was about how you’d build and link code without using Xcode to do the work for you. So why does this article go back to leaning on Xcode?
You’ll probably be doing most of your building using Xcode, so growing acquainted with how it does things will be more applicable to your everyday work than the more academic “If we didn’t have Xcode, what would we do?” question. (Yes, the Swift Package Manager exists as an alternative route to building Swift projects, but, no, we will not be talking about it today.)
Reading how Xcode does its work is a good way to reverse-engineer the build process so you can do it without using Xcode. More usefully, if you learn to see past the noise in the Xcode build logs, you learn to pull out the salient details needed to debug most any build-related issue.
We’re going to start with looking at how a pure Obj-C project comes together. Here’s the on-disk directory layout of a small iOS app named “ImportSwift”:
ImportSwift ├── ImportSwift │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── main.m └── ImportSwift.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ └── jeremy.xcuserdatad │ └── UserInterfaceState.xcuserstate └── xcuserdata └── jeremy.xcuserdatad └── xcschemes ├── ImportSwift.xcscheme └── xcschememanagement.plist 11 directories, 12 files
Note that this project has only two Obj-C files to compile:
The rest of the files either describe the project and IDE state (everything under the .xcodeproj), the app (Info.plist), or get run through their own compilers to spit out assets used by the app (the storyboard files and the .xcassets Asset Catalog bundle).
There’s more to building an app than just compiling and linking Obj-C files. Let’s look at where those steps fit into the overall build flow before digging in.
Here’s a build log trimmed down to highlight the overall flow:
Build target ImportSwift of project ImportSwift with configuration Debug Write auxiliary files write-file ImportSwift.hmap Create product structure // We're going to pick up here: CompileC AppDelegate.o ImportSwift/AppDelegate.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler CompileC main.o ImportSwift/main.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler Ld ImportSwift.app/ImportSwift normal x86_64 // And stop here. CompileStoryboard ImportSwift/Base.lproj/Main.storyboard CompileAssetCatalog ImportSwift/Assets.xcassets CompileStoryboard ImportSwift/Base.lproj/LaunchScreen.storyboard ProcessInfoPlistFile ImportSwift/Info.plist LinkStoryboards Touch ImportSwift.app CodeSign ImportSwift.app Build succeeded
When it comes to understanding how Obj-C code manages to use Swift code, we only care about the three steps in the middle of the whole shebang that compile some C-ish stuff, compile some more C-ish stuff, and finally link it all together to make an app:
CompileC AppDelegate.o ImportSwift/AppDelegate.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler CompileC main.o ImportSwift/main.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler Ld ImportSwift.app/ImportSwift normal x86_64
In truth, the two
CompileC steps are virtually identical, so we only have
to understand two things:
CompileC: How Xcode compiles C-like code
Ld: How Xcode links object files into an executable
Let’s get started.
Let’s look at how we compile AppDelegate.m into AppDelegate.o.
But first, let’s take a good look at that high-level summary of the build step:
CompileC AppDelegate.o ImportSwift/AppDelegate.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
This is hard to read, so let’s work it over to where we can better understand it. To start, let’s convert spaces to linebreaks:
CompileC AppDelegate.o ImportSwift/AppDelegate.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
Next, undo the path elisions, which were performed earlier to call attention to the overall flow, so that we can experience the build step summary in its full glory:
CompileC /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.o ImportSwift/AppDelegate.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
Let’s break this down:
CompileC: This step is about compiling some C (well, C-ish) stuff.
/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.o: This is the product of this build step, a
.o object file.
We can make this mouthful of a path more meaningful by rewriting it using some of Xcode’s handy-dandy build configuration environment variables as:
Translated from variable names into words, this says to stick the object file under the object file directory, in a folder specific to the build variant (you’ll probably only ever see “normal” here), in a subfolder specific to the target architecture. Neat and tidy!
ImportSwift/AppDelegate.m: Xcode is using the usual “name the parent folder of all the source file for an app named YourAppNameHere
YourAppNameHere/” trick, so this is the source file in situ in our project directory.
normal: This is that
$BUILD_VARIANTsetting that came up as embedded in the build product output path.
x86_64: And here’s the
$CURRENT_ARCH current architecture setting that came up in the same path, yet again. This one says to build for the i3/4/5/6/whatever-86 – x86 – architecture, only the 64-bit version.
x86_64 stands in contrast to
i386, which is the 32-bit version, which isn’t flagged as 32-bit explicitly because why would you ever need more than 32 bits? (And wasn’t that 32- to 64-bit default integer size change a fun migration!)
If you play around with Linux, you might see the same architecture called
amd64 rather than
x86_64, because AMD beat Intel to market with a 64-bit variant of the i386 architecture.
x86_64 also tells you that we’re compiling this code for a simulator rather than the actual device, because all the iOS devices use one flavor of ARM architecture or another.
objective-c: This is the language we’re compiling using the C (slash Obj-C) compiler.
com.apple.compilers.llvm.clang.1_0.compiler: This happens to match the
$DEFAULT_COMPILERbuild setting (and also, thanks to historical accident, path dependency, and the desire for backwards compatibility, the value for
$GCC_VERSION, as well, even though Clang is very much not GCC!). It’s Ye Olde Reverse DNS Identifier, pointing at a version of clang. My current clang version actually self-identifies as part of
clang-800.0.42.1, so I’m not quite sure what they’re getting at with the
1_0, but the rest makes sense: “This is one of Apple’s compilers, part of the LLVM project, called clang, version 1.0, and it’s, uh, a compiler, as you might have guessed from that earlier ‘compilers’ bit, but let’s just make sure we’re on the same page, OK?”
Phew! Now we can actually start to dig into the tool invocations that implement this build step. Note that a lot of this is actually driven by settings in the Build Settings configuration; this project had no customization done in there, so we’ll be looking at the Xcode template defaults, which is most likely what any project you look at will be based on, as well.
Establish working directory First, the build step establishes the working directory used for the rest of the invocations:
This happens to be the
$SRCROOT directory of the project, also known as the root directory holding all the target’s files.
onfigure locale Then, it sets the language and encoding, in case some locale-aware helper gets clever:
Xcode is expecting to parse out warnings and errors to annotate your code by parsing literally “warning: filename:linenumber: some text” or “error: filename:linenumber: some text”. If some process invoked during compilation started dumping out “advertencia:” or whatever, Xcode would be a lot less helpful - though it would still be aware of whether the build succeeded or failed, since that depends on the human language independent process exit code convention, where exiting with 0 is A-OK, and anything else is bad news.
Ensure PATH includes platform-specific binaries
Then, it sets the
PATH used to resolve a command name, like
clang, to an actual path, like
Each directory in the colon-separated
PATH list is searched in order from left to right for a match for the command name, and the first one wins. If no match is found, you get an “Unknown command” error in your shell. (Other stuff can go wrong, too, but “no such number, no such name” is the most common problem you’ll encounter.)
This allows Xcode to control what executable runs. Here’s the list reformatted to be a smidge more readable:
You can see it giving preference to the binaries inside its Platforms folder and its Developer folder, before following with the standard system binary locations. Notably, it does not include any of the paths you might have added on to your own
PATH – no Fink
/sw/ stuff, no MacPorts
/opt/ stuff, and if you set up Homebrew somewhere other than
/usr/local, you’re out of luck there, too.
Invoke the compiler Next, we come to the moment we’ve been waiting for - the actual compiler invocation. And ain’t this a humdinger:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -arch x86_64 -fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu99 -fobjc-arc -fmodules -gmodules -fmodules-cache-path=/Users/jeremy/Library/Developer/Xcode/DerivedData/ModuleCache -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -fbuild-session-file=/Users/jeremy/Library/Developer/Xcode/DerivedData/ModuleCache/Session.modulevalidation -fmodules-validate-once-per-build-session -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -Wno-trigraphs -fpascal-strings -O0 -fno-common -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wdocumentation -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wconditional-uninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wno-deprecated-implementations -DDEBUG=1 -DOBJC_OLD_DISPATCH_PROTOTYPES=0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.1.sdk -fasm-blocks -fstrict-aliasing -Wprotocol -Wdeprecated-declarations -mios-simulator-version-min=10.1 -g -Wno-sign-conversion -Winfinite-recursion -fobjc-abi-version=2 -fobjc-legacy-dispatch -iquote /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-generated-files.hmap -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-own-target-headers.hmap -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-all-target-headers.hmap -iquote /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-project-headers.hmap -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Products/Debug-iphonesimulator/include -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/DerivedSources/x86_64 -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/DerivedSources -F/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Products/Debug-iphonesimulator -MMD -MT dependencies -MF /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.d --serialize-diagnostics /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.dia -c /Users/jeremy/Workpad/BNR/ImportSwift/ImportSwift/AppDelegate.m -o /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.o
Yeah, that’s all one line. It’s how Xcode shows it loves you.
Use a specific clang Let’s take this bit by bit, starting with the highlight reel:
Remember all that work setting up the
PATH? Yeah, Xcode isn’t going to trust that. It uses an absolute path to
clang, the Obj/C compiler.
Specify language and platform
-x objective-c -arch x86_64
It wants to make sure Clang is 100% on the same page about what language it’s compiling, so it sets it explicitly with
It also needs to tell
clang what architecture to target, so there’s that,
There are various flavors of C that we could glom the Obj- part onto. This says to use the version that corresponds to C99 – the revision of the C standard promulgated in 1999 – with GNU extensions. (Note that clang hasn’t implemened all of GCC’s extensions, and that some GCC extensions are intentionally not implemented. But your favorite GNU extension probably made the cut!)
Specify Obj-C flavor
-fobjc-arc -fobjc-abi-version=2 -fobjc-legacy-dispatch -DOBJC_OLD_DISPATCH_PROTOTYPES=0
-fobjc-arc puts the compiler into the modern world by flipping on ARC.
You probably have run into this more frequently (if at all) as the version that turns ARC off,
-fno-objc-arc, which some extant library code requires to compile successfully.
The ABI version specified is the modern, non-fragile Obj-C application binary interface. This has to do with what runtime functions the compiler can expect to be available and lets the compiler make a variety of other assumptions about how to generate Obj-C binary code that can interoperate with the runtime and other Obj-C binary code.
-fobjc-legacy-dispatch tells clang what sort of Objective-C message-send
dispatch approach to take.
it appears that GNUstep uses a newer version, while macOS 10.6+ uses the legacy
dispatch approach everywhere,
though comments for UseObjCMixedDispatch()
would seem to say otherwise.
it looks like there’s been some back-and-forth around using a vtable to
dispatch selectors or not, and the current options are “legacy”, “non-legacy”,
Confusion around what flavor of dispatch would be the default for macOS aside, this is an iOS project, and the dispatch method is explicitly set to “legacy”, so let’s move on!
0, which is commonly used to represent a Boolean false.
This setting causes the header <objc/message.h> to expose the
core message-sending functions as
() -> Void functions,
which forces the caller to cast them to an appropriate type
– as is required for ARC to work its magic –
rather than as varargs
(Any, Selector, Any...) -> Any,
which invites trouble under ARC.
Configure some features
-fpascal-strings -fno-common -fasm-blocks -fstrict-aliasing
-fpascal-strings enables support for Pascal strings.
Pascal used length-prefixed strings rather than NUL-terminated strings.
The Pascal-string support lets you write a Pascal-compatible string like
"\pRockin' it Pascal style" and have the compiler replace the
strlen of the string. It also turns the string literal into an explicit
character array literal, as you’ll find as you hammer on the variable
If you don’t pass
-fpascal-strings, the compiler rejects the
pascal.c:6:37: warning: unknown escape sequence '\p' [-Wunknown-escape-sequence] static const char PascalString = "\pWirth"; ^~ 1 warning generated.
If you declare as
const char *, you get a wrist slap for signedness, because
Pascal strings apparently are explicitly unsigned characters:
pascal.c:6:20: warning: initializing 'const char *' with an expression of type 'unsigned char ' converts between pointers to integer types with different sign [-Wpointer-sign] static const char *PascalString = "\pWirth"; ^ ~~~~~~~~~ 1 warning generated.
If you declare as
const unsigned char, you run into signedness problems
const char* APIs:
pascal.c:14:9: warning: passing 'const unsigned char ' to parameter of type 'const char *' converts between pointers to integer types with different sign [-Wpointer-sign] PascalString, ^~~~~~~~~~~~ /usr/include/vis.h:105:32: note: passing argument to parameter here int strvis(char *, const char *, int); ^ 1 warning generated.
So you’ll find that
const char PascalString is your friend.
A lot of classic Mac Toolbox APIs used Pascal calling conventions and expected Pascal strings, but enabling this support for iOS is something of an anachronism.
-fmodules -gmodules -fmodules-cache-path=/Users/jeremy/Library/Developer/Xcode/DerivedData/ModuleCache -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -fmodules-validate-once-per-build-session -fbuild-session-file=/Users/jeremy/Library/Developer/Xcode/DerivedData/ModuleCache/Session.modulevalidation -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module
This cluster of options relates to Clang’s support for modules.
-fmodules enables modules, module-based imports, and modules-related syntax,
-gmodules enables module debugging.
-fmodules-cache-path tells the compiler where to cache module lookup results.
Xcode is building the cache in a folder named
ModuleCache in a per-user
-fmodules-prune-interval sets a minimum bound on how long the compiler should go before trying to prune the module cache.
Xcode is setting it to 24 hours, which is a lot more aggressive than the compiler default of 7 days.
-fmodules-prune-after says that, if a module cache file hasn’t been accessed in the number of seconds specified, then it can be pruned.
Xcode is setting this to 4 days, rather than the compiler default of 31 days.
-fmodules-validate-once-per-build-session avoids repeatedly validating a
module during a single build session.
-fbuild-session-file names a file whose modification time is treated as the time when the build session started, which matters for some decision-making related to what-to-validate-when for modules.
-Wnon-modular-include-in-framework-module flips on everyone’s favorite warning
when using older frameworks, “warning: include of non-modular header inside
framework module…”. The
-Werror flag following turns this into a hard
error, rather than an ignorable one.
Notice that the module cache is not project-specific, but is instead in a shared location. If you take a peek in your module cache directory, you’ll see something like:
$(MODULE_NAME)-$(THIRTEEN_ALPHANUMERICS).pcm. Some of the
pcmfiles might have a suffix of a hyphen and eight lowercase alphanumerics. Some of these might have an individual .pcm.timestamp corersponding or a .pcm.lock symbolic link. And some of the folders might have a modules.idx file.
At a high level, we can say that these are pre-compiled module files and related tracking info needed to safely use and invalidate the cached compilation outputs in the face of possible underlying module changes. Digging in deeper would be wandering into compiler-internal details that are part of clang’s module implementation.
Configure message output
-fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 --serialize-diagnostics /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.dia
This batch of options ensures that clang dumps as much info as it can about anything that goes wrong, and tells it to send the warnings directly to a file, rather than stderr.
Disable optimization, enable debug info
-O0 -g -DDEBUG=1
This batch of options enables debug information generation (
disables optimization (
-O0) so that the compiler output more closely
matches the code input to it, which make stepping by line and setting
breakpoints by line less confusing.
This also defines
DEBUG to be true so app code can adapt to being compiled
with debugging enabled using code like:
#if DEBUG [self ensureInvariantsHaveBeenMaintained]; #endif
You might conditionally compile code based on
DEBUG that is used to
perform time-consuming sanity checks on data structures
or other additional work that is useful during debugging
but is inappropriate to ship to end users.
-Wno-trigraphs -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wdocumentation -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wconditional-uninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wno-deprecated-implementations -Wprotocol -Wdeprecated-declarations -Wno-sign-conversion -Winfinite-recursion
These are all warning-related flags.
-Wno- options disable warnings; the rest enable them.
-Werror= options cause the compiler to emit the named warnings
Specify SDK and deployment target
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.1.sdk -mios-simulator-version-min=10.1
clang to pretend the provided directory is the root
of the filesystem, and look for system headers, libraries, and tools
relative to that folder rather than
/. This is a quick shorthand to
rewrite the standard lookup paths all at once without needing a pile of
-F, etc. flags to do so piecemeal.
-mios-simulator-version-min tells the build system what your minimum
deployment target is.
Recall that you can build and link against a base SDK of one version while still producing a build product that works with an earlier version; this is the distinction made by the base SDK vs the deployment target. Most of the time, you’ll have only whatever the latest SDK is that your Xcode shipped with, and you’ll target back to whatever OS version you’re supporting back to while building against that.
Configure include and framework search paths
-iquote /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-generated-files.hmap -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-own-target-headers.hmap -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-all-target-headers.hmap -iquote /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/ImportSwift-project-headers.hmap -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Products/Debug-iphonesimulator/include -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/DerivedSources/x86_64 -I/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/DerivedSources -F/Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Products/Debug-iphonesimulator
-isysroot does a lot of work for us in aiming the file lookup at the right
places, but there are still a lot of search paths specific to the app’s build
process, so that’s what this
clump of options rigs up.
There are three flavors of option used here:
-iquoteadds a directory to the search path for headers included using quotes. This means
#include "header.h"will look in this directory, but
-Iadds a directory to the overall include search path. Both the quote and angle bracket flavors of include will look in this directory.
-Fadds a directory to the framework search path, which means that
-framework Some.frameworkwill now look in this directory for
Some.framework, before searching the rest of the search path.
-F flag is used to include the built-products directory. This way,
if you’re building a framework alongside your app, you can easily
link against it.
The last couple
-I flags add the derived sources directory and the
architecture-specific derived sources directory to the include file search
path. This lets you generate headers during a build step and have them
picked up by the compilation build step. You can also clobber a general
header with an architecture-specific one if necessary, because the
arch-specific directory will be checked before the more general one.
-I flags that lead this batch of options off
supply the path to a header map
(.hmap) file rather than to a directory.
A header map is a binary file containing a hashtable. You could replace it with a pile of symlink files, but the header map is more compact and supports faster header path lookup.
The header maps are used to allow including various flavor of intermediate
build files, and they all live under the
The ones that show up here are:
There are actually a few more, which you can find by looking at the
$CPP_HEADERMAP_FILE_FOR family of build settings.
If you poke around
in the header-map data structure, most of these are
actually empty in this case, because this is such a simple project.
The only ones with some content are the project headers and ALL the headers.
In fact, these both contain one and the same entry,
pointing at the sole header file in the project:
Generate file dependency info
-MMD -MT dependencies -MF /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.d
-M family of arguments relates to writing out dependencies
between files. The original purpose of this was autogenerating the
“if this changes, then rebuild that thing” information for Make, hence the
-MF gives the path for the output Makefile.
-MT names the main target, which will be the first one listed in the
output Makefile, and is what a
make without any arguments would try to build.
-MMD causes the compiler to dump dependencies alongside its other work,
rather than as its sole work, during the invocation. This flavor omits
emitting any dependencies on or of system headers.
This information can be used to drive incremental recompilation as well as incremental reindexing for language-aware syntax highlighting and code completion, though your guess is as good as mine as to what Xcode actually does with it.
Say what to compile and to where
-c /Users/jeremy/Workpad/BNR/ImportSwift/ImportSwift/AppDelegate.m -o /Users/jeremy/Library/Developer/Xcode/DerivedData/ImportSwift-fvxpitolsctgjmemgmjamfehgmaf/Build/Intermediates/ImportSwift.build/Debug-iphonesimulator/ImportSwift.build/Objects-normal/x86_64/AppDelegate.o
This is the real meat of the compiler invocation; it says what file to
-compile and where to
-output the result.
Now you know what goes into a
CompileC build step. Compiling
main.m is basically the same as
AppDelegate.m, only with different files, so we’re going to move along.
In this post, you learned exactly how the compiler compiles the source files. Next time, you’ll see how everything is pulled together.
Interested in learning more about our basic and advanced iOS Courses?
Learn from the experts at a Big Nerd Ranch Bootcamp!
Interested in leveling up your coding skills from the same authors of the Big Nerd Ranch Guide? Subscribe to The Frontier today!
Chris Downie and Sam Landfried
Chris Downie and Sam Landfried