cache/examples.md
Mikhail Zabaluev 25c295a81d Added a more elaborate Rust example
This example could be used by projects that build Rust
end product crates on an OS matrix, but share the cargo cache.
To avoid fetching the dependencies from the network in
each matrix job in case of a cache miss, a dedicated job
pre-populates the cache on Linux, to be reused by the
jobs in the matrix.

Also demonstrate separate proximate caching of the registry index.
2020-06-18 17:29:52 +03:00

15 KiB

Examples

C# - NuGet

Using NuGet lock files:

- uses: actions/cache@v2
  with:
    path: ~/.nuget/packages
    key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
    restore-keys: |
      ${{ runner.os }}-nuget-      

Depending on the environment, huge packages might be pre-installed in the global cache folder. With actions/cache@v2 you can now exclude unwanted packages with exclude pattern

- uses: actions/cache@v2
  with:
    path: | 
      ~/.nuget/packages
      !~/.nuget/packages/unwanted
    key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
    restore-keys: |
      ${{ runner.os }}-nuget-      

Or you could move the cache folder like below.

Note: This workflow does not work for projects that require files to be placed in user profile package folder

env:
  NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
  - uses: actions/cache@v2
    with:
      path: ${{ github.workspace }}/.nuget/packages
      key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
      restore-keys: |
        ${{ runner.os }}-nuget-        

D - DUB

POSIX

- uses: actions/cache@v2
  with:
    path: ~/.dub
    key: ${{ runner.os }}-dub-${{ hashFiles('**/dub.json') }}
    restore-keys: |
      ${{ runner.os }}-dub-      

Windows

- uses: actions/cache@v2
  with:
    path: ~\AppData\Local\dub
    key: ${{ runner.os }}-dub-${{ hashFiles('**/dub.json') }}
    restore-keys: |
      ${{ runner.os }}-dub-      

Elixir - Mix

- uses: actions/cache@v2
  with:
    path: deps
    key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
    restore-keys: |
      ${{ runner.os }}-mix-      

Go - Modules

- uses: actions/cache@v2
  with:
    path: ~/go/pkg/mod
    key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    restore-keys: |
      ${{ runner.os }}-go-      

Haskell - Cabal

We cache the elements of the Cabal store separately, as the entirety of ~/.cabal can grow very large for projects with many dependencies.

- uses: actions/cache@v2
  name: Cache ~/.cabal/packages, ~/.cabal/store and dist-newstyle
  with:
    path: |
      ~/.cabal/packages
      ~/.cabal/store
      dist-newstyle      
    key: ${{ runner.os }}-${{ matrix.ghc }}

Java - Gradle

- uses: actions/cache@v2
  with:
    path: ~/.gradle/caches
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
    restore-keys: |
      ${{ runner.os }}-gradle-      

Java - Maven

- uses: actions/cache@v2
  with:
    path: ~/.m2/repository
    key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
    restore-keys: |
      ${{ runner.os }}-maven-      

Node - npm

For npm, cache files are stored in ~/.npm on Posix, or %AppData%/npm-cache on Windows. See https://docs.npmjs.com/cli/cache#cache

If using npm config to retrieve the cache directory, ensure you run actions/setup-node first to ensure your npm version is correct.

Note: It is not recommended to cache node_modules, as it can break across Node versions and won't work with npm ci

macOS and Ubuntu

- uses: actions/cache@v2
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-      

Windows

- name: Get npm cache directory
  id: npm-cache
  run: |
    echo "::set-output name=dir::$(npm config get cache)"    
- uses: actions/cache@v2
  with:
    path: ${{ steps.npm-cache.outputs.dir }}
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-      

Using multiple systems and npm config

- name: Get npm cache directory
  id: npm-cache
  run: |
    echo "::set-output name=dir::$(npm config get cache)"    
- uses: actions/cache@v2
  with:
    path: ${{ steps.npm-cache.outputs.dir }}
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-      

Node - Lerna

- name: restore lerna
  uses: actions/cache@v2
  with:
    path: |
      node_modules
      */*/node_modules      
    key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}

Node - Yarn

The yarn cache directory will depend on your operating system and version of yarn. See https://yarnpkg.com/lang/en/docs/cli/cache/ for more info.

- name: Get yarn cache directory path
  id: yarn-cache-dir-path
  run: echo "::set-output name=dir::$(yarn cache dir)"

- uses: actions/cache@v2
  id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
  with:
    path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-yarn-      

OCaml/Reason - esy

Esy allows you to export built dependencies and import pre-built dependencies.

    - name: Restore Cache
      id: restore-cache
      uses: actions/cache@v2
      with:
        path: _export
        key:  ${{ runner.os }}-esy-${{ hashFiles('esy.lock/index.json') }}
        restore-keys: |
          ${{ runner.os }}-esy-          
    - name: Esy install
      run: 'esy install'
    - name: Import Cache
      run: |
        esy import-dependencies _export
        rm -rf _export        

    ...(Build job)...

    # Re-export dependencies if anything has changed or if it is the first time
    - name: Setting dependency cache
      run: |
        esy export-dependencies        
      if: steps.restore-cache.outputs.cache-hit != 'true'

PHP - Composer

- name: Get Composer Cache Directory
  id: composer-cache
  run: |
    echo "::set-output name=dir::$(composer config cache-files-dir)"    
- uses: actions/cache@v2
  with:
    path: ${{ steps.composer-cache.outputs.dir }}
    key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
    restore-keys: |
      ${{ runner.os }}-composer-      

Python - pip

For pip, the cache directory will vary by OS. See https://pip.pypa.io/en/stable/reference/pip_install/#caching

Locations:

  • Ubuntu: ~/.cache/pip
  • Windows: ~\AppData\Local\pip\Cache
  • macOS: ~/Library/Caches/pip

Simple example

- uses: actions/cache@v2
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-      

Replace ~/.cache/pip with the correct path if not using Ubuntu.

Multiple OS's in a workflow

- uses: actions/cache@v2
  if: startsWith(runner.os, 'Linux')
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-      

- uses: actions/cache@v2
  if: startsWith(runner.os, 'macOS')
  with:
    path: ~/Library/Caches/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-      

- uses: actions/cache@v2
  if: startsWith(runner.os, 'Windows')
  with:
    path: ~\AppData\Local\pip\Cache
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-      

Using pip to get cache location

Note: This requires pip 20.1+

- name: Get pip cache dir
  id: pip-cache
  run: |
    echo "::set-output name=dir::$(pip cache dir)"    

- name: pip cache
  uses: actions/cache@v2
  with:
    path: ${{ steps.pip-cache.outputs.dir }}
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-      

Using a script to get cache location

Note: This uses an internal pip API and may not always work

- name: Get pip cache dir
 id: pip-cache
 run: |
   python -c "from pip._internal.locations import USER_CACHE_DIR; print('::set-output name=dir::' + USER_CACHE_DIR)"   

- uses: actions/cache@v2
  with:
    path: ${{ steps.pip-cache.outputs.dir }}
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-      

R - renv

For renv, the cache directory will vary by OS. Look at https://rstudio.github.io/renv/articles/renv.html#cache

Locations:

  • Ubuntu: ~/.local/share/renv
  • macOS: ~/Library/Application Support/renv
  • Windows: %LOCALAPPDATA%/renv

Simple example

- uses: actions/cache@v2
  with:
    path: ~/.local/share/renv
    key: ${{ runner.os }}-renv-${{ hashFiles('**/renv.lock') }}
    restore-keys: |
      ${{ runner.os }}-renv-      

Replace ~/.local/share/renv with the correct path if not using Ubuntu.

Multiple OS's in a workflow

- uses: actions/cache@v2
  if: startsWith(runner.os, 'Linux')
  with:
    path: ~/.local/share/renv
    key: ${{ runner.os }}-renv-${{ hashFiles('**/renv.lock') }}
    restore-keys: |
      ${{ runner.os }}-renv-      

- uses: actions/cache@v2
  if: startsWith(runner.os, 'macOS')
  with:
    path: ~/Library/Application Support/renv
    key: ${{ runner.os }}-renv-${{ hashFiles('**/renv.lock') }}
    restore-keys: |
      ${{ runner.os }}-renv-      

- uses: actions/cache@v2
  if: startsWith(runner.os, 'Windows')
  with:
    path: ~\AppData\Local\renv
    key: ${{ runner.os }}-renv-${{ hashFiles('**/renv.lock') }}
    restore-keys: |
      ${{ runner.os }}-renv-      

Ruby - Bundler

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

When dependencies are installed later in the workflow, we must specify the same path for the bundler.

- name: Bundle install
  run: |
    bundle config path vendor/bundle
    bundle install --jobs 4 --retry 3    

Rust - Cargo

Simple end product build

If Cargo.lock is checked into git, its hash can be used as a key to cache filesystem state suitable for the build. Use the --locked option with cargo build and test commands to ensure that the state cached at the post step corresponds to the contents of Cargo.lock that were hashed for the key.

- name: Cache cargo dependencies
  uses: actions/cache@v2
  with:
    path: |
      ~/.cargo/registry/index
      ~/.cargo/registry/cache
      ~/.cargo/git/db      
    key: cargo-deps-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
  uses: actions/cache@v2
  with:
    path: target
    key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}

A separate job to fetch and cache the dependencies

The files cached from $CARGO_HOME are platform-independent. If cargo build/test jobs are run on a matrix and Cargo.lock changes often, it might make sense to populate the cache with the matching state in one job, then reuse it in the matrix jobs.

This example also uses a separate cache to avoid expensive syncs with the crates.io-index repository.

jobs:
  update-deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - id: cargo-deps
        name: Cache cargo dependencies
        uses: actions/cache@v2
        with:
          path: |
            ~/.cargo/registry/index
            ~/.cargo/registry/cache
            ~/.cargo/git/db            
          key: cargo-deps-${{ hashFiles('**/Cargo.lock') }}

      - if: ${{ steps.cargo-deps.outputs.cache-hit != 'true' }}
        id: ls-crates-io-index
        name: Get head commit hash of crates.io registry index
        shell: bash
        run: |
          commit=$(
            git ls-remote --heads https://github.com/rust-lang/crates.io-index.git master |
            cut -f 1
          )
          echo "::set-output name=head::$commit"          
      - if: ${{ steps.cargo-deps.outputs.cache-hit != 'true' }}
        name: Cache cargo registry index
        uses: actions/cache@v2
        with:
          path: ~/.cargo/registry/index
          key: cargo-index-${{ steps.ls-crates-io-index.outputs.head }}
          restore-keys: cargo-index-

      - if: ${{ steps.cargo-deps.outputs.cache-hit != 'true' }}
        name: Fetch dependencies and update registry index
        run: cargo fetch --locked

  test:
    needs: update-deps
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2

      # https://github.com/actions/runner/issues/498
      - if: ${{ runner.os == 'Windows' }}
        name: Fix up Cargo.lock hash
        shell: powershell
        run: |
          Get-ChildItem . -Recurse -Filter Cargo.lock |
          Foreach-Object {
            ((Get-Content $_.FullName) -join "`n") + "`n" |
            Set-Content -NoNewline $_.FullName
          }          

      - name: Restore cargo dependencies
        uses: actions/cache@v2
        with:
          path: |
            ~/.cargo/registry/index
            ~/.cargo/registry/cache
            ~/.cargo/git/db            
          key: cargo-deps-${{ hashFiles('**/Cargo.lock') }}

      - name: Build and test
        uses: actions-rs/cargo@v1
        with:
          command: test
          args: --locked

Scala - SBT

- name: Cache SBT
  uses: actions/cache@v2
  with:
    path: | 
      ~/.ivy2/cache
      ~/.sbt
    key: ${{ runner.os }}-sbt-${{ hashFiles('**/build.sbt') }}

Swift, Objective-C - Carthage

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

Swift, Objective-C - CocoaPods

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

Swift - Swift Package Manager

- uses: actions/cache@v2
  with:
    path: .build
    key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
    restore-keys: |
      ${{ runner.os }}-spm-