Jabbering Giraffe

Xcode Build Settings

One thing I keep coming up against in Xcode projects is that the build settings aren’t quite right. Why is ZERO_LINK enabled when target T2 is built with configuration C4? Unfortunately, you quickly end up with a very large combination of options. Six targets and four configurations means twenty four combinations to think about. Which is madness.

There is an easier way: build settings files (*.xcconfig). These are text files with the same settings that are normally embedded into your xcodeproj file. For example, on a newly created cocoa application, these are the settings that are present by default in the Debug configuration.

ARCHS=$(ARCHS_STANDARD_32_BIT)
GCC_C_LANGUAGE_STANDARD=c99
GCC_OPTIMIZATION_LEVEL=0
GCC_WARN_ABOUT_RETURN_TYPE=YES
GCC_WARN_UNUSED_VARIABLE=YES
ONLY_ACTIVE_ARCH=YES
PREBINDING=NO
SDKROOT=macosx10.5

And these are the ones in the Release configuration.

ARCHS=$(ARCHS_STANDARD_32_BIT)
GCC_C_LANGUAGE_STANDARD=c99
GCC_WARN_ABOUT_RETURN_TYPE=YES
GCC_WARN_UNUSED_VARIABLE=YES
PREBINDING=NO
SDKROOT=macosx10.5

Hmmm, they look quite similar (run diff(1) to see). We can extract a common file (Choose File → New File… → Other → Configuration Setting File).

// Project-Common.xcconfig

ARCHS=$(ARCHS_STANDARD_32_BIT)
GCC_C_LANGUAGE_STANDARD=c99
GCC_WARN_ABOUT_RETURN_TYPE=YES
GCC_WARN_UNUSED_VARIABLE=YES
PREBINDING=NO
SDKROOT=macosx10.5


// Project-Debug.xcconfig

#include "Project-Common.xcconfig"

GCC_OPTIMIZATION_LEVEL=0
ONLY_ACTIVE_ARCH=YES


// Project-Release.xcconfig

#include "Project-Common.xcconfig"

To apply these, go into the Build tab of the Project Info pane, and select the Debug configuration. Delete all the settings in there (⌘-A, ⌫) and then select “Based On: Project-Debug”. Do the same for the Release configuration, except base it on Project-Release.

Now, you can add a setting to Project-Common.xcconfig and it will show up in all your configurations. As a nice side effect, it’ll be much easier to see the change in version control.

What about targets? Well, the same principle applies. Except that you want to “push up” as much configuration as possible into your project level xcconfig files.

Let’s take a look at the default Debug configuration for my “Polynomials” target.

INSTALL_PATH = $(HOME)/Applications
COPY_PHASE_STRIP = NO
INFOPLIST_FILE = Info.plist
PRODUCT_NAME = Polynomials
ALWAYS_SEARCH_USER_PATHS = NO
GCC_ENABLE_FIX_AND_CONTINUE = YES
GCC_DYNAMIC_NO_PIC = NO
GCC_MODEL_TUNING = G5
GCC_ENABLE_OBJC_GC = required
GCC_OPTIMIZATION_LEVEL = 0
GCC_PRECOMPILE_PREFIX_HEADER = YES
GCC_PREFIX_HEADER = Polynomials_Prefix.pch

The only really target specific settings are INFOPLIST_FILE and PRODUCT_NAME. The rest can either be deleted (GCC_MODEL_TUNING?!?), or pushed up into the project level settings (GCC_OPTIMIZATION_LEVEL, which is already defined there).

It’s a really worthwhile exercise going through your project in order to clean up your build settings. You’d be surprised at what’s going on in there. Keep a copy of the Xcode Build Settings Reference handy to look things up and see if you really need them. If you want an example, I noticed that Eric Czarny’s lovely XMLRPC library is organised somewhat like this.

You can do a lot more mad things with xcconfig’s, like choosing alternate signing personalities. But extracting your current configuration is a good start.

In order to help the transition, I wrote a bit of applescript to pull out existing settings. It’s pretty nasty because I don’t really get applescript. But it Works For Me™.

set newLine to ASCII character 10

tell application "Xcode"
    set proj to project 1
    -- This is a string of a POSIX path.
    set dir to project directory of proj

    repeat with cf in build configurations of proj
        set str to ""
        repeat with stg in build settings of cf
            set str to str & (name of stg) & "=" & (value of stg) & newLine
        end repeat

        -- Write the results to a file
        set filename to "Project-" & (name of cf) & ".xcconfig"
        set fileno to open for access (dir & "/" & filename) as POSIX file with write permission
        write str to fileno
        close access fileno

        -- Sort the file to make it easier to diff.
        do shell script "cd " & dir & " && sort " & quoted form of filename & " >" & quoted form of filename & ".tmp && mv " & quoted form of filename & ".tmp " & quoted form of filename
    end repeat

    -- Now, iterate through each target.
    repeat with tgt in targets of proj
        repeat with cf in build configurations of tgt
            set str to ""
            repeat with stg in build settings of build configuration (name of cf) of tgt
                set str to str & (name of stg) & "=" & (value of stg) & newLine
            end repeat

            -- Write the results to a file
            set filename to "Target-" & (name of tgt) & "-" & (name of cf) & ".xcconfig"
            set fileno to open for access (dir & "/" & filename) as POSIX file with write permission
            write str to fileno
            close access fileno

            -- Sort the file to make it easier to diff.
            do shell script "cd " & dir & " && sort " & quoted form of filename & " >" & quoted form of filename & ".tmp && mv " & quoted form of filename & ".tmp " & quoted form of filename
        end repeat
    end repeat
end tell

And yes — applescript doesn’t have a sort function builtin. WTF?