Tag: xcode

 

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.

Debug configuration settings

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?

Run Script Phase

Some things I’ve discovered in the past few days about Run Script phases in Xcode…

  1. Name them after what they do. It’s no use looking at three identical phases, all of which say “Run Script”.
  2. It’s a good idea to always stick a set -x at the top. It’s only build output, so being verbose isn’t a bad idea. It’ll be really handy for debugging later on.
  3. If the script’s more than a line or two, consider sticking it in an external file and sourcing it instead. This is much more amenable to version control. For example:
    set -x
    . "${PROJECT_DIR}/bin/My_Script_Phase.sh"

    Note that you can also pass command line arguments in here, so you can greatly reduce the cut’n’paste between similar phases of different targets, if you’ve got that complex a build.

Embedding Cocoa Frameworks

I’m (re)learning Cocoa at the moment. I’m working my way through the wonderful Cocoa Programming for Mac OS X. But for my first solo project, I found myself needing to XML-RPC. I took one look at Web Services Core Programming Guide and barfed. After a quick hunt, I found Eric Czarny’s lovely XMLRPC project for cocoa. This looked exactly like what I needed.

I had one small problem though — it’s a framework. I’ve not used one before. How do I actually get this code included in my project?

After looking at the Framework Programming Guide, there are a few options for embedding the framework into my project (embedding is the only sensible choice — I don’t want the user to have to install this on their system).

  1. Build and install the framework locally, then just drag it into my project.
  2. Build the framework, and check the results into my source control system.
  3. Embed the framework into my own project, so it gets built on its own each time.

The first option means that the build is no dependent on my workstation. The second option works, but I dislike binaries in version control (and how non-reproduceable these can be). The third option is ideal as it means the build is self contained. However, it’s also the most non-obvious to implement.

After quite a few misdirected searches, I eventually found Jonathan “wolf” Rentzsch’s marvellous tutorial Embedded Cocoa Frameworks. Unfortunately, it’s gone 404. Archive.org has a copy, including the 8 minute screencast.

In case it goes away again, here’s what I did to integrate the XMLRPC framework. I’ll start with a brand-new Cocoa project, MyApp.

Brand new Xcode project

Next you need to git clone git://github.com/eczarny/xmlrpc.git in a nearby place (a sibling directory is ideal, though you could set up inside MyApp using git submodules).

Before we can get started, we need to open up the XMLRPC project and make a couple of changes. First, we need to ensure that the Configurations match the ones in my project. Otherwise, Xcode will use the default configuration, which may produce unexpected results. I did this by duplicating Development into Debug.

XMLRPC Project Configurations

Now, build the XMLRPC project (⌘-B). You should end up with …/xmlrpc/build/Debug/XMLRPC.framework. Right-click on MyAppFrameworksLinked Frameworks and choose Add…Existing Frameworks….
Navigate to the previously built framework and select it. In the sheet that pops up, ensure that you choose “Reference Type: Relative to Project,” as well as ensuring that MyApp is selected in “Add To Targets.”

Add XMLRPC framework to MyApp

If you’ve been fastidiously keeping an eye on things in git (as I like to do), you can see the changes in your xcode project file. As well as introducing a reference to the framework directory, you will also see that the FRAMEWORK_SEARCH_PATHS build setting has been set up for you:

FRAMEWORK_SEARCH_PATHS = (
  "$(inherited)",
  "\"$(SRCROOT)/../xmlrpc/build/Debug\"",
);

Unfortunately, this the point at which I realised that what I was doing wouldn’t work. We’re linking against the Debug configuration of the framework. I need to embed a reference in my project that’s independent of which configuration the XMLRPC project was built with. In order to do that, we have to modify the XMLRPC project a bit more.

Go back to the XMLRPC project and right click on the XMLRPC target. Choose Add…New Build Phase…New Run Script Build Phase. Type in the code:

Add Run Script phase to XMLRPC project
set -x
rm -rf "${BUILD_DIR}/${FULL_PRODUCT_NAME}"
cp -r "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}" "${BUILD_DIR}/${FULL_PRODUCT_NAME}"

The net effect is to copy the framework from …/xmlrpc/build/Debug to …/xmlrpc/build. This means that there’s a single location to pull in. You may wish to rename the Run Script phase to something like “Run Script: Copy to well-known location.”

In case you’re wondering, I figured out which environment variables to use by looking at the build output, as it has another Run Script phase which shows you what they all are.

Now, go back into MyApp, remove the reference to XMLRPC.framwork. Add back the version in the new location, which should be directly under build. You also need to clean up the FRAMEWORK_SEARCH_PATHS setting on the MyApp target for both Release and Debug configurations. Ooops. I wish I’d spotted my mistake earlier!

So now we’ve linked against the framework, and set up the header search path. To check the latter, add a new Objective-C class and add in:

#import <XMLRPC/XMLRPC.h>

A build of MyApp should succeed without error.

But there’s one more trick. We want to build the XMLRPC project as part of our own build. In order to do this, you need to drag XMLRPC.xcodeproj into MyApp.

Drag XMLRPC project file into MyApp

This should pop up the same sheet where you need to choose “Reference Type: Relative to Project.”

With that done, right-click on the MyApp target and choose Get Info. On the general tab, add XMLRPC as a direct dependency.

Add dependency on XMLRPC to MyApp

Now, when you build MyApp, you should see the XMLRPC project being built too. Even better, if you switch configurations, you should see the framework being built in the same configuration.

Build log from MyApp

In summary, the steps were:

  1. Modify the framework to support the same configurations as your project.
  2. Modify the framework to put the built artifact in a consistent location.
  3. Add the framework to your project (making sure to select “relative to project” and “add to target”).
  4. Add a reference to the framework’s project to your project.
  5. Add the target for the framework as a direct dependency of your target that needs it.

It may seem like a lot of effort, but it really only takes a few seconds. And now you’ve got the ability to pull in hundreds of wonderful open-source projects to make your app better!