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