Caching SwiftPM for iOS app build on CI env

I know SwiftPM folks such as @Aciid and @ddunbar are working towards deterministic builds & caching in the future. The llbuild2 project is one such effort.

In the meantime, I'm currently implementing a basic caching strategy based on a checksum of the Swift source file contents. The basic idea is to introduce a shell script that wraps swift run and explicitly passes --skip-build if the checksum has not changed.

#!/bin/sh
#
# bin/run: Wraps swift-run and provides caching.
#
# Usage:
#
#   bin/run <executable-product> [<args>...] # wraps 'swift run' to run an executable product
#   bin/run --build                          # run 'swift build' if the cache is invalid
#   bin/run --clean                          # run 'swift package clean' and invalidate the cache
#   bin/run --resolve                        # run 'swift package resolve'
#   bin/run --update [<dependency>]          # run 'swift package update', optionally for a specific dependency
#   bin/run --shasum                         # print out the current cache key

set -e

cache_key() {
  cat Package.resolved {Sources,.build/checkouts}/**/*.swift \
    | shasum \
    | cut -f1 -d' '
}

cache_valid() {
  [ "$IGNORE_CACHE" != 1 ] \
    && [ -f "$SWIFTPM_CACHE_KEY_PATH" ] \
    && [ "$(cat "$SWIFTPM_CACHE_KEY_PATH")" = "$(cache_key)" ]
}

write_cache_key() {
  if [ -n "$SWIFTPM_CACHE_KEY_PATH" ]; then
    mkdir -p "$(dirname "$SWIFTPM_CACHE_KEY_PATH")"
    cache_key > "$SWIFTPM_CACHE_KEY_PATH"
  fi
}

# wrap the system swift binary to set some defaults
swift() {
  env SDKROOT=macosx swift "$@"
}

build_if_necessary() {
  if ! cache_valid; then
    swift build
    write_cache_key
  fi
}

# set up a default value for SWIFTPM_CACHE_KEY_PATH which stores
# the cache key inside .build
if [ -z ${SWIFTPM_CACHE_KEY_PATH+x} ]; then
  export SWIFTPM_CACHE_KEY_PATH=.build/contents.sha
fi

if [ $# = 0 ]; then
  echo >&2 "bin/run: Specify a subcommand to run."
  exit 1
fi

case "$1" in
  --build)
    build_if_necessary
    ;;
  --clean)
    swift package clean
    ;;
  --resolve)
    swift package resolve
    ;;
  --update)
    shift
    swift package update "$@"
    ;;
  --shasum)
    cache_key
    ;;
  *)
    build_if_necessary
    swift run --skip-build "$@"
    ;;
esac
3 Likes