diff --git a/src/cargo/ops/cargo_add/dependency.rs b/src/cargo/ops/cargo_add/dependency.rs index caf2314e025..560b68302a2 100644 --- a/src/cargo/ops/cargo_add/dependency.rs +++ b/src/cargo/ops/cargo_add/dependency.rs @@ -28,6 +28,8 @@ pub struct Dependency { pub features: Option>, /// Whether default features are enabled pub default_features: Option, + /// List of features inherited from a workspace dependency + pub inherited_features: Option>, /// Where the dependency comes from pub source: Option, @@ -50,6 +52,7 @@ impl Dependency { optional: None, features: None, default_features: None, + inherited_features: None, source: None, registry: None, rename: None, @@ -152,6 +155,12 @@ impl Dependency { self } + /// Set features as an array of string (does some basic parsing) + pub fn set_inherited_features(mut self, features: IndexSet) -> Self { + self.inherited_features = Some(features); + self + } + /// Get the dependency source pub fn source(&self) -> Option<&Source> { self.source.as_ref() @@ -350,6 +359,7 @@ impl Dependency { features, available_features, optional, + inherited_features: None, }; Ok(dep) } else { diff --git a/src/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs index 4033f1af1bb..b49a4df251d 100644 --- a/src/cargo/ops/cargo_add/mod.rs +++ b/src/cargo/ops/cargo_add/mod.rs @@ -106,23 +106,27 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<( ) } } + + let available_features = dep + .available_features + .keys() + .map(|s| s.as_ref()) + .collect::>(); + let mut unknown_features: Vec<&str> = Vec::new(); if let Some(req_feats) = dep.features.as_ref() { let req_feats: BTreeSet<_> = req_feats.iter().map(|s| s.as_str()).collect(); - - let available_features = dep - .available_features - .keys() - .map(|s| s.as_ref()) - .collect::>(); - - let mut unknown_features: Vec<&&str> = - req_feats.difference(&available_features).collect(); - unknown_features.sort(); - - if !unknown_features.is_empty() { - anyhow::bail!("unrecognized features: {unknown_features:?}"); - } + unknown_features.extend(req_feats.difference(&available_features).copied()); } + if let Some(inherited_features) = dep.inherited_features.as_ref() { + let inherited_features: BTreeSet<_> = + inherited_features.iter().map(|s| s.as_str()).collect(); + unknown_features.extend(inherited_features.difference(&available_features).copied()); + } + unknown_features.sort(); + if !unknown_features.is_empty() { + anyhow::bail!("unrecognized features: {unknown_features:?}"); + } + manifest.insert_into_table(&dep_table, &dep)?; manifest.gc_dep(dep.toml_key()); } @@ -564,6 +568,9 @@ fn populate_available_features( dependency.toml_key(), dep_item, )?; + if let Some(features) = dep.features.clone() { + dependency = dependency.set_inherited_features(features); + } let query = dep.query(config)?; match query { MaybeWorkspace::Workspace(_) => { @@ -645,6 +652,7 @@ fn print_msg(shell: &mut Shell, dep: &Dependency, section: &[String]) -> CargoRe if dep.default_features().unwrap_or(true) { activated.insert("default"); } + activated.extend(dep.inherited_features.iter().flatten().map(|s| s.as_str())); let mut walk: VecDeque<_> = activated.iter().cloned().collect(); while let Some(next) = walk.pop_front() { walk.extend( diff --git a/tests/snapshots/add/merge_activated_features.in/Cargo.toml b/tests/snapshots/add/merge_activated_features.in/Cargo.toml new file mode 100644 index 00000000000..b1d9b399536 --- /dev/null +++ b/tests/snapshots/add/merge_activated_features.in/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["primary", "dependency"] + +[workspace.dependencies] +foo = { version = "0.0.0", path = "./dependency", features = ["merge"] } diff --git a/tests/snapshots/add/merge_activated_features.in/dependency/Cargo.toml b/tests/snapshots/add/merge_activated_features.in/dependency/Cargo.toml new file mode 100644 index 00000000000..f34d7a68506 --- /dev/null +++ b/tests/snapshots/add/merge_activated_features.in/dependency/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "foo" +version = "0.0.0" + +[features] +default-base = [] +default-test-base = [] +default-merge-base = [] +default = ["default-base", "default-test-base", "default-merge-base"] +test-base = [] +test = ["test-base", "default-test-base"] +merge-base = [] +merge = ["merge-base", "default-merge-base"] +unrelated = [] \ No newline at end of file diff --git a/tests/snapshots/add/merge_activated_features.in/dependency/src/lib.rs b/tests/snapshots/add/merge_activated_features.in/dependency/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/snapshots/add/merge_activated_features.in/primary/Cargo.toml b/tests/snapshots/add/merge_activated_features.in/primary/Cargo.toml new file mode 100644 index 00000000000..7ab3f9dd2c1 --- /dev/null +++ b/tests/snapshots/add/merge_activated_features.in/primary/Cargo.toml @@ -0,0 +1,8 @@ +cargo-features = ["workspace-inheritance"] + +[package] +name = "bar" +version = "0.0.0" + +[dependencies] +foo = { workspace = true, features = ["test"] } \ No newline at end of file diff --git a/tests/snapshots/add/merge_activated_features.in/primary/src/lib.rs b/tests/snapshots/add/merge_activated_features.in/primary/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/snapshots/add/merge_activated_features.out/Cargo.toml b/tests/snapshots/add/merge_activated_features.out/Cargo.toml new file mode 100644 index 00000000000..b1d9b399536 --- /dev/null +++ b/tests/snapshots/add/merge_activated_features.out/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["primary", "dependency"] + +[workspace.dependencies] +foo = { version = "0.0.0", path = "./dependency", features = ["merge"] } diff --git a/tests/snapshots/add/merge_activated_features.out/dependency/Cargo.toml b/tests/snapshots/add/merge_activated_features.out/dependency/Cargo.toml new file mode 100644 index 00000000000..f34d7a68506 --- /dev/null +++ b/tests/snapshots/add/merge_activated_features.out/dependency/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "foo" +version = "0.0.0" + +[features] +default-base = [] +default-test-base = [] +default-merge-base = [] +default = ["default-base", "default-test-base", "default-merge-base"] +test-base = [] +test = ["test-base", "default-test-base"] +merge-base = [] +merge = ["merge-base", "default-merge-base"] +unrelated = [] \ No newline at end of file diff --git a/tests/snapshots/add/merge_activated_features.out/primary/Cargo.toml b/tests/snapshots/add/merge_activated_features.out/primary/Cargo.toml new file mode 100644 index 00000000000..0f91d71a3c8 --- /dev/null +++ b/tests/snapshots/add/merge_activated_features.out/primary/Cargo.toml @@ -0,0 +1,8 @@ +cargo-features = ["workspace-inheritance"] + +[package] +name = "bar" +version = "0.0.0" + +[dependencies] +foo = { workspace = true, features = ["test"] } diff --git a/tests/snapshots/add/merge_activated_features.stderr b/tests/snapshots/add/merge_activated_features.stderr new file mode 100644 index 00000000000..86d9fb3b857 --- /dev/null +++ b/tests/snapshots/add/merge_activated_features.stderr @@ -0,0 +1,10 @@ + Adding foo (workspace) to dependencies. + Features: + + default-base + + default-merge-base + + default-test-base + + merge + + merge-base + + test + + test-base + - unrelated diff --git a/tests/snapshots/add/merge_activated_features.stdout b/tests/snapshots/add/merge_activated_features.stdout new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/snapshots/add/unknown_inherited_feature.in/Cargo.toml b/tests/snapshots/add/unknown_inherited_feature.in/Cargo.toml new file mode 100644 index 00000000000..b2a34c92e4c --- /dev/null +++ b/tests/snapshots/add/unknown_inherited_feature.in/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["primary", "dependency"] + +[workspace.dependencies] +foo = { version = "0.0.0", path = "./dependency", features = ["not_recognized"] } diff --git a/tests/snapshots/add/unknown_inherited_feature.in/dependency/Cargo.toml b/tests/snapshots/add/unknown_inherited_feature.in/dependency/Cargo.toml new file mode 100644 index 00000000000..f34d7a68506 --- /dev/null +++ b/tests/snapshots/add/unknown_inherited_feature.in/dependency/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "foo" +version = "0.0.0" + +[features] +default-base = [] +default-test-base = [] +default-merge-base = [] +default = ["default-base", "default-test-base", "default-merge-base"] +test-base = [] +test = ["test-base", "default-test-base"] +merge-base = [] +merge = ["merge-base", "default-merge-base"] +unrelated = [] \ No newline at end of file diff --git a/tests/snapshots/add/unknown_inherited_feature.in/dependency/src/lib.rs b/tests/snapshots/add/unknown_inherited_feature.in/dependency/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/snapshots/add/unknown_inherited_feature.in/primary/Cargo.toml b/tests/snapshots/add/unknown_inherited_feature.in/primary/Cargo.toml new file mode 100644 index 00000000000..0f91d71a3c8 --- /dev/null +++ b/tests/snapshots/add/unknown_inherited_feature.in/primary/Cargo.toml @@ -0,0 +1,8 @@ +cargo-features = ["workspace-inheritance"] + +[package] +name = "bar" +version = "0.0.0" + +[dependencies] +foo = { workspace = true, features = ["test"] } diff --git a/tests/snapshots/add/unknown_inherited_feature.in/primary/src/lib.rs b/tests/snapshots/add/unknown_inherited_feature.in/primary/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/snapshots/add/unknown_inherited_feature.out/Cargo.toml b/tests/snapshots/add/unknown_inherited_feature.out/Cargo.toml new file mode 100644 index 00000000000..b2a34c92e4c --- /dev/null +++ b/tests/snapshots/add/unknown_inherited_feature.out/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["primary", "dependency"] + +[workspace.dependencies] +foo = { version = "0.0.0", path = "./dependency", features = ["not_recognized"] } diff --git a/tests/snapshots/add/unknown_inherited_feature.out/dependency/Cargo.toml b/tests/snapshots/add/unknown_inherited_feature.out/dependency/Cargo.toml new file mode 100644 index 00000000000..f34d7a68506 --- /dev/null +++ b/tests/snapshots/add/unknown_inherited_feature.out/dependency/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "foo" +version = "0.0.0" + +[features] +default-base = [] +default-test-base = [] +default-merge-base = [] +default = ["default-base", "default-test-base", "default-merge-base"] +test-base = [] +test = ["test-base", "default-test-base"] +merge-base = [] +merge = ["merge-base", "default-merge-base"] +unrelated = [] \ No newline at end of file diff --git a/tests/snapshots/add/unknown_inherited_feature.out/primary/Cargo.toml b/tests/snapshots/add/unknown_inherited_feature.out/primary/Cargo.toml new file mode 100644 index 00000000000..0f91d71a3c8 --- /dev/null +++ b/tests/snapshots/add/unknown_inherited_feature.out/primary/Cargo.toml @@ -0,0 +1,8 @@ +cargo-features = ["workspace-inheritance"] + +[package] +name = "bar" +version = "0.0.0" + +[dependencies] +foo = { workspace = true, features = ["test"] } diff --git a/tests/snapshots/add/unknown_inherited_feature.stderr b/tests/snapshots/add/unknown_inherited_feature.stderr new file mode 100644 index 00000000000..693e8872105 --- /dev/null +++ b/tests/snapshots/add/unknown_inherited_feature.stderr @@ -0,0 +1,12 @@ + Adding foo (workspace) to dependencies. + Features: + + default-base + + default-merge-base + + default-test-base + + not_recognized + + test + + test-base + - merge + - merge-base + - unrelated +error: unrecognized features: ["not_recognized"] diff --git a/tests/snapshots/add/unknown_inherited_feature.stdout b/tests/snapshots/add/unknown_inherited_feature.stdout new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add.rs b/tests/testsuite/cargo_add.rs index ae7c04bcc1e..2f43c6ee7f9 100644 --- a/tests/testsuite/cargo_add.rs +++ b/tests/testsuite/cargo_add.rs @@ -1168,6 +1168,28 @@ fn manifest_path_package() { ); } +#[cargo_test] +fn merge_activated_features() { + let project = Project::from_template("tests/snapshots/add/merge_activated_features.in"); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo() + .masquerade_as_nightly_cargo() + .arg("add") + .args(["foo", "-p", "bar"]) + .current_dir(cwd) + .assert() + .success() + .stdout_matches_path("tests/snapshots/add/merge_activated_features.stdout") + .stderr_matches_path("tests/snapshots/add/merge_activated_features.stderr"); + + assert().subset_matches( + "tests/snapshots/add/merge_activated_features.out", + &project_root, + ); +} + #[cargo_test] fn multiple_conflicts_with_features() { init_registry(); @@ -2019,6 +2041,28 @@ fn target_cfg() { assert().subset_matches("tests/snapshots/add/target_cfg.out", &project_root); } +#[cargo_test] +fn unknown_inherited_feature() { + let project = Project::from_template("tests/snapshots/add/unknown_inherited_feature.in"); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo() + .masquerade_as_nightly_cargo() + .arg("add") + .args(["foo", "-p", "bar"]) + .current_dir(cwd) + .assert() + .failure() + .stdout_matches_path("tests/snapshots/add/unknown_inherited_feature.stdout") + .stderr_matches_path("tests/snapshots/add/unknown_inherited_feature.stderr"); + + assert().subset_matches( + "tests/snapshots/add/unknown_inherited_feature.out", + &project_root, + ); +} + #[cargo_test] fn vers() { init_registry();