1mod crate_spec;
4
5use std::collections::BTreeMap;
6use std::collections::BTreeSet;
7use std::collections::VecDeque;
8use std::fmt::Write;
9use std::path::Path;
10use std::str::FromStr;
11
12use anyhow::Context as _;
13use cargo_util::paths;
14use cargo_util_schemas::core::PartialVersion;
15use cargo_util_schemas::manifest::PathBaseName;
16use cargo_util_schemas::manifest::RustVersion;
17use indexmap::IndexSet;
18use itertools::Itertools;
19use toml_edit::Item as TomlItem;
20
21use crate::core::dependency::DepKind;
22use crate::core::registry::PackageRegistry;
23use crate::core::FeatureValue;
24use crate::core::Features;
25use crate::core::Package;
26use crate::core::Registry;
27use crate::core::Shell;
28use crate::core::Summary;
29use crate::core::Workspace;
30use crate::sources::source::QueryKind;
31use crate::util::cache_lock::CacheLockMode;
32use crate::util::edit_distance;
33use crate::util::style;
34use crate::util::toml::lookup_path_base;
35use crate::util::toml_mut::dependency::Dependency;
36use crate::util::toml_mut::dependency::GitSource;
37use crate::util::toml_mut::dependency::MaybeWorkspace;
38use crate::util::toml_mut::dependency::PathSource;
39use crate::util::toml_mut::dependency::Source;
40use crate::util::toml_mut::dependency::WorkspaceSource;
41use crate::util::toml_mut::manifest::DepTable;
42use crate::util::toml_mut::manifest::LocalManifest;
43use crate::CargoResult;
44use crate::GlobalContext;
45use crate_spec::CrateSpec;
46
47const MAX_FEATURE_PRINTS: usize = 30;
48
49#[derive(Clone, Debug)]
51pub struct AddOptions<'a> {
52 pub gctx: &'a GlobalContext,
54 pub spec: &'a Package,
56 pub dependencies: Vec<DepOp>,
58 pub section: DepTable,
60 pub dry_run: bool,
62 pub honor_rust_version: Option<bool>,
64}
65
66pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<()> {
68 let dep_table = options
69 .section
70 .to_table()
71 .into_iter()
72 .map(String::from)
73 .collect::<Vec<_>>();
74
75 let manifest_path = options.spec.manifest_path().to_path_buf();
76 let mut manifest = LocalManifest::try_new(&manifest_path)?;
77 let original_raw_manifest = manifest.to_string();
78 let legacy = manifest.get_legacy_sections();
79 if !legacy.is_empty() {
80 anyhow::bail!(
81 "Deprecated dependency sections are unsupported: {}",
82 legacy.join(", ")
83 );
84 }
85
86 let mut registry = workspace.package_registry()?;
87
88 let deps = {
89 let _lock = options
90 .gctx
91 .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
92 registry.lock_patches();
93 options
94 .dependencies
95 .iter()
96 .map(|raw| {
97 resolve_dependency(
98 &manifest,
99 raw,
100 workspace,
101 &options.spec,
102 &options.section,
103 options.honor_rust_version,
104 options.gctx,
105 &mut registry,
106 )
107 })
108 .collect::<CargoResult<Vec<_>>>()?
109 };
110
111 let was_sorted = manifest
112 .get_table(&dep_table)
113 .map(TomlItem::as_table)
114 .map_or(true, |table_option| {
115 table_option.map_or(true, |table| {
116 table
117 .get_values()
118 .iter_mut()
119 .map(|(key, _)| {
120 key.remove(0)
122 })
123 .is_sorted()
124 })
125 });
126 for dep in deps {
127 print_action_msg(&mut options.gctx.shell(), &dep, &dep_table)?;
128 if let Some(Source::Path(src)) = dep.source() {
129 if src.path == manifest.path.parent().unwrap_or_else(|| Path::new("")) {
130 anyhow::bail!(
131 "cannot add `{}` as a dependency to itself",
132 manifest.package_name()?
133 )
134 }
135 }
136
137 let available_features = dep
138 .available_features
139 .keys()
140 .map(|s| s.as_ref())
141 .collect::<BTreeSet<&str>>();
142 let mut unknown_features: Vec<&str> = Vec::new();
143 if let Some(req_feats) = dep.features.as_ref() {
144 let req_feats: BTreeSet<_> = req_feats.iter().map(|s| s.as_str()).collect();
145 unknown_features.extend(req_feats.difference(&available_features).copied());
146 }
147 if let Some(inherited_features) = dep.inherited_features.as_ref() {
148 let inherited_features: BTreeSet<_> =
149 inherited_features.iter().map(|s| s.as_str()).collect();
150 unknown_features.extend(inherited_features.difference(&available_features).copied());
151 }
152
153 unknown_features.sort();
154
155 if !unknown_features.is_empty() {
156 let (mut activated, mut deactivated) = dep.features();
157 deactivated.retain(|f| !unknown_features.contains(f));
160 activated.retain(|f| !unknown_features.contains(f));
161
162 let mut message = format!(
163 "unrecognized feature{} for crate {}: {}",
164 if unknown_features.len() == 1 { "" } else { "s" },
165 dep.name,
166 unknown_features.iter().format(", "),
167 );
168 if activated.is_empty() && deactivated.is_empty() {
169 write!(message, "\n\nno features available for crate {}", dep.name)?;
170 } else {
171 let mut suggested = false;
172 for unknown_feature in &unknown_features {
173 let suggestion = edit_distance::closest_msg(
174 unknown_feature,
175 deactivated.iter().chain(activated.iter()),
176 |dep| *dep,
177 "feature",
178 );
179 if !suggestion.is_empty() {
180 write!(message, "{suggestion}")?;
181 suggested = true;
182 }
183 }
184 if !deactivated.is_empty() && !suggested {
185 if deactivated.len() <= MAX_FEATURE_PRINTS {
186 write!(
187 message,
188 "\n\ndisabled features:\n {}",
189 deactivated
190 .iter()
191 .map(|s| s.to_string())
192 .coalesce(|x, y| if x.len() + y.len() < 78 {
193 Ok(format!("{x}, {y}"))
194 } else {
195 Err((x, y))
196 })
197 .into_iter()
198 .format("\n ")
199 )?;
200 } else {
201 write!(
202 message,
203 "\n\n{} disabled features available",
204 deactivated.len()
205 )?;
206 }
207 }
208 if !activated.is_empty() && !suggested {
209 if deactivated.len() + activated.len() <= MAX_FEATURE_PRINTS {
210 writeln!(
211 message,
212 "\n\nenabled features:\n {}",
213 activated
214 .iter()
215 .map(|s| s.to_string())
216 .coalesce(|x, y| if x.len() + y.len() < 78 {
217 Ok(format!("{x}, {y}"))
218 } else {
219 Err((x, y))
220 })
221 .into_iter()
222 .format("\n ")
223 )?;
224 } else {
225 writeln!(
226 message,
227 "\n\n{} enabled features available",
228 activated.len()
229 )?;
230 }
231 }
232 }
233 anyhow::bail!(message.trim().to_owned());
234 }
235
236 print_dep_table_msg(&mut options.gctx.shell(), &dep)?;
237
238 manifest.insert_into_table(
239 &dep_table,
240 &dep,
241 workspace.gctx(),
242 workspace.root(),
243 options.spec.manifest().unstable_features(),
244 )?;
245 if dep.optional == Some(true) {
246 let is_namespaced_features_supported =
247 check_rust_version_for_optional_dependency(options.spec.rust_version())?;
248 if is_namespaced_features_supported {
249 let dep_key = dep.toml_key();
250 if !manifest.is_explicit_dep_activation(dep_key) {
251 let table = manifest.get_table_mut(&[String::from("features")])?;
252 let dep_name = dep.rename.as_deref().unwrap_or(&dep.name);
253 let new_feature: toml_edit::Value =
254 [format!("dep:{dep_name}")].iter().collect();
255 table[dep_key] = toml_edit::value(new_feature);
256 options
257 .gctx
258 .shell()
259 .status("Adding", format!("feature `{dep_key}`"))?;
260 }
261 }
262 }
263 manifest.gc_dep(dep.toml_key());
264 }
265
266 if was_sorted {
267 if let Some(table) = manifest
268 .get_table_mut(&dep_table)
269 .ok()
270 .and_then(TomlItem::as_table_like_mut)
271 {
272 table.sort_values();
273 }
274 }
275
276 if let Some(locked_flag) = options.gctx.locked_flag() {
277 let new_raw_manifest = manifest.to_string();
278 if original_raw_manifest != new_raw_manifest {
279 anyhow::bail!(
280 "the manifest file {} needs to be updated but {locked_flag} was passed to prevent this",
281 manifest.path.display()
282 );
283 }
284 }
285
286 if options.dry_run {
287 options.gctx.shell().warn("aborting add due to dry run")?;
288 } else {
289 manifest.write()?;
290 }
291
292 Ok(())
293}
294
295#[derive(Clone, Debug, PartialEq, Eq)]
297pub struct DepOp {
298 pub crate_spec: Option<String>,
300 pub rename: Option<String>,
302
303 pub features: Option<IndexSet<String>>,
305 pub default_features: Option<bool>,
307
308 pub optional: Option<bool>,
310
311 pub public: Option<bool>,
313
314 pub registry: Option<String>,
316
317 pub path: Option<String>,
319 pub base: Option<String>,
321
322 pub git: Option<String>,
324 pub branch: Option<String>,
326 pub rev: Option<String>,
328 pub tag: Option<String>,
330}
331
332fn resolve_dependency(
333 manifest: &LocalManifest,
334 arg: &DepOp,
335 ws: &Workspace<'_>,
336 spec: &Package,
337 section: &DepTable,
338 honor_rust_version: Option<bool>,
339 gctx: &GlobalContext,
340 registry: &mut PackageRegistry<'_>,
341) -> CargoResult<DependencyUI> {
342 let crate_spec = arg
343 .crate_spec
344 .as_deref()
345 .map(CrateSpec::resolve)
346 .transpose()?;
347 let mut selected_dep = if let Some(url) = &arg.git {
348 let mut src = GitSource::new(url);
349 if let Some(branch) = &arg.branch {
350 src = src.set_branch(branch);
351 }
352 if let Some(tag) = &arg.tag {
353 src = src.set_tag(tag);
354 }
355 if let Some(rev) = &arg.rev {
356 src = src.set_rev(rev);
357 }
358
359 let selected = if let Some(crate_spec) = &crate_spec {
360 if let Some(v) = crate_spec.version_req() {
361 anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{v}`).");
363 }
364 let dependency = crate_spec.to_dependency()?.set_source(src);
365 let selected = select_package(&dependency, gctx, registry)?;
366 if dependency.name != selected.name {
367 gctx.shell().warn(format!(
368 "translating `{}` to `{}`",
369 dependency.name, selected.name,
370 ))?;
371 }
372 selected
373 } else {
374 let mut source = crate::sources::GitSource::new(src.source_id()?, gctx)?;
375 let packages = source.read_packages()?;
376 let package = infer_package_for_git_source(packages, &src)?;
377 Dependency::from(package.summary())
378 };
379 selected
380 } else if let Some(raw_path) = &arg.path {
381 let path = paths::normalize_path(&std::env::current_dir()?.join(raw_path));
382 let mut src = PathSource::new(path);
383 src.base = arg.base.clone();
384
385 if let Some(base) = &arg.base {
386 let workspace_root = || Ok(ws.root_manifest().parent().unwrap());
388 lookup_path_base(
389 &PathBaseName::new(base.clone())?,
390 &gctx,
391 &workspace_root,
392 spec.manifest().unstable_features(),
393 )?;
394 }
395
396 let selected = if let Some(crate_spec) = &crate_spec {
397 if let Some(v) = crate_spec.version_req() {
398 anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
400 }
401 let dependency = crate_spec.to_dependency()?.set_source(src);
402 let selected = select_package(&dependency, gctx, registry)?;
403 if dependency.name != selected.name {
404 gctx.shell().warn(format!(
405 "translating `{}` to `{}`",
406 dependency.name, selected.name,
407 ))?;
408 }
409 selected
410 } else {
411 let mut source = crate::sources::PathSource::new(&src.path, src.source_id()?, gctx);
412 let package = source.root_package()?;
413 let mut selected = Dependency::from(package.summary());
414 if let Some(Source::Path(selected_src)) = &mut selected.source {
415 selected_src.base = src.base;
416 }
417 selected
418 };
419 selected
420 } else if let Some(crate_spec) = &crate_spec {
421 crate_spec.to_dependency()?
422 } else {
423 anyhow::bail!("dependency name is required");
424 };
425 selected_dep = populate_dependency(selected_dep, arg);
426
427 let lookup = |dep_key: &_| {
428 get_existing_dependency(
429 ws,
430 spec.manifest().unstable_features(),
431 manifest,
432 dep_key,
433 section,
434 )
435 };
436 let old_dep = fuzzy_lookup(&mut selected_dep, lookup, gctx)?;
437 let mut dependency = if let Some(mut old_dep) = old_dep.clone() {
438 if old_dep.name != selected_dep.name {
439 if selected_dep.optional.is_none() {
441 selected_dep.optional = old_dep.optional;
442 }
443 selected_dep
444 } else {
445 if selected_dep.source().is_some() {
446 old_dep.source = selected_dep.source;
448 }
449 populate_dependency(old_dep, arg)
450 }
451 } else {
452 selected_dep
453 };
454
455 if dependency.source().is_none() {
456 let lookup = |toml_key: &_| {
459 Ok(find_workspace_dep(toml_key, ws, ws.root_manifest(), ws.unstable_features()).ok())
460 };
461 if let Some(_dep) = fuzzy_lookup(&mut dependency, lookup, gctx)? {
462 dependency = dependency.set_source(WorkspaceSource::new());
463 } else if let Some(package) = ws.members().find(|p| p.name().as_str() == dependency.name) {
464 let mut src = PathSource::new(package.root());
467 if section.kind() != DepKind::Development {
469 let op = "";
470 let v = format!("{op}{version}", version = package.version());
471 src = src.set_version(v);
472 }
473 dependency = dependency.set_source(src);
474 } else {
475 let latest =
476 get_latest_dependency(spec, &dependency, honor_rust_version, gctx, registry)?;
477
478 if dependency.name != latest.name {
479 gctx.shell().warn(format!(
480 "translating `{}` to `{}`",
481 dependency.name, latest.name,
482 ))?;
483 dependency.name = latest.name; }
485 dependency = dependency.set_source(latest.source.expect("latest always has a source"));
486 }
487 }
488
489 if let Some(Source::Workspace(_)) = dependency.source() {
490 check_invalid_ws_keys(dependency.toml_key(), arg)?;
491 }
492
493 let version_required = dependency.source().and_then(|s| s.as_registry()).is_some();
494 let version_optional_in_section = section.kind() == DepKind::Development;
495 let preserve_existing_version = old_dep
496 .as_ref()
497 .map(|d| d.version().is_some())
498 .unwrap_or(false);
499 if !version_required && !preserve_existing_version && version_optional_in_section {
500 dependency = dependency.clear_version();
502 }
503
504 let query = dependency.query(gctx)?;
505 let query = match query {
506 MaybeWorkspace::Workspace(_workspace) => {
507 let dep = find_workspace_dep(
508 dependency.toml_key(),
509 ws,
510 ws.root_manifest(),
511 ws.unstable_features(),
512 )?;
513 if let Some(features) = dep.features.clone() {
514 dependency = dependency.set_inherited_features(features);
515 }
516 let query = dep.query(gctx)?;
517 match query {
518 MaybeWorkspace::Workspace(_) => {
519 unreachable!("This should have been caught when parsing a workspace root")
520 }
521 MaybeWorkspace::Other(query) => query,
522 }
523 }
524 MaybeWorkspace::Other(query) => query,
525 };
526
527 let dependency = populate_available_features(dependency, &query, registry)?;
528
529 Ok(dependency)
530}
531
532fn fuzzy_lookup(
533 dependency: &mut Dependency,
534 lookup: impl Fn(&str) -> CargoResult<Option<Dependency>>,
535 gctx: &GlobalContext,
536) -> CargoResult<Option<Dependency>> {
537 if let Some(rename) = dependency.rename() {
538 return lookup(rename);
540 }
541
542 for name_permutation in [
543 dependency.name.clone(),
544 dependency.name.replace('-', "_"),
545 dependency.name.replace('_', "-"),
546 ] {
547 let Some(dep) = lookup(&name_permutation)? else {
548 continue;
549 };
550
551 if dependency.name != name_permutation {
552 if !matches!(dep.source, Some(Source::Registry(_))) {
554 continue;
555 }
556 gctx.shell().warn(format!(
557 "translating `{}` to `{}`",
558 dependency.name, &name_permutation,
559 ))?;
560 dependency.name = name_permutation;
561 }
562 return Ok(Some(dep));
563 }
564
565 Ok(None)
566}
567
568fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> {
578 fn err_msg(toml_key: &str, flag: &str, field: &str) -> String {
579 format!(
580 "cannot override workspace dependency with `{flag}`, \
581 either change `workspace.dependencies.{toml_key}.{field}` \
582 or define the dependency exclusively in the package's manifest"
583 )
584 }
585
586 if arg.default_features.is_some() {
587 anyhow::bail!(
588 "{}",
589 err_msg(toml_key, "--default-features", "default-features")
590 )
591 }
592 if arg.registry.is_some() {
593 anyhow::bail!("{}", err_msg(toml_key, "--registry", "registry"))
594 }
595 if arg.rename.is_some() {
597 anyhow::bail!("{}", err_msg(toml_key, "--rename", "package"))
598 }
599 Ok(())
600}
601
602fn check_rust_version_for_optional_dependency(
611 rust_version: Option<&RustVersion>,
612) -> CargoResult<bool> {
613 match rust_version {
614 Some(version) => {
615 let syntax_support_version = RustVersion::from_str("1.60.0")?;
616 Ok(&syntax_support_version <= version)
617 }
618 None => Ok(true),
619 }
620}
621
622fn get_existing_dependency(
627 ws: &Workspace<'_>,
628 unstable_features: &Features,
629 manifest: &LocalManifest,
630 dep_key: &str,
631 section: &DepTable,
632) -> CargoResult<Option<Dependency>> {
633 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
634 enum Key {
635 Error,
636 Dev,
637 Build,
638 Normal,
639 Existing,
640 }
641
642 let mut possible: Vec<_> = manifest
643 .get_dependency_versions(dep_key, ws, unstable_features)
644 .map(|(path, dep)| {
645 let key = if path == *section {
646 (Key::Existing, true)
647 } else if dep.is_err() {
648 (Key::Error, path.target().is_some())
649 } else {
650 let key = match path.kind() {
651 DepKind::Normal => Key::Normal,
652 DepKind::Build => Key::Build,
653 DepKind::Development => Key::Dev,
654 };
655 (key, path.target().is_some())
656 };
657 (key, dep)
658 })
659 .collect();
660 possible.sort_by_key(|(key, _)| *key);
661 let Some((key, dep)) = possible.pop() else {
662 return Ok(None);
663 };
664 let mut dep = dep?;
665
666 if key.0 != Key::Existing {
667 let unrelated = dep;
670 dep = Dependency::new(&unrelated.name);
671 dep.source = unrelated.source.clone();
672 dep.registry = unrelated.registry.clone();
673
674 let version_required = unrelated.source().and_then(|s| s.as_registry()).is_some();
677 let version_optional_in_section = section.kind() == DepKind::Development;
678 if !version_required && version_optional_in_section {
679 dep = dep.clear_version();
680 }
681 }
682
683 Ok(Some(dep))
684}
685
686fn get_latest_dependency(
687 spec: &Package,
688 dependency: &Dependency,
689 honor_rust_version: Option<bool>,
690 gctx: &GlobalContext,
691 registry: &mut PackageRegistry<'_>,
692) -> CargoResult<Dependency> {
693 let query = dependency.query(gctx)?;
694 match query {
695 MaybeWorkspace::Workspace(_) => {
696 unreachable!("registry dependencies required, found a workspace dependency");
697 }
698 MaybeWorkspace::Other(query) => {
699 let possibilities = loop {
700 match registry.query_vec(&query, QueryKind::Normalized) {
701 std::task::Poll::Ready(res) => {
702 break res?;
703 }
704 std::task::Poll::Pending => registry.block_until_ready()?,
705 }
706 };
707
708 let mut possibilities: Vec<_> = possibilities
709 .into_iter()
710 .map(|s| s.into_summary())
711 .collect();
712
713 possibilities.sort_by_key(|s| {
714 let stable = s.version().pre.is_empty();
717 (stable, s.version().clone())
718 });
719
720 let mut latest = possibilities.last().ok_or_else(|| {
721 anyhow::format_err!(
722 "the crate `{dependency}` could not be found in registry index."
723 )
724 })?;
725
726 if honor_rust_version.unwrap_or(true) {
727 let (req_msrv, is_msrv) = spec
728 .rust_version()
729 .cloned()
730 .map(|msrv| CargoResult::Ok((msrv.clone().into_partial(), true)))
731 .unwrap_or_else(|| {
732 let rustc = gctx.load_global_rustc(None)?;
733
734 let rustc_version = rustc.version.clone().into();
736 Ok((rustc_version, false))
737 })?;
738
739 let msrvs = possibilities
740 .iter()
741 .map(|s| (s, s.rust_version()))
742 .collect::<Vec<_>>();
743
744 let latest_msrv = latest_compatible(&msrvs, &req_msrv).ok_or_else(|| {
749 let name = spec.name();
750 let dep_name = &dependency.name;
751 let latest_version = latest.version();
752 let latest_msrv = latest
753 .rust_version()
754 .expect("as `None` are compatible, we can't be here");
755 if is_msrv {
756 anyhow::format_err!(
757 "\
758no version of crate `{dep_name}` can maintain {name}'s rust-version of {req_msrv}
759help: pass `--ignore-rust-version` to select {dep_name}@{latest_version} which requires rustc {latest_msrv}"
760 )
761 } else {
762 anyhow::format_err!(
763 "\
764no version of crate `{dep_name}` is compatible with rustc {req_msrv}
765help: pass `--ignore-rust-version` to select {dep_name}@{latest_version} which requires rustc {latest_msrv}"
766 )
767 }
768 })?;
769
770 if latest_msrv.version() < latest.version() {
771 let latest_version = latest.version();
772 let latest_rust_version = latest.rust_version().unwrap();
773 let name = spec.name();
774 if is_msrv {
775 gctx.shell().warn(format_args!(
776 "\
777ignoring {dependency}@{latest_version} (which requires rustc {latest_rust_version}) to maintain {name}'s rust-version of {req_msrv}",
778 ))?;
779 } else {
780 gctx.shell().warn(format_args!(
781 "\
782ignoring {dependency}@{latest_version} (which requires rustc {latest_rust_version}) as it is incompatible with rustc {req_msrv}",
783 ))?;
784 }
785
786 latest = latest_msrv;
787 }
788 }
789
790 let mut dep = Dependency::from(latest);
791 if let Some(reg_name) = dependency.registry.as_deref() {
792 dep = dep.set_registry(reg_name);
793 }
794 Ok(dep)
795 }
796 }
797}
798
799fn latest_compatible<'s>(
804 msrvs: &[(&'s Summary, Option<&RustVersion>)],
805 pkg_msrv: &PartialVersion,
806) -> Option<&'s Summary> {
807 msrvs
808 .iter()
809 .filter(|(_, dep_msrv)| {
810 dep_msrv
811 .as_ref()
812 .map(|dep_msrv| dep_msrv.is_compatible_with(pkg_msrv))
813 .unwrap_or(true)
814 })
815 .map(|(s, _)| s)
816 .last()
817 .copied()
818}
819
820fn select_package(
821 dependency: &Dependency,
822 gctx: &GlobalContext,
823 registry: &mut PackageRegistry<'_>,
824) -> CargoResult<Dependency> {
825 let query = dependency.query(gctx)?;
826 match query {
827 MaybeWorkspace::Workspace(_) => {
828 unreachable!("path or git dependency expected, found workspace dependency");
829 }
830 MaybeWorkspace::Other(query) => {
831 let possibilities = loop {
832 match registry.query_vec(&query, QueryKind::Normalized) {
834 std::task::Poll::Ready(res) => {
835 break res?;
836 }
837 std::task::Poll::Pending => registry.block_until_ready()?,
838 }
839 };
840
841 let possibilities: Vec<_> = possibilities
842 .into_iter()
843 .map(|s| s.into_summary())
844 .collect();
845
846 match possibilities.len() {
847 0 => {
848 let source = dependency
849 .source()
850 .expect("source should be resolved before here");
851 anyhow::bail!("the crate `{dependency}` could not be found at `{source}`")
852 }
853 1 => {
854 let mut dep = Dependency::from(&possibilities[0]);
855 if let Some(reg_name) = dependency.registry.as_deref() {
856 dep = dep.set_registry(reg_name);
857 }
858 if let Some(Source::Path(PathSource { base, .. })) = dependency.source() {
859 if let Some(Source::Path(dep_src)) = &mut dep.source {
860 dep_src.base = base.clone();
861 }
862 }
863 Ok(dep)
864 }
865 _ => {
866 let source = dependency
867 .source()
868 .expect("source should be resolved before here");
869 anyhow::bail!(
870 "unexpectedly found multiple copies of crate `{dependency}` at `{source}`"
871 )
872 }
873 }
874 }
875 }
876}
877
878fn infer_package_for_git_source(
879 mut packages: Vec<Package>,
880 src: &dyn std::fmt::Display,
881) -> CargoResult<Package> {
882 let package = match packages.len() {
883 0 => unreachable!(
884 "this function should only be called with packages from `GitSource::read_packages` \
885 and that call should error for us when there are no packages"
886 ),
887 1 => packages.pop().expect("match ensured element is present"),
888 _ => {
889 let mut names: Vec<_> = packages
890 .iter()
891 .map(|p| p.name().as_str().to_owned())
892 .collect();
893 names.sort_unstable();
894 anyhow::bail!(
895 "multiple packages found at `{src}`:\n {}\nTo disambiguate, run `cargo add --git {src} <package>`",
896 names
897 .iter()
898 .map(|s| s.to_string())
899 .coalesce(|x, y| if x.len() + y.len() < 78 {
900 Ok(format!("{x}, {y}"))
901 } else {
902 Err((x, y))
903 })
904 .into_iter()
905 .format("\n "),
906 );
907 }
908 };
909 Ok(package)
910}
911
912fn populate_dependency(mut dependency: Dependency, arg: &DepOp) -> Dependency {
913 if let Some(registry) = &arg.registry {
914 if registry.is_empty() {
915 dependency.registry = None;
916 } else {
917 dependency.registry = Some(registry.to_owned());
918 }
919 }
920 if let Some(value) = arg.optional {
921 if value {
922 dependency.optional = Some(true);
923 } else {
924 dependency.optional = None;
925 }
926 }
927 if let Some(value) = arg.public {
928 if value {
929 dependency.public = Some(true);
930 } else {
931 dependency.public = None;
932 }
933 }
934 if let Some(value) = arg.default_features {
935 if value {
936 dependency.default_features = None;
937 } else {
938 dependency.default_features = Some(false);
939 }
940 }
941 if let Some(value) = arg.features.as_ref() {
942 dependency = dependency.extend_features(value.iter().cloned());
943 }
944
945 if let Some(rename) = &arg.rename {
946 dependency = dependency.set_rename(rename);
947 }
948
949 dependency
950}
951
952pub struct DependencyUI {
955 dep: Dependency,
957 available_version: Option<semver::Version>,
959 available_features: BTreeMap<String, Vec<String>>,
961}
962
963impl DependencyUI {
964 fn new(dep: Dependency) -> Self {
965 Self {
966 dep,
967 available_version: None,
968 available_features: Default::default(),
969 }
970 }
971
972 fn apply_summary(&mut self, summary: &Summary) {
973 self.available_version = Some(summary.version().clone());
974 self.available_features = summary
975 .features()
976 .iter()
977 .map(|(k, v)| {
978 (
979 k.as_str().to_owned(),
980 v.iter()
981 .filter_map(|v| match v {
982 FeatureValue::Feature(f) => Some(f.as_str().to_owned()),
983 FeatureValue::Dep { .. } | FeatureValue::DepFeature { .. } => None,
984 })
985 .collect::<Vec<_>>(),
986 )
987 })
988 .collect();
989 }
990
991 fn features(&self) -> (IndexSet<&str>, IndexSet<&str>) {
992 let mut activated: IndexSet<_> =
993 self.features.iter().flatten().map(|s| s.as_str()).collect();
994 if self.default_features().unwrap_or(true) {
995 activated.insert("default");
996 }
997 activated.extend(self.inherited_features.iter().flatten().map(|s| s.as_str()));
998 let mut walk: VecDeque<_> = activated.iter().cloned().collect();
999 while let Some(next) = walk.pop_front() {
1000 walk.extend(
1001 self.available_features
1002 .get(next)
1003 .into_iter()
1004 .flatten()
1005 .map(|s| s.as_str())
1006 .filter(|s| !activated.contains(s)),
1007 );
1008 activated.extend(
1009 self.available_features
1010 .get(next)
1011 .into_iter()
1012 .flatten()
1013 .map(|s| s.as_str()),
1014 );
1015 }
1016 activated.swap_remove("default");
1017 activated.sort();
1018 let mut deactivated = self
1019 .available_features
1020 .keys()
1021 .filter(|f| !activated.contains(f.as_str()) && *f != "default")
1022 .map(|f| f.as_str())
1023 .collect::<IndexSet<_>>();
1024 deactivated.sort();
1025 (activated, deactivated)
1026 }
1027}
1028
1029impl<'s> From<&'s Summary> for DependencyUI {
1030 fn from(other: &'s Summary) -> Self {
1031 let dep = Dependency::from(other);
1032 let mut dep = Self::new(dep);
1033 dep.apply_summary(other);
1034 dep
1035 }
1036}
1037
1038impl std::fmt::Display for DependencyUI {
1039 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1040 self.dep.fmt(f)
1041 }
1042}
1043
1044impl std::ops::Deref for DependencyUI {
1045 type Target = Dependency;
1046
1047 fn deref(&self) -> &Self::Target {
1048 &self.dep
1049 }
1050}
1051
1052fn populate_available_features(
1054 dependency: Dependency,
1055 query: &crate::core::dependency::Dependency,
1056 registry: &mut PackageRegistry<'_>,
1057) -> CargoResult<DependencyUI> {
1058 let mut dependency = DependencyUI::new(dependency);
1059
1060 if !dependency.available_features.is_empty() {
1061 return Ok(dependency);
1062 }
1063
1064 let possibilities = loop {
1065 match registry.query_vec(&query, QueryKind::Normalized) {
1066 std::task::Poll::Ready(res) => {
1067 break res?;
1068 }
1069 std::task::Poll::Pending => registry.block_until_ready()?,
1070 }
1071 };
1072 let lowest_common_denominator = possibilities
1075 .iter()
1076 .map(|s| s.as_summary())
1077 .min_by_key(|s| {
1078 let is_pre = !s.version().pre.is_empty();
1081 (is_pre, s.version())
1082 })
1083 .ok_or_else(|| {
1084 anyhow::format_err!("the crate `{dependency}` could not be found in registry index.")
1085 })?;
1086 dependency.apply_summary(&lowest_common_denominator);
1087
1088 Ok(dependency)
1089}
1090
1091fn print_action_msg(shell: &mut Shell, dep: &DependencyUI, section: &[String]) -> CargoResult<()> {
1092 if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
1093 return Ok(());
1094 }
1095
1096 let mut message = String::new();
1097 write!(message, "{}", dep.name)?;
1098 match dep.source() {
1099 Some(Source::Registry(src)) => {
1100 if src.version.chars().next().unwrap_or('0').is_ascii_digit() {
1101 write!(message, " v{}", src.version)?;
1102 } else {
1103 write!(message, " {}", src.version)?;
1104 }
1105 }
1106 Some(Source::Path(_)) => {
1107 write!(message, " (local)")?;
1108 }
1109 Some(Source::Git(_)) => {
1110 write!(message, " (git)")?;
1111 }
1112 Some(Source::Workspace(_)) => {
1113 write!(message, " (workspace)")?;
1114 }
1115 None => {}
1116 }
1117 write!(message, " to")?;
1118 if dep.optional().unwrap_or(false) {
1119 write!(message, " optional")?;
1120 }
1121 if dep.public().unwrap_or(false) {
1122 write!(message, " public")?;
1123 }
1124 let section = if section.len() == 1 {
1125 section[0].clone()
1126 } else {
1127 format!("{} for target `{}`", §ion[2], §ion[1])
1128 };
1129 write!(message, " {section}")?;
1130 shell.status("Adding", message)
1131}
1132
1133fn print_dep_table_msg(shell: &mut Shell, dep: &DependencyUI) -> CargoResult<()> {
1134 if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
1135 return Ok(());
1136 }
1137
1138 let stderr = shell.err();
1139 let good = style::GOOD;
1140 let error = style::ERROR;
1141
1142 let (activated, deactivated) = dep.features();
1143 if !activated.is_empty() || !deactivated.is_empty() {
1144 let prefix = format!("{:>13}", " ");
1145 let suffix = format_features_version_suffix(&dep);
1146
1147 writeln!(stderr, "{prefix}Features{suffix}:")?;
1148
1149 let total_activated = activated.len();
1150 let total_deactivated = deactivated.len();
1151
1152 if total_activated <= MAX_FEATURE_PRINTS {
1153 for feat in activated {
1154 writeln!(stderr, "{prefix}{good}+{good:#} {feat}")?;
1155 }
1156 } else {
1157 writeln!(stderr, "{prefix}{total_activated} activated features")?;
1158 }
1159
1160 if total_activated + total_deactivated <= MAX_FEATURE_PRINTS {
1161 for feat in deactivated {
1162 writeln!(stderr, "{prefix}{error}-{error:#} {feat}")?;
1163 }
1164 } else {
1165 writeln!(stderr, "{prefix}{total_deactivated} deactivated features")?;
1166 }
1167 }
1168
1169 Ok(())
1170}
1171
1172fn format_features_version_suffix(dep: &DependencyUI) -> String {
1173 if let Some(version) = &dep.available_version {
1174 let mut version = version.clone();
1175 version.build = Default::default();
1176 let version = version.to_string();
1177 let version_req = dep
1180 .version()
1181 .and_then(|v| semver::VersionReq::parse(v).ok())
1182 .and_then(|v| precise_version(&v));
1183 if version_req.as_deref() != Some(version.as_str()) {
1184 format!(" as of v{version}")
1185 } else {
1186 "".to_owned()
1187 }
1188 } else {
1189 "".to_owned()
1190 }
1191}
1192
1193fn find_workspace_dep(
1194 toml_key: &str,
1195 ws: &Workspace<'_>,
1196 root_manifest: &Path,
1197 unstable_features: &Features,
1198) -> CargoResult<Dependency> {
1199 let manifest = LocalManifest::try_new(root_manifest)?;
1200 let manifest = manifest
1201 .data
1202 .as_item()
1203 .as_table_like()
1204 .context("could not make `manifest.data` into a table")?;
1205 let workspace = manifest
1206 .get("workspace")
1207 .context("could not find `workspace`")?
1208 .as_table_like()
1209 .context("could not make `manifest.data.workspace` into a table")?;
1210 let dependencies = workspace
1211 .get("dependencies")
1212 .context("could not find `dependencies` table in `workspace`")?
1213 .as_table_like()
1214 .context("could not make `dependencies` into a table")?;
1215 let dep_item = dependencies
1216 .get(toml_key)
1217 .with_context(|| format!("could not find {toml_key} in `workspace.dependencies`"))?;
1218 Dependency::from_toml(
1219 ws.gctx(),
1220 ws.root(),
1221 root_manifest.parent().unwrap(),
1222 unstable_features,
1223 toml_key,
1224 dep_item,
1225 )
1226}
1227
1228fn precise_version(version_req: &semver::VersionReq) -> Option<String> {
1231 version_req
1232 .comparators
1233 .iter()
1234 .filter(|c| {
1235 matches!(
1236 c.op,
1237 semver::Op::Exact
1239 | semver::Op::GreaterEq
1240 | semver::Op::LessEq
1241 | semver::Op::Tilde
1242 | semver::Op::Caret
1243 | semver::Op::Wildcard
1244 )
1245 })
1246 .filter_map(|c| {
1247 c.minor.and_then(|minor| {
1249 c.patch.map(|patch| semver::Version {
1250 major: c.major,
1251 minor,
1252 patch,
1253 pre: c.pre.clone(),
1254 build: Default::default(),
1255 })
1256 })
1257 })
1258 .max()
1259 .map(|v| v.to_string())
1260}