Avoiding merge conflicts with XcodeGen
I - Intro
This article provides a solution to instantly solve merge conflicts on .xcodeproj file, which is one of the most time consuming problems that iOS and macOS engineering teams face today.
By the end of this article you’ll have a working environment that provides a seamless merge experience.
II — Before we start
1. Introduction XcodeGen
XcodeGen is a command line tool written in Swift that generates your Xcode project using your folder structure and a project spec.
We’ll use Brew to install XcodeGen. Just run on terminalbrew install xcodegen
(more install options and instructions can be found here ).
2. Use case project
Nowadays, most projects aren’t simple. They have multiple configurations, dependencies (provided by third parties or developed as part of the project), scripts that run as part of the build process, CI integrations, etc.
During this tutorial we will see how to setup, step by step, an example project that is simple enough to explain the gist of XcodeGen while being complex enough to cover a good range of case uses.
As we can see above, the project have:
III — Setting up
1. Set up sample project
The sample project that we’ll use on this walk through has carthage dependencies. Just run makeProject.sh
on a terminal. This will setup the project for you with all its dependencies and we can start the tutorial.
2. Creating your XcodeGen project specs file
At the project root directory, create a folder named XcodeGen(folder name is optional) with a file named project.yml
(file name is optional).
During our inicial setup we will choose the .xcodeproj file name — GoodToGo — , the bundle prefix — com.GoodToGo — , Xcode version, deployment target…
3. First XcodeGen run
Using the above setup, we can create our new project running xcodegen -s ./XcodeGen/project.yml -p ./
on the terminal.
Notice that ./XcodeGen/project.yml
is our project spec file path. If you choose other folder/file name you need to change this too.
Green message saying that project was created? We’ve successfully created our project!
Lets now open it and see how it looks:
Not what we were expecting… All of our schemes are missing, all the frameworks, everything. They are missing because we didn’t defined them on our project.yml
file.
4. Adding configurations and project main target
Just append on your project.yml
file:
Basically, we are creating 3 configurations, Debug.Dev, Debug.Prod, Release, and stating that our app main target will be named GoodToGo, targeted for iOS 12 (minimum version) and the source code files are on folder GoodToGo.
The folder must exist on system folders or else you’ll have the following error:
Spec validation error: Target “GoodToGo” has a missing source directory “/Users/ricardosantos/Desktop/GitHub/RJPS_Articles/7/sourcecode/THE_FOLDER_THAT_DOES_NOT_EXISTS_NAME”
Run again with our updated configuration (previous image) and…
…we have (preview image) Schemes, configurations, and app main target!
5. Adding frameworks
Next thing is to add our framework dependencies. For each one, we need to choose the platform, type, deployment target and other settings.
We know from the get go that, usually, all the frameworks share the same settings, so we will create a template and call it Framework.
Bellow, we have a simple template that we will refer to on our main project.yml
file and then reference it where needed.
This template states that all the targets will have this conditions:
- type framework,
- platform iOS,
- version number as 1.0,
- deployment target equal to, or above, iOS 11.0.
From our sample project, we had the following framework dependencies (see below).
Now, we’ll add 2 for a quick test: AppTheme and AppResources.
Starting from our project.yml
file, on targets section, we will add 2 new targets (AppTheme and AppResources). To these targets, we must define the source folder and the template name, as explained in the previous step.
Run again with our updated configuration (previous image) and…
…we now have our app main target and the 2 dependencies we just added on our spec file.
Small recap: our project.yml
on the target section should look like:
To complete the setup we need to add the rest of the frameworks, after which our project.yml
file should look like:
At this point, after executing XcodeGen, we should have the project with all the frameworks!
While setting up these kind of things, we always hit bumps on the road. One of those is spotting a missing .plist file reference. This can be fixed in 2 ways: one is to configure on the project.yml
the .plist
file path and the second option is to rename GoodToGo-Info.plist to Info.plist.
6. Fixing dependencies frameworks
At this point we have our basic project configuration, the schemes back and our frameworks back. It is time to fix our dependencies and compile our project successfully.
For instance, we noticed that Domain depends on RxCocoa and RxSwift, and these dependencies are resolved via carthage.
First, we need to find the Domain target on our project.yml
file…
…and then add the missing (carthage) dependencies. As simple as that!
To add some dependencies we have several options, but on our sample project we just need 3:
- Add a carthage dependency,
- Add a project dependency and linking,
- Add a project dependency and don’t link.
You can learn more about adding dependencies here.
It’s time to fix all the dependencies on our project and we will be basically done! “Basically” is just a way of saying it, this task is probably the most time consuming one, its a cycle of:
- 1. Generating .xcodeproject and open it,
- 2. Compiling the project and identifying missing dependencies,
- 3. Adding missing dependencies on
project.yml,
- 4. Go back to Step 1.
7. Extra: Build fase scripts
On the original project, we had some script build phases (as we can see on the image below):
We can achieve the same result by adding a postCompileScript child.
8. Extra: Documents folder
We can add also a Documents folder. The files inside this folder will not be added to any target, and therefore will not be processed as part of the build. Just add the path of your documents folder to your fileGroups section (more about fileGroups here).
IV - Recap
- We created a basic
project.yml
file and used XcodeGen to take it and generate our project (more info here) - We added some project configurations (more info here)
- We added all targets we needed
- We fixed the targets’ dependencies (more info here)
- We added the documents folder (more info here)
- We added build phases scripts
1. Some problems you may face while setting up project the first time: Lost files
Happens to all of us, sometimes. While removing some old/deprecated files from the project, we choose the option to Remove Reference instead of moving the file to trash. These files will come back again when we use XcodeGen (remember that all files inside the folder will be added to the target). If you really want to keep these files, put them on the Documents folder.
2. Some problems you may face while setting up project the first time: Miscellaneous files inside folders
Sometimes we have miscellaneous files inside our targets’ folders. Again, remember that all the files inside a target folder will be add to the target and therefore compiled. You can avoid this by putting those files inside the Documents folder.
V - Materials
- XcodeGen can be found here.
- The project before the conversion can be found at here.
- The final project source code can be found here.
- The final project.yml can be found at here.
- More examples of similar projects can be found here.