1 //! Core of cargo-add command
8 use std
::collections
::BTreeSet
;
9 use std
::collections
::VecDeque
;
12 use cargo_util
::paths
;
13 use indexmap
::IndexSet
;
14 use termcolor
::Color
::Green
;
15 use termcolor
::Color
::Red
;
16 use termcolor
::ColorSpec
;
17 use toml_edit
::Item
as TomlItem
;
19 use crate::core
::dependency
::DepKind
;
20 use crate::core
::registry
::PackageRegistry
;
21 use crate::core
::Package
;
22 use crate::core
::Registry
;
23 use crate::core
::Shell
;
24 use crate::core
::Workspace
;
25 use crate::CargoResult
;
27 use crate_spec
::CrateSpec
;
28 use dependency
::Dependency
;
29 use dependency
::GitSource
;
30 use dependency
::PathSource
;
31 use dependency
::RegistrySource
;
32 use dependency
::Source
;
33 use manifest
::LocalManifest
;
35 use crate::ops
::cargo_add
::dependency
::MaybeWorkspace
;
36 pub use manifest
::DepTable
;
38 /// Information on what dependencies should be added
39 #[derive(Clone, Debug)]
40 pub struct AddOptions
<'a
> {
41 /// Configuration information for cargo operations
42 pub config
: &'a Config
,
43 /// Package to add dependencies to
44 pub spec
: &'a Package
,
45 /// Dependencies to add or modify
46 pub dependencies
: Vec
<DepOp
>,
47 /// Which dependency section to add these to
48 pub section
: DepTable
,
49 /// Act as if dependencies will be added
53 /// Add dependencies to a manifest
54 pub fn add(workspace
: &Workspace
<'_
>, options
: &AddOptions
<'_
>) -> CargoResult
<()> {
55 let dep_table
= options
62 let manifest_path
= options
.spec
.manifest_path().to_path_buf();
63 let mut manifest
= LocalManifest
::try_new(&manifest_path
)?
;
64 let legacy
= manifest
.get_legacy_sections();
65 if !legacy
.is_empty() {
67 "Deprecated dependency sections are unsupported: {}",
72 let mut registry
= PackageRegistry
::new(options
.config
)?
;
75 let _lock
= options
.config
.acquire_package_cache_lock()?
;
76 registry
.lock_patches();
90 .collect
::<CargoResult
<Vec
<_
>>>()?
93 let was_sorted
= manifest
94 .get_table(&dep_table
)
95 .map(TomlItem
::as_table
)
96 .map_or(true, |table_option
| {
97 table_option
.map_or(true, |table
| is_sorted(table
.iter().map(|(name
, _
)| name
)))
100 print_msg(&mut options
.config
.shell(), &dep
, &dep_table
)?
;
101 if let Some(Source
::Path(src
)) = dep
.source() {
102 if src
.path
== manifest
.path
.parent().unwrap_or_else(|| Path
::new("")) {
104 "cannot add `{}` as a dependency to itself",
105 manifest
.package_name()?
110 let available_features
= dep
114 .collect
::<BTreeSet
<&str>>();
115 let mut unknown_features
: Vec
<&str> = Vec
::new();
116 if let Some(req_feats
) = dep
.features
.as_ref() {
117 let req_feats
: BTreeSet
<_
> = req_feats
.iter().map(|s
| s
.as_str()).collect();
118 unknown_features
.extend(req_feats
.difference(&available_features
).copied());
120 if let Some(inherited_features
) = dep
.inherited_features
.as_ref() {
121 let inherited_features
: BTreeSet
<_
> =
122 inherited_features
.iter().map(|s
| s
.as_str()).collect();
123 unknown_features
.extend(inherited_features
.difference(&available_features
).copied());
125 unknown_features
.sort();
126 if !unknown_features
.is_empty() {
127 anyhow
::bail
!("unrecognized features: {unknown_features:?}");
130 manifest
.insert_into_table(&dep_table
, &dep
)?
;
131 manifest
.gc_dep(dep
.toml_key());
135 if let Some(table
) = manifest
136 .get_table_mut(&dep_table
)
138 .and_then(TomlItem
::as_table_like_mut
)
145 options
.config
.shell().warn("aborting add due to dry run")?
;
153 /// Dependency entry operation
154 #[derive(Clone, Debug, PartialEq, Eq)]
156 /// Describes the crate
157 pub crate_spec
: Option
<String
>,
158 /// Dependency key, overriding the package name in crate_spec
159 pub rename
: Option
<String
>,
161 /// Feature flags to activate
162 pub features
: Option
<IndexSet
<String
>>,
163 /// Whether the default feature should be activated
164 pub default_features
: Option
<bool
>,
166 /// Whether dependency is optional
167 pub optional
: Option
<bool
>,
169 /// Registry for looking up dependency version
170 pub registry
: Option
<String
>,
172 /// Git repo for dependency
173 pub path
: Option
<String
>,
174 /// Git repo for dependency
175 pub git
: Option
<String
>,
176 /// Specify an alternative git branch
177 pub branch
: Option
<String
>,
178 /// Specify a specific git rev
179 pub rev
: Option
<String
>,
180 /// Specify a specific git tag
181 pub tag
: Option
<String
>,
184 fn resolve_dependency(
185 manifest
: &LocalManifest
,
190 registry
: &mut PackageRegistry
<'_
>,
191 ) -> CargoResult
<Dependency
> {
195 .map(CrateSpec
::resolve
)
197 let mut selected_dep
= if let Some(url
) = &arg
.git
{
198 let mut src
= GitSource
::new(url
);
199 if let Some(branch
) = &arg
.branch
{
200 src
= src
.set_branch(branch
);
202 if let Some(tag
) = &arg
.tag
{
203 src
= src
.set_tag(tag
);
205 if let Some(rev
) = &arg
.rev
{
206 src
= src
.set_rev(rev
);
209 let selected
= if let Some(crate_spec
) = &crate_spec
{
210 if let Some(v
) = crate_spec
.version_req() {
211 // crate specifier includes a version (e.g. `docopt@0.8`)
212 anyhow
::bail
!("cannot specify a git URL (`{url}`) with a version (`{v}`).");
214 let dependency
= crate_spec
.to_dependency()?
.set_source(src
);
215 let selected
= select_package(&dependency
, config
, registry
)?
;
216 if dependency
.name
!= selected
.name
{
217 config
.shell().warn(format
!(
218 "translating `{}` to `{}`",
219 dependency
.name
, selected
.name
,
224 let mut source
= crate::sources
::GitSource
::new(src
.source_id()?
, config
)?
;
225 let packages
= source
.read_packages()?
;
226 let package
= infer_package(packages
, &src
)?
;
227 Dependency
::from(package
.summary())
230 } else if let Some(raw_path
) = &arg
.path
{
231 let path
= paths
::normalize_path(&std
::env
::current_dir()?
.join(raw_path
));
232 let src
= PathSource
::new(&path
);
234 let selected
= if let Some(crate_spec
) = &crate_spec
{
235 if let Some(v
) = crate_spec
.version_req() {
236 // crate specifier includes a version (e.g. `docopt@0.8`)
237 anyhow
::bail
!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
239 let dependency
= crate_spec
.to_dependency()?
.set_source(src
);
240 let selected
= select_package(&dependency
, config
, registry
)?
;
241 if dependency
.name
!= selected
.name
{
242 config
.shell().warn(format
!(
243 "translating `{}` to `{}`",
244 dependency
.name
, selected
.name
,
249 let source
= crate::sources
::PathSource
::new(&path
, src
.source_id()?
, config
);
250 let packages
= source
.read_packages()?
;
251 let package
= infer_package(packages
, &src
)?
;
252 Dependency
::from(package
.summary())
255 } else if let Some(crate_spec
) = &crate_spec
{
256 crate_spec
.to_dependency()?
258 anyhow
::bail
!("dependency name is required");
260 selected_dep
= populate_dependency(selected_dep
, arg
);
262 let old_dep
= get_existing_dependency(manifest
, selected_dep
.toml_key(), section
)?
;
263 let mut dependency
= if let Some(mut old_dep
) = old_dep
.clone() {
264 if old_dep
.name
!= selected_dep
.name
{
265 // Assuming most existing keys are not relevant when the package changes
266 if selected_dep
.optional
.is_none() {
267 selected_dep
.optional
= old_dep
.optional
;
271 if selected_dep
.source().is_some() {
272 // Overwrite with `crate_spec`
273 old_dep
.source
= selected_dep
.source
;
275 old_dep
= populate_dependency(old_dep
, arg
);
276 old_dep
.available_features
= selected_dep
.available_features
;
283 if dependency
.source().is_none() {
284 if let Some(package
) = ws
.members().find(|p
| p
.name().as_str() == dependency
.name
) {
285 // Only special-case workspaces when the user doesn't provide any extra
286 // information, otherwise, trust the user.
287 let mut src
= PathSource
::new(package
.root());
288 // dev-dependencies do not need the version populated
289 if section
.kind() != DepKind
::Development
{
291 let v
= format
!("{op}{version}", version
= package
.version());
292 src
= src
.set_version(v
);
294 dependency
= dependency
.set_source(src
);
296 let latest
= get_latest_dependency(&dependency
, false, config
, registry
)?
;
298 if dependency
.name
!= latest
.name
{
299 config
.shell().warn(format
!(
300 "translating `{}` to `{}`",
301 dependency
.name
, latest
.name
,
303 dependency
.name
= latest
.name
; // Normalize the name
305 dependency
= dependency
306 .set_source(latest
.source
.expect("latest always has a source"))
307 .set_available_features(latest
.available_features
);
311 let version_required
= dependency
.source().and_then(|s
| s
.as_registry()).is_some();
312 let version_optional_in_section
= section
.kind() == DepKind
::Development
;
313 let preserve_existing_version
= old_dep
315 .map(|d
| d
.version().is_some())
317 if !version_required
&& !preserve_existing_version
&& version_optional_in_section
{
318 // dev-dependencies do not need the version populated
319 dependency
= dependency
.clear_version();
322 dependency
= populate_available_features(dependency
, config
, registry
, ws
)?
;
327 /// Provide the existing dependency for the target table
329 /// If it doesn't exist but exists in another table, let's use that as most likely users
330 /// want to use the same version across all tables unless they are renaming.
331 fn get_existing_dependency(
332 manifest
: &LocalManifest
,
335 ) -> CargoResult
<Option
<Dependency
>> {
336 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
345 let mut possible
: Vec
<_
> = manifest
346 .get_dependency_versions(dep_key
)
348 let key
= if path
== *section
{
349 (Key
::Existing
, true)
350 } else if dep
.is_err() {
351 (Key
::Error
, path
.target().is_some())
353 let key
= match path
.kind() {
354 DepKind
::Normal
=> Key
::Normal
,
355 DepKind
::Build
=> Key
::Build
,
356 DepKind
::Development
=> Key
::Dev
,
358 (key
, path
.target().is_some())
363 possible
.sort_by_key(|(key
, _
)| *key
);
364 let (key
, dep
) = if let Some(item
) = possible
.pop() {
371 if key
.0 != Key
::Existing
{
372 // When the dep comes from a different section, we only care about the source and not any
373 // of the other fields, like `features`
375 dep
= Dependency
::new(&unrelated
.name
);
376 dep
.source
= unrelated
.source
.clone();
377 dep
.registry
= unrelated
.registry
.clone();
379 // dev-dependencies do not need the version populated when path is set though we
380 // should preserve it if the user chose to populate it.
381 let version_required
= unrelated
.source().and_then(|s
| s
.as_registry()).is_some();
382 let version_optional_in_section
= section
.kind() == DepKind
::Development
;
383 if !version_required
&& version_optional_in_section
{
384 dep
= dep
.clear_version();
391 fn get_latest_dependency(
392 dependency
: &Dependency
,
393 _flag_allow_prerelease
: bool
,
395 registry
: &mut PackageRegistry
<'_
>,
396 ) -> CargoResult
<Dependency
> {
397 let query
= dependency
.query(config
)?
;
399 MaybeWorkspace
::Workspace(_
) => {
400 unreachable
!("registry dependencies required, found a workspace dependency");
402 MaybeWorkspace
::Other(query
) => {
403 let possibilities
= loop {
405 match registry
.query_vec(&query
, fuzzy
) {
406 std
::task
::Poll
::Ready(res
) => {
409 std
::task
::Poll
::Pending
=> registry
.block_until_ready()?
,
412 let latest
= possibilities
415 // Fallback to a pre-release if no official release is available by sorting them as
417 let stable
= s
.version().pre
.is_empty();
418 (stable
, s
.version())
422 "the crate `{dependency}` could not be found in registry index."
425 let mut dep
= Dependency
::from(latest
);
426 if let Some(reg_name
) = dependency
.registry
.as_deref() {
427 dep
= dep
.set_registry(reg_name
);
435 dependency
: &Dependency
,
437 registry
: &mut PackageRegistry
<'_
>,
438 ) -> CargoResult
<Dependency
> {
439 let query
= dependency
.query(config
)?
;
441 MaybeWorkspace
::Workspace(_
) => {
442 unreachable
!("path or git dependency expected, found workspace dependency");
444 MaybeWorkspace
::Other(query
) => {
445 let possibilities
= loop {
446 let fuzzy
= false; // Returns all for path/git
447 match registry
.query_vec(&query
, fuzzy
) {
448 std
::task
::Poll
::Ready(res
) => {
451 std
::task
::Poll
::Pending
=> registry
.block_until_ready()?
,
454 match possibilities
.len() {
456 let source
= dependency
458 .expect("source should be resolved before here");
459 anyhow
::bail
!("the crate `{dependency}` could not be found at `{source}`")
462 let mut dep
= Dependency
::from(&possibilities
[0]);
463 if let Some(reg_name
) = dependency
.registry
.as_deref() {
464 dep
= dep
.set_registry(reg_name
);
469 let source
= dependency
471 .expect("source should be resolved before here");
473 "unexpectedly found multiple copies of crate `{dependency}` at `{source}`"
481 fn infer_package(mut packages
: Vec
<Package
>, src
: &dyn std
::fmt
::Display
) -> CargoResult
<Package
> {
482 let package
= match packages
.len() {
484 anyhow
::bail
!("no packages found at `{src}`");
486 1 => packages
.pop().expect("match ensured element is present"),
488 let mut names
: Vec
<_
> = packages
490 .map(|p
| p
.name().as_str().to_owned())
492 names
.sort_unstable();
493 anyhow
::bail
!("multiple packages found at `{src}`: {}", names
.join(", "));
499 fn populate_dependency(mut dependency
: Dependency
, arg
: &DepOp
) -> Dependency
{
500 if let Some(registry
) = &arg
.registry
{
501 if registry
.is_empty() {
502 dependency
.registry
= None
;
504 dependency
.registry
= Some(registry
.to_owned());
507 if let Some(value
) = arg
.optional
{
509 dependency
.optional
= Some(true);
511 dependency
.optional
= None
;
514 if let Some(value
) = arg
.default_features
{
516 dependency
.default_features
= None
;
518 dependency
.default_features
= Some(false);
521 if let Some(value
) = arg
.features
.as_ref() {
522 dependency
= dependency
.extend_features(value
.iter().cloned());
525 if let Some(rename
) = &arg
.rename
{
526 dependency
= dependency
.set_rename(rename
);
532 /// Lookup available features
533 fn populate_available_features(
534 mut dependency
: Dependency
,
536 registry
: &mut PackageRegistry
<'_
>,
538 ) -> CargoResult
<Dependency
> {
539 if !dependency
.available_features
.is_empty() {
540 return Ok(dependency
);
543 let query
= dependency
.query(config
)?
;
544 let query
= match query
{
545 MaybeWorkspace
::Workspace(_workspace
) => {
546 let dep
= find_workspace_dep(dependency
.toml_key(), ws
.root_manifest())?
;
547 if let Some(features
) = dep
.features
.clone() {
548 dependency
= dependency
.set_inherited_features(features
);
550 let query
= dep
.query(config
)?
;
552 MaybeWorkspace
::Workspace(_
) => {
553 unreachable
!("This should have been caught when parsing a workspace root")
555 MaybeWorkspace
::Other(query
) => query
,
558 MaybeWorkspace
::Other(query
) => query
,
560 let possibilities
= loop {
561 match registry
.query_vec(&query
, true) {
562 std
::task
::Poll
::Ready(res
) => {
565 std
::task
::Poll
::Pending
=> registry
.block_until_ready()?
,
568 // Ensure widest feature flag compatibility by picking the earliest version that could show up
569 // in the lock file for a given version requirement.
570 let lowest_common_denominator
= possibilities
573 // Fallback to a pre-release if no official release is available by sorting them as
575 let is_pre
= !s
.version().pre
.is_empty();
576 (is_pre
, s
.version())
579 anyhow
::format_err
!("the crate `{dependency}` could not be found in registry index.")
581 dependency
= dependency
.set_available_features_from_cargo(lowest_common_denominator
.features());
586 fn print_msg(shell
: &mut Shell
, dep
: &Dependency
, section
: &[String
]) -> CargoResult
<()> {
589 if matches
!(shell
.verbosity(), crate::core
::shell
::Verbosity
::Quiet
) {
593 let mut message
= String
::new();
594 write
!(message
, "{}", dep
.name
)?
;
596 Some(Source
::Registry(src
)) => {
597 if src
.version
.chars().next().unwrap_or('
0'
).is_ascii_digit() {
598 write
!(message
, " v{}", src
.version
)?
;
600 write
!(message
, " {}", src
.version
)?
;
603 Some(Source
::Path(_
)) => {
604 write
!(message
, " (local)")?
;
606 Some(Source
::Git(_
)) => {
607 write
!(message
, " (git)")?
;
609 Some(Source
::Workspace(_
)) => {
610 write
!(message
, " (workspace)")?
;
614 write
!(message
, " to")?
;
615 if dep
.optional().unwrap_or(false) {
616 write
!(message
, " optional")?
;
618 let section
= if section
.len() == 1 {
621 format
!("{} for target `{}`", §ion
[2], §ion
[1])
623 write
!(message
, " {section}")?
;
624 write
!(message
, ".")?
;
625 shell
.status("Adding", message
)?
;
627 let mut activated
: IndexSet
<_
> = dep
.features
.iter().flatten().map(|s
| s
.as_str()).collect();
628 if dep
.default_features().unwrap_or(true) {
629 activated
.insert("default");
631 activated
.extend(dep
.inherited_features
.iter().flatten().map(|s
| s
.as_str()));
632 let mut walk
: VecDeque
<_
> = activated
.iter().cloned().collect();
633 while let Some(next
) = walk
.pop_front() {
635 dep
.available_features
639 .map(|s
| s
.as_str()),
642 dep
.available_features
646 .map(|s
| s
.as_str()),
649 activated
.remove("default");
651 let mut deactivated
= dep
654 .filter(|f
| !activated
.contains(f
.as_str()) && *f
!= "default")
655 .collect
::<Vec
<_
>>();
657 if !activated
.is_empty() || !deactivated
.is_empty() {
658 let prefix
= format
!("{:>13}", " ");
659 shell
.write_stderr(format_args
!("{}Features:\n", prefix
), &ColorSpec
::new())?
;
660 for feat
in activated
{
661 shell
.write_stderr(&prefix
, &ColorSpec
::new())?
;
662 shell
.write_stderr('
+'
, &ColorSpec
::new().set_bold(true).set_fg(Some(Green
)))?
;
663 shell
.write_stderr(format_args
!(" {}\n", feat
), &ColorSpec
::new())?
;
665 for feat
in deactivated
{
666 shell
.write_stderr(&prefix
, &ColorSpec
::new())?
;
667 shell
.write_stderr('
-'
, &ColorSpec
::new().set_bold(true).set_fg(Some(Red
)))?
;
668 shell
.write_stderr(format_args
!(" {}\n", feat
), &ColorSpec
::new())?
;
675 // Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized.
676 fn is_sorted(mut it
: impl Iterator
<Item
= impl PartialOrd
>) -> bool
{
677 let mut last
= match it
.next() {
692 fn find_workspace_dep(toml_key
: &str, root_manifest
: &Path
) -> CargoResult
<Dependency
> {
693 let manifest
= LocalManifest
::try_new(root_manifest
)?
;
694 let manifest
= manifest
698 .context("could not make `manifest.data` into a table")?
;
699 let workspace
= manifest
701 .context("could not find `workspace`")?
703 .context("could not make `manifest.data.workspace` into a table")?
;
704 let dependencies
= workspace
706 .context("could not find `dependencies` table in `workspace`")?
708 .context("could not make `dependencies` into a table")?
;
709 let dep_item
= dependencies
.get(toml_key
).context(format
!(
710 "could not find {} in `workspace.dependencies`",
713 Dependency
::from_toml(root_manifest
.parent().unwrap(), toml_key
, dep_item
)