Giter VIP home page Giter VIP logo

cargo-machete's Introduction

cargo-machete

Remove unused Rust dependencies with this one weird trick!

build status matrix chat supported rustc stable dependency status

Thanks to Diego F. Goberna for the lovely logo 🥰

Introduction

cargo-machete is a Cargo tool that detects unused dependencies in Rust projects, in a fast (yet imprecise) way.

See also the blog post for a detailed writeup.

Installation

Install cargo-machete with cargo:

cargo install cargo-machete

Usage

Run cargo-machete in a directory that contains one or more Rust projects (using Cargo for dependency management):

cd my-directory && cargo machete

# alternatively

cargo machete /absolute/path/to/my/directory

The return code gives an indication whether unused dependencies have been found:

  • 0 if machete found no unused dependencies,
  • 1 if it found at least one unused dependency,
  • 2 if there was an error during processing (in which case there's no indication whether any unused dependency was found or not).

This can be used in CI situations.

False positives

To ignore a certain set of dependencies in a crate, add package.metadata.cargo-machete to Cargo.toml (or workspace.metadata.cargo-machete to a workspace Cargo.toml), and specify an ignored array:

For example:

[dependencies]
prost = "0.10" # Used in code generated by build.rs output, which cargo-machete cannot check

# in an individual package Cargo.toml
[package.metadata.cargo-machete]
ignored = ["prost"]

# in a workspace Cargo.toml
[workspace.metadata.cargo-machete]
ignored = ["prost"]

If there are too many false positives, consider using the --with-metadata CLI flag, which will call cargo metadata --all-features to find final dependency names, more accurate dependencies per build type, etc. ⚠ This may modify the Cargo.lock files in your projects.

Docker Image

A docker image for cargo machete.

For instance, run cargo-machete in the $(pwd) directory using:

docker run -v $(pwd):/src ghcr.io/bnjbvr/cargo-machete:latest

Cargo Machete Action

A github action for cargo machete.

Example usage

The step given by,

      - uses: bnjbvr/cargo-machete@main

can be added to any workflow.

An example workflow is shown below:

name: Cargo Machete
on:
  pull_request: { branches: "*" }

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Machete
        uses: bnjbvr/cargo-machete@main

Contributing

Contributor Covenant

We welcome community contributions to this project.

License

MIT license.

cargo-machete's People

Contributors

0xdeafbeef avatar bnjbvr avatar bromeon avatar daniel5151 avatar dcjanus avatar dependabot[bot] avatar fu5ha avatar istellino-chub avatar lorenzschueler avatar mickvangelderen avatar mjpieters avatar nanocryk avatar poliorcetics avatar realkc avatar spenserblack avatar tgnottingham avatar tottoto avatar vsuryamurthy avatar xxchan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cargo-machete's Issues

False positives when indirect dependency is used for enabling features

For example, I have a repo that depends on parry3d-f64, which does not expose the glam conversion feature. But I am able to enable this feature by adding the nalgebra dependency like this:

nalgebra = { version = "0.32", features = ["convert-glam022"] }
parry3d-f64 = { version = "0.13" }

cargo machete will return a false positive for nalgebra because I am not useing it anywhere, but it is nonetheless still a necessary dependency.

False positives for multi-dep single `use` statements

Not sure if this is a known drawback of this crate, but my project has a rustfmt.toml that collapses all imports into one use/pub use {} block. This seems to cause all the crates to be returned as false positives.

pub use {async_trait, futures, reqwest};
cargo-machete found the following unused dependencies in /var/home/...
my_crate -- /var/home/ ... /Cargo.toml:
        async_trait
        futures
        reqwest

How to exclude crates not part of the root crate from analysis

I have a project that includes a git submodule for .proto files.
Inside of the submodule there is an example rust crate that has nothing to do with the main repository I am running cargo machete on.

How can I exclude this unrelated crate from the cargo machete analysis?

I can explicitly pass the paths to the projects I want to include, but I just don't like having to do so.

I see multiple possible implementations:

  1. only analyze crates that are members of the workspace if it exists
  2. allow adding paths or directories to ignore in the file system discovery

dev-dependencies

This tool doesnt appear to remove unneeded dev-dependencies.
Is that feasible to perform, perhaps opt-in?

Be more precise with respect to build/bench/example dependencies

Right now all the build/test/bench/example/"normal" dependencies are bundled into one set of dependencies that are searched for the src/ directory. We ought to be more precise than that, at least for build, bench and example dependencies (tests can be included in the src/ directory).

Add ignore list in workspace Cargo.toml

It would be great to have an equivalent to [package.metadata.cargo-machete] inside a workspace main Cargo.toml to ignore false positive dependencies present in lots of packages of a same workspace.

Fail if `Cargo.toml` annotations don't match what we expect

It's easy to write ignore = true instead of ignored = true in the machete metadata section in a Cargo.toml file. Right now it'll fail the analysis of that specific package, but not overall; it might be better to make this a hard error instead, that fails the whole analysis (after displaying the analysis results for other crates).

`--fix` is not format-preserving

I'd recommend re-loading the manifest using toml_edit and editing the manfiest in there. Soon we'll have cargo rm in cargo (its the next in cargo-edit to be merged) and you could shell out to that instead, if you want (trade offs of course)

Hall Of Fame :sparkles:

If cargo-machete has been useful for you and has helped removing dependencies in your project, feel free to add a comment to this issue :)

Support for skipping sub-directories

Thanks for this tool, it's awesome! I'd really like to be able to skip subdirectories in a project I have.

The current setup is that there is a tests directory and many of the tests are their own Cargo project, with a unique Cargo.toml that has unused deps. I don't want to edit the tests to ensure they have only used dependencies.

From what I can understand skipping specified subdirectories isn't currently supported. Is this feature something you would accept a contribution for?

0.6.0 tagged but not released to crates.io

I saw that version 0.6.0 has been prepared in a commit, and there is a corresponding tag on the repository. In case this version is considered complete and final, could we please see a release on crates.io? The latest version I see there is 0.5.0.

Remove unused deps from workspace Cargo.toml

Currently, cargo-machete doesn't check if the workspace Cargo.toml contains any unused deps or not. I think it should be straightforward as you only need to deal with Cargo.toml files, although things never are.

Renaming in compound use statements cause false positives

// use rustc_rayon_core as rayon_core; // Correctly marked used
use { rustc_rayon_core as rayon_core }; // Incorrectly marked unused

pub fn notify(_: &rayon_core::Registry) {
}

fn main() {
    println!("Hello, benjy bworld!");
}

Also: multiline compound use statements would be an issue as well.

Thanks @lqd for the report!

Picks up dependencies from other crates

Note: this is purely from code inspection

It looks like if I ran this in a workspace root where a crate is in the root, it will search all other crates within the workspace

You can use cargo package --list to see what will be put on crates.io. The downside is people might exclude files related to dev-dependencies, like tests.

False positive with humantime_serde

Hi, folk. Thanks for this crate!
I got false positive error while using humantime_serde crate

pub struct Settings {
    #[serde(with = "humantime_serde")]
    some_duration: Duration,
}

output:

my_crate -- /builds/..my_crate/Cargo.toml:
	humantime-serde

Is it possible to fix that?

Wrong arguments are passed to machete when the process is launched from within rust

When the machete process is launched from within another rust script, like it does in our ci tool, it seems to receive the wrong arguments. I made this little script that reproduces it.

use std::process::Command;
use std::process::Stdio;
use std::env;

type Besult = Result<(), String>;


fn run_cmd(program: &str, args: &[&str]) -> Besult {
    run_cmd_with_env(program, args, false, std::iter::empty()).map(|_| ())
}

fn run_cmd_with_env<'env>(
    program: &str,
    args: &[&str],
    capture_stdout: bool,
    env: impl Iterator<Item = (&'env str, &'env str)>,
) -> Result<Option<String>, String> {
    let mut cmd = Command::new(program);
    cmd.envs(env);
    cmd.args(args);

    cmd.stderr(Stdio::inherit());
    if capture_stdout {
        cmd.stdout(Stdio::piped());
    }

    let child = cmd.spawn().map_err(|e| format!("failed to spawn: {e}"))?;
    let output = child
        .wait_with_output()
        .map_err(|e| format!("failed to run: {e}"))?;

    if output.status.success() {
        if capture_stdout {
            Ok(Some(String::from_utf8_lossy(&output.stdout).to_string()))
        } else {
            Ok(None)
        }
    } else {
        Err(format!(
            "running {program} returned with status {}",
            output
                .status
                .code()
                .map_or_else(|| "unknown".into(), |x| x.to_string())
        ))
    }
}

fn cargo(args: &[&str]) -> Besult {
    run_cmd("cargo", args)
}


// Run as `cargo run -- machete` to see broken behavior, just do cargo run to see working.
fn main() {
    let args: Vec<String> = env::args().collect();
    let _ = if args.len() > 1 && args[1] == "cargo" {
        cargo(&["machete"])
    } else if args.len() > 1 && args[1] == "standalone" {
        run_cmd("cargo-machete", &[])
    } else {
        println!("Wrong argument, use standalone or cargo");
        Err("Wrong argument".to_string())
    };
}

When running I get

martin@puff:~/Code/broken-machete$ cargo run -- standalone
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/broken-machete standalone`
Analyzing dependencies of crates in this directory...
cargo-machete didn't find any unused dependencies in /home/martin/Code/broken-machete. Good job!
Done!
martin@puff:~/Code/broken-machete$ cargo run -- cargo
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/broken-machete cargo`
Analyzing dependencies of crates in machete...
error when walking over subdirectories: IO error for operation on machete: No such file or directory (os error 2)
cargo-machete didn't find any unused dependencies in machete. Good job!
Done!

Provide pre-compiled binaries

It would be awesome if cargo-machete could provide pre-compiled binaries making it much faster to download from CI.
See typos CLI that uses github Release to upload pre-compiled binaries of their typos CLI.

Doesn't work with workspace inheritance of package.edition

When running cargo-machete with workspace inheritance the CLI outputs errors. This happens when the package.edition key is used in the workspace-level manifest and inherited in the package-level manifest. Inheriting other keys doesn't seem to break cargo machete.

Reproduction

You may use the reproduction git repository:

git clone [email protected]:Veetaha/cargo-machete-workspace-inheritance-bug-repro.git
cd cargo-machete-workspace-inheritance-bug-repro
cargo machete
cargo machete --with-metadata

You'll see that both cargo machete invocations fail

~/dev/cargo-machete-workspace-inheritance-bug-repro (master) $ cargo machete 
Analyzing dependencies of crates in this directory...
error when handling /home/veetaha/dev/cargo-machete-workspace-inheritance-bug-repro/foo/Cargo.toml: value from workspace hasn't been set
cargo-machete didn't find any unused dependencies in /home/veetaha/dev/cargo-machete-workspace-inheritance-bug-repro. Good job!
Done!
~/dev/cargo-machete-workspace-inheritance-bug-repro (master) $ cargo machete --with-metadata
Analyzing dependencies of crates in this directory...
error when handling /home/veetaha/dev/cargo-machete-workspace-inheritance-bug-repro/foo/Cargo.toml: value from workspace hasn't been set
cargo-machete didn't find any unused dependencies in /home/veetaha/dev/cargo-machete-workspace-inheritance-bug-repro. Good job!
Done!

Or you can create the cargo workspace manually the following way.

Workspace-level Cargo.toml manifest:

workspace.members = ["foo"]
workspace.package.edition = "2021"

Package-level Cargo.toml manifest:

[package]
name = "foo"
version = "0.1.0"
edition = { workspace = true }

Meta

CLI version is 0.4.0. Suprisingly, there is no --version command in the CLI.

cargo-machette is both a cargo subcommand a direct command except when it isn't

    // ...and the "machete" command if ran as cargo subcommand.
    if i == 1 && arg == "machete" {
        continue;
    }

Since your command accepts positional arguments, this will not allow me to run cargo-machette machette where machette is a directory but I'd have to know to run cargo-machette machette machette. Personally, I find it dubious to have a command be dual natured (run in either cargo or out) especially when accepting positional arguments.

Create a docker image?

It'd be really useful to have an official docker image along with your releases, that we could use in our CI pipeline.

False positive

I had cargo-machete spit out a dep as removable that actually isn't.

Check out this repo and run cargo-machete:

❯ cargo machete                               
Analyzing dependencies of crates in this directory...
cargo-machete found the following unused dependencies in /home/moritz/code/crates/truck:
truck-stepio -- /home/moritz/code/crates/truck/truck-stepio/Cargo.toml:
        truck-geotrait
Done!

Stripping truck-geotrait from truck-stepio/Cargo.toml will break the build with a bunch of #derive's not being found.

Add an option skip analyzing a directory

Context

When running trybuild tests, that library creates new crates under target/tests directory. Unfortunately, it does it without caring to remove unused dependencies; thus, we get a warning from cargo-machete.

Summary

I was surprised to discover cargo-machete looks into target/ dir. We could have a CLI option or better a knob that we could specify via a config file or workspace-level metadata to skip checking some directories.

Add support for `--locked` flag

This is a feature request to add support for a --locked flag which would cause cargo-machete to produce an error rather than inadvertently modify Cargo.toml files (currently only applicable when --with-metadata is used).

support new cargo package table workspaces

Newer versions of cargo let you specify crate metadata in the workspace and then inherit in each crate, but cargo-machete fails with this error when we do that:

error when handling /XXXX/Cargo.toml: invalid type: map, expected a string for key `package.version` at line 3 column 21

See here for more details.

And thanks for a wonderful tool that's been super helpful!

Github Action fails in CI on windows-latest

Hi I've just discovered, tried and adopted right away cargo-machete ❤️ Great crate !

When setting it up in Github workflow, I ran into some error on windows-latest:

error: could not compile `cargo-machete` (bin "cargo-machete") due to 1 previous error
error: failed to compile `cargo-machete v0.6.1`, intermediate artifacts can be found at `C:\Users\RUNNER~1\AppData\Local\Temp\cargo-install5qeHSQ`.
To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.
Error: The process 'C:\Users\runneradmin\.cargo\bin\cargo.exe' failed with exit code 101

➡️ full details available in Action.

💡 A simple alternative is to install it with taiki-e/install-action, as in following commit.

note that here the workflow failed for another reason: that I had unused dependencies indeed 😄

Hope this helps !

Error about non-existing file that exists

I was trying out machete on https://github.com/apollographql/apollo-rs and came upon a weird error: it tries to read fuzz/Cargo.toml and prints an OS error about that file not existing, but it does exist.

I've managed to reduce the issue to the cargo_toml manifest creation:

let path = std::path::Path::new("../apollo-rs/fuzz/Cargo.toml");
println!("path: {:?}, exists: {}", path, path.exists());
let manifest = cargo_toml::Manifest::from_path(path);
println!("file manifest: {:?}", manifest);

prints on this windows machine:

path: "../apollo-rs/fuzz/Cargo.toml", exists: true
file manifest: Err(Io(Os { code: 3, kind: NotFound, message: "Le chemin d’accès spécifié est introuvable." }))

So I guess parsing this specific manifest is trying to read other files, and opening them fails, but at this point the error lacks the context of which file it is ?

The result of cargo machete is wrong when using macro from other crate.

Crate A:

#[macro_export]
macro_rules! a_macro {
    crate_c::macro(xxx)
}

Crate A's Cargo.toml:

[dependencies]
create_c = "xxx"

Crate B:

// use a_macro
a_macro();

Crate B's Cargo.toml:

[dependencies]
crate_a = "xxx"
crate_c = "xxx"

Machete thinks crate C is not used in crate B.

But only if we make crate C as a dependency of crate B can we compile crate B.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.