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