XcodeGen & Sourcery - A Case Study

Jan Borowski

November 28, 2023


XcodeGen & Sourcery: A Case Study

Introduction

In this article, I would like to share how we introduced Sourcery and XcodeGen in our VOD project. I will explain the pros and cons of Sourcery and XcodeGen, why we decided to give them a chance, and what problems they are solving. Let’s get started!

VOD project

First, let’s say a few words about our VOD project. One of our flag VOD products is a project dedicated to iOS and tvOS. This single project holds many brands like BET+, Smithsonian, MTV, and more. The project contains many modules, and each module has many targets. Some modules are feature-oriented, like search or analytics; others contain design systems or shared functionality.

Having such a large project brings a lot of challenges. I want to focus on challenges on two levels: project level and test level.

Project level

Test level

Solutions

To solve these problems, we picked two tools: XcodeGen and Sourcery.

XcodeGen

It is a tool to generate Xcode projects based on project specs and directory structure. Project spec contains information about required dependencies, targets, and configuration of specific targets. XcodeGen uses a directory structure to map group hierarchy in the Xcode project. This approach ensures that files and directories are always in sync with the Xcode project.

Sourcery

Tool for code generation in Swift. Sourcery uses templates written in Stencil language to generate code. Developers around the world use Sourcery for generating mocks, decorators, and others. Any repetitive, boilerplate code can be a candidate for Sourcery migration.

How we did it

Not all in

We did it one step at a time. XcodeGen and Sourcery share an essential feature: they are not all-in tools. You can decide what modules you want to use them in and when. We didn’t have to migrate all modules at once. Gradual adoption helped us polish configuration files and quickly deliver features to the whole team.

Keep things manageable

We had two distinct epics in JIRA, one for XcodeGen migration and the other for Sourcery. For all modules eligible for migration to XcodeGen, we created one task. For Sourcery, we started with one task per module. We quickly discovered that some test targets contain so many mocks to migrate that it would be overwhelming for one person to complete. We decided to split one task per directory. Sometimes, we even added tasks for sub-directories, too! We started with one task for the biggest test target and divided it into more than 30 smaller stories!

Do not track generated code

Sourcery and XcodeGen help in code generation. Yet, we wanted to avoid committing generated code to the repository. We would have to deal with conflicts in git again! We would have to deal with conflicts in git again! We only kept the definition of what we want to generate. We added generated code and generated pbxproj to gitignore and, as a result, excluded them from history.

The neat thing is anytime we can decide to stop using any of these tools. In such a situation, we can add generated files to git. There is no danger of entangled dependencies that we would need to maintain for years.

Git hooks to the rescue

One thing is to migrate mocks/modules on the machine of the initial developer. But how did we manage to make sure that migration is happening on other workstations? The answer is a set of tools: Brew, git hooks, and in-house scripts.

We use Brew to install external tools. Brew uses Brewfile to keep track of tools to install. We added “XcodeGen”, “Sourcery”, and “pre-commit” - git hooks tool.

We created simple, in-house scripts in bash to run XcodeGen and Sourcery. Those scripts fetched a list of modules to work on from the configuration file. There is a separate file for XcodeGen and Sourcery. Thanks to this approach, these tools work only in ready modules and do not perform any work for frameworks we haven’t migrated yet.

Git hooks enabled us to run in-house scripts whenever the developer commits changes, checkouts, or merges branches. In the pre-commit configuration file, we specified what and when it should be executed. For example, when a user checkouts the branch, pre-commit runs XcodeGen generation script. Most of the time, developers don’t have to worry about running scripts manually, since it is automatically done by pre-commit.

Moreover, we added Sourcery generation to build phases for Xcode projects. That made working with mocks super easy. Every time the developer runs unit tests, Sourcery generates all mocks. There is no need to run scripts by hand!

Avoid competence silos

We wanted to avoid a competence silo, and we encouraged team members to take on migration tasks. Thanks to this, everyone gained experience with at least one of the technologies. Doing tasks in parallel by different team members encouraged knowledge transfer. We also avoided dumping the whole responsibility on a single dev.

What we gained

Thanks to introducing XcodeGen and Sourcery, we were able to deliver the following improvements:

XcodeGen

Sourcery

Every rose has its thorns

Although XcodeGen and Sourcery are improvements to our VOD project, they come with its quirks:

Summary

Both Sourcery and XcodeGen are exciting tools to use. They can improve project quality and make day-to-day work more pleasant. After playing with Sourcery a bit, code generation feels like a superpower to me! I encourage you to look at all the tools. See for yourself if they fit into your project.