Strategies to reduce compile times for macros in Packages

Context

It has become a well-known pain point that practically any Swift project that uses Swift macros will compile extremely slowly. This is because in order to develop a macro, your Package must depend on SwiftSyntax which is massive and takes a long time to build. In my projects, this makes SwiftUI previews completely unusable, because the preview gives up before the compiler can finish compiling.

I believe that ultimately this is a language level problem and thankfully some are already working on improving the situation! :raised_hands:t3:

Here, though, I would like to discuss What are strategies/best practices that we could use to mitigate this problem while we wait for a better, native solution?

It seems to me that this is really only a major issue when SwiftSyntax needs to be recompiled. It's understandable, and even expected for a first build to take a long time, but it becomes particularly problematic when small, incremental builds take a long time. So my theory is, anything that we can do to avoid recompiling SwiftSyntax, will result in much better build times.

4 Likes

So here is a strategy that I would like to propose: using @_exported import rather than import.

Scenario

Suppose you have the following packages:
A -- depends on --> B -- depends on --> C
Package A distributes a macro (and therefore it must depend on SwiftSyntax).
Package B imports that macro A.
Package C imports B and macro A.

Question

If we use @_exported import rather than import could this avoid recompiling SwiftSyntax in at least some scenarios? See here.

For example, what if Package A used @_exported import SwiftSyntax? Perhaps that would mean that SwiftSyntax only needs to be recompiled when a change is made to A?

In that scenario if a change is made to B, then B needs to be recompiled. But it simply pulls A from the cache (instead of recompiling it) so it doesn't need to recompile SwiftSyntax.

Another scenario

Same dependency tree: A -- depends on --> B -- depends on --> C
What if B and C uses @_exported import AMacros rather than import AMacros?
Then if C needs to be recompiled, we are explictily telling SPM, that C can get AMacros from B rather than having to recompile A.

In Summary

Unfortunately, I don't know enough about SPM to know about how it caches packages, and determines when a package needs to be recompiled. But perhaps, @_exported import could avoid some recompiles.

1 Like