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