SE-0547: SwiftPM Support for Compilation Caching

Hello, Swift community!

The review of SE-0547: SwiftPM Support for Compilation Caching begins now and runs through September 1st, 2026.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by email or DM. When contacting the review manager directly, please put "SE-0547" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?

  • Is the problem being addressed significant enough to warrant a change to Swift?

  • Does this proposal fit well with the feel and direction of Swift?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at:

swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub

Thank you,

Mishal
Review Manager

18 Likes

Please, yes, and a huge +1.

I've leveraged build systems with caching in different projects, and I think this would meaningfully improve Swift's iterative build capability with support for some caching mechanisms.

The specifics of the tiering are deeper than I've explored or used, but they sound reasonable and my primary desire is to see improved iterative build times for more extensive projects, which I think this could readily provide.

1 Like

Yes, please!

Is this the same caching that Xcode added in 26? Are the caches compatible? Can we configure SPM to use Xcode's cache?

3 Likes

Having followed the upstream CAS work a little bit its fantastic to see this packaged now.

Huge +1, this will be immensely helpful for us.

The overall knobs seems to be straightforward and I have no concrete feedback really.

1 Like

Good to see this being added, but for those considering using it, is there more detailed info somewhere on exactly how this new compilation caching works? That would help us decide whether to use it, and when we do, figure out if we're seeing bugs or expected behavior.

For example, the long-standing SwiftPM caching in the .build/ directory had some issues in the past. Simply adding the -v flag alone, with nothing else changed, would sometimes trigger additional work too. I'm unable to reproduce that issue with Swift 6.3.3 on a package I just tried it on in linux, so maybe it's since been fixed, but something to watch out for.

It would help to have a document somewhere on how this caching works and link it from this proposal, so that those interested can learn more.

2 Likes

It's the same compiler and low level build system infrastructure, yes. It's possible to point both tools at the same cache path, although getting cross-tool cache hits would require using the same toolchain and a build request that resulted in identical compile command lines.

https://youtu.be/E9GdNKjGZ7Y and https://www.youtube.com/watch?v=c629Cggdes4 from the LLVM dev meetings are a bit older but a good introduction to the mechanics of compiler caching in the context of Clang. Caching Swift compiles introduces some additional complications, but the fundamental approach is very similar. I plan on moving at least some of this info (at least what's relevant to a user debugging cache effectiveness) into the SwiftPM docs is the proposal is accepted. I've been holding off primarily so it doesn't need to be rewritten if names/CLI args/etc. change during the review.

7 Likes

I have spent a lot of time the past week playing around with the existing support for CAS in LLVM and Swift. In general, I am very supportive of build caching and think this is desperately needed in Swift PM; however, I do have a few concerns with the proposal.

In SwiftPM's user-facing interface, we consistently use the terminology "build cache" instead of "compilation cache". This is intended to future proof these options so that they could evolve naturally in the future to configure CAS-based caching of additional task types

This is my biggest concern. While I understand that we want to future proof the caching we are at the same time tying the entire feature to existing APIs defined by LLVM. I don't like that we are tying such an important feature in Swift PM to something that is outside of our control. What if we want to make changes to the gRPC protocol in the future for Swift specific changes? Do we expect that if Swift Build gains first class support for remote caching that we still enable LLVM's CAS plugin and remote caching service or do we expect to cache at the build system level?
I would like to see more content around how we expect this to evolve in the future. I am imagining that we would offer a standalone repository that contains the gRPC definitions of the Swift Build cache protocol.

On macOS, SwiftPM will default to using the CAS plugin from an Xcode toolchain if it is available, allowing the user to specify only a remote service path. A plugin is not required to take advantage of compilation caching. If none is available or provided, the LLVM on-disk CAS implementation will be used.

When playing around with the caching interfaces and building my own remote cache I found the LLVM plugin experience very frustrating. I don't think expecting users to implement the LLVM plugin themselves is a great developer experience. Nor is a different experience on macOS vs other platforms supported by Swift. Can we ship an LLVM plugin on all platforms and expect developers to only provide an implementation of the gRPC remote caching protocol instead?

optionally a unix domain socket path for a gRPC remote caching service

Is there any reason why the gRPC service can only be specified as a unix domain socket and not as a remote service?

3 Likes

I do not think this is a major concern in practice. The set of API provided by a CAS plugin or service is minimal, for the most part it's just get and set primitives for the CAS and action cache. Higher level tools then compose these to implement caching. We technically already have 3 caching implementations built on this set of primitives and all using slightly different CAS data structures for cache keys:

I think these demonstrate that the Core API as it exists today can be extended to support a wide variety of use cases without any domain specific extensions.

I think this is desirable in the long term, but I don't think it should block incremental progress in this area.

This maximizes compatibility with existing plugins like the one shipped with Xcode. The actual options passed to the plugin support freeform configuration options internally and we could choose to expose a more flexible interface if we think it'd be useful.

1 Like

I don't doubt that the Core API can support this but I still feel that the Swift Package Manager should own that Core API. If that happens to align with the APIs that lower level pieces use for caching as well that's all good. In particular, I would expect that the Swift project publishes the gRPC definition file for build caching in a repo to make it easy for others to implement remote caching services similar to how Bazel publishes its remote APIs. In the future, if our build system gains support for remote logging or remote execution we can add those APIs to the same repository.

I understand the compatibility argument but I don't think a closed source IDE should influence evolution decisions of Swift PM here. If we were to start from a clean slate, I personally would expect a different evolution where Swift PM would define the remote caching protocol itself. Under the hood Swift PM would itself then implement and provide a plugin for Clang and Swift but it would never expose options to developers to change the plugin. Just options to either use local disk caching or to configure where the gRPC service is. I don't see a reason why a developer would swap in a custom plugin if we provide both those options. Similar to how Bazel doesn't allow you to swap in a plugin how caching works but they expect users to configure where the remote caching services runs.

This shouldn't prohibit advanced users to pass the appropriate Clang and Swift flags through when they want to override the plugin but if they want to use Swift PMs build caching they get an out-of-box experience with a plugin provided.

3 Likes

It is very important that we don't create competing standards here. Introducing incompatible CAS interfaces prevents us from presenting a unified UX for cache management to developers. By standardizing on core LLVM infrastructure, we can leverage a single CAS with a single on-disk location, size limit, and easy-to-understand eviction policy. Introducing alternative interfaces at the build system layer means asking the user to manage independent caches across different components with different eviction criteria, significantly complicating the user-facing UX of features like size limiting. For example, developers would need to specify which percentage of their total size limit to allocate to which build tool.

There's also extensive precedent for deferring to LLVM's specification of core technologies. We don't attempt to invent our own specs for things like code coverage, serialized diagnostics, header maps, etc. at the build system layer, and rightfully so. The CAS spec is no different. That doesn't preclude us from thoroughly documenting it and building tools on top of it to make it more accessible, but we should not attempt to take ownership of it or duplicate it.

1 Like

I agree that we want to offer a single CAS that can be used by all tools across our stack. The LLVM plugin defines a single interface that can be implemented however we want. There is nothing that forces us to use the remote caching gRPC defined in LLVM from what I understand. We could in theory implement a different LLVM plugin that speaks a different protocol, right? Moreover, we can always bridge the LLVM definition to our remote service definition allowing us to use a single CAS.

I think it's really important that we as the Swift project own the definition of the remote caching interface for our build system. Similar, to how we should own the definition of the any other remote service like remote execution or remote telemetry. We expect to create an ecosystem around these services and I could see a future where there are actual service implementations offered that people can deploy themselves e.g. a remote caching service that talks to an S3 compatible blob storage.

I think we just have a philosophical disagreement. IMO needlessly duplicating effort in this area will lead to a more fragmented and insular ecosystem as opposed to one which is open and interoperable with projects outside of SwiftPM. I don't see a concrete benefit to reinventing the wheel here.

1 Like

In general a massive +1 to supporting compilation caching.

I'm 50/50 on whether the CAS specification is Swift-specific or just the LLVM one, I don't have any particular feelings either way, on the assumption that we're not really expecting many people to develop a plugin and it's not aimed at end developers.

One thing I would like to see clarification around is:

On macOS, SwiftPM will default to using the CAS plugin from an Xcode toolchain if it is available, allowing the user to specify only a remote service path

What happens on Linux if you only specify a remote service path? This should have the same behaviour on all build platforms and we should be shipping a plugin with the toolchain rather than relying on one from Xcode the diverges behaviour across platforms

1 Like

A plugin implementation is required for remote caching, so currently on Linux a user is required to bring their own. I do think that Swift should at some point ship a cross platform plugin as part of the standard distribution, but the design and implementation is outside the scope of this initial proposal and I do not think it should block incremental adoption given the value of local caching alone from a perf perspective.

1 Like

Let me re-frame my questions a bit differently.

If we were shipping a LLVM plugin that supports on disk caching and speaks the LLVM gRPC remote caching protocol for all platforms as part of the Swift toolchain. Would we expect user's to still provide their own plugins? If we do, do we expect this to be the normal case or an advanced case?

How high is our confidence that the gRPC remote caching protocol from LLVM will generalize across all caching use-cases? How does it compare to other caching protocols like the one from Bazel?

1 Like

No, we wouldn't expect users to provide a plugin, the option would be there for an advanced case.

The gRPC protocol is just a simple adapter for connecting to remote storage for downloading and uploading blobs, it's not intended to be a standard. It's intended to connect to any blob storage provider.

Right. So if that's the case then I personally think we should wait to pitch the remote caching feature once we have a cross-platform plugin available. Then we only need the gRPC protocol that this plugin talks and users can configure any service that speaks that protocol. The advanced users can still directly pass the clang/swift flags to pass their own plugin.

Right and since the gRPC protocol is plugin deepened and we want to ship a Swift remote caching plugin. I think the Swift project should own that definition even if initially it is verbatim copied from what LLVM defines.