Skip to content

Commit c4723e5

Browse files
authored
Merge pull request rustsec#860 from graydon/improve-tree-command
Various improvements to the "cargo-lock tree" subcommand
2 parents 479bf73 + b900b2b commit c4723e5

2 files changed

Lines changed: 107 additions & 40 deletions

File tree

cargo-lock/src/bin/cargo-lock/main.rs

Lines changed: 89 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ use cargo_lock::{
77
dependency::graph::EdgeDirection,
88
dependency::Tree,
99
package::{self},
10-
Dependency, Lockfile, ResolveVersion,
10+
Dependency, Lockfile, Package, ResolveVersion, Version,
1111
};
1212
use gumdrop::Options;
13+
use petgraph::graph::NodeIndex;
1314
use std::{
1415
env, fs, io,
1516
path::{Path, PathBuf},
1617
process::exit,
18+
str::FromStr,
1719
};
1820

1921
/// `cargo lock` subcommands
@@ -126,12 +128,66 @@ impl TranslateCmd {
126128
#[derive(Debug, Options)]
127129
struct TreeCmd {
128130
/// Input `Cargo.lock` file
129-
#[options(short = "f", help = "input Cargo.lock file to translate")]
131+
#[options(
132+
short = "f",
133+
long = "file",
134+
help = "input Cargo.lock file to translate"
135+
)]
130136
file: Option<PathBuf>,
131137

132-
/// Dependencies names to draw a tree for
133-
#[options(free, help = "dependency names to draw trees for")]
134-
dependencies: Vec<package::Name>,
138+
/// Show exact package identities (checksums or specific source versions) when available
139+
#[options(
140+
short = "x",
141+
long = "exact",
142+
help = "show exact package identies (checksums or specific source versions) when available"
143+
)]
144+
exact: bool,
145+
146+
// Show inverse dependencies rather than forward dependencies
147+
#[options(
148+
short = "i",
149+
long = "invert",
150+
help = "show inverse dependencies _on_ a package, rather than forward dependencies _of_ a package"
151+
)]
152+
inverse: bool,
153+
154+
/// Dependencies names or hashes to draw a tree for
155+
#[options(free, help = "dependency names or hashes to draw trees for")]
156+
dependencies: Vec<String>,
157+
}
158+
159+
fn package_matches_name(pkg: &Package, name: &str) -> bool {
160+
pkg.name.as_str() == name
161+
}
162+
163+
fn package_matches_ver(pkg: &Package, ver: &str) -> bool {
164+
// Try interpreting ver as a semver string.
165+
if let Ok(v) = Version::from_str(ver) {
166+
return pkg.version == v;
167+
}
168+
// Try comparing ver to hashes in either the package checksum or the source
169+
// precise field
170+
if let Some(cksum) = &pkg.checksum {
171+
if cksum.to_string() == ver {
172+
return true;
173+
}
174+
}
175+
if let Some(src) = &pkg.source {
176+
if let Some(precise) = src.precise() {
177+
if precise == ver {
178+
return true;
179+
}
180+
}
181+
}
182+
false
183+
}
184+
185+
fn package_matches(pkg: &Package, spec: &str) -> bool {
186+
if let Some((name, ver)) = spec.split_once('@') {
187+
package_matches_name(pkg, name) && package_matches_ver(pkg, ver)
188+
} else {
189+
package_matches_name(pkg, spec) || package_matches_ver(pkg, spec)
190+
}
135191
}
136192

137193
impl TreeCmd {
@@ -144,43 +200,40 @@ impl TreeCmd {
144200
exit(1);
145201
});
146202

147-
if self.dependencies.is_empty() {
148-
self.dependency_tree(&tree);
203+
let indices: Vec<NodeIndex> = if self.dependencies.is_empty() {
204+
tree.roots().to_vec()
149205
} else {
150-
self.inverse_dependency_tree(&lockfile, &tree);
151-
}
152-
}
153-
154-
/// Show forward dependency tree for detected root dependencies
155-
fn dependency_tree(&self, tree: &Tree) {
156-
for (i, index) in tree.roots().iter().enumerate() {
157-
if i > 0 {
158-
println!();
159-
}
160-
161-
tree.render(&mut io::stdout(), *index, EdgeDirection::Outgoing)
162-
.unwrap();
163-
}
206+
self.dependencies
207+
.iter()
208+
.map(|dep| {
209+
let package = lockfile
210+
.packages
211+
.iter()
212+
.find(|pkg| package_matches(pkg, dep))
213+
.unwrap_or_else(|| {
214+
eprintln!("*** error: invalid dependency name: `{}`", dep);
215+
exit(1);
216+
});
217+
tree.nodes()[&package.into()]
218+
})
219+
.collect()
220+
};
221+
222+
self.dependency_tree(&tree, &indices);
164223
}
165224

166-
/// Show inverse dependency tree for the provided dependencies
167-
fn inverse_dependency_tree(&self, lockfile: &Lockfile, tree: &Tree) {
168-
for (i, dep) in self.dependencies.iter().enumerate() {
225+
/// Show dependency tree for the provided dependencies
226+
fn dependency_tree(&self, tree: &Tree, indices: &[NodeIndex]) {
227+
for (i, index) in indices.iter().enumerate() {
169228
if i > 0 {
170229
println!();
171230
}
172-
173-
let package = lockfile
174-
.packages
175-
.iter()
176-
.find(|pkg| pkg.name == *dep)
177-
.unwrap_or_else(|| {
178-
eprintln!("*** error: invalid dependency name: `{}`", dep);
179-
exit(1);
180-
});
181-
182-
let index = tree.nodes()[&package.into()];
183-
tree.render(&mut io::stdout(), index, EdgeDirection::Incoming)
231+
let direction = if self.inverse {
232+
EdgeDirection::Incoming
233+
} else {
234+
EdgeDirection::Outgoing
235+
};
236+
tree.render(&mut io::stdout(), *index, direction, self.exact)
184237
.unwrap();
185238
}
186239
}

cargo-lock/src/dependency/tree.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ impl Tree {
5555
w: &mut impl io::Write,
5656
node_index: NodeIndex,
5757
direction: EdgeDirection,
58+
exact: bool,
5859
) -> io::Result<()> {
59-
self.render_with_symbols(w, node_index, direction, &Symbols::default())
60+
self.render_with_symbols(w, node_index, direction, &Symbols::default(), exact)
6061
}
6162

6263
/// Render the dependency graph for the given [`NodeIndex`] using the
@@ -67,8 +68,9 @@ impl Tree {
6768
node_index: NodeIndex,
6869
direction: EdgeDirection,
6970
symbols: &Symbols,
71+
exact: bool,
7072
) -> io::Result<()> {
71-
Presenter::new(&self.graph, symbols).print_node(w, node_index, direction)
73+
Presenter::new(&self.graph, symbols).print_node(w, node_index, direction, exact)
7274
}
7375

7476
/// Get the indexes of the root packages in the workspace
@@ -139,6 +141,7 @@ impl<'g, 's> Presenter<'g, 's> {
139141
w: &mut impl io::Write,
140142
node_index: NodeIndex,
141143
direction: EdgeDirection,
144+
exact: bool,
142145
) -> io::Result<()> {
143146
let package = &self.graph[node_index];
144147
let new = self.visited.insert(node_index);
@@ -158,7 +161,18 @@ impl<'g, 's> Presenter<'g, 's> {
158161
write!(w, "{0}{1}{1} ", c, self.symbols.right)?;
159162
}
160163

161-
writeln!(w, "{} {}", &package.name, &package.version)?;
164+
if exact {
165+
let spec = if let Some(checksum) = &package.checksum {
166+
format!("checksum:{}", checksum)
167+
} else if let Some(src) = &package.source {
168+
src.to_string()
169+
} else {
170+
"inexact".to_string()
171+
};
172+
writeln!(w, "{} {} {}", &package.name, &package.version, spec)?;
173+
} else {
174+
writeln!(w, "{} {}", &package.name, &package.version)?;
175+
}
162176

163177
if !new {
164178
return Ok(());
@@ -176,7 +190,7 @@ impl<'g, 's> Presenter<'g, 's> {
176190

177191
for (i, dependency) in dependencies.iter().enumerate() {
178192
self.levels_continue.push(i < (dependencies.len() - 1));
179-
self.print_node(w, *dependency, direction)?;
193+
self.print_node(w, *dependency, direction, exact)?;
180194
self.levels_continue.pop();
181195
}
182196

0 commit comments

Comments
 (0)