Notice: This tutorial has not been updated to the lastest XCode and Swift release. Please forgive any errors found in the code. I plan on releasing an update but it might take a while to find the time. Please read the comments below to see highlighted changes in the syntax and comment if you yourself find outdated statements. Cheers!
If you have ever been stuck with project file conflicts in a multi collaborative developer environment in XCode it might be worth while adopting xcconfig files. My experience is that it reduces project file conflicts by moving build settings into separate files which are version tracked independently. I was first introduced to the concept many years ago at a local CocoaHeads meeting but didn’t take much notice of it at the time. Later on I was re-introduced to the benefits of this approach by my good friend Edward Patel (www.memention.com). So what is xcconfig files all about and how can you adopt them in your workflow?
Working as a developer you sooner or later will have to mess with the projects build settings. It might be that you have to link to added frameworks or static libraries, or that some of these need you to set other C flags or whatever. Doing so changes the project file, and even though the structure of the project file has greatly improved over the years, you often end up solving project file conflicts manually. By moving these settings to explicit xcconfig files and telling XCode to use these xcconfig files for the build process, your changes never really messes with the project file, but instead you make changes to these Key-Value text files. The (rare) merge conflicts are easier to solve, and the overall build settings get, in my opinion, a lot easier to get a grip on.
It takes some setting up, and in this little tutorial I hope to show you how this can be done from scratch, hopefully giving you a better understanding of how you can use xcconfig files in your workflow.
EDIT! For good reasons I’ve received comments on the sloppy treatment and mixup of Targets and Configurations in this article. I’ve tried to straighten out the misunderstandings and hope it brings some clarity to the article.
First, it helps to understand the differences between a target and a configuration. A target is pretty much what it sounds like. You can for instance target the iPad and iPhone platforms respectively and without writing a universal app. Or if you want a target to run unit tests (as you’ve mostly seen if you use the scaffolding test settings in XCode). Any resource or source file in your project, you can add to a certain target (see image below). In the image we’ve specified that the ViewController.swift file is part of the ConfigDemo target, but not the ConfigDemoTests target.
A configuration on the other hand is a set of settings, which can span multiple targets. Debug can be a configuration with compiler flags and settings, which gives you as much information about the software as possible to simplify bug hunting and development, while a release target might strip all those debug-logs and focus on optimization. If your target is iPhone or iPad, you might still want the same debug settings for both platforms, and perhaps also the same release settings for optimizations. A Configuration stores these settings and makes it easy to apply to different targets.
A configuration can be created on a project level and on a target level, as this example will show.
The steps presented here are based on a clean project, but nothing stops you from adopting this workflow in an existing project. However, good to know is that some resource management systems, like CocoaPods, uses xcconfig files to do their magic, and therefor can mess upp your own beautiful structure. Be aware and evaluate if this is still worth the while…
NOTE! See update at end of the article on CocoaPods and custom XCConfig files!
Of we go!
Start by creating a new single view application project. I name it ConfigDemo. Having saved it in a convenient location on your disk your project files overview should look something like this:
I like to add my files in a dedicated folder on the drive to keep things clean and tidy. Ctrl click on the project name (ConfigDemo at the top of the overview) and choose “Show in Finder”.
Make a new folder and name it Config.
Drag the newly created folder back to the project so that you have a folder in you project with a direct reference to the folder in the Finder. Accept in the sheet that is presented.
We will need a couple of config files. One for the general settings of the project, preferably named the same as the project. And we need one for each configuration in the app. In this demo we will create configurations for debug and release. So, Ctrl-click the Config folder in the Project navigator. Choose New File. Click “Other” and select the Configuration Settings File.
Name the file the same as your project. Make sure it is saved in your Config folder (we want to keep this nice and clean, right) and notice that these files should not be added to any target! Repeat the process and create two new files, Debug and Release. Your Project Navigation should look something like this when you’re done.
Selecting any of the xcconfig files, you see that they are nothing but empty text files, apart from the ubiquitous copyright comment. Now you need to let XCode know that these files should be used for building your project. You do so by selecting the project in the navigator (like in the image above). You are probably familiar with the project / target navigation that is presented in the main editor area.
Start by localizing the Configuration settings. You should by default have a Debug Configuration and a Release Configuration and both of these should have the “No Configuration Set” in their “Based on Configuration File” column.
Press the disclosure triangle next to Debug. You will see a list with the project followed by target and test target for the Debug build.
Press the “None” menu on the row with your project name (ConfigDemo in my case) and choose the xcconfig file for you project. Repeat the process for the main target and choose the Debug file (ignore the test target for now). Also repeat the process for the Release Target. You should end up with a configuration that looks like this.
Now you are set to start moving settings from the Project file to the xcconfig files. But how is this done? I mentioned that these files are Key-Value text files, but which keys should be used, and which values are valid? Well we start by diving into the Build Settings of the Project. With your project selected in the left part of the editor, select the Build Settings Tab in the top of the editor.
Make sure the Levels tab is selected in the top of the table. You will find a long list of build settings for you project. There are many columns here that give you a hint of what XCode will use as the build setting in each individual case. So far, and with a clean xcconfig file for the project, you should see three columns, Resolved, ProjectName and iOS Default. A beautiful feature here is that by selecting a setting and pressing Cmd-C (as in copy) you will copy a text version of the setting which, tada, suits the xcconfig file like the hand in the glove. So go on and select the Base SDK line, and press Cmd-C. Now select the config file for the Project and paste.
How convenient! Not only did XCode provide you with the Key and the value, it also gave you all the setting for each configuration. But here we have exactly the same setting for both configurations, is that really necessary? Answer is no. So now you can start cleaning up your xcconfig file to match the actual configuration the config file represents. Remeber to save the file. The final result should look like this.
Going back to the Project Settings table, you now should see a fourth column, namely that of you config file.
Also note that in gray, the same setting is found in the Config.File column as in the ConfigDemo column. Also note that the ConfigDemo setting is marked in green. This is an indication that though we have set this setting in the config file, the default Project setting is trumping the settings of the config file. This is important to note, as XCode prioritizes settings from left to right, with the leftmost having the highest priority. And the Resolved column shows the final result. So how do you make XCode use the settings in the config file? Easy! Press Delete with the row selected and the default setting of the Project will be deleted. You will now see that the xcconfig file settings is marked green instead.
Now, the next setting “Build Active Architecture Only” is trickier. Here we have multiple values for the targets, eacj for every configuration available. The iOS Defaults is set to “No” yet the debug configuration is set to Yes. Perhaps this setting should be placed in our config file for the target instead. Remember we created a separate config file for each configuration? Now in the detail navigation to the left select the ConfigDemo Target instead of the Project. You will se a new column appearing to the far left with a target icon and the name of the Target (same as the project, in this case ConfigDemo)
Note that since the target setting column is to the left of the Project settings Columns, the target settings always Trumps the Project settings. This is great because you can sett general settings for the configuration of the project and put configuration specific settings where appropriate. A great way to keep it all nice and tidy. Now reselect Project in the detail navigator. Then select the “Build Active Architecture Only” row. press Cmd-C again to copy the settings.
Note that you need to have the Project selected to get the right settings. Having the same row selected, but from the Target does not copy the correct value. Now select you Debug config file and paste the result.
Now Select the Project in the Project Navigation and the Target in the Detail Navigation. You should now see that the target config files column is visible with the settings we just pasted into our targets config file.
Note that the targets config file is already marked in green, showing that it trumps the Project Settings. But for the sake of tidiness, we mark the Project and delete the Project Settings in the same way we did previously.
Returning to the Target we now see that the Project Column is clear of settings.
So, this is it basically. Going through all the setting in the table, you will see that there are Target Specific settings in the Project File as well as Project Specific settings like the once I’ve shown here. Have a look at Runpath Search Paths for instance.
This setting is Target specific, and following the same procedure as above, you can easily copy out all these target specific settings and place them in the appropriate config file. A note of causion though. Adding external frameworks for instance will require you to add entries to you build settings. If you normally do so in the build settings menu, remember to add them to the config files instead, to keep all settings in the right place. Might seem obvious, but a friendly reminder for the future.
So now you would go through the entire list and move all settings of the project from the Project File to the xcconfig files, placing them in the files most appropriate to your workflow. Normally you would end up with a rather fat xcconfig file for the project with minor tweaks in the respective configurations xcconfig file. Which makes it easy to overview and maintain.
Finally, you can use these config files for other settings as well. Say for instance that you want to put you Facebook App Id in the config file with your custom key APP_FACEBOOK_ID and retrieve it from you info.plist, this value can be accessed by wrapping it in curly bracers and the $-prefix.
Good luck and happy configuring!
Edit: CocoaPods actually rocks really great with custom config files, what you need to do is add the path to the CocoaPods generated config files from your own xcconfig files. The CocoaPods generated files ended up in ‘/Pods/Target Support Files/Pods/’ so I added
#include "Pods/Target Support Files/Pods/Pods.debug.xcconfig"
To my Debug.xcconfig file and respective CocoaPods Files to the other xcconfig files accordingly.
Edit 2: I apologize for the late update on this article. As mentioned in the beginning, I’ve received comments on my mixup and sloppy treatment of targets and configurations. I hope this edit helps clarify the issue.
General note: My comments on CocoaPods where accurate at the time of the writing. New and interesting solutions like Carthage have emerged and risen in popularity since. The general idea of configurations is still the same though and can help you maintain consistency and facilitate source management over a team accordingly.