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