Building iOS dependencies with Carthage
In all my iOS projects I use and strongly prefer Carthage. It is easy to use, does not do any changes to your project, all the dependencies are built just once and then linked to the project as dynamic frameworks. There are many good posts about the advantages of Carthage compared to CocoaPods so in this post I will just focus on the actual usage, mainly in CI.
Carthage basics
All your Carthage dependencies are listed in the Cartfile
file in the root of your project. In case you split your app into multiple projects like I do, there is a Cartfile
for every project in the workspace. Next to every Cartfile
there is a Cartfile.resolved
file pinning all your dependencies to a specific version.
You just need to keep those two files in your source control and then run carthage bootstrap
when you clone the project so Carthage downloads and builds all the dependencies. This happens just once for a developer, but it is slow and time consuming. If you use a CI for automatic builds, it becomes a real time waste rebuilding all the dependencies before each build.
Carthage approaches
Developers typically try to speed things up with multiple approaches
- Keeping
Carthage/Checkouts
in source control. This makes the repository bigger by keeping unnecessary files, the checkout is faster but the build is still slow. - Keeping
Carthage/Build
in source control. This also makes the repository bigger, potentially much bigger if you update your dependencies often, but the build times are super fast as there is nothing to actually build. - Caching the Carthage builds in CI. This does not make the repository bigger and can be really fast when done properly
- Caching the Carthage builds using tools like Rome. This does not make the repository bigger and can be very powerful and flexible, but typically requires a paid 3rd party storage service like Amazon S3.
My current approach
I personally started with the second approach, but had to abandon it because of the repository size. After a few trials and errors I came up with this variation with the third approach
- Keep just
Cartfile
andCartfiles.resolved
in the repository - Have the CI cache the
Carthage/Build
folder between builds
I use Gitlab CI to automate my development and deployment workflow, so I just need to define the cache and run the carthage bootstrap
before each build
cache:
key: xcode94
paths:
- YourApp/Carthage/
before_script:
- carthage bootstrap --platform iOS --cache-builds
Gitlab CI restores the built dependencies from the cache, uses them in the build and then caches them again when the build finishes. I use the Xcode version as the cache key (e.g xcode94
) because every Xcode version typically comes with a different version of Swift causing a need for rebuilding all the dependencies.
This approach is really simple, your repository will not get big and all the dependencies are built just once per Xcode version in the CI.
When a new developer joins the project or does a git pull
with changes requiring building some new dependencies, they can do a neat trick: downloading the CI cache. With a simple
scp user@build.sever/cache/xcode94/cache.zip && unzip cache.zip && rm cache.zip
the developer can download and use the current build dependencies without wasting time doing any Carthage builds. This of course requires the developers to use the same version of Xcode as the build server, but that is necessary anyways with the way Swift is coupled with Xcode.