Skip to content
This repository has been archived by the owner on Jun 5, 2020. It is now read-only.

Commit

Permalink
Switch to using cargo-metadata
Browse files Browse the repository at this point in the history
This both makes the crate compile way faster and avoids having to touch
tons of unstable, undocumented cargo internals. This requires Rust 1.41
(the current beta) for some cargo-metadata features.

There are some subtle differences in the new output (particularly in
handling dependencies between crates in workspaces), but they're pretty
minor.

Closes #73
Closes #69
Closes #66
Closes #65
Closes #63
Closes #61
Closes #57
Closes #20
  • Loading branch information
sfackler committed Dec 30, 2019
1 parent de50a9d commit fc8e4f4
Show file tree
Hide file tree
Showing 8 changed files with 702 additions and 1,673 deletions.
1,143 changes: 78 additions & 1,065 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ repository = "https://github.com/sfackler/cargo-tree"
readme = "README.md"

[dependencies]
cargo = "0.41"
cargo-platform = "0.1"
env_logger = "0.7"
failure = "0.1"
anyhow = "1.0"
cargo_metadata = "0.9"
petgraph = "0.4"
semver = "0.9"
serde_json = "1.0"
structopt = "0.3"
109 changes: 109 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::path::PathBuf;
use std::str::FromStr;
use structopt::clap::AppSettings;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(bin_name = "cargo")]
pub enum Opts {
#[structopt(
name = "tree",
setting = AppSettings::UnifiedHelpMessage,
setting = AppSettings::DeriveDisplayOrder,
setting = AppSettings::DontCollapseArgsInUsage
)]
/// Display a tree visualization of a dependency graph
Tree(Args),
}

#[derive(StructOpt)]
pub struct Args {
#[structopt(long = "package", short = "p", value_name = "SPEC")]
/// Package to be used as the root of the tree
pub package: Option<String>,
#[structopt(long = "features", value_name = "FEATURES")]
/// Space-separated list of features to activate
pub features: Option<String>,
#[structopt(long = "all-features")]
/// Activate all available features
pub all_features: bool,
#[structopt(long = "no-default-features")]
/// Do not activate the `default` feature
pub no_default_features: bool,
#[structopt(long = "target", value_name = "TARGET")]
/// Set the target triple
pub target: Option<String>,
#[structopt(long = "all-targets")]
/// Return dependencies for all targets. By default only the host target is matched.
pub all_targets: bool,
#[structopt(long = "no-dev-dependencies")]
/// Skip dev dependencies.
pub no_dev_dependencies: bool,
#[structopt(long = "manifest-path", value_name = "PATH", parse(from_os_str))]
/// Path to Cargo.toml
pub manifest_path: Option<PathBuf>,
#[structopt(long = "invert", short = "i")]
/// Invert the tree direction
pub invert: bool,
#[structopt(long = "no-indent")]
/// Display the dependencies as a list (rather than a tree)
pub no_indent: bool,
#[structopt(long = "prefix-depth")]
/// Display the dependencies as a list (rather than a tree), but prefixed with the depth
pub prefix_depth: bool,
#[structopt(long = "all", short = "a")]
/// Don't truncate dependencies that have already been displayed
pub all: bool,
#[structopt(long = "duplicate", short = "d")]
/// Show only dependencies which come in multiple versions (implies -i)
pub duplicates: bool,
#[structopt(long = "charset", value_name = "CHARSET", default_value = "utf8")]
/// Character set to use in output: utf8, ascii
pub charset: Charset,
#[structopt(
long = "format",
short = "f",
value_name = "FORMAT",
default_value = "{p}"
)]
/// Format string used for printing dependencies
pub format: String,
#[structopt(long = "verbose", short = "v", parse(from_occurrences))]
/// Use verbose output (-vv very verbose/build.rs output)
pub verbose: u32,
#[structopt(long = "quiet", short = "q")]
/// No output printed to stdout other than the tree
pub quiet: bool,
#[structopt(long = "color", value_name = "WHEN")]
/// Coloring: auto, always, never
pub color: Option<String>,
#[structopt(long = "frozen")]
/// Require Cargo.lock and cache are up to date
pub frozen: bool,
#[structopt(long = "locked")]
/// Require Cargo.lock is up to date
pub locked: bool,
#[structopt(long = "offline")]
/// Do not access the network
pub offline: bool,
#[structopt(short = "Z", value_name = "FLAG")]
/// Unstable (nightly-only) flags to Cargo
pub unstable_flags: Vec<String>,
}

pub enum Charset {
Utf8,
Ascii,
}

impl FromStr for Charset {
type Err = &'static str;

fn from_str(s: &str) -> Result<Charset, &'static str> {
match s {
"utf8" => Ok(Charset::Utf8),
"ascii" => Ok(Charset::Ascii),
_ => Err("invalid charset"),
}
}
}
45 changes: 25 additions & 20 deletions src/format/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use cargo::core::manifest::ManifestMetadata;
use cargo::core::PackageId;
use std::error::Error;
use std::fmt;

use crate::format::parse::{Parser, RawChunk};
use anyhow::{anyhow, Error};
use cargo_metadata::Package;
use std::fmt;

mod parse;

Expand All @@ -17,7 +15,7 @@ enum Chunk {
pub struct Pattern(Vec<Chunk>);

impl Pattern {
pub fn new(format: &str) -> Result<Pattern, Box<dyn Error>> {
pub fn new(format: &str) -> Result<Pattern, Error> {
let mut chunks = vec![];

for raw in Parser::new(format) {
Expand All @@ -27,48 +25,55 @@ impl Pattern {
RawChunk::Argument("l") => Chunk::License,
RawChunk::Argument("r") => Chunk::Repository,
RawChunk::Argument(ref a) => {
return Err(format!("unsupported pattern `{}`", a).into());
return Err(anyhow!("unsupported pattern `{}`", a).into());
}
RawChunk::Error(err) => return Err(err.into()),
RawChunk::Error(err) => return Err(anyhow!("{}", err)),
};
chunks.push(chunk);
}

Ok(Pattern(chunks))
}

pub fn display<'a>(
&'a self,
package: &'a PackageId,
metadata: &'a ManifestMetadata,
) -> Display<'a> {
pub fn display<'a>(&'a self, package: &'a Package) -> Display<'a> {
Display {
pattern: self,
package: package,
metadata: metadata,
package,
}
}
}

pub struct Display<'a> {
pattern: &'a Pattern,
package: &'a PackageId,
metadata: &'a ManifestMetadata,
package: &'a Package,
}

impl<'a> fmt::Display for Display<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
for chunk in &self.pattern.0 {
match *chunk {
Chunk::Raw(ref s) => fmt.write_str(s)?,
Chunk::Package => write!(fmt, "{}", self.package)?,
Chunk::Package => {
write!(fmt, "{} v{}", self.package.name, self.package.version)?;

match &self.package.source {
Some(source) if !source.is_crates_io() => write!(fmt, " ({})", source)?,
// https://github.com/rust-lang/cargo/issues/7483
None => write!(
fmt,
" ({})",
self.package.manifest_path.parent().unwrap().display()
)?,
_ => {}
}
}
Chunk::License => {
if let Some(ref license) = self.metadata.license {
if let Some(ref license) = self.package.license {
write!(fmt, "{}", license)?
}
}
Chunk::Repository => {
if let Some(ref repository) = self.metadata.repository {
if let Some(ref repository) = self.package.repository {
write!(fmt, "{}", repository)?
}
}
Expand Down
77 changes: 77 additions & 0 deletions src/graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::args::Args;
use anyhow::{anyhow, Error};
use cargo_metadata::{DependencyKind, Metadata, Package, PackageId};
use petgraph::graph::NodeIndex;
use petgraph::stable_graph::StableGraph;
use petgraph::visit::Dfs;
use std::collections::HashMap;

pub struct Graph {
pub graph: StableGraph<Package, DependencyKind>,
pub nodes: HashMap<PackageId, NodeIndex>,
pub root: Option<PackageId>,
}

pub fn build(args: &Args, metadata: Metadata) -> Result<Graph, Error> {
let resolve = metadata.resolve.unwrap();

let mut graph = Graph {
graph: StableGraph::new(),
nodes: HashMap::new(),
root: resolve.root,
};

for package in metadata.packages {
let id = package.id.clone();
let index = graph.graph.add_node(package);
graph.nodes.insert(id, index);
}

for node in resolve.nodes {
if node.deps.len() != node.dependencies.len() {
return Err(anyhow!("cargo tree requires cargo 1.41 or newer"));
}

let from = graph.nodes[&node.id];
for dep in node.deps {
if dep.dep_kinds.is_empty() {
return Err(anyhow!("cargo tree requires cargo 1.41 or newer"));
}

// https://github.com/rust-lang/cargo/issues/7752
let mut kinds = vec![];
for kind in dep.dep_kinds {
if !kinds.iter().any(|k| *k == kind.kind) {
kinds.push(kind.kind);
}
}

let to = graph.nodes[&dep.pkg];
for kind in kinds {
if args.no_dev_dependencies && kind == DependencyKind::Development {
continue;
}

graph.graph.add_edge(from, to, kind);
}
}
}

// prune nodes not reachable from the root package (directionally)
if let Some(root) = &graph.root {
let mut dfs = Dfs::new(&graph.graph, graph.nodes[root]);
while dfs.next(&graph.graph).is_some() {}

let g = &mut graph.graph;
graph.nodes.retain(|_, idx| {
if !dfs.discovered.contains(idx.index()) {
g.remove_node(*idx);
false
} else {
true
}
});
}

Ok(graph)
}
Loading

0 comments on commit fc8e4f4

Please sign in to comment.