Getting to the rock bottom of Xcode build settings.
If you have ever done any Mac OS or iOS development, you eventually had to deal with Xcode build settings.
So what are those are what do we know about them?
For a standard iOS project there’s roughly 500 build settings grouped into around 50 categories. These build settings control virtually every single aspect of how you app is built and packaged. At the very least, build settings is what makes Debug build so different from Release build.
In this article I’ll mostly focus on build settings that control the behavior of Apple Clang and Swift compilers and the linker.
🗄 Background Check
Build settings are not as simple as they may seem.
For starters, there are project and target level build settings as well as default OS settings. The default OS build settings are inherited on a project level and project level build settings are inherited on a target level.
Then there are
.xcconfig files, which can be used to represent build settings in plain text format.
The xcconfigs can be set on target and project level and add two more levels to inheritance flow.
To make things even more complicated, you can include other xcconfigs using C-like
And then there’s much more to build settings and xcconfigs.
To get familiar with all of the above, I’d highly recommend to start with The Unofficial Guide to xcconfig files. Check out the rest of this amazing blog for even more hands-on information about understanding and managing build settings.
⬇️ Level Down
Now let’s get one more level down. When it comes to compiling and linking the source code, Xcode build system manages 3 main tools under the hood:
- Clang Cxx compiler for compiling C/C++ and Objective-C/C++ code
- Swift compiler
- Linker to link all object files together
Each of those tools has its own set of command line flags.
Clang compiler and linker flags are documented here.
swiftc --help command provides list of Swift command line flags. Surprisingly, I failed to find online documentation similar to Clang.
When a build setting is set in Xcode UI, Xcode then translates it to appropriate flags for underlying tools.
For example, setting
YES will add
-Werror flag for Clang Cxx compiler.
YES will add
-warnings-as-errors flag to all invocations of Swift compiler.
Finally, some build settings are translated into flags for all three tools, for example, enabling code coverage using
CLANG_ENABLE_CODE_COVERAGE = YES build setting, will translate into the following*:
-fcoverage-mappingfor Clang compiler
-profile-generatefor Swift compiler
* Technically, it takes more than just setting
CLANG_ENABLE_CODE_COVERAGE to enable code coverage, more details to come.
So, how does Xcode know which flags to map build settings to? Where is this mapping information stored and can it be extracted?
⚙️ Xcode Specs
The answer to the question in previous section is Xcode Specs or xcspecs.
Xcspecs are ASCII plist files with
.xcspec file extension stored deep inside Xcode.app bundle.
Xcode uses these specs to render build settings UI and to translate build settings to command line flags.
There are xcspecs for Clang compiler (
Clang LLVM 1.0.xcspec), Swift compiler (
Swift.xcspec) and linker (
Ld.xcspec) as well as a number of xcspecs for core build system and other tools.
These xcspecs reference each other and work together as a system.
Each xcspec contains specification for one or more tools, for example, Swift xcspec contains specification for Swift compiler tool, while Clang LLVM xcspec contais specifications for Clang compiler, analyzer, couple of migrators and an AST builder tool.
A tool specification includes such details as name, description, identifier, executable path, supported file types and more.
In context of this article we are most interested in
Options array entry of the tool’s specification, which is where information about build settings is stored.
For example, the value of
SWIFT_EXEC is stored as an option and is used by Xcode to resolve
ExecPath = "$(SWIFT_EXEC)"; statement:
All build settings (options) have
Name defines the build setting name, e.g.
Build settings may also have a
DisplayName properties. For example, display name for
A few build settings are hidden and never appear in Xcode UI. Some of them have corresponding comment in the code like
// Hidden., but not all.
There are 6 different types of build settings:
ℹ️ String and Path
A build setting of
String type has, well, a string value.
Note that string value doesn’t have to be quoted using double quotes.
As a matter of fact, some build setting that have integer values are represented using
String type with default value set to
0, but not to
Path type is identical to
String when used in xcspecs.
My guess is that
Path type build settings are handled differently with regards to escaping whitespaces and other special characters.
They also get special treatment when resolving wildcard characters like
ℹ️ StringList and PathList
PathList are used to represent list of string and path values correspondingly.
If not provided, default value is an empty list.
You can tell a list type in Xcode UI because it allows multiple values input:
Enumeration type have a fixed list of values (cases), defined using
Values key, for example:
Another good example is build settings that have
YES_ERROR value on top of
NO. These build settings are declared as enumerations too:
Finally, values of
Boolean type have either
Note that only
NO are correct values for
Boolean type, but not
🗺 Command Line Flags Mapping
As we already know, a lot of build setting values map to different command line flags. The mapping information is defined in xcspecs using one of the following keys:
ℹ️ Command Line Arguments
CommandLineArgs key-value entry is used to map build setting value to a list of command line arguments.
A good example is
SWIFT_MODULE_NAME build setting of
$(value) is resolved to current build setting value, e.g.
MyModule value is mapped to
-module-name "MyModule" Swift compiler flag.
For list types each build setting value in the list is mapped to one or more command line flags, for example:
If the value of
"checker1" "checker2", it will be mapped to the following:
For enumerations types, mapping is defined for each enumeration case, for example:
ansivalue is mapped to
compiler-defaultmaps to no flags.
- All other enumeration values map to
-std=$(value)compiler flag. Note the use of
"<<otherwise>>", this is similar to
default:enum switch in C-like languages.
Boolean types are mapped just like enumerations with 2
ℹ️ Command Line Flag
CommandLineFlag is used to prepend command line flag to the value of build setting.
SDKROOT is defined like so:
So if the value of
iphoneos, the corresponding Clang compiler flag will be
In a way
CommandLineFlag is a shorthand for using
CommandLineArgs. I.e. in
CommandLineFlag = "-isysroot"; could be replaced with
CommandLineArgs = ("-isysroot", "$(value)");.
Handling of list, enumeration and Boolean types is similar to handling
For example, given the definition:
The value like
SYSTEM_FRAMEWORK_SEARCH_PATHS = "A" "B" "C" will be mapped to:
Enumerations deserve a special mention, because build flag mapping can be defined next to the value. A good example is
ℹ️ Command Line Prefix Flag
CommandLinePrefixFlag maps build setting value to itself prefixed with a build flag.
It works just like
CommandLineFlag, the only difference is that there’s no space between the build flag and the build settings value.
A good examples are
FRAMEWORK_SEARCH_PATHS build settings of list type, where each list entry is mapped to
Another similar build setting often used by developers is
Finally, whenever you see a flag like
-fmessage-length=0 it’s most likely mapped using prefix flag rule.
ℹ️ Additional Linker Flags
Certain Swift or Clang compiler build settings map not only to compiler flags, but to linker flags as well.
Those build settings have an additional
AdditionalLinkerArgs key-value pair, for example:
In this example, the
-fobjc-arc flag will be added both to Clang compiler and linker invocations.
The way mapping works for different build setting types is identical to handling of
As I’ve mentioned earlier, build settings from different xcspecs can reference each other.
Some build settings have a
It can reference other build settings, e.g.
DefaultValue = "$(BITCODE_GENERATION_MODE)";.
Default value of some build settings is defined by referencing other build setting:
The references can be nested as well, for example:
$(DEPLOYMENT_TARGET_SETTING_NAME) will be first resolved into a value like
SOME_SETTING and then
$(SOME_SETTING) is resolved once again to a final value. Similar to how build settings are resolved in xcconfigs.
Build settings reference can be used inside
CommandLineArgs and all other mapping key-value entries.
Conditions are defined using
Condition key-value pair and are used to control when certain build setting is enabled or not.
SWIFT_BITCODE_GENERATION_MODE build setting only makes sense when
ENABLE_BITCODE is set to
Conditions have a C-like syntax, although string values do not have to be quoted. Such boolean operators as
!= can be used:
There are other properties, such as
Architectures- defines which target architectures the build setting is applicable for.
AppearsAfter- controls the order in which 2 specific build settings appear in Xcode UI.
FileTypes- list of applicable file types.
ConditionFlavors- must have something to do with the way conditions are checked.
- Some other flags I didn’t have time to fully investigate yet.
Let’s have a look at one build setting example and see how we can apply all the knowledge from this article to figure out which flags it will map to.
The build setting is
CLANG_ENABLE_CODE_COVERAGE and it enables one very important feature - code coverage.
CLANG_ENABLE_CODE_COVERAGE is defined in
Clang LLVM 1.0.xcspec but strangely maps to no flags at all…
CLANG_ENABLE_CODE_COVERAGE is referenced by
So now we know the Clang compiler flags added to compiler invocation when code coverage is enabled.
Additionally, there’s another build setting that controls the linker flag:
It also comes with a very detailed comment.
CLANG_COVERAGE_MAPPING is also defined in
Now all the pieces of the puzzle come together and it’s clear how Xcode does the mapping.
The only problem is that both
CLANG_COVERAGE_MAPPING_LINKER_ARGS are hidden and don’t show up in Xcode UI. So how do those get set if they don’t reference
CLANG_ENABLE_CODE_COVERAGE in their default value?
The code coverage can be enabled by editing the scheme in Xcode UI or by passing
-enableCodeCoverage YES to
Xcode will then set both
YES under the hood.
OK then, so it’s more or less clear how build settings are resolved, but what’s the practical application?
Well, there’s the purely academic application where you get to know how things work, which occasionally will come handy when you have hard time figuring out some build settings mess.
While the official Xcode Build Settings reference page is a good resource to use, the Extended Xcode Build Settings reference like this one includes information about compiler and linker flags, build settings cross-reference and more.
There are other ways to use this knowledge.
Let’s say you want to try out alternative build system like Buck or Bazel. Those build systems are gaining popularity these days. Buck was created by Facebook while Bazel came from Google. Other companies such as Uber, Airbnb and Dropbox use Buck; while Lyft is using Bazel to build their mobile apps.
Let’s further assume that over the years you have created and maintained a number of amazing xcconfig files. Those xcconfigs have all the compiler and linker build settings fine-tuned for your use. While moving to tools like Buck, you can’t just bring xcconfigs over, instead you’d want to translate xcconfigs to compiler and linker flags and then use those with Buck.
Well, now you have all the knowledge to do so. You’d need to start with reading and resolving xcconfigs and then map resolved build settings to build flags. It’s not a straightforward task and may take a while to implement. Luckily for you, there’s a Fastlane plugin that does just that.
Like many unofficial tools, this plugin is reverse engineering the ways Xcode works, so use it at your own risk.