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