]> git.proxmox.com Git - cargo.git/blob - src/cargo/ops/cargo_add/mod.rs
b6491f59dbbf9490934f800ac64c2b2c5a65f7b2
[cargo.git] / src / cargo / ops / cargo_add / mod.rs
1 //! Core of cargo-add command
2
3 mod crate_spec;
4 mod dependency;
5 mod manifest;
6
7 use std::collections::BTreeMap;
8 use std::collections::BTreeSet;
9 use std::collections::VecDeque;
10 use std::path::Path;
11
12 use anyhow::Context as _;
13 use cargo_util::paths;
14 use indexmap::IndexSet;
15 use termcolor::Color::Green;
16 use termcolor::Color::Red;
17 use termcolor::ColorSpec;
18 use toml_edit::Item as TomlItem;
19
20 use crate::core::dependency::DepKind;
21 use crate::core::registry::PackageRegistry;
22 use crate::core::FeatureValue;
23 use crate::core::Package;
24 use crate::core::QueryKind;
25 use crate::core::Registry;
26 use crate::core::Shell;
27 use crate::core::Summary;
28 use crate::core::Workspace;
29 use crate::CargoResult;
30 use crate::Config;
31 use crate_spec::CrateSpec;
32 use dependency::Dependency;
33 use dependency::GitSource;
34 use dependency::PathSource;
35 use dependency::RegistrySource;
36 use dependency::Source;
37 use manifest::LocalManifest;
38
39 use crate::ops::cargo_add::dependency::{MaybeWorkspace, WorkspaceSource};
40 pub use manifest::DepTable;
41
42 /// Information on what dependencies should be added
43 #[derive(Clone, Debug)]
44 pub struct AddOptions<'a> {
45 /// Configuration information for cargo operations
46 pub config: &'a Config,
47 /// Package to add dependencies to
48 pub spec: &'a Package,
49 /// Dependencies to add or modify
50 pub dependencies: Vec<DepOp>,
51 /// Which dependency section to add these to
52 pub section: DepTable,
53 /// Act as if dependencies will be added
54 pub dry_run: bool,
55 }
56
57 /// Add dependencies to a manifest
58 pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<()> {
59 let dep_table = options
60 .section
61 .to_table()
62 .into_iter()
63 .map(String::from)
64 .collect::<Vec<_>>();
65
66 let manifest_path = options.spec.manifest_path().to_path_buf();
67 let mut manifest = LocalManifest::try_new(&manifest_path)?;
68 let original_raw_manifest = manifest.to_string();
69 let legacy = manifest.get_legacy_sections();
70 if !legacy.is_empty() {
71 anyhow::bail!(
72 "Deprecated dependency sections are unsupported: {}",
73 legacy.join(", ")
74 );
75 }
76
77 let mut registry = PackageRegistry::new(options.config)?;
78
79 let deps = {
80 let _lock = options.config.acquire_package_cache_lock()?;
81 registry.lock_patches();
82 options
83 .dependencies
84 .iter()
85 .map(|raw| {
86 resolve_dependency(
87 &manifest,
88 raw,
89 workspace,
90 &options.section,
91 options.config,
92 &mut registry,
93 )
94 })
95 .collect::<CargoResult<Vec<_>>>()?
96 };
97
98 let was_sorted = manifest
99 .get_table(&dep_table)
100 .map(TomlItem::as_table)
101 .map_or(true, |table_option| {
102 table_option.map_or(true, |table| is_sorted(table.iter().map(|(name, _)| name)))
103 });
104 for dep in deps {
105 print_msg(&mut options.config.shell(), &dep, &dep_table)?;
106 if let Some(Source::Path(src)) = dep.source() {
107 if src.path == manifest.path.parent().unwrap_or_else(|| Path::new("")) {
108 anyhow::bail!(
109 "cannot add `{}` as a dependency to itself",
110 manifest.package_name()?
111 )
112 }
113 }
114
115 let available_features = dep
116 .available_features
117 .keys()
118 .map(|s| s.as_ref())
119 .collect::<BTreeSet<&str>>();
120 let mut unknown_features: Vec<&str> = Vec::new();
121 if let Some(req_feats) = dep.features.as_ref() {
122 let req_feats: BTreeSet<_> = req_feats.iter().map(|s| s.as_str()).collect();
123 unknown_features.extend(req_feats.difference(&available_features).copied());
124 }
125 if let Some(inherited_features) = dep.inherited_features.as_ref() {
126 let inherited_features: BTreeSet<_> =
127 inherited_features.iter().map(|s| s.as_str()).collect();
128 unknown_features.extend(inherited_features.difference(&available_features).copied());
129 }
130 unknown_features.sort();
131 if !unknown_features.is_empty() {
132 anyhow::bail!("unrecognized features: {unknown_features:?}");
133 }
134
135 manifest.insert_into_table(&dep_table, &dep)?;
136 manifest.gc_dep(dep.toml_key());
137 }
138
139 if was_sorted {
140 if let Some(table) = manifest
141 .get_table_mut(&dep_table)
142 .ok()
143 .and_then(TomlItem::as_table_like_mut)
144 {
145 table.sort_values();
146 }
147 }
148
149 if options.config.locked() {
150 let new_raw_manifest = manifest.to_string();
151 if original_raw_manifest != new_raw_manifest {
152 anyhow::bail!(
153 "the manifest file {} needs to be updated but --locked was passed to prevent this",
154 manifest.path.display()
155 );
156 }
157 }
158
159 if options.dry_run {
160 options.config.shell().warn("aborting add due to dry run")?;
161 } else {
162 manifest.write()?;
163 }
164
165 Ok(())
166 }
167
168 /// Dependency entry operation
169 #[derive(Clone, Debug, PartialEq, Eq)]
170 pub struct DepOp {
171 /// Describes the crate
172 pub crate_spec: Option<String>,
173 /// Dependency key, overriding the package name in crate_spec
174 pub rename: Option<String>,
175
176 /// Feature flags to activate
177 pub features: Option<IndexSet<String>>,
178 /// Whether the default feature should be activated
179 pub default_features: Option<bool>,
180
181 /// Whether dependency is optional
182 pub optional: Option<bool>,
183
184 /// Registry for looking up dependency version
185 pub registry: Option<String>,
186
187 /// Git repo for dependency
188 pub path: Option<String>,
189 /// Git repo for dependency
190 pub git: Option<String>,
191 /// Specify an alternative git branch
192 pub branch: Option<String>,
193 /// Specify a specific git rev
194 pub rev: Option<String>,
195 /// Specify a specific git tag
196 pub tag: Option<String>,
197 }
198
199 fn resolve_dependency(
200 manifest: &LocalManifest,
201 arg: &DepOp,
202 ws: &Workspace<'_>,
203 section: &DepTable,
204 config: &Config,
205 registry: &mut PackageRegistry<'_>,
206 ) -> CargoResult<DependencyUI> {
207 let crate_spec = arg
208 .crate_spec
209 .as_deref()
210 .map(CrateSpec::resolve)
211 .transpose()?;
212 let mut selected_dep = if let Some(url) = &arg.git {
213 let mut src = GitSource::new(url);
214 if let Some(branch) = &arg.branch {
215 src = src.set_branch(branch);
216 }
217 if let Some(tag) = &arg.tag {
218 src = src.set_tag(tag);
219 }
220 if let Some(rev) = &arg.rev {
221 src = src.set_rev(rev);
222 }
223
224 let selected = if let Some(crate_spec) = &crate_spec {
225 if let Some(v) = crate_spec.version_req() {
226 // crate specifier includes a version (e.g. `docopt@0.8`)
227 anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{v}`).");
228 }
229 let dependency = crate_spec.to_dependency()?.set_source(src);
230 let selected = select_package(&dependency, config, registry)?;
231 if dependency.name != selected.name {
232 config.shell().warn(format!(
233 "translating `{}` to `{}`",
234 dependency.name, selected.name,
235 ))?;
236 }
237 selected
238 } else {
239 let mut source = crate::sources::GitSource::new(src.source_id()?, config)?;
240 let packages = source.read_packages()?;
241 let package = infer_package(packages, &src)?;
242 Dependency::from(package.summary())
243 };
244 selected
245 } else if let Some(raw_path) = &arg.path {
246 let path = paths::normalize_path(&std::env::current_dir()?.join(raw_path));
247 let src = PathSource::new(&path);
248
249 let selected = if let Some(crate_spec) = &crate_spec {
250 if let Some(v) = crate_spec.version_req() {
251 // crate specifier includes a version (e.g. `docopt@0.8`)
252 anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
253 }
254 let dependency = crate_spec.to_dependency()?.set_source(src);
255 let selected = select_package(&dependency, config, registry)?;
256 if dependency.name != selected.name {
257 config.shell().warn(format!(
258 "translating `{}` to `{}`",
259 dependency.name, selected.name,
260 ))?;
261 }
262 selected
263 } else {
264 let source = crate::sources::PathSource::new(&path, src.source_id()?, config);
265 let packages = source.read_packages()?;
266 let package = infer_package(packages, &src)?;
267 Dependency::from(package.summary())
268 };
269 selected
270 } else if let Some(crate_spec) = &crate_spec {
271 crate_spec.to_dependency()?
272 } else {
273 anyhow::bail!("dependency name is required");
274 };
275 selected_dep = populate_dependency(selected_dep, arg);
276
277 let old_dep = get_existing_dependency(manifest, selected_dep.toml_key(), section)?;
278 let mut dependency = if let Some(mut old_dep) = old_dep.clone() {
279 if old_dep.name != selected_dep.name {
280 // Assuming most existing keys are not relevant when the package changes
281 if selected_dep.optional.is_none() {
282 selected_dep.optional = old_dep.optional;
283 }
284 selected_dep
285 } else {
286 if selected_dep.source().is_some() {
287 // Overwrite with `crate_spec`
288 old_dep.source = selected_dep.source;
289 }
290 populate_dependency(old_dep, arg)
291 }
292 } else {
293 selected_dep
294 };
295
296 if dependency.source().is_none() {
297 // Checking for a workspace dependency happens first since a member could be specified
298 // in the workspace dependencies table as a dependency
299 if let Some(_dep) = find_workspace_dep(dependency.toml_key(), ws.root_manifest()).ok() {
300 dependency = dependency.set_source(WorkspaceSource::new());
301 } else if let Some(package) = ws.members().find(|p| p.name().as_str() == dependency.name) {
302 // Only special-case workspaces when the user doesn't provide any extra
303 // information, otherwise, trust the user.
304 let mut src = PathSource::new(package.root());
305 // dev-dependencies do not need the version populated
306 if section.kind() != DepKind::Development {
307 let op = "";
308 let v = format!("{op}{version}", version = package.version());
309 src = src.set_version(v);
310 }
311 dependency = dependency.set_source(src);
312 } else {
313 let latest = get_latest_dependency(&dependency, false, config, registry)?;
314
315 if dependency.name != latest.name {
316 config.shell().warn(format!(
317 "translating `{}` to `{}`",
318 dependency.name, latest.name,
319 ))?;
320 dependency.name = latest.name; // Normalize the name
321 }
322 dependency = dependency.set_source(latest.source.expect("latest always has a source"));
323 }
324 }
325
326 if let Some(Source::Workspace(_)) = dependency.source() {
327 check_invalid_ws_keys(dependency.toml_key(), arg)?;
328 }
329
330 let version_required = dependency.source().and_then(|s| s.as_registry()).is_some();
331 let version_optional_in_section = section.kind() == DepKind::Development;
332 let preserve_existing_version = old_dep
333 .as_ref()
334 .map(|d| d.version().is_some())
335 .unwrap_or(false);
336 if !version_required && !preserve_existing_version && version_optional_in_section {
337 // dev-dependencies do not need the version populated
338 dependency = dependency.clear_version();
339 }
340
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)?;
360
361 Ok(dependency)
362 }
363
364 /// When { workspace = true } you cannot define other keys that configure
365 /// the source of the dependency such as `version`, `registry`, `registry-index`,
366 /// `path`, `git`, `branch`, `tag`, `rev`, or `package`. You can also not define
367 /// `default-features`.
368 ///
369 /// Only `default-features`, `registry` and `rename` need to be checked
370 /// for currently. This is because `git` and its associated keys, `path`, and
371 /// `version` should all bee checked before this is called. `rename` is checked
372 /// for as it turns into `package`
373 fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> {
374 fn err_msg(toml_key: &str, flag: &str, field: &str) -> String {
375 format!(
376 "cannot override workspace dependency with `{flag}`, \
377 either change `workspace.dependencies.{toml_key}.{field}` \
378 or define the dependency exclusively in the package's manifest"
379 )
380 }
381
382 if arg.default_features.is_some() {
383 anyhow::bail!(
384 "{}",
385 err_msg(toml_key, "--default-features", "default-features")
386 )
387 }
388 if arg.registry.is_some() {
389 anyhow::bail!("{}", err_msg(toml_key, "--registry", "registry"))
390 }
391 // rename is `package`
392 if arg.rename.is_some() {
393 anyhow::bail!("{}", err_msg(toml_key, "--rename", "package"))
394 }
395 Ok(())
396 }
397
398 /// Provide the existing dependency for the target table
399 ///
400 /// If it doesn't exist but exists in another table, let's use that as most likely users
401 /// want to use the same version across all tables unless they are renaming.
402 fn get_existing_dependency(
403 manifest: &LocalManifest,
404 dep_key: &str,
405 section: &DepTable,
406 ) -> CargoResult<Option<Dependency>> {
407 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
408 enum Key {
409 Error,
410 Dev,
411 Build,
412 Normal,
413 Existing,
414 }
415
416 let mut possible: Vec<_> = manifest
417 .get_dependency_versions(dep_key)
418 .map(|(path, dep)| {
419 let key = if path == *section {
420 (Key::Existing, true)
421 } else if dep.is_err() {
422 (Key::Error, path.target().is_some())
423 } else {
424 let key = match path.kind() {
425 DepKind::Normal => Key::Normal,
426 DepKind::Build => Key::Build,
427 DepKind::Development => Key::Dev,
428 };
429 (key, path.target().is_some())
430 };
431 (key, dep)
432 })
433 .collect();
434 possible.sort_by_key(|(key, _)| *key);
435 let (key, dep) = if let Some(item) = possible.pop() {
436 item
437 } else {
438 return Ok(None);
439 };
440 let mut dep = dep?;
441
442 if key.0 != Key::Existing {
443 // When the dep comes from a different section, we only care about the source and not any
444 // of the other fields, like `features`
445 let unrelated = dep;
446 dep = Dependency::new(&unrelated.name);
447 dep.source = unrelated.source.clone();
448 dep.registry = unrelated.registry.clone();
449
450 // dev-dependencies do not need the version populated when path is set though we
451 // should preserve it if the user chose to populate it.
452 let version_required = unrelated.source().and_then(|s| s.as_registry()).is_some();
453 let version_optional_in_section = section.kind() == DepKind::Development;
454 if !version_required && version_optional_in_section {
455 dep = dep.clear_version();
456 }
457 }
458
459 Ok(Some(dep))
460 }
461
462 fn get_latest_dependency(
463 dependency: &Dependency,
464 _flag_allow_prerelease: bool,
465 config: &Config,
466 registry: &mut PackageRegistry<'_>,
467 ) -> CargoResult<Dependency> {
468 let query = dependency.query(config)?;
469 match query {
470 MaybeWorkspace::Workspace(_) => {
471 unreachable!("registry dependencies required, found a workspace dependency");
472 }
473 MaybeWorkspace::Other(query) => {
474 let possibilities = loop {
475 match registry.query_vec(&query, QueryKind::Fuzzy) {
476 std::task::Poll::Ready(res) => {
477 break res?;
478 }
479 std::task::Poll::Pending => registry.block_until_ready()?,
480 }
481 };
482 let latest = possibilities
483 .iter()
484 .max_by_key(|s| {
485 // Fallback to a pre-release if no official release is available by sorting them as
486 // less.
487 let stable = s.version().pre.is_empty();
488 (stable, s.version())
489 })
490 .ok_or_else(|| {
491 anyhow::format_err!(
492 "the crate `{dependency}` could not be found in registry index."
493 )
494 })?;
495 let mut dep = Dependency::from(latest);
496 if let Some(reg_name) = dependency.registry.as_deref() {
497 dep = dep.set_registry(reg_name);
498 }
499 Ok(dep)
500 }
501 }
502 }
503
504 fn select_package(
505 dependency: &Dependency,
506 config: &Config,
507 registry: &mut PackageRegistry<'_>,
508 ) -> CargoResult<Dependency> {
509 let query = dependency.query(config)?;
510 match query {
511 MaybeWorkspace::Workspace(_) => {
512 unreachable!("path or git dependency expected, found workspace dependency");
513 }
514 MaybeWorkspace::Other(query) => {
515 let possibilities = loop {
516 // Exact to avoid returning all for path/git
517 match registry.query_vec(&query, QueryKind::Exact) {
518 std::task::Poll::Ready(res) => {
519 break res?;
520 }
521 std::task::Poll::Pending => registry.block_until_ready()?,
522 }
523 };
524 match possibilities.len() {
525 0 => {
526 let source = dependency
527 .source()
528 .expect("source should be resolved before here");
529 anyhow::bail!("the crate `{dependency}` could not be found at `{source}`")
530 }
531 1 => {
532 let mut dep = Dependency::from(&possibilities[0]);
533 if let Some(reg_name) = dependency.registry.as_deref() {
534 dep = dep.set_registry(reg_name);
535 }
536 Ok(dep)
537 }
538 _ => {
539 let source = dependency
540 .source()
541 .expect("source should be resolved before here");
542 anyhow::bail!(
543 "unexpectedly found multiple copies of crate `{dependency}` at `{source}`"
544 )
545 }
546 }
547 }
548 }
549 }
550
551 fn infer_package(mut packages: Vec<Package>, src: &dyn std::fmt::Display) -> CargoResult<Package> {
552 let package = match packages.len() {
553 0 => {
554 anyhow::bail!("no packages found at `{src}`");
555 }
556 1 => packages.pop().expect("match ensured element is present"),
557 _ => {
558 let mut names: Vec<_> = packages
559 .iter()
560 .map(|p| p.name().as_str().to_owned())
561 .collect();
562 names.sort_unstable();
563 anyhow::bail!("multiple packages found at `{src}`: {}", names.join(", "));
564 }
565 };
566 Ok(package)
567 }
568
569 fn populate_dependency(mut dependency: Dependency, arg: &DepOp) -> Dependency {
570 if let Some(registry) = &arg.registry {
571 if registry.is_empty() {
572 dependency.registry = None;
573 } else {
574 dependency.registry = Some(registry.to_owned());
575 }
576 }
577 if let Some(value) = arg.optional {
578 if value {
579 dependency.optional = Some(true);
580 } else {
581 dependency.optional = None;
582 }
583 }
584 if let Some(value) = arg.default_features {
585 if value {
586 dependency.default_features = None;
587 } else {
588 dependency.default_features = Some(false);
589 }
590 }
591 if let Some(value) = arg.features.as_ref() {
592 dependency = dependency.extend_features(value.iter().cloned());
593 }
594
595 if let Some(rename) = &arg.rename {
596 dependency = dependency.set_rename(rename);
597 }
598
599 dependency
600 }
601
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
665 /// Lookup available features
666 fn populate_available_features(
667 dependency: Dependency,
668 query: &crate::core::dependency::Dependency,
669 registry: &mut PackageRegistry<'_>,
670 ) -> CargoResult<DependencyUI> {
671 let mut dependency = DependencyUI::new(dependency);
672
673 if !dependency.available_features.is_empty() {
674 return Ok(dependency);
675 }
676
677 let possibilities = loop {
678 match registry.query_vec(&query, QueryKind::Fuzzy) {
679 std::task::Poll::Ready(res) => {
680 break res?;
681 }
682 std::task::Poll::Pending => registry.block_until_ready()?,
683 }
684 };
685 // Ensure widest feature flag compatibility by picking the earliest version that could show up
686 // in the lock file for a given version requirement.
687 let lowest_common_denominator = possibilities
688 .iter()
689 .min_by_key(|s| {
690 // Fallback to a pre-release if no official release is available by sorting them as
691 // more.
692 let is_pre = !s.version().pre.is_empty();
693 (is_pre, s.version())
694 })
695 .ok_or_else(|| {
696 anyhow::format_err!("the crate `{dependency}` could not be found in registry index.")
697 })?;
698 dependency.apply_summary(&lowest_common_denominator);
699
700 Ok(dependency)
701 }
702
703 fn print_msg(shell: &mut Shell, dep: &DependencyUI, section: &[String]) -> CargoResult<()> {
704 use std::fmt::Write;
705
706 if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
707 return Ok(());
708 }
709
710 let mut message = String::new();
711 write!(message, "{}", dep.name)?;
712 match dep.source() {
713 Some(Source::Registry(src)) => {
714 if src.version.chars().next().unwrap_or('0').is_ascii_digit() {
715 write!(message, " v{}", src.version)?;
716 } else {
717 write!(message, " {}", src.version)?;
718 }
719 }
720 Some(Source::Path(_)) => {
721 write!(message, " (local)")?;
722 }
723 Some(Source::Git(_)) => {
724 write!(message, " (git)")?;
725 }
726 Some(Source::Workspace(_)) => {
727 write!(message, " (workspace)")?;
728 }
729 None => {}
730 }
731 write!(message, " to")?;
732 if dep.optional().unwrap_or(false) {
733 write!(message, " optional")?;
734 }
735 let section = if section.len() == 1 {
736 section[0].clone()
737 } else {
738 format!("{} for target `{}`", &section[2], &section[1])
739 };
740 write!(message, " {section}")?;
741 write!(message, ".")?;
742 shell.status("Adding", message)?;
743
744 let mut activated: IndexSet<_> = dep.features.iter().flatten().map(|s| s.as_str()).collect();
745 if dep.default_features().unwrap_or(true) {
746 activated.insert("default");
747 }
748 activated.extend(dep.inherited_features.iter().flatten().map(|s| s.as_str()));
749 let mut walk: VecDeque<_> = activated.iter().cloned().collect();
750 while let Some(next) = walk.pop_front() {
751 walk.extend(
752 dep.available_features
753 .get(next)
754 .into_iter()
755 .flatten()
756 .map(|s| s.as_str()),
757 );
758 activated.extend(
759 dep.available_features
760 .get(next)
761 .into_iter()
762 .flatten()
763 .map(|s| s.as_str()),
764 );
765 }
766 activated.remove("default");
767 activated.sort();
768 let mut deactivated = dep
769 .available_features
770 .keys()
771 .filter(|f| !activated.contains(f.as_str()) && *f != "default")
772 .collect::<Vec<_>>();
773 deactivated.sort();
774 if !activated.is_empty() || !deactivated.is_empty() {
775 let prefix = format!("{:>13}", " ");
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 )?;
798 for feat in activated {
799 shell.write_stderr(&prefix, &ColorSpec::new())?;
800 shell.write_stderr('+', &ColorSpec::new().set_bold(true).set_fg(Some(Green)))?;
801 shell.write_stderr(format_args!(" {}\n", feat), &ColorSpec::new())?;
802 }
803 for feat in deactivated {
804 shell.write_stderr(&prefix, &ColorSpec::new())?;
805 shell.write_stderr('-', &ColorSpec::new().set_bold(true).set_fg(Some(Red)))?;
806 shell.write_stderr(format_args!(" {}\n", feat), &ColorSpec::new())?;
807 }
808 }
809
810 Ok(())
811 }
812
813 // Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized.
814 fn is_sorted(mut it: impl Iterator<Item = impl PartialOrd>) -> bool {
815 let mut last = match it.next() {
816 Some(e) => e,
817 None => return true,
818 };
819
820 for curr in it {
821 if curr < last {
822 return false;
823 }
824 last = curr;
825 }
826
827 true
828 }
829
830 fn find_workspace_dep(toml_key: &str, root_manifest: &Path) -> CargoResult<Dependency> {
831 let manifest = LocalManifest::try_new(root_manifest)?;
832 let manifest = manifest
833 .data
834 .as_item()
835 .as_table_like()
836 .context("could not make `manifest.data` into a table")?;
837 let workspace = manifest
838 .get("workspace")
839 .context("could not find `workspace`")?
840 .as_table_like()
841 .context("could not make `manifest.data.workspace` into a table")?;
842 let dependencies = workspace
843 .get("dependencies")
844 .context("could not find `dependencies` table in `workspace`")?
845 .as_table_like()
846 .context("could not make `dependencies` into a table")?;
847 let dep_item = dependencies.get(toml_key).context(format!(
848 "could not find {} in `workspace.dependencies`",
849 toml_key
850 ))?;
851 Dependency::from_toml(root_manifest.parent().unwrap(), toml_key, dep_item)
852 }
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 }