In Fuchsia, we are integrating Rust into a larger (ninja-based) build system by having that system invoke cargo when necessary. There are other ways to do it; for example, I believe Bazel addresses the problem by invoking rustc directly and not relying on cargo.
A major challenge is defining “when necessary.” We could invoke cargo every time, but that cargo null builds are not exactly lightning fast, and slowing down the null build for the larger project would be bad. Especially if the number of Rust binaries were to increase, it could easily go into the unacceptable range.
One time-tested way to solve this issue is a depfile. Basically, when writing an artifact (say, binary), it also writes a file (traditionally with a .d
suffix) that has the path to the artifact, a colon, and a list of source files that were used as inputs. The build system only runs the build rule when any of these files have been changed since the last build. Note that rustc has long supported --emit dep-info
, which works in exactly this way, and this is how cargo knows what it needs to rebuild.
Ninja has explicit support for depfiles (linked above), and certainly GN lets individual build rules use this mechanism (doc). Without having done a lot of research, I believe it’s a fairly general mechanism and likely to be supported broadly. We also use this mechanism to integrate with Go’s build tool, using a purpose-built tool called godepfile.
I’ve written a prototype, using cargo as a library, that aggregates the depfiles internally produced as part of a cargo build (these are stored in the .fingerprint
directory as dep-*
files). However, none of this is part of cargo’s public interface, and indeed, the tool that I developed for nightly doesn’t run on stable because various details have changed. The prototype has some other limitations (like not incorporating build script inputs), but those can be fixed.
My first preference would be for such a mechanism to be included into cargo, so the depfile could be produced at the same time as the actual build artifact. This would prevent the dependency analysis from getting out of sync from the actual artifact. However, adding a feature to cargo is an additional burden.
I’m definitely willing to patch cargo, based on my initial prototype. But I’d like to gauge community interest first. It seems much more worth doing if there are other build system integrations that will benefit. The first cut at the patch will have some limitations, so maybe it should be considered an experimental feature.
One design question is the scope of how far the dependencies reach - should they extend into dependent libraries at all? The decision I’ve made so far is that they cover files in the crate, but not external crates. For crates.io and git dependencies, we would count Cargo.lock
as an input to the build rule. For libraries specified using path dependencies, those would need to be added explicitly in the parent build language. This seems to me a good compromise between simplicity and ergonomics, but there are other ways to think about it.
Comments and suggestions welcome.