---
title: Caching Carthage con CircleCI
teaser: Use CircleCI's caching feature to speed up your builds with Carthage.
tags: ci,ios,carthage
author: Gordon Fontenot
published_on: 2016-01-18
---

I hope we can agree that Using a CI system for automating your tests is
important. Unfortunately, continuous integration for iOS apps hasn't always
been a great experience. Here at thoughtbot, we've run the gamut of services
as I'm sure most have. [Jenkins], [Travis], [Xcode bots], [Circle]. We've
tried them all. We ended up choosing Circle for a number of reasons. A big
reason was Circle's ability to cache directories, which improves build times
immensely.

[Jenkins]: https://jenkins-ci.org/
[Travis]: https://travis-ci.org/
[Xcode bots]: https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/xcode_guide-continuous_integration/
[Circle]: https://circleci.com/

By default, Circle already supports caching builds with [CocoaPods]. This is
fantastic, and for a lot of users will be more than enough. However, we've
been using [Carthage] more and more internally, due to the lightweight nature
of the tooling, and the ability to use binary frameworks.

[CocoaPods]: https://cocoapods.org/
[Carthage]: https://github.com/Carthage/Carthage

Unfortunately, trying to use Carthage caused problems with CI. Of note, the
need to code-sign frameworks as they were built meant that we'd need to share
signing certificates with our CI service. Additionally, Carthage is itself
written in Swift. That means that it needs to be built with a specific version
of Xcode, which complicates the build process using [Homebrew].

[Homebrew]: http://brew.sh/

Thankfully, these issues have been fixed! Carthage has been updated to Swift
2.0, which means that Homebrew can build with the latest stable version of
Xcode. In addition, since [Carthage version 0.11], it no longer requires
code-signing when building frameworks, so there's no need to pass around
signing certificates. Even better, Circle recently started pre-installing
Carthage on their Mac build systems so we no longer have to worry about
manually installing it.

[Carthage version 0.11]: https://github.com/Carthage/Carthage/releases/tag/0.11

So where does all of this leave us? We can now build iOS projects on Circle,
and we can use Carthage as our dependency management tool of choice. But
there's a problem: Circle doesn't know how to cache Carthage dependencies by
default, and as a result, our builds are really really slow. Since Carthage
has to re-build _all_ of our dependencies every time, it could easily take
over 10 minutes to build a project and all of its dependencies.

Luckily for us, Circle's build process is easily customized in way that will
let us work around this issue.

Note that we're assuming that you have added your `Carthage/` directory to
your `.gitignore` for the purposes of this post. If you are checking in
`Carthage/Checkouts` or `Carthage/Build`, this post might not be super
useful for you.

## Improving Carthage

To begin, we need to look at adding some smarts around Carthage. By design,
Carthage is a simple tool, but that same simplicity will let us write a thin
wrapper to add custom behavior. In this case, what we'd like to do is to only
run `carthage bootstrap` if the dependencies have changed. Carthage doesn't do
this by default, but it's fairly trivial to handle ourselves.

Carthage generates a file named `Cartfile.resolved` that declares the exact
dependencies it expects to be installed, similar to `Podfile.lock` or
`Gemfile.lock`. We can use that to determine which dependencies we have
locally and which ones we expect based on the current state of the repo.

To do this, we will first wrap the `carthage bootstrap` command to perform an
additional action. We'll save this as `bin/bootstrap`:

```shell
#!/bin/sh

carthage bootstrap
cp Cartfile.resolved Carthage
```

Don't forget to `chmod +x bin/bootstrap` so that this becomes executable.

This small script will run `carthage bootstrap`, and then copy the
`Cartfile.resolved` into our (gitignored) `Carthage/` directory. This means
that `Carthage/Cartfile.resolved` will always reflect the currently
downloaded/built dependencies, while `./Cartfile.resolved` will reflect the
dependencies that the project expects.

Now we can write another small script that will use this new
`Cartfile.resolved` file to determine if it needs to update the dependencies.
We'll save this as `bin/bootstrap-if-needed`:

```shell
#!/bin/sh

if ! cmp -s Cartfile.resolved Carthage/Cartfile.resolved; then
  bin/bootstrap
fi
```

Don't forget to `chmod +x bin/bootstrap-if-needed` so that this becomes
executable.

This script will compare `./Cartfile.resolved` and
`Carthage/Cartfile.resolved`. If they are different (or if
`Carthage/Cartfile.resolved` doesn't exist), we will run our `bin/bootstrap`
script, which will in turn update the dependencies and move the new
`Cartfile.resolved` into place.

You can now test this. Running `bin/bootstrap-if-needed` should update your
dependencies the first time, but running it a second time should become a
no-op. Updating the dependencies (or deleting `Carthage/Cartfile.resolved`)
should also result in the script re-installing the dependencies.

## Caching with Circle

So now comes the fun part. Since we're now able to determine if we need to
update our dependencies, we can leverage Circle's built in (and fantastic)
support for caching to speed up our builds.

To accomplish this, we're going to [add some config to our `circle.yml`
file][circle-config]. Specifically, we're going to override the `dependencies`
key:

[circle-config]: https://circleci.com/docs/configuration

```yaml
dependencies:
  override:
    - bin/bootstrap-if-needed
  cache_directories:
    - "Carthage"
```

Seriously, that's it. The `override` key tells Circle what command to run when
installing dependencies. We tell it to use our smarter version of `carthage
bootsrap`. Then, we tell it to cache the entire `Carthage` directory. Circle
automatically moves the cache into place before the dependency step, and saves
it at the end. By the time we run `bin/bootstrap-if-needed`, everything should
be in place.

Since we're caching the entire directory, that means we're also caching the
`Carthage/Cartfile.resolved` file we create with `bin/bootstrap`. So when
Circle runs `bin/bootstrap-if-needed`, it will only build our dependencies if
the cached dependencies are out of date.

And there you have it. Once you push this up and Circle starts to use the new
config, you should see a dramatic decrease in build times. We saw our times
drop from 14 minutes to under 2 minutes.
