Build tool cache Beta
| Build tool caching is in beta on CircleCI Cloud. The feature is still evolving, so you may encounter bugs or changes to behavior. There is no additional cost to use it beyond standard Cache Storage Usage. |
Use the with_tool_cache step to back a build tool’s own remote cache with a CircleCI-managed cache backend. For example, this can back Gradle’s build cache, Bazel’s remote cache, or Xcode’s Swift/Clang compilation cache. This removes the need to hand-roll a save_cache/restore_cache pair for tool-specific build outputs, and works the same way across executors.
How it works
with_tool_cache wraps the steps you give it and automatically sets up and tears down a remote cache for the named tool:
-
Starts a local cache server in the background.
-
Configures
toolto point at that cache server as its remote cache. -
Runs your wrapped steps.
-
Stops the cache server, regardless of whether your steps passed or failed.
You do not need to install or configure anything else. The cache server and per-tool configuration are already available on CircleCI’s execution environments.
How this differs from save_cache/restore_cache
The save_cache/restore_cache steps take an eager, bulk approach: each write saves an entire directory as a single immutable blob under a key. If a single file in that directory changes, the next run saves the whole directory again under a new key. Successive caches end up nearly identical to each other.
with_tool_cache takes a lazy, granular approach instead. The build tool’s remote cache stores and fetches individual entries as needed. A small change adds only a small amount of new data instead of duplicating the entire cache.
This distinction matters more as a cache grows:
-
With a small cache, bulk caching can perform just as well, since there is only one archive to transfer.
-
As a cache grows to hold more entries, granular caching pulls ahead. Bulk caching keeps writing new copies of the same large cache that are nearly identical to the previous one.
Two practical benefits follow from this:
-
Reduced storage use: A small change adds a small new entry to the remote cache instead of duplicating the whole cache under a new key. Bulk caching, by contrast, tends to accumulate multiple large caches that are nearly identical to each other and use storage until they expire.
-
Less configuration:
with_tool_cacherequires no cache key design, checksum templates, or key rotation. You do not choose what to cache, build a key, or bump a version prefix to force a clean cache.
In internal benchmarks, a clean build using with_tool_cache completed about 90% faster than the same build with no cache at all. Actual results depend on the size of your build’s cache and how often it changes.
|
In a separate benchmark on a large, real-world iOS codebase (WordPress-iOS), with_tool_cache with tool: xcode cut median clean-build time by about 29% versus a build with no cache. Xcode’s compilation cache is object-level (compiler outputs keyed on source content), not task-level like Gradle’s or Bazel’s. The speedup is smaller as a result, but still meaningful for large native codebases.
|
Supported tools
with_tool_cache currently supports the following values for tool:
| Tool | What gets cached |
|---|---|
|
Gradle’s remote build cache (task outputs). You still manage the module/dependency cache separately with |
|
Bazel’s remote cache. |
|
Turborepo’s remote cache. |
|
Xcode’s Swift/Clang compilation cache (object-level compiler outputs), enabled by pointing |
Quickstart
The following examples show how to use the with_tool_cache step for each supported tool.
The with_tool_cache works on all executors. Put checkout before the with_tool_cache step, not inside it.
|
-
Gradle
-
Bazel
-
Turborepo
-
Xcode
jobs:
build:
docker:
- image: cimg/base:current # use any image with Gradle installed
steps:
- checkout
- with_tool_cache:
tool: gradle
steps:
- run: ./gradlew build
jobs:
build:
docker:
- image: cimg/base:current # use any image with Bazel installed
steps:
- checkout
- with_tool_cache:
tool: bazel
steps:
- run: bazel build //...
jobs:
build:
docker:
- image: cimg/base:current # use any image with Turborepo installed
steps:
- checkout
- with_tool_cache:
tool: turborepo
steps:
- run: turbo run build
with_tool_cache starts a compilation cache server and exposes its address to your steps as COMPILATION_CACHE_REMOTE_SERVICE_PATH (a unix:// socket URL). Pass the socket path, stripped of its unix:// prefix, to xcodebuild along with the compilation cache build settings:
jobs:
build:
macos:
xcode: 26.6.0
steps:
- checkout
- with_tool_cache:
tool: xcode
steps:
- run:
name: xcodebuild build
command: |
sock="${COMPILATION_CACHE_REMOTE_SERVICE_PATH#unix://}"
xcodebuild build \
-workspace MyApp.xcworkspace \
-scheme "MyApp" \
-destination 'generic/platform=iOS Simulator' \
COMPILATION_CACHE_REMOTE_SERVICE_PATH="$sock" \
COMPILATION_CACHE_ENABLE_PLUGIN=YES \
SWIFT_ENABLE_COMPILE_CACHE=YES \
CLANG_ENABLE_COMPILE_CACHE=YES \
SWIFT_ENABLE_EXPLICIT_MODULES=YES \
SWIFT_USE_INTEGRATED_DRIVER=YES \
CLANG_ENABLE_MODULES=YES \
COMPILATION_CACHE_ENABLE_INTEGRATED_QUERIES=YES \
COMPILATION_CACHE_ENABLE_DETACHED_KEY_QUERIES=YES
Compilation caching requires Xcode’s Swift/Clang compile cache support. Check that your Xcode version and toolchain support the SWIFT_ENABLE_COMPILE_CACHE and CLANG_ENABLE_COMPILE_CACHE build settings before adopting this in CI.
|
Use with dependency caching
The with_tool_cache step only manages the build tool’s own remote/task-output cache. It does not replace dependency caching. For example, with Gradle, with_tool_cache handles the Gradle build cache, but a separate save_cache/restore_cache pair still caches ~/.gradle/caches/modules-2 (the downloaded dependencies). The two mechanisms are complementary: one caches what Gradle downloads, the other caches what Gradle builds.
The same applies to Xcode: with_tool_cache with tool: xcode handles the compilation cache, but you still need a separate save_cache/restore_cache pair (or equivalent) for CocoaPods' or Swift Package Manager’s downloaded dependencies.