Skip to content

Commit c633b27

Browse files
committed
Auto merge of #11075 - epage:feat, r=weihanglo
fix(add): Clarify which version the features are added for ### What does this PR try to resolve? This gives a hint to users that we might not be showing the feature list for the latest version but the earliest version. Also when using a workspace dependency, this is a good reminder of what the version requirement is that was selected. That could also be useful for reused dependencies but didn't want to bother with the relevant plumbing for that. ie we are going from ```console $ cargo add [email protected] Updating crates.io index Adding chrono v0.4 to dependencies. Features: - rustc-serialize - serde ``` to ```console $ cargo add [email protected] Updating crates.io index Adding chrono v0.4 to dependencies. Features as of v0.4.2: - rustc-serialize - serde ``` ### How should we test and review this PR? I'd recommend looking at this commit-by-commit. This is broken up into several refactors leading up the main change. The refactors are focused on pulling UI logic out of dependency editing so we can more easily evolve the UI without breaking the editing API. I then tweaked the behavior in the final commit to be less redundant / noisy. The existing tests are used to demonstrate this is working. ### Additional information I'm also mixed on whether the meta version should show up. Fixes #11073
2 parents fedd172 + 5a6cfc9 commit c633b27

File tree

7 files changed

+158
-82
lines changed

7 files changed

+158
-82
lines changed

src/cargo/ops/cargo_add/dependency.rs

+1-44
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
use std::collections::BTreeMap;
21
use std::fmt::{Display, Formatter};
32
use std::path::{Path, PathBuf};
43

54
use indexmap::IndexSet;
65
use toml_edit::KeyMut;
76

87
use super::manifest::str_or_1_len_table;
9-
use crate::core::FeatureMap;
10-
use crate::core::FeatureValue;
118
use crate::core::GitReference;
129
use crate::core::SourceId;
1310
use crate::core::Summary;
@@ -40,9 +37,6 @@ pub struct Dependency {
4037
/// If the dependency is renamed, this is the new name for the dependency
4138
/// as a string. None if it is not renamed.
4239
pub rename: Option<String>,
43-
44-
/// Features that are exposed by the dependency
45-
pub available_features: BTreeMap<String, Vec<String>>,
4640
}
4741

4842
impl Dependency {
@@ -57,7 +51,6 @@ impl Dependency {
5751
source: None,
5852
registry: None,
5953
rename: None,
60-
available_features: Default::default(),
6154
}
6255
}
6356

@@ -85,37 +78,6 @@ impl Dependency {
8578
self
8679
}
8780

88-
/// Set the available features of the dependency to a given vec
89-
pub fn set_available_features(
90-
mut self,
91-
available_features: BTreeMap<String, Vec<String>>,
92-
) -> Self {
93-
self.available_features = available_features;
94-
self
95-
}
96-
97-
/// Populate from cargo
98-
pub fn set_available_features_from_cargo(
99-
mut self,
100-
available_features: &FeatureMap,
101-
) -> Dependency {
102-
self.available_features = available_features
103-
.iter()
104-
.map(|(k, v)| {
105-
(
106-
k.as_str().to_owned(),
107-
v.iter()
108-
.filter_map(|v| match v {
109-
FeatureValue::Feature(f) => Some(f.as_str().to_owned()),
110-
FeatureValue::Dep { .. } | FeatureValue::DepFeature { .. } => None,
111-
})
112-
.collect::<Vec<_>>(),
113-
)
114-
})
115-
.collect();
116-
self
117-
}
118-
11981
/// Set whether the dependency is optional
12082
#[allow(dead_code)]
12183
pub fn set_optional(mut self, opt: bool) -> Self {
@@ -347,8 +309,6 @@ impl Dependency {
347309
None
348310
};
349311

350-
let available_features = BTreeMap::default();
351-
352312
let optional = table.get("optional").and_then(|v| v.as_bool());
353313

354314
let dep = Self {
@@ -358,7 +318,6 @@ impl Dependency {
358318
registry,
359319
default_features,
360320
features,
361-
available_features,
362321
optional,
363322
inherited_features: None,
364323
};
@@ -646,9 +605,7 @@ impl<'s> From<&'s Summary> for Dependency {
646605
} else {
647606
RegistrySource::new(other.version().to_string()).into()
648607
};
649-
Dependency::new(other.name().as_str())
650-
.set_source(source)
651-
.set_available_features_from_cargo(other.features())
608+
Dependency::new(other.name().as_str()).set_source(source)
652609
}
653610
}
654611

src/cargo/ops/cargo_add/mod.rs

+152-33
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ mod crate_spec;
44
mod dependency;
55
mod manifest;
66

7-
use anyhow::Context;
7+
use std::collections::BTreeMap;
88
use std::collections::BTreeSet;
99
use std::collections::VecDeque;
1010
use std::path::Path;
1111

12+
use anyhow::Context as _;
1213
use cargo_util::paths;
1314
use indexmap::IndexSet;
1415
use termcolor::Color::Green;
@@ -18,10 +19,12 @@ use toml_edit::Item as TomlItem;
1819

1920
use crate::core::dependency::DepKind;
2021
use crate::core::registry::PackageRegistry;
22+
use crate::core::FeatureValue;
2123
use crate::core::Package;
2224
use crate::core::QueryKind;
2325
use crate::core::Registry;
2426
use crate::core::Shell;
27+
use crate::core::Summary;
2528
use crate::core::Workspace;
2629
use crate::CargoResult;
2730
use crate::Config;
@@ -200,7 +203,7 @@ fn resolve_dependency(
200203
section: &DepTable,
201204
config: &Config,
202205
registry: &mut PackageRegistry<'_>,
203-
) -> CargoResult<Dependency> {
206+
) -> CargoResult<DependencyUI> {
204207
let crate_spec = arg
205208
.crate_spec
206209
.as_deref()
@@ -284,9 +287,7 @@ fn resolve_dependency(
284287
// Overwrite with `crate_spec`
285288
old_dep.source = selected_dep.source;
286289
}
287-
old_dep = populate_dependency(old_dep, arg);
288-
old_dep.available_features = selected_dep.available_features;
289-
old_dep
290+
populate_dependency(old_dep, arg)
290291
}
291292
} else {
292293
selected_dep
@@ -318,9 +319,7 @@ fn resolve_dependency(
318319
))?;
319320
dependency.name = latest.name; // Normalize the name
320321
}
321-
dependency = dependency
322-
.set_source(latest.source.expect("latest always has a source"))
323-
.set_available_features(latest.available_features);
322+
dependency = dependency.set_source(latest.source.expect("latest always has a source"));
324323
}
325324
}
326325

@@ -339,7 +338,25 @@ fn resolve_dependency(
339338
dependency = dependency.clear_version();
340339
}
341340

342-
dependency = populate_available_features(dependency, config, registry, ws)?;
341+
let query = dependency.query(config)?;
342+
let query = match query {
343+
MaybeWorkspace::Workspace(_workspace) => {
344+
let dep = find_workspace_dep(dependency.toml_key(), ws.root_manifest())?;
345+
if let Some(features) = dep.features.clone() {
346+
dependency = dependency.set_inherited_features(features);
347+
}
348+
let query = dep.query(config)?;
349+
match query {
350+
MaybeWorkspace::Workspace(_) => {
351+
unreachable!("This should have been caught when parsing a workspace root")
352+
}
353+
MaybeWorkspace::Other(query) => query,
354+
}
355+
}
356+
MaybeWorkspace::Other(query) => query,
357+
};
358+
359+
let dependency = populate_available_features(dependency, &query, registry)?;
343360

344361
Ok(dependency)
345362
}
@@ -582,34 +599,81 @@ fn populate_dependency(mut dependency: Dependency, arg: &DepOp) -> Dependency {
582599
dependency
583600
}
584601

602+
/// Track presentation-layer information with the editable representation of a `[dependencies]`
603+
/// entry (Dependency)
604+
pub struct DependencyUI {
605+
/// Editable representation of a `[depednencies]` entry
606+
dep: Dependency,
607+
/// The version of the crate that we pulled `available_features` from
608+
available_version: Option<semver::Version>,
609+
/// The widest set of features compatible with `Dependency`s version requirement
610+
available_features: BTreeMap<String, Vec<String>>,
611+
}
612+
613+
impl DependencyUI {
614+
fn new(dep: Dependency) -> Self {
615+
Self {
616+
dep,
617+
available_version: None,
618+
available_features: Default::default(),
619+
}
620+
}
621+
622+
fn apply_summary(&mut self, summary: &Summary) {
623+
self.available_version = Some(summary.version().clone());
624+
self.available_features = summary
625+
.features()
626+
.iter()
627+
.map(|(k, v)| {
628+
(
629+
k.as_str().to_owned(),
630+
v.iter()
631+
.filter_map(|v| match v {
632+
FeatureValue::Feature(f) => Some(f.as_str().to_owned()),
633+
FeatureValue::Dep { .. } | FeatureValue::DepFeature { .. } => None,
634+
})
635+
.collect::<Vec<_>>(),
636+
)
637+
})
638+
.collect();
639+
}
640+
}
641+
642+
impl<'s> From<&'s Summary> for DependencyUI {
643+
fn from(other: &'s Summary) -> Self {
644+
let dep = Dependency::from(other);
645+
let mut dep = Self::new(dep);
646+
dep.apply_summary(other);
647+
dep
648+
}
649+
}
650+
651+
impl std::fmt::Display for DependencyUI {
652+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
653+
self.dep.fmt(f)
654+
}
655+
}
656+
657+
impl std::ops::Deref for DependencyUI {
658+
type Target = Dependency;
659+
660+
fn deref(&self) -> &Self::Target {
661+
&self.dep
662+
}
663+
}
664+
585665
/// Lookup available features
586666
fn populate_available_features(
587-
mut dependency: Dependency,
588-
config: &Config,
667+
dependency: Dependency,
668+
query: &crate::core::dependency::Dependency,
589669
registry: &mut PackageRegistry<'_>,
590-
ws: &Workspace<'_>,
591-
) -> CargoResult<Dependency> {
670+
) -> CargoResult<DependencyUI> {
671+
let mut dependency = DependencyUI::new(dependency);
672+
592673
if !dependency.available_features.is_empty() {
593674
return Ok(dependency);
594675
}
595676

596-
let query = dependency.query(config)?;
597-
let query = match query {
598-
MaybeWorkspace::Workspace(_workspace) => {
599-
let dep = find_workspace_dep(dependency.toml_key(), ws.root_manifest())?;
600-
if let Some(features) = dep.features.clone() {
601-
dependency = dependency.set_inherited_features(features);
602-
}
603-
let query = dep.query(config)?;
604-
match query {
605-
MaybeWorkspace::Workspace(_) => {
606-
unreachable!("This should have been caught when parsing a workspace root")
607-
}
608-
MaybeWorkspace::Other(query) => query,
609-
}
610-
}
611-
MaybeWorkspace::Other(query) => query,
612-
};
613677
let possibilities = loop {
614678
match registry.query_vec(&query, QueryKind::Fuzzy) {
615679
std::task::Poll::Ready(res) => {
@@ -631,12 +695,12 @@ fn populate_available_features(
631695
.ok_or_else(|| {
632696
anyhow::format_err!("the crate `{dependency}` could not be found in registry index.")
633697
})?;
634-
dependency = dependency.set_available_features_from_cargo(lowest_common_denominator.features());
698+
dependency.apply_summary(&lowest_common_denominator);
635699

636700
Ok(dependency)
637701
}
638702

639-
fn print_msg(shell: &mut Shell, dep: &Dependency, section: &[String]) -> CargoResult<()> {
703+
fn print_msg(shell: &mut Shell, dep: &DependencyUI, section: &[String]) -> CargoResult<()> {
640704
use std::fmt::Write;
641705

642706
if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
@@ -709,7 +773,28 @@ fn print_msg(shell: &mut Shell, dep: &Dependency, section: &[String]) -> CargoRe
709773
deactivated.sort();
710774
if !activated.is_empty() || !deactivated.is_empty() {
711775
let prefix = format!("{:>13}", " ");
712-
shell.write_stderr(format_args!("{}Features:\n", prefix), &ColorSpec::new())?;
776+
let suffix = if let Some(version) = &dep.available_version {
777+
let mut version = version.clone();
778+
version.build = Default::default();
779+
let version = version.to_string();
780+
// Avoid displaying the version if it will visually look like the version req that we
781+
// showed earlier
782+
let version_req = dep
783+
.version()
784+
.and_then(|v| semver::VersionReq::parse(v).ok())
785+
.and_then(|v| precise_version(&v));
786+
if version_req.as_deref() != Some(version.as_str()) {
787+
format!(" as of v{version}")
788+
} else {
789+
"".to_owned()
790+
}
791+
} else {
792+
"".to_owned()
793+
};
794+
shell.write_stderr(
795+
format_args!("{}Features{}:\n", prefix, suffix),
796+
&ColorSpec::new(),
797+
)?;
713798
for feat in activated {
714799
shell.write_stderr(&prefix, &ColorSpec::new())?;
715800
shell.write_stderr('+', &ColorSpec::new().set_bold(true).set_fg(Some(Green)))?;
@@ -765,3 +850,37 @@ fn find_workspace_dep(toml_key: &str, root_manifest: &Path) -> CargoResult<Depen
765850
))?;
766851
Dependency::from_toml(root_manifest.parent().unwrap(), toml_key, dep_item)
767852
}
853+
854+
/// Convert a `semver::VersionReq` into a rendered `semver::Version` if all fields are fully
855+
/// specified.
856+
fn precise_version(version_req: &semver::VersionReq) -> Option<String> {
857+
version_req
858+
.comparators
859+
.iter()
860+
.filter(|c| {
861+
matches!(
862+
c.op,
863+
// Only ops we can determine a precise version from
864+
semver::Op::Exact
865+
| semver::Op::GreaterEq
866+
| semver::Op::LessEq
867+
| semver::Op::Tilde
868+
| semver::Op::Caret
869+
| semver::Op::Wildcard
870+
)
871+
})
872+
.filter_map(|c| {
873+
// Only do it when full precision is specified
874+
c.minor.and_then(|minor| {
875+
c.patch.map(|patch| semver::Version {
876+
major: c.major,
877+
minor,
878+
patch,
879+
pre: c.pre.clone(),
880+
build: Default::default(),
881+
})
882+
})
883+
})
884+
.max()
885+
.map(|v| v.to_string())
886+
}

tests/testsuite/cargo_add/detect_workspace_inherit_features/stderr.log

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Adding foo (workspace) to dependencies.
2-
Features:
2+
Features as of v0.0.0:
33
+ default-base
44
+ default-merge-base
55
+ default-test-base
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
Adding cargo-list-test-fixture-dependency (local) to dev-dependencies.
2-
Features:
2+
Features as of v0.0.0:
33
- one
44
- two

tests/testsuite/cargo_add/merge_activated_features/stderr.log

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Adding foo (workspace) to dependencies.
2-
Features:
2+
Features as of v0.0.0:
33
+ default-base
44
+ default-merge-base
55
+ default-test-base
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
Adding foo (workspace) to dependencies.
2-
Features:
2+
Features as of v0.0.0:
33
+ test

0 commit comments

Comments
 (0)