When working on a large project with multiple developers, we often find ourselves in contention with the Xcode project.pbxproj file. Conflicts here are not easy to solve manually, and if you make a mistake Xcode won’t load the project at all, requiring you to fix it manually. In this episode we will explore migrating to a solution where the project file is generated using a tool called Xcodegen.
When working on a large project with multiple developers, we often find ourselves in contention with the Xcode project.pbxproj file. Conflicts here are not easy to solve manually, and if you make a mistake Xcode won’t load the project at all, requiring you to fix it manually. There are some solutions to mitigate these problems and one of them is to treat the Xcode project as a generated artifact instead of the source of truth. Doing so changes a bit of your workflow, but the benefits are pretty obvious once you get used to it. In this episode we’ll migrate our Wurdle project from a past series to use Xcodegen. If you want to follow along, make sure to download the source for that episode first. The completed project.yml file will be provided in this episode's git repository. Installing Xcodegen We can install Xcodegen with Homebrew. $ brew install xcodegen Once installed, you will run xcodegen any time you want to regenerate a project. But in order to do that, we first need a project.yml file. The Project YML file The project.yml file contains all of the information needed to create your Xcode project. At first it may seem a little overwhelming, however it contains lots of sensible default behavior and you start to get used to the structure over time. Be prepared to reference the Project Spec documentation regularly as you build it out. Our project.yml contains some top-level information: name: Wurdle options: bundleIdPrefix: com.nsscreencast.wurdle Adding Swift Packages To specify which packages we may want to install, we add a packages section to the top level as well: packages: Yams: url: https://github.com/jpsim/Yams from: 2.0.0 Note: We don’t actually need this package, but I wanted to show how this is done. Configuring Targets Your project is made up of targets. In this example, we’ll create two targets: iOS Application Framework We’ll put our model code and any other code we may want to share or test separately in the framework. All of the iOS UI code will go in the application target. targets: Wurdle: type: application platform: iOS deploymentTarget: "16.0" sources: [Sources/Wurdle] info: path: Sources/Wurdle/Info.plist properties: CFBundleDisplayName: WURDLE! UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait] UILaunchStoryboardName: LaunchScreen dependencies: - target: WurdleKit WurdleKit: type: framework platform: iOS sources: [Sources/WurdleKit] dependencies: - package: Yams info: path: Sources/WurdleKit/Info.plist properties: In this example, we have indicated that we want an Info.plist file generated with the specified properties. You can also reference an existing Info.plist file if you have one already and don't want to migrate to a generated one. At this point all source files that exist in the folder structure you specified will be picked up automatically. Generating the project Now run xcodegen to generate your project. You will run this often: every time you pull new changes from the server every time you add new files or configuration every time you switch branches Since we are continually generating this project file, it’s a good idea now to ignore it from the git repository entirely: $ git rm -r --cached Wurdle.xcodeproj (deletes the file from the git index) $ echo "Wurdle.xcodeproj" >> .gitignore $ git add . && git commit -m "Ignore xcode project"