Skip to content

Instantly share code, notes, and snippets.

@est31
Created August 30, 2019 16:34
Show Gist options
  • Save est31/3d9e880be746c3a443c699d9ff1888d2 to your computer and use it in GitHub Desktop.
Save est31/3d9e880be746c3a443c699d9ff1888d2 to your computer and use it in GitHub Desktop.

Announcing cargo-udeps

One of the biggest issues that most people have with Rust are the long compile times. One of the reasons why compile times are so long is because many projects use quite a few dependencies from crates.io. Your dependencies have dependencies of their own, and they in turn have dependencies as well, and so on. This results in really big graphs of crates that all have to be compiled by cargo. Sometimes however, a crate actually doesn't use anything of some of its dependencies. Then those dependencies can be removed, resulting in faster builds for that crate. But how do you detect them? Often they sit in Cargo.toml for a long time until someone discovers they are actually unused and removes them (example). This is where cargo-udeps comes in. cargo-udeps is an automated tool to find dependencies that were specified in Cargo.toml but never used in the crate itself.

The name is a short form of "unused dependencies" but it is also a name play on the common ascii-ification of the greek µ letter, standing for "micro-deps". Helping to mitigate the dependency bloat on crates.io is one of the goals of cargo-udeps.

I was inspired to do this project after I wanted to check which dependencies of nushell were unused. I ended up ripgrepping for dependency names one by one and removing them if they didn't appear anywhere in src and then checking whether this caused the build to fail. I then made a PR with the results. Then @eijebong suggested that I should create an automated tool for this.

I pondered over different approaches and for the MVP I ended up with a cargo plugin that reads save-analysis info (that was originally introduced for rls). The solution isn't perfect as it relies on an undocumented feature of rustc's save-analysis, but it actually runs the compiler so it is good at detecting actual usages. Alternative approaches that I want to explore later, like parsing of the crate's source code using syn, have this as a weakness.

After I finished the MVP I ran it on nushell again and I actually found two more crates that could be removed/disabled.

This 0.1.0 MVP release is meant to gauge interest in the ecosystem in such a tool. If there is demand, I want to invest more time into it and explore some of the more time-consuming approaches like using a syn parser. This would give faster performance, avoid having to compile the dependencies of the crate, and work on stable Rust instead of just nightly.

To install cargo-udeps, do cargo install cargo-udeps. Then you can run it on your project by doing cargo +nightly udeps.

@lesterli
Copy link

I run cargo +nightly udeps and get the following error:

Running /abc/target/debug/build/abc-runtime-a1002fbc69f0d1e8/build-script-build
error: failed to run custom build command for abc-runtime v2.0.0 (/abc/runtime)

Caused by:
process didn't exit successfully: /abc/target/debug/build/abc-runtime-a1002fbc69f0d1e8/build-script-build (exit code: 1)
--- stderr
error: 'run' isn't a valid value for ''
[possible values: udeps]

USAGE:
cargo-udeps --manifest-path --message-format

For more information try --help

warning: build failed, waiting for other jobs to finish...
Error: StrErr("build failed")

@est31
Copy link
Author

est31 commented Aug 31, 2019

@lesterli is abc-runtime your project? It's interesting. My guess would be that the build script reads the CARGO env var and then executes it at that path. I think you'll get the same problem when running some other cargo plugin like cargo-tree. Not sure. Can you check the other plugins? If the other plugins are fine, it's a bug in cargo-udeps, for that case I suggest filing one on github: https://github.com/est31/cargo-udeps . If they error as well, the build script might need adjustments.

@the8472
Copy link

the8472 commented Sep 4, 2019

Wouldn't it be even better if cargo pulled dependencies lazily when rustc has to resolve a symbol from an external crate? If the crate is unused it doesn't have to fetch or compile it.

@est31
Copy link
Author

est31 commented Sep 4, 2019

@the8472 I've considered this idea and am on the fence with it. On one hand, it seems like the right thing to do. rustc is already quite lazy in its loading so it only loads external crates that it cares about (an earlier version of cargo-udeps even tried to exploit that but it ran into problems). You'd only have to add a callback to rustc to ask cargo to please compile that crate. Ideally then only the used crates would be compiled and cargo would automatically get information it can use to complain about unused crates. However, in large crate graphs, this might cause tons of memory usage as there could be long chains of rustc processes waiting on each other. There are further disadvantages like the compilation of the crate starting later so possibly there is less wall clock time spent in the build. idk whether it's worth it.

@the8472
Copy link

the8472 commented Sep 5, 2019

I see. And this is a tangent, but what if such a callback told cargo exactly which symbols it would need from the other crate? then it wouldn't have to build unusued modules and such, which might make up for the delay of starting later. cargo check is already relatively fast, so it should be able to gather the necessary metadata for partial dependency compilation. A whole crate not being compiled would just be the limit case of that.

@faern
Copy link

faern commented Sep 6, 2019

This is a really needed tool! Awesome. Should definitely be the default in cargo to perform this analysis. I already found one unused dependency over here :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment