Home > builds > Building the Build System – Part 1 – Abandoning the Build Panel

Building the Build System – Part 1 – Abandoning the Build Panel

XCode has a decent build system, but it doesn’t work as well as it could out of the box. With just a little work, you can make your projects easier to setup and maintain just the way you want them, improve your code, and even speed up your build times.

The first thing we want to do is get rid of one of the great obfuscations of Xcode: The Build panel.

Build Panel

The build panel seems convenient at first, but in practice it makes it hard to see what’s going on in your build. It especially gets confusing as your build settings get complicated. When you need to turn off Thumb Code Generation because of an obscure assembler conflict in legacy C++ code (true story), it would be nice to put a comment somewhere indicating why you’ve done this so someone doesn’t come along later switch the setting. The Build Panel doesn’t give you an easy way to include comments right along with the setting (the “Comments” panel is pretty useless in my experience), and it’s easy to lose settings or accidentally apply them to only to one configuration.

XCode provides a better solution called xcconfig files. Everything you can do in the build panel can be done in xcconfig files, and you can actually read them and make comments. So let’s make some.

  1. Create a new Window-Based iPhone Application. (Everything we do here works exactly the same for Mac.)
  2. Add a new group to your Groups & Files called “Build Configuration”.
  3. Add a new file to the group. Under “Other” select “Configuration Settings File.” Call it “Shared.xcconfig”.
  4. Create three more xcconfig files called Debug, Release and Application.
  5. Double-click the Project to open the Project Info panel, and select Build.
  6. Select Configuration: “Debug” and Show: “Settings Defined at This Level.”
  7. Select all the settings (you can use Cmd-A here).
  8. Copy them with Cmd-C.
  9. Go back to Debug.xcconfig and paste with Cmd-V. You now know how to find out the xcconfig syntax for any Build Panel setting you’re uncertain of.
  10. Go back to the Build Panel and hit Delete to set all settings to default. Select “Show: All Settings” so you can see your settings again.
  11. In the lower-right of the panel, for “Based On:” select “Debug.” You should see your old settings show back up, but not bold this time.
  12. Repeat for Configuration: “Release”. Copy them to Release.xcconfig and delete them from the Build Panel.
  13. Double-click on the Target, and repeat the process, moving both its Debug and Release settings to Application. Put in a comment to indicate which are the Debug settings and which are the Release settings. We’ll clean them up shortly. Select “Configuration: All Configurations” and “Based On: Application.”

We’ve now moved everything from the build panel to the xcconfig files, and the system should build. Debug and Release are slightly confused because we mixed the settings in Application, but we’ll fix that now.

Look in Application.xcconfig, and move anything that’s in Debug but not in Release to Debug.xcconfig, and anything in Release but not Debug to Release.xcconfig. Anything that’s in both, delete the second copy of.

Anything that is in both Release and Debug, move to Shared, and put #include "Shared.xcconfig" at the top of the Release and Debug configs.

If you’ve followed all the instructions, you should have four files that look like this (assuming your product’s name is “Test”):

Shared.xcconfig

ARCHS = $(ARCHS_STANDARD_32_BIT)
SDKROOT = iphoneos2.2.1
CODE_SIGN_IDENTITY = 
CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
PREBINDING = NO
GCC_C_LANGUAGE_STANDARD = c99
GCC_WARN_ABOUT_RETURN_TYPE = YES
GCC_WARN_UNUSED_VARIABLE = YES

Release.xcconfig

#include "Shared.xcconfig"
COPY_PHASE_STRIP = YES

Debug.xcconfig

#include "Shared.xcconfig"
ONLY_ACTIVE_ARCH = YES
COPY_PHASE_STRIP = NO
GCC_DYNAMIC_NO_PIC = NO
GCC_OPTIMIZATION_LEVEL = 0

Application.xcconfig

INFOPLIST_FILE = Info.plist
PRODUCT_NAME = Test
ALWAYS_SEARCH_USER_PATHS = NO
GCC_PRECOMPILE_PREFIX_HEADER = YES
GCC_PREFIX_HEADER = Test_Prefix.pch

If you Build&Run now, everything should work. It’s not a great build configuration, but it’s Apple’s default in a form that we can start understanding and improving.

But before that, we’re going to convert this to a Project Template, so we don’t have to go through this process again. We’ll do that in the next part of this series.

Categories: builds Tags: , ,
  1. ramzez
    September 30th, 2009 at 16:37 | #1

    Here the situation we have and were wondering your advice/opinion, we have a project which uses many libraries and we build multiple configurations (10 for adhoc, 10 for distribution), we have a project which includes the source code for all libararies and then builds each as a static library and links all together, after adding Configuration the project loading and especially switching between Simulator and Device is extremely slow, also accessing any project settings is a hog.

    Static libraries share a lot of the preprocessors, the idea is to decouple static libraries into the different project files and include those in the main project. But here is the Challenge, some libraries require Macros to be set depending on which product we are building. i.e. Configuration A, sets Macro USE_SPECIAL_CASE, which a few static libraries needs to compile with, Configuration B, sets USE_SPECIAL_CASE2 etc.

    We have tried to add GCC_PREPROCESSOR_DEFINITIONS = $(GCC_PREPROCESSOR_DEFINITIONS) USE_SPECIAL_CASE2 but it is not propagated to other projects.

    Thanks for the help.

    • October 3rd, 2009 at 15:03 | #2

      First, read over the Build Settings page in the docs to make sure you understand how layering and setting evaluation work. In particular note how you can use both Project-level and Target-level settings to compliment each other.

      More specific to your issue, you almost certainly want to use #include files. So you put the common settings in some SharedLibrary.xcconfig and you then #include that file into each project’s xcconfig. You can overload settings after you’ve done the #include. In the case of GCC_PREPROCESSOR_DEFINITIONS above, make sure that you’ve defined that in something you’ve #included. That would be my suspicion.

      Also, remember $(inherited), which represents the value this variable has based on higher (rather than earlier) levels. What I mean is that if you set VAR to something and then #include that, $(VAR) will give you the value it was given in the earlier #include. If you set VAR at the Project level, then at the Target level $(inherited) will give you the value from the Project level. So often the first setter of VAR at a level should use $(inherited), and later setters should use $(VAR) as their first element.

      Remember that each project compiles independently. What you’ve done in the “parent” project has almost no impact on the “children” projects (there really is no such thing as “parent” and “children” in xcodebuild). I say “almost no impact” because a couple of things are passed down; specifically the SDK and the Configuration. But setting GCC_PREPROCESSOR_DEFINITIONS in one project has no impact at all on other projects. If you want to share a value, you need to hoist that into a separate xcconfig file and #include it into the xcconfig files for each project.

  2. ramzez
    October 3rd, 2009 at 15:38 | #3

    Right i see, i assumed that it maybe a problem. Is there a way to query what Configuration has been choosen, cause what we have is 10 Adhoc Configuraitons and 10 Distributions, so what i was thinking to make

    adhoc1.xconfig which would Configuration 1 be based on, if Configuration is passed to other project, in the shared.xcconfig i may have something like :

    if $(CONFIGURATION) == ADHOC1

    // set macros

    else if

    etc etc, but i am not sure if it is possible to query which configuration has been chosen in xcconfig and set settings dynamically.

    Or maybe you have some other ideas you don’t mind to share?

    thanks a lot.

  3. October 3rd, 2009 at 15:50 | #4

    You can’t do if() logic like this on configurations. The only conditionals you can do are architecture, SDK and variant (I’ve never really found a good way to make variant useful).

    The way you create conditionals is to have separate files and base a Configuration off of that file (at the Project or Target level). So rather than having Shared.xcconfig set the macros, you set the macros in adhoc1.xcconfig and #include Shared.xcconfig there to get the rest. You can see an example of this in my template project. I have Shared.xcconfig (which is included by others, but no configuration is based on it), then I have Debug.xcconfig which the Debug Configuration is based on at the Project level, Release.xcconfig which the Release Configuration is based on at the Project level, and Application.xcconfig which all configurations are based on at the Target level. So Shared is read, then either Debug or Release overrides settings in Shared, and finally Application overrides settings in Shared, Debug or Release.

    I’m not certain what “10 Distributions” mean. Do you mean 10 targets, or 10 adhoc configurations and 10 distribution configurations (20 configs in total). If 20 configs, then you’d do exactly as I’ve done, but instead of just 2 (“Debug” and “Release”) you’d have 20.

  4. ramzez
    October 3rd, 2009 at 15:53 | #5

    @ramzez Another question , is that possible to define Configuration as part of the xcconfig ?

  5. ramzez
    October 3rd, 2009 at 16:00 | #6

    @Rob Napier yeah that’s correct, we have 10 Distribution Configuraitons and 10 Adhoc Configurations. I understood your approach and did the same, but then hit the problem described above. It is pity XCode doesn’t have Parent>Child relationship in that case everything would be so perfect.

  6. October 3rd, 2009 at 16:17 | #7

    @ramzez You can’t define Configuration in the xcconfig, because the appropriate xcconfig file is chosen by the Configuration.

    So in your case, there would probably be a Shared.xcconfig that is shared by every project in the system. Then there would be 20 “configuration” xcconfigs that would #import Shared and would also be shared by every project in the system. At the Project level of every project, you would make 20 configurations, and base them on each of these 20 xcconfig files. That’s a bit complex, I know, and very manual if you have to add a new configuration, but it is the best solution I know of. You can Applescript it, though. Here’s the idea to get you started:

    tell application "Xcode"
        duplicate build configuration type named "Debug" of first project
        set aConfig to build configuration type named "Debug copy" of first project
        set name of aConfig to "New"
    end tell
    

    Then each project would have its own xcconfig that all configurations are based on at the Target level. At this level, you can do things like:

    GCC_PREPROCESSOR_DEFINITIONS = $(inherited) USE_SPECIAL_CASE2

    Note the use of $(inherited) here; we’re doing it at the Target level, so we want to inherit from the Project level.

  7. ramzez
    October 3rd, 2009 at 16:26 | #8

    Yeah that’s an interesting approach and unbelievable short comming of IDE, if i have time i would have a look at it. Interstingly all other platforms we work with don’t have such limitation. I just used bugreports to ask for Parent-Child relationship as a new Feature.

    Thank you very much for your help and time!

  8. clozach
    July 9th, 2010 at 01:07 | #9

    This is awesome…great to actually have this info humanely accessible to version control! One caveat worth appending to your instructions: Xcode 3.2.3 appears to have a bug whereby the option to select between Device and Simulator disappears from the “Overview” target menu if you delete ‘Base SDK’ (i.e., SDKROOT) from the project info’s Build settings.

    • July 9th, 2010 at 08:46 | #10

      Thanks for the tip. The SDK is one of the few things I recommend setting outside of the xcconfig files. I set it in the General pane (rather than the build pane, though doing so updates the build pane). It seems to be used by a lot of things in Xcode.

  9. rpv
    November 8th, 2010 at 11:23 | #11

    If i have a project which builds exectuable but also has a few static libraries, what the correct way to set preprocessor in such a way that when i build executable the static library inherits the settings of the exectuable, i.e. i build programA and programB and need static library to be built with settings from from programA when the target is A and B when the target is B ?

  10. November 8th, 2010 at 12:10 | #12

    @rpv. Consider the case of a library that could be compiled with encryption or without encryption. In that case, you would create two library targets, libfoo.a and libfoocrypto.a for instance. One target would have an xcconfig file with crypto and the other would omit it. You could create a LibraryShared.xcconfig file to hold common values for your LibraryCrypto and LibraryNonCrypto xcconfigs. Each executable would then have a dependency on the specific library it wants.

  11. rpv
    November 8th, 2010 at 12:38 | #13

    @Rob Napier so there is no way to have a setting defined at the target level which is propagated to the libraries level ? i was hoping i when i select i..e debug the DEBUG macro goes all the way to the dependable libraries. the reason i want this is that the files which are common for both application and library i put to the static library, rather then the application to avoid maintenance nightmare, but those files have multiple platform definitions i.e (iphone, desktop, etc), so what you propose to have another level, i.e. common_library_iphone, application_iphone, common_library_desktop, application_desktop, but that also means i will need to create debug/release versions as well then. i was really hoping there is a way how to set some kind of settings at the very high level which is goes all the way to every file you build.

  12. November 8th, 2010 at 13:10 | #14

    @rpv, Things like DEBUG should be defined in the Debug.xcconfig and Release.xcconfig, which are applied at the Project level. This way it will apply for everything you build, executable and library.

    I’m somewhat confused about your creation of static libraries here to avoid maintenance problems, though. Static libraries can sometimes help build times for very large projects, but generally it is easiest to have one project file per executable, and then add all the files you want into that project file, shared or not. I’ve seldom had libraries actually make maintenance any easier. Between symbol visibility, debugging headaches, and lack of automatic refactoring, I’ve generally found libraries to be more hassle than help, unless the library can really be shared between multiple projects without a lot of funky build differences.

  13. rpv
    November 9th, 2010 at 06:51 | #15

    @Rob Napier Thanks for the help, i think the problem was that i didn’t set project level, just executable level, that’s actually very annoying in xcode that you can be confused by those two.

    How else you would avoid building the same files for different targets, i.e. use it in your test app, shared library, static library and executable, if you add new file you need to add it to the 4 targets rather then one.

  14. pankaj sejwal
    September 10th, 2011 at 06:27 | #16

    excellant article…thanks pal..!

  1. May 20th, 2009 at 17:58 | #1
  2. February 25th, 2011 at 15:05 | #2