Speeding up Carthage in your CI (iOS/macOS)


It’s well known that Carthage can be a bit slow with it’s build times. In this post I’ll try to explain what’s going on, and suggest a really simple way one can improve things.

What’s up?

Most people configure their CI and/or use Carthage in the “wrong” way:

  1. Most people install & setup Jenkins. Then, in their first build job, add:

carthage update

to the beginning of the build job. At first, this makes total sense. You’d want your code to build with the latest and greatest dependencies, right?

Uhm, actually, probably not. In most dev environments, you have a branching scheme (similar to Git flow):

  • /develop for new work
  • /qa to push a build to QA (internal or external)
  • /{hotfix/release/feature} etc etc.

The build job which is responsible for building your develop branch should be written such that it rarely updates Carthage. This is what will save you time. The job should be intelligent enough to know this.

Wait, what?

Unless you clear your job’s workspace on the CI server each build, there is no need to run carthage update/bootstrap on every build run for a basic /develop-branch build job[1]. That's just waisting bandwidth and cpu cycles. I suggest there are simply two instances when you want Carthage to run on said job:

  1. On the first build for that Job (until a successful completed job).
  2. When the Cartfile.resolved file changes

That’s it. Think about it; if you’ve two developers working on an app Foo, and developer A changes the Cartfile from

github "Alamofire/Alamofire" == 4.0.1 to github "Alamofire/Alamofire" == 4.1.0

and commits that, how does developer B know they must run carthage update? They see a commit that changes it, and so they run Carthage. Any other time, developer B, and A for that matter, are not going to run Carthage to update their dependencies on a daily basis.

The developers certainly aren’t going to run Carthage every time they build in Xcode. Why would you want your CI system to do otherwise?

Developers only run Carthage locally when Cartfile.resolved has changed or they’re investigating updates to the depencenies they are using.

Your build job for your /develop branch needs similar intelligence.

Here’s an example (if you’re using Jenkins) of what to put as one of the top steps in your build job:

https://gist.github.com/4b65500de97a5d42358806c5cb892405

An explanation:

The Jenkins Git-Plugin provides a few essential environment variables, a couple of which we care about for this post:

  • GIT_PREVIOUS_COMMIT
  • GIT_PREVIOUS_SUCCESSFUL_COMMIT

We want our build job to be able to answer this simple question: Was the Cartfile.resolved file from the last successful build changed since that build? If so, then we need Carthage to update our things. If not, we want Carthage to do nothing (hence, speeding up the build).

If you’re thinking “why not use GIT_PREVIOUS_COMMIT"? There may have been a few, even hundred's, of commits since the last build. The CI system doesn't care about any of those (with respect to this post's topic). It cares about what it had last built, and what it will build. Did the developers change any dependencies since then? Oh they did? Ok, we need Carthage to do it's thing.

You may be curious why I’m using carthage bootstrap instead of carthage update. Bootstrap checks out and builds the dependencies at the versions listed in the cartfile. That's exactly what we want for this type of build job in our CI. If we'd used update, the update would change the Cartfile.resolved, thereby potentially building code that even the developers aren't building on their local workstations. For most build jobs for develop branches, the goal is building what the developers are building - exactly. The goal is not to be proactive and introduce new versions of dependencies that have yet to be vetted by the development team.

There are other ways to speed up Carthage. The most obvious is to make sure you are running the latest version of Carthage on your development machine and on your CI machines. If there’s interest I can write more about additional ways.

If you’ve found this posting interesting, I’d appreciate if you’d let me know.

PS Have you ever not understood the difference between carthage update and carthage bootstrap? If so, this explanation will help.

[1] If one’s goal is to have a build job do proactive builds (ie to test updated dependencies w/o having developer interaction) this would not be the case. That may be the topic of a future post too.