diff --git a/src/cargo/ops/tree/format/mod.rs b/src/cargo/ops/tree/format/mod.rs
index ee09751a16d..c478049e9ac 100644
--- a/src/cargo/ops/tree/format/mod.rs
+++ b/src/cargo/ops/tree/format/mod.rs
@@ -14,6 +14,7 @@ enum Chunk {
Repository,
Features,
LibName,
+ VersionRequirement,
}
pub struct Pattern(Vec);
@@ -30,6 +31,7 @@ impl Pattern {
RawChunk::Argument("r") => Chunk::Repository,
RawChunk::Argument("f") => Chunk::Features,
RawChunk::Argument("lib") => Chunk::LibName,
+ RawChunk::Argument("ver-req") => Chunk::VersionRequirement,
RawChunk::Argument(a) => {
bail!("unsupported pattern `{}`", a);
}
@@ -111,6 +113,13 @@ impl<'a> fmt::Display for Display<'a> {
write!(fmt, "{}", target.crate_name())?;
}
}
+ Chunk::VersionRequirement => {
+ if let Some(version_req) =
+ self.graph.version_req_for_id(package.package_id())
+ {
+ write!(fmt, "{}", version_req)?;
+ }
+ }
}
}
}
diff --git a/src/cargo/ops/tree/format/parse.rs b/src/cargo/ops/tree/format/parse.rs
index ee112fbee50..a157f748fe6 100644
--- a/src/cargo/ops/tree/format/parse.rs
+++ b/src/cargo/ops/tree/format/parse.rs
@@ -3,6 +3,7 @@
use std::iter;
use std::str;
+#[derive(Debug, PartialEq, Eq)]
pub enum RawChunk<'a> {
/// Raw text to include in the output.
Text(&'a str),
@@ -23,9 +24,9 @@ pub enum RawChunk<'a> {
/// (and optionally source), and the `{l}` will be the license from
/// `Cargo.toml`.
///
-/// Substitutions are alphabetic characters between curly braces, like `{p}`
-/// or `{foo}`. The actual interpretation of these are done in the `Pattern`
-/// struct.
+/// Substitutions are alphabetic characters or hyphens between curly braces,
+/// like `{p}`, {foo} or `{bar-baz}`. The actual interpretation of these is
+/// done in the `Pattern` struct.
///
/// Bare curly braces can be included in the output with double braces like
/// `{{` will include a single `{`, similar to Rust's format strings.
@@ -67,7 +68,7 @@ impl<'a> Parser<'a> {
loop {
match self.it.peek() {
- Some(&(_, ch)) if ch.is_alphanumeric() => {
+ Some(&(_, ch)) if ch.is_alphanumeric() || ch == '-' => {
self.it.next();
}
Some(&(end, _)) => return &self.s[start..end],
@@ -121,3 +122,116 @@ impl<'a> Iterator for Parser<'a> {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{Parser, RawChunk};
+
+ #[test]
+ fn plain_text() {
+ let chunks: Vec<_> = Parser::new("Hello World").collect();
+ assert_eq!(chunks, vec![RawChunk::Text("Hello World")]);
+ }
+
+ #[test]
+ fn basic_argument() {
+ let chunks: Vec<_> = Parser::new("{pkg}").collect();
+ assert_eq!(chunks, vec![RawChunk::Argument("pkg")]);
+ }
+
+ #[test]
+ fn mixed_content() {
+ let chunks: Vec<_> = Parser::new("Package {p} version:{v}").collect();
+ assert_eq!(
+ chunks,
+ vec![
+ RawChunk::Text("Package "),
+ RawChunk::Argument("p"),
+ RawChunk::Text(" version:"),
+ RawChunk::Argument("v"),
+ ]
+ );
+ }
+
+ #[test]
+ fn escaped_braces() {
+ let chunks: Vec<_> = Parser::new("{{text}} in {{braces}}").collect();
+ assert_eq!(
+ chunks,
+ vec![
+ RawChunk::Text("{"),
+ RawChunk::Text("text"),
+ RawChunk::Text("}"),
+ RawChunk::Text(" in "),
+ RawChunk::Text("{"),
+ RawChunk::Text("braces"),
+ RawChunk::Text("}"),
+ ]
+ );
+ }
+
+ #[test]
+ fn hyphenated_argument() {
+ let chunks: Vec<_> = Parser::new("{foo-bar}").collect();
+ assert_eq!(chunks, vec![RawChunk::Argument("foo-bar")]);
+ }
+
+ #[test]
+ fn unclosed_brace() {
+ let chunks: Vec<_> = Parser::new("{unclosed").collect();
+ assert_eq!(chunks, vec![RawChunk::Error("expected '}'")])
+ }
+
+ #[test]
+ fn unexpected_close_brace() {
+ let chunks: Vec<_> = Parser::new("unexpected}").collect();
+ assert_eq!(
+ chunks,
+ vec![
+ RawChunk::Text("unexpected"),
+ RawChunk::Error("unexpected '}'"),
+ ]
+ );
+ }
+
+ #[test]
+ fn empty_argument() {
+ let chunks: Vec<_> = Parser::new("{}").collect();
+ assert_eq!(chunks, vec![RawChunk::Argument("")]);
+ }
+
+ #[test]
+ fn invalid_argument_chars() {
+ let chunks: Vec<_> = Parser::new("{a-b} {123}").collect();
+ assert_eq!(
+ chunks,
+ vec![
+ RawChunk::Argument("a-b"),
+ RawChunk::Text(" "),
+ RawChunk::Error("expected '}'"),
+ ]
+ );
+ }
+
+ #[test]
+ fn complex_format() {
+ let format = "Pkg{{name}}: {p} [{v}] (License: {l})";
+ let chunks: Vec<_> = Parser::new(format).collect();
+ assert_eq!(
+ chunks,
+ vec![
+ RawChunk::Text("Pkg"),
+ RawChunk::Text("{"),
+ RawChunk::Text("name"),
+ RawChunk::Text("}"),
+ RawChunk::Text(": "),
+ RawChunk::Argument("p"),
+ RawChunk::Text(" ["),
+ RawChunk::Argument("v"),
+ RawChunk::Text("] (License: "),
+ RawChunk::Argument("l"),
+ RawChunk::Text(")"),
+ ]
+ );
+ }
+}
diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs
index 16951be3d7e..fed366df20e 100644
--- a/src/cargo/ops/tree/graph.rs
+++ b/src/cargo/ops/tree/graph.rs
@@ -6,8 +6,8 @@ use crate::core::dependency::DepKind;
use crate::core::resolver::Resolve;
use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
use crate::core::{FeatureMap, FeatureValue, Package, PackageId, PackageIdSpec, Workspace};
-use crate::util::CargoResult;
use crate::util::interning::{INTERNED_DEFAULT, InternedString};
+use crate::util::{CargoResult, OptVersionReq};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Copy, Clone)]
@@ -161,6 +161,8 @@ pub struct Graph<'a> {
/// Key is the index of a package node, value is a map of `dep_name` to a
/// set of `(pkg_node_index, is_optional)`.
dep_name_map: HashMap>>,
+ /// Map for looking up version requirements for dependency packages.
+ version_req_map: HashMap,
}
impl<'a> Graph<'a> {
@@ -172,6 +174,7 @@ impl<'a> Graph<'a> {
package_map,
cli_features: HashSet::new(),
dep_name_map: HashMap::new(),
+ version_req_map: HashMap::new(),
}
}
@@ -240,6 +243,12 @@ impl<'a> Graph<'a> {
}
}
+ /// Returns the version requirement for the given package ID. Returns `None`
+ /// if no version requirement is recorded (e.g., root packages).
+ pub fn version_req_for_id(&self, package_id: PackageId) -> Option<&OptVersionReq> {
+ self.version_req_map.get(&package_id)
+ }
+
/// Returns `true` if the given feature node index is a feature enabled
/// via the command-line.
pub fn is_cli_feature(&self, index: NodeId) -> bool {
@@ -523,6 +532,12 @@ fn add_pkg(
requested_kind,
opts,
);
+ // Store the version requirement for this dependency for
+ // later use in formatting.
+ graph.version_req_map.insert(
+ graph.package_id_for_index(dep_index),
+ dep.version_req().clone(),
+ );
let new_edge = Edge {
kind: EdgeKind::Dep(dep.kind()),
node: dep_index,
diff --git a/src/doc/man/cargo-tree.md b/src/doc/man/cargo-tree.md
index fc3521c9939..e617bc6a6c9 100644
--- a/src/doc/man/cargo-tree.md
+++ b/src/doc/man/cargo-tree.md
@@ -167,6 +167,7 @@ strings will be replaced with the corresponding value:
- `{r}` --- The package repository URL.
- `{f}` --- Comma-separated list of package features that are enabled.
- `{lib}` --- The name, as used in a `use` statement, of the package's library.
+- `{ver-req}` --- The version requirement resolved for the package.
{{/option}}
{{#option "`--prefix` _prefix_" }}
diff --git a/src/doc/man/generated_txt/cargo-tree.txt b/src/doc/man/generated_txt/cargo-tree.txt
index 0fc542f429f..926c58824a5 100644
--- a/src/doc/man/generated_txt/cargo-tree.txt
+++ b/src/doc/man/generated_txt/cargo-tree.txt
@@ -166,6 +166,8 @@ OPTIONS
o {lib} — The name, as used in a use statement, of the
package’s library.
+ o {ver-req} — The version requirement resolved for the package.
+
--prefix prefix
Sets how each line is displayed. The prefix value can be one of:
diff --git a/src/doc/src/commands/cargo-tree.md b/src/doc/src/commands/cargo-tree.md
index ee8c90fa7bc..c517fe07f20 100644
--- a/src/doc/src/commands/cargo-tree.md
+++ b/src/doc/src/commands/cargo-tree.md
@@ -170,6 +170,7 @@ strings will be replaced with the corresponding value:
{r} — The package repository URL.
{f} — Comma-separated list of package features that are enabled.
{lib} — The name, as used in a use statement, of the package’s library.
+{ver-req} — The version requirement resolved for the package.
diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1
index cd2739f9efa..b1fd0a4e90d 100644
--- a/src/etc/man/cargo-tree.1
+++ b/src/etc/man/cargo-tree.1
@@ -208,6 +208,10 @@ strings will be replaced with the corresponding value:
.RS 4
\h'-04'\(bu\h'+03'\fB{lib}\fR \[em] The name, as used in a \fBuse\fR statement, of the package\[cq]s library.
.RE
+.sp
+.RS 4
+\h'-04'\(bu\h'+03'\fB{ver\-req}\fR \[em] The version requirement resolved for the package.
+.RE
.RE
.sp
\fB\-\-prefix\fR \fIprefix\fR
diff --git a/tests/testsuite/cargo_tree/deps.rs b/tests/testsuite/cargo_tree/deps.rs
index f207ba705e2..49f34784afc 100644
--- a/tests/testsuite/cargo_tree/deps.rs
+++ b/tests/testsuite/cargo_tree/deps.rs
@@ -1128,7 +1128,10 @@ foo v0.1.0 ([ROOT]/foo)
#[cargo_test]
fn format() {
Package::new("dep", "1.0.0").publish();
- Package::new("other-dep", "1.0.0").publish();
+ Package::new("dep", "2.0.0").publish();
+ Package::new("other-dep", "1.0.0")
+ .dep("dep", "^1.0")
+ .publish();
Package::new("dep_that_is_awesome", "1.0.0")
.file(
@@ -1140,6 +1143,10 @@ fn format() {
[lib]
name = "awesome_dep"
+
+ [dependencies]
+ dep1 = {package="dep", version="<2.0"}
+ dep2 = {package="dep", version="2.0"}
"#,
)
.file("src/lib.rs", "pub struct Straw;")
@@ -1156,9 +1163,9 @@ fn format() {
repository = "https://github.com/rust-lang/cargo"
[dependencies]
- dep = {version="1.0", optional=true}
+ dep = {version="=1.0"}
other-dep = {version="1.0", optional=true}
- dep_that_is_awesome = {version="1.0", optional=true}
+ dep_that_is_awesome = {version=">=1.0, <2", optional=true}
[features]
@@ -1173,6 +1180,7 @@ fn format() {
p.cargo("tree --format <<<{p}>>>")
.with_stdout_data(str![[r#"
<<>>
+└── <<>>
"#]])
.run();
@@ -1191,6 +1199,7 @@ Caused by:
p.cargo("tree --format {p}-{{hello}}")
.with_stdout_data(str![[r#"
foo v0.1.0 ([ROOT]/foo)-{hello}
+└── dep v1.0.0-{hello}
"#]])
.run();
@@ -1199,6 +1208,7 @@ foo v0.1.0 ([ROOT]/foo)-{hello}
.arg("{p} {l} {r}")
.with_stdout_data(str![[r#"
foo v0.1.0 ([ROOT]/foo) MIT https://github.com/rust-lang/cargo
+└── dep v1.0.0
"#]])
.run();
@@ -1207,6 +1217,7 @@ foo v0.1.0 ([ROOT]/foo) MIT https://github.com/rust-lang/cargo
.arg("{p} {f}")
.with_stdout_data(str![[r#"
foo v0.1.0 ([ROOT]/foo) bar,default,foo
+└── dep v1.0.0
"#]])
.run();
@@ -1214,10 +1225,11 @@ foo v0.1.0 ([ROOT]/foo) bar,default,foo
p.cargo("tree --all-features --format")
.arg("{p} [{f}]")
.with_stdout_data(str![[r#"
-foo v0.1.0 ([ROOT]/foo) [bar,default,dep,dep_that_is_awesome,foo,other-dep]
+foo v0.1.0 ([ROOT]/foo) [bar,default,dep_that_is_awesome,foo,other-dep]
├── dep v1.0.0 []
├── dep_that_is_awesome v1.0.0 []
└── other-dep v1.0.0 []
+ └── dep v1.0.0 []
"#]])
.run();
@@ -1227,8 +1239,34 @@ foo v0.1.0 ([ROOT]/foo) [bar,default,dep,dep_that_is_awesome,foo,other-dep]
.arg("--format={lib}")
.with_stdout_data(str![[r#"
+├── dep
├── awesome_dep
└── other_dep
+ └── dep
+
+"#]])
+ .run();
+
+ p.cargo("tree --all-features")
+ .arg("--format={p} satisfies {ver-req}")
+ .with_stdout_data(str![[r#"
+foo v0.1.0 ([ROOT]/foo) satisfies
+├── dep v1.0.0 satisfies ^1.0
+├── dep_that_is_awesome v1.0.0 satisfies >=1.0, <2
+└── other-dep v1.0.0 satisfies ^1.0
+ └── dep v1.0.0 satisfies ^1.0
+
+"#]])
+ .run();
+
+ p.cargo("tree --all-features")
+ .arg("--format={p} satisfies {ver-req}")
+ .arg("--invert=dep")
+ .with_stdout_data(str![[r#"
+dep v1.0.0 satisfies ^1.0
+├── foo v0.1.0 ([ROOT]/foo) satisfies
+└── other-dep v1.0.0 satisfies ^1.0
+ └── foo v0.1.0 ([ROOT]/foo) satisfies
"#]])
.run();