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