How many times when working on a Mac OSX or iOS app with a team have you had a merge conflict on the project.pbxproj
file? I guess more than a few, a lot more than a few.
In this article we are going to show a way to reduce the chances of such issues using a tool called xUnique.
Xcode's project file
Just in case you're not familiar with it, the infamous project.pbxproj
is the way Xcode tracks which files are in the project, and describes the project settings, like targets and build configurations.
At the core of the way Xcode keeps track of all these things is the fact that every entry has it's own unique identifier.
You can read more about how the Xcode's project.pbxproj
work in the Apple documentation, this old post on mokacoding, and this more recent one from Michele Titolo.
When working on a team it can happen, and because of Murphy's law it will, that some crazy edits to the project, like adding a file, made by two team members on two different commits will result in a nasty merge conflict on the project.pbxproj
.
This happens because the unique identifiers used by Xcode are not that unique, and some changes to the project may result in a partial re-generation of the project file itself.
Enter xUnique
xUnique is a tool made by Sean Wang that makes sure the unique identifiers used in the project.pbxproj
are actually unique.
It also sorts the project files for you, which is quite handy in my opinion.
Install xUnique
xUniqe is a Python tool, a fact rather unusual for iOS/OSX utilities.
The best way to install it is through PyPi.
pip install xUniqe
Before doing that you might want to make sure that your Python toolchain is up to date. I'd reccomend using homebrew for that.
Uniquifying the project
With xUnique now installed you can "uniquify" the project.pbxproj
like this:
python -mxUnique MyAwesomeApp.xcodeproj/project.pbxproj
The first thing you'll see is that all your groups and files in Xcode have been sorted alphabetically, and if you'll look at the git diff
you'll see that all the identifiers in the project file have changed.
And example diff can be seen here, in the example project we setup for the occasion.
Automating the process
You might now be saying: "But I don't want to run that command every time I touch the project!" . And you'd be right!
Everything that can be automated should be automated
We can easily automate this process by adding a Run Script Build Phase to our target and have it run xUnique for as after every build.
I like to have my run scripts in their own file rather than in the Run Script text view, it makes it easier to edit them with your text editor of choice.
# uniquify.sh
#!/bin/bash
# uniquify and sort the Xcode projct files
python -mxUnique "${PROJECT_FILE_PATH}/project.pbxproj"
The version of the script above uses the PROJECT_FILE_PATH
environmet variable provided by Xcode to the Run Scripts. If you want to be able to run the script yourself replace it with MyAwesomeApp.xcodeproj
.
Something to keep in mind
While once automated this process is almost frictionless, there are some things to keep in mind:
- Every time the project is touched by xUnique, the state of the Project Navigator resets.
- All the users of the project will have to install xUnique, or their build will fail.
- xUnique replaces Xcode's identifiers with MD5 ones. Up till now this hasn't been a problem, but what would happen if Xcode stopped understanding IDs in such format?
- The tool will run for every build, but if a developer were to change the project and commit without building then
project.pbxproj
changes wouldn't be uniquified.
In the next post we'll see how to make sure xUnique always run, even someone doesn't build, through a git pre-commit hook.
Xcode has still a long way to go before becoming the IDE Mac and iOS developer deserve, but it's getting closer every point update. As users we can cope with some of it's limitations using some of the excellent plugings that the OSS community has realised, checkout Alcatraz for a starting point, open radars, and sometimes give it some extra help with scripts like xUnique.
I hope of have triggered your curiosity with this post, as usual there is an example repo that you can use to see the full code, and I'd be happy to help you setup xUnique if you need extra help, just tweet @mokagio.