Caching dependencies in Github Actions

How to cache Pods, Ruby gem, and Carthage in your iOS project.

iOS CI Workflow

Caching is one of the most requested features in Github Actions. Now the waiting is over. Caching is now natively supported via the cache action[1].

The following are examples of how to cache Ruby Gem, Cocoapods, and Carthage.

Ruby - Gem

- uses: actions/cache@v1
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-

Swift, Objective-C - Carthage

- uses: actions/cache@v1
with:
path: Carthage
key: ${{ runner.os }}-carthage-${{ hashFiles('**/Cartfile.resolved') }}

Swift, Objective-C - CocoaPods

- uses: actions/cache@v1
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-

The cache action required two input parameters and one optional parameter.

path: Required The file path on the runner to cache or restore.
key: Required An explicit key for restoring and saving the cache.
restore-keys: Optional - An ordered list of keys to use for restoring the cache if no cache hit occurred for key.

Hit or Miss

When key matches an existing cache, it's called a cache hit, and the action restores the cached files to the path directory.

Cache hit
Cache hit

When key doesn't match an existing cache, it's called a cache miss, and a new cache is created if the job completes successfully. You will see something like at the end of your steps (if the job completes successfully).

Cache miss
Cache miss
Creating a cache
Creating a cache

When a cache miss occurs, the action searches for alternate keys called restore-keys. If you provide restore-keys, the cache action sequentially searches for any caches that partial match the list of restore-keys. When the action finds a partial match, the most recent cache is restored to the path directory.

Skipping steps based on cache-hit

If the cache hit, you might want to skip some steps. Typically you would want to skip the step that downloads dependencies or generate the cached output.

Cache hit result is in the output id cache-hit and accessible through the following command.

steps.<step_id>.outputs.cache-hit

For our Carthage example, it will look like this.

steps:
- name: Checkout
uses: actions/checkout@v1
- uses: actions/cache@v1
id: carthage-cache
with:
path: Carthage
key: ${{ runner.os }}-carthage-${{ hashFiles('**/Cartfile.resolved') }}
- name: Carthage
if: steps.carthage-cache.outputs.cache-hit != 'true'
run: |
carthage bootstrap --no-use-binaries --platform iOS --cache-builds

Techincally, if there is a cache hit for Ruby gem (vendor/bundle) and Cocoapods (Pods), we should be able to skip bundle install --deployment[2] and pod install entirely, but in my test, it resulting in GemNotFound and The sandbox is not in sync with the Podfile.lock errors.

So if you try to cache gem and pods you still need to run pod install and bundle install. You still get speed benefit since you don't need to fetch most gems and pods from the internet.

- name: Bundle Install
run: bundle install --deployment
- name: Cocoapods
run: bundle exec pod install

Limitation

  • Cache entries that have not been accessed in over 7 days will be removed.
  • Individual files in a cache must not exceed 400 MB. You will receive a 400 - Bad Request if you exceed this limit.
  • The total size of all caches in a repository don't exceed 2 GB. If you exceed this limit, GitHub will save your cache but will begin evicting caches until the total size is less than 2 GB.

  1. I can't find the public announcement about this feature, but It definitely came out within this month (November 2019). ↩︎

  2. You need --deployment flag here to installs gems to the vendor/bundle directory in the application, which we use as cache path. https://bundler.io/man/bundle-install.1.html#DEPLOYMENT-MODE ↩︎

If you enjoy this article, you can subscribe to the weekly newsletter.

Every Friday, you’ll get a quick recap of all articles and tips posted on this site — entirely for free.


← Home