Skip to content

Commit 5c8f865

Browse files
authored
Consider public dependencies when choosing a version in cargo add (#1… (#15966)
### What does this PR try to resolve? This change considers public dependencies when adding a new dependency. For example, if you depend on `foo`, which depends publicly on `bar 1.0`, and you run `cargo add bar`, you will now get `bar 1.0` even if `bar 2.0` is available. Fixes #13038 ### How to test and review this PR? The test suite has been updated with an example scenario. More tests might be warrented to see how this interacts with other features though.
2 parents 0c3fa19 + 0227114 commit 5c8f865

File tree

8 files changed

+240
-19
lines changed

8 files changed

+240
-19
lines changed

src/cargo/ops/cargo_add/mod.rs

Lines changed: 139 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@ use toml_edit::Item as TomlItem;
2020

2121
use crate::CargoResult;
2222
use crate::GlobalContext;
23+
use crate::core::Feature;
2324
use crate::core::FeatureValue;
2425
use crate::core::Features;
2526
use crate::core::Package;
27+
use crate::core::PackageId;
2628
use crate::core::Registry;
2729
use crate::core::Shell;
2830
use crate::core::Summary;
2931
use crate::core::Workspace;
3032
use crate::core::dependency::DepKind;
3133
use crate::core::registry::PackageRegistry;
34+
use crate::ops::resolve_ws;
3235
use crate::sources::source::QueryKind;
36+
use crate::util::OptVersionReq;
3337
use crate::util::cache_lock::CacheLockMode;
3438
use crate::util::edit_distance;
3539
use crate::util::style;
@@ -38,6 +42,7 @@ use crate::util::toml_mut::dependency::Dependency;
3842
use crate::util::toml_mut::dependency::GitSource;
3943
use crate::util::toml_mut::dependency::MaybeWorkspace;
4044
use crate::util::toml_mut::dependency::PathSource;
45+
use crate::util::toml_mut::dependency::RegistrySource;
4146
use crate::util::toml_mut::dependency::Source;
4247
use crate::util::toml_mut::dependency::WorkspaceSource;
4348
use crate::util::toml_mut::manifest::DepTable;
@@ -471,6 +476,13 @@ fn resolve_dependency(
471476
src = src.set_version(v);
472477
}
473478
dependency = dependency.set_source(src);
479+
} else if let Some((registry, public_source)) =
480+
get_public_dependency(spec, manifest, ws, section, gctx, &dependency)?
481+
{
482+
if let Some(registry) = registry {
483+
dependency = dependency.set_registry(registry);
484+
}
485+
dependency = dependency.set_source(public_source);
474486
} else {
475487
let latest =
476488
get_latest_dependency(spec, &dependency, honor_rust_version, gctx, registry)?;
@@ -501,6 +513,125 @@ fn resolve_dependency(
501513
dependency = dependency.clear_version();
502514
}
503515

516+
let query = query_dependency(ws, gctx, &mut dependency)?;
517+
let dependency = populate_available_features(dependency, &query, registry)?;
518+
519+
Ok(dependency)
520+
}
521+
522+
fn get_public_dependency(
523+
spec: &Package,
524+
manifest: &LocalManifest,
525+
ws: &Workspace<'_>,
526+
section: &DepTable,
527+
gctx: &GlobalContext,
528+
dependency: &Dependency,
529+
) -> CargoResult<Option<(Option<String>, Source)>> {
530+
if spec
531+
.manifest()
532+
.unstable_features()
533+
.require(Feature::public_dependency())
534+
.is_err()
535+
{
536+
return Ok(None);
537+
}
538+
539+
let (package_set, resolve) = resolve_ws(ws, true)?;
540+
541+
let mut latest: Option<(PackageId, OptVersionReq)> = None;
542+
543+
for (_, path, dep) in manifest.get_dependencies(ws, ws.unstable_features()) {
544+
if path != *section {
545+
continue;
546+
}
547+
548+
let Some(mut dep) = dep.ok() else {
549+
continue;
550+
};
551+
552+
let dep = query_dependency(ws, gctx, &mut dep)?;
553+
let Some(dep_pkgid) = package_set
554+
.package_ids()
555+
.filter(|package_id| {
556+
package_id.name() == dep.package_name()
557+
&& dep.version_req().matches(package_id.version())
558+
})
559+
.max_by_key(|x| x.version())
560+
else {
561+
continue;
562+
};
563+
564+
let mut pkg_ids_and_reqs = Vec::new();
565+
let mut pkg_id_queue = VecDeque::new();
566+
let mut examined = BTreeSet::new();
567+
pkg_id_queue.push_back(dep_pkgid);
568+
569+
while let Some(dep_pkgid) = pkg_id_queue.pop_front() {
570+
let got_deps = resolve.deps(dep_pkgid).filter_map(|(id, deps)| {
571+
deps.iter()
572+
.find(|dep| dep.is_public() && dep.kind() == DepKind::Normal)
573+
.map(|dep| (id, dep))
574+
});
575+
576+
for (pkg_id, got_dep) in got_deps {
577+
if got_dep.package_name() == dependency.name.as_str() {
578+
pkg_ids_and_reqs.push((pkg_id, got_dep.version_req().clone()));
579+
}
580+
581+
if examined.insert(pkg_id.clone()) {
582+
pkg_id_queue.push_back(pkg_id)
583+
}
584+
}
585+
}
586+
587+
for (pkg_id, req) in pkg_ids_and_reqs {
588+
if let Some((old_pkg_id, _)) = &latest
589+
&& old_pkg_id.version() >= pkg_id.version()
590+
{
591+
continue;
592+
}
593+
latest = Some((pkg_id, req))
594+
}
595+
}
596+
597+
let Some((pkg_id, version_req)) = latest else {
598+
return Ok(None);
599+
};
600+
601+
let source = pkg_id.source_id();
602+
if source.is_git() {
603+
Ok(Some((
604+
Option::<String>::None,
605+
Source::Git(GitSource::new(source.as_encoded_url().to_string())),
606+
)))
607+
} else if let Some(path) = source.local_path() {
608+
Ok(Some((None, Source::Path(PathSource::new(path)))))
609+
} else {
610+
let toml_source = match version_req {
611+
crate::util::OptVersionReq::Any => {
612+
Source::Registry(RegistrySource::new(pkg_id.version().to_string()))
613+
}
614+
crate::util::OptVersionReq::Req(version_req)
615+
| crate::util::OptVersionReq::Locked(_, version_req)
616+
| crate::util::OptVersionReq::Precise(_, version_req) => {
617+
Source::Registry(RegistrySource::new(version_req.to_string()))
618+
}
619+
};
620+
Ok(Some((
621+
source
622+
.alt_registry_key()
623+
.map(|x| x.to_owned())
624+
.filter(|_| !source.is_crates_io()),
625+
toml_source,
626+
)))
627+
}
628+
}
629+
630+
fn query_dependency(
631+
ws: &Workspace<'_>,
632+
gctx: &GlobalContext,
633+
dependency: &mut Dependency,
634+
) -> CargoResult<crate::core::Dependency> {
504635
let query = dependency.query(gctx)?;
505636
let query = match query {
506637
MaybeWorkspace::Workspace(_workspace) => {
@@ -511,7 +642,7 @@ fn resolve_dependency(
511642
ws.unstable_features(),
512643
)?;
513644
if let Some(features) = dep.features.clone() {
514-
dependency = dependency.set_inherited_features(features);
645+
*dependency = dependency.clone().set_inherited_features(features);
515646
}
516647
let query = dep.query(gctx)?;
517648
match query {
@@ -523,10 +654,7 @@ fn resolve_dependency(
523654
}
524655
MaybeWorkspace::Other(query) => query,
525656
};
526-
527-
let dependency = populate_available_features(dependency, &query, registry)?;
528-
529-
Ok(dependency)
657+
Ok(query)
530658
}
531659

532660
fn fuzzy_lookup(
@@ -640,8 +768,11 @@ fn get_existing_dependency(
640768
}
641769

642770
let mut possible: Vec<_> = manifest
643-
.get_dependency_versions(dep_key, ws, unstable_features)
644-
.map(|(path, dep)| {
771+
.get_dependencies(ws, unstable_features)
772+
.filter_map(|(key, path, dep)| {
773+
if key.as_str() != dep_key {
774+
return None;
775+
}
645776
let key = if path == *section {
646777
(Key::Existing, true)
647778
} else if dep.is_err() {
@@ -654,7 +785,7 @@ fn get_existing_dependency(
654785
};
655786
(key, path.target().is_some())
656787
};
657-
(key, dep)
788+
Some((key, dep))
658789
})
659790
.collect();
660791
possible.sort_by_key(|(key, _)| *key);

src/cargo/util/toml_mut/manifest.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,11 @@ impl LocalManifest {
327327
}
328328

329329
/// Lookup a dependency.
330-
pub fn get_dependency_versions<'s>(
330+
pub fn get_dependencies<'s>(
331331
&'s self,
332-
dep_key: &'s str,
333332
ws: &'s Workspace<'_>,
334333
unstable_features: &'s Features,
335-
) -> impl Iterator<Item = (DepTable, CargoResult<Dependency>)> + 's {
334+
) -> impl Iterator<Item = (String, DepTable, CargoResult<Dependency>)> + 's {
336335
let crate_root = self.path.parent().expect("manifest path is absolute");
337336
self.get_sections()
338337
.into_iter()
@@ -341,13 +340,7 @@ impl LocalManifest {
341340
Some(
342341
table
343342
.into_iter()
344-
.filter_map(|(key, item)| {
345-
if key.as_str() == dep_key {
346-
Some((table_path.clone(), key, item))
347-
} else {
348-
None
349-
}
350-
})
343+
.map(|(key, item)| (table_path.clone(), key, item))
351344
.collect::<Vec<_>>(),
352345
)
353346
})
@@ -361,7 +354,7 @@ impl LocalManifest {
361354
&dep_key,
362355
&dep_item,
363356
);
364-
(table_path, dep)
357+
(dep_key, table_path, dep)
365358
})
366359
}
367360

tests/testsuite/cargo_add/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ mod preserve_features_unsorted;
134134
mod preserve_sorted;
135135
mod preserve_unsorted;
136136
mod public;
137+
mod public_common_version;
137138
mod quiet;
138139
mod registry;
139140
mod rename;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
cargo-features = ["public-dependency"]
2+
[workspace]
3+
4+
[package]
5+
name = "cargo-list-test-fixture"
6+
version = "0.0.0"
7+
edition = "2015"
8+
9+
[dependencies]
10+
my-package = "0.1.0"

tests/testsuite/cargo_add/public_common_version/in/src/lib.rs

Whitespace-only changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::prelude::*;
2+
use cargo_test_support::Project;
3+
use cargo_test_support::compare::assert_ui;
4+
use cargo_test_support::current_dir;
5+
use cargo_test_support::file;
6+
use cargo_test_support::registry::Dependency;
7+
use cargo_test_support::str;
8+
9+
#[cargo_test]
10+
fn case() {
11+
cargo_test_support::registry::init();
12+
cargo_test_support::registry::Package::new("my-package-dep", "0.1.0").publish();
13+
cargo_test_support::registry::Package::new("my-package-dep", "0.2.0").publish();
14+
cargo_test_support::registry::Package::new("my-package", "0.1.0")
15+
.add_dep(Dependency::new("my-package-dep", "0.1.0").public(true))
16+
.publish();
17+
cargo_test_support::registry::Package::new("my-package", "0.2.0")
18+
.add_dep(Dependency::new("my-package-dep", "0.2.0").public(true))
19+
.publish();
20+
let project = Project::from_template(current_dir!().join("in"));
21+
let project_root = project.root();
22+
let cwd = &project_root;
23+
24+
snapbox::cmd::Command::cargo_ui()
25+
.arg("add")
26+
.arg_line("my-package-dep")
27+
.current_dir(cwd)
28+
.masquerade_as_nightly_cargo(&["public-dependency"])
29+
.assert()
30+
.success()
31+
.stdout_eq(str![""])
32+
.stderr_eq(file!["stderr.term.svg"]);
33+
34+
assert_ui().subset_matches(current_dir!().join("out"), &project_root);
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cargo-features = ["public-dependency"]
2+
[workspace]
3+
4+
[package]
5+
name = "cargo-list-test-fixture"
6+
version = "0.0.0"
7+
edition = "2015"
8+
9+
[dependencies]
10+
my-package = "0.1.0"
11+
my-package-dep = "^0.1.0"
Lines changed: 40 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)