Rome + Carthage = Build Your Dependencies Faster
What is more, not every library provides itself as a zip file. In that situation, Carthage has to compile the dependency, and that of course takes time. Multiple this by X, where X is the number of projects in your organization that use the same library and, in case the cache is empty on your CI server, you lost quite a lot of time. But there is a way to make it faster. Omnes viae Romam ducunt.
Rome is a cache tool for Carthage. It means it can download already built dependencies so that Carthage doesn't have to compile them.
There are three main benefits of using central a cache repository:
-
faster CI builds,
-
shorter CI queue, which is a result of the first point,
-
faster checkout builds on developer’s machines.
Producer/Consumer
There are two workflows when using Rome: producer and consumer, and they are very simple to understand.
Producer
-
Creates a Cartfile or updates it if it's already created
-
Runs carthage update && rome upload
In general: Producer’s goal is to produce dependencies by compiling them on a machine with Carthage and upload it to the central cache using Rome.
Consumer
-
Downloads dependencies built by Producer using rome download.
Note: Consumer can also be a Producer. It may happen that some dependencies are missing from the cache. Rome allows us to list and update those.
From Rome’sreadme:
rome list --missing --platform ios | awk '{print $1}' | xargs carthage update --platform ios --cache-builds # list what is missing and update/build if needed
rome list --missing --platform ios | awk '{print $1}' | xargs rome upload --platform ios # upload what is missing
You can read more about Rome workflowshere.
Setup
After installing Rome through Homebrew, you can use the rome command in your terminal. Alternatively, it’s possible to set up Rome as a Fastlane plugin. We’ll use both approaches, mixed together.
Romefile
A Romefile to Rome is what a Fastfile is to Fastlane, a Cartfile is to Carthage, a Podfile is to CocoaPods, and a Gemfile is to Bundler.
Also, it's in the YAML format. In its simplest form, it would look like this:
cache:
s3Bucket: carthage-rome
cache is the only required keyword. You can read more about Romefileshere.
Ignore map
If we want, we can ignore some dependencies. Like this:
ignoreMap:
- xcconfigs:
- name: xcconfigs
The ignore map will be very helpful and crucial in some situations. We’ll get back to it in a minute. More about the ignore maphere.
Repository map
This is an interesting Rome feature. To understand it well, we'll use the RxSwift repo. As you probably know, the RxSwift project has a few targets in it. Apart from the obvious one (RxSwift) it contains RxBlocking and RxCocoa. A simple carthage update && rome upload will only upload RxSwift into cache. Thankfully, there’s the repository map which allows us to "map repository and framework names", but also helps us specify which frameworks should be copied into the cache.
repositoryMap:
- RxSwift:
- name: RxSwift
- name: RxCocoa
- name: RxBlocking
More about the repository map.
As you can see, Romefiles are rather short and easy to grasp. We only need the required cache setting; ignoreMap and repositoryMap can be used if needed, per project-specific requirements.
Using Rome with Fastlane
At least three different lanes will be needed to handle Rome in Fastlane. Here they are.
lane :carthage_update do
carthage(
command: "update",
platform: "iOS",
cache_builds: true, # This is Carthage's cache command, it has nothing to do with Rome cache.
)
rome(
command: "upload",
platform: "iOS",
)
end
lane :carthage_bootstrap do
carthage(
command: "bootstrap",
platform: "iOS",
no_build: true,
)
rome(
command: "download",
platform: "iOS",
)
end
lane :carthage_install_missing do
sh("(cd ..; rome download --platform iOS)")
sh("(cd ..; rome list --missing --platform ios | awk '{print $1}' | xargs carthage update --platform ios --cache-builds)")
sh("(cd ..; rome list --missing --platform ios | awk '{print $1}' | xargs rome upload --platform ios)")
end
carthage_install_missing
You may be wondering why lane carthage_install_missing calls Rome directly, using the sh command, and not by using the Fastlane command or plugin.Rome uses two different commands to list missing dependencies (rome list --missing) and to build and upload them (carthage update + rome upload).
Since it's not possible to pass the output of one Fastlane command to another, we have to use Rome through sh.
Using Rome within bitrise.yml
At Netguru we use bitrise.io, so bitrise.yml is a natural habitat for scripts we use in the build system. Because there is no Bitrise step for Rome, we need to use it in the same way as any other script. There will be one additional step, which is installing Rome via Homebrew. So if we wanted want to define a run-rome step that lists the missing dependencies, it would look like this:
run-rome:
before_run:
- install-rome
- rome-list-missing
#We also need to define two steps, install-rome & rome-list-missing:
install-rome:
steps:
- script:
inputs:
- content: |
#!/bin/bash
brew install blender/homebrew-tap/rome
rome-list-missing:
steps:
- script:
title: 'rome-list-missing'
inputs:
- content: rome list --missing
You can pass any other Rome commands as content.
How does Rome work with forked repositories?
Neither Carthage nor Rome recognize the namespaces (GitHub user accounts) which hold dependencies. For Carthage, and thus Rome, github "Alamofire/Alamofire" is the same as github "JaneDoe/Alamofire". This is a potentially dangerous situation where the forked repository will be distributed to other projects. To fix this issue, we must use IgnoreMap to avoid caching forked repositories.
Of course, caching is not a trivial thing, YMMV, but so far it has worked well for us.
Photo: Lukasz Pikor