I'm interested in finding a way to warn, or lint, about dependency cycles in Swift code. I'm talking about symbol dependencies, like two types in different files of a module that use one another, not package dependencies.
The hypothesis is that eliminating or reducing these cycles could improve incremental build time, and therefore that identifying cycles would be good for developers who want to reduce them.
This idea arrived when I read an article about the design of Go, which is pretty uncompromising about compile time. Here's an excerpt. Note they are talking about dependencies between modules, not files or symbols, but the principle may hold water anyway. Emphasis mine:
Another feature of the Go dependency graph is that it has no cycles. The language defines that there can be no circular imports in the graph, and the compiler and linker both check that they do not exist. Although they are occasionally useful, circular imports introduce significant problems at scale. They require the compiler to deal with larger sets of source files all at once, which slows down incremental builds. More important, when allowed, in our experience such imports end up entangling huge swaths of the source tree into large subpieces that are difficult to manage independently, bloating binaries and complicating initialization, testing, refactoring, releasing, and other tasks of software development.
It does make sense that when dependencies are acyclic, a code change would incur fewer dependent rebuilds than otherwise, and that the area surrounding a cycle can form a group of nodes that must all rebuild when one does. On the larger Swift projects I've built, I went to the trouble of splitting the app into several frameworks with the goal of reducing build work by straightening out the graph into more of a DAG, even if only at module boundaries. So it would be worth it to me to pursue that benefit, if cycles were identified.
I understand that .swiftdeps files exist, and what's in them, and that the format changes. I feel like I could at least do an experiment with the most recent text .swiftdeps format to see if locating and removing cycles improves build time to a degree that matters.
I have questions about this:
- Is this worthwhile or misguided? Why?
- Is parsing .swiftdeps the best way to go about this experiment, or does something more convenient exist? Would it be better to find a way to run this experiment against the latest .swiftdeps format?
- If experiments pan out, what's the right way to implement a cycle warning for real such that it continues to work given changes to the .swiftdeps format? Maybe, contributing an output option to
swiftc
? Or is it the kind of thing you could just do in a script build step? Would SwiftLint be the right home for this?