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