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