From 9e7d5f3eaa5e967080a00c92e93d69ddfd1e64f5 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Thu, 18 Apr 2024 14:12:42 -0600 Subject: [PATCH] feat(fix): Add features for unused deps on Edition 2024 --- src/cargo/ops/fix.rs | 43 ++++++++++++- tests/testsuite/fix.rs | 135 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index aaf731b4e853..88c90e31af42 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -53,12 +53,13 @@ use tracing::{debug, trace, warn}; use crate::core::compiler::RustcTargetData; use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor}; use crate::core::resolver::{HasDevUnits, Resolve, ResolveBehavior}; -use crate::core::PackageIdSpecQuery as _; use crate::core::{Edition, MaybePackage, Package, PackageId, Workspace}; +use crate::core::{FeatureValue, PackageIdSpecQuery as _}; use crate::ops::resolve::WorkspaceResolve; use crate::ops::{self, CompileOptions}; use crate::util::diagnostic_server::{Message, RustfixDiagnosticServer}; use crate::util::errors::CargoResult; +use crate::util::interning::InternedString; use crate::util::GlobalContext; use crate::util::{existing_vcs_repo, LockServer, LockServerClient}; use crate::{drop_eprint, drop_eprintln}; @@ -253,6 +254,7 @@ fn migrate_manifests(ws: &Workspace<'_>, pkgs: &[&Package]) -> CargoResult<()> { let mut fixes = 0; let root = document.as_table_mut(); + fixes += add_feature_for_unused_deps(pkg, root); if rename_table(root, "project", "package") { fixes += 1; } @@ -287,6 +289,45 @@ fn rename_table(parent: &mut dyn toml_edit::TableLike, old: &str, new: &str) -> true } +fn add_feature_for_unused_deps(pkg: &Package, parent: &mut dyn toml_edit::TableLike) -> usize { + let manifest = pkg.manifest(); + + let activated_opt_deps = manifest + .resolved_toml() + .features() + .map(|map| { + map.values() + .flatten() + .filter_map(|f| match FeatureValue::new(InternedString::new(f)) { + FeatureValue::Dep { dep_name } => Some(dep_name.as_str()), + _ => None, + }) + .collect::>() + }) + .unwrap_or_default(); + + let mut fixes = 0; + for dep in manifest.dependencies() { + let dep_name_in_toml = dep.name_in_toml(); + if dep.is_optional() && !activated_opt_deps.contains(dep_name_in_toml.as_str()) { + fixes += 1; + if let Some(features) = parent + .entry("features") + .or_insert(toml_edit::table()) + .as_table_like_mut() + { + features.insert( + dep_name_in_toml.as_str(), + toml_edit::Item::Value(toml_edit::Value::Array(toml_edit::Array::from_iter( + &[format!("dep:{}", dep_name_in_toml)], + ))), + ); + } + } + } + fixes +} + fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<()> { let root = ws.root_maybe(); match root { diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs index 485511ab7648..7997a68ae623 100644 --- a/tests/testsuite/fix.rs +++ b/tests/testsuite/fix.rs @@ -2049,3 +2049,138 @@ edition = "2021" "# ); } + +#[cargo_test] +fn add_feature_for_unused_dep() { + Package::new("bar", "0.1.0").publish(); + Package::new("baz", "0.1.0").publish(); + Package::new("target-dep", "0.1.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +[dependencies] +bar = { version = "0.1.0", optional = true } + +[build-dependencies] +baz = { version = "0.1.0", optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +target-dep = { version = "0.1.0", optional = true } +"#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("fix --edition --allow-no-vcs") + .masquerade_as_nightly_cargo(&["edition2024"]) + .with_stderr( + "\ +[MIGRATING] Cargo.toml from 2021 edition to 2024 +[FIXED] Cargo.toml (3 fixes) +[UPDATING] `dummy-registry` index +[LOCKING] 4 packages to latest compatible versions +[CHECKING] foo v0.1.0 ([CWD]) +[MIGRATING] src/lib.rs from 2021 edition to 2024 +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); + assert_eq!( + p.read_file("Cargo.toml"), + r#" +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +[dependencies] +bar = { version = "0.1.0", optional = true } + +[build-dependencies] +baz = { version = "0.1.0", optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +target-dep = { version = "0.1.0", optional = true } + +[features] +bar = ["dep:bar"] +baz = ["dep:baz"] +target-dep = ["dep:target-dep"] +"# + ); +} + +#[cargo_test] +fn add_feature_for_unused_dep_existing_table() { + Package::new("bar", "0.1.0").publish(); + Package::new("baz", "0.1.0").publish(); + Package::new("target-dep", "0.1.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +[dependencies] +bar = { version = "0.1.0", optional = true } + +[build-dependencies] +baz = { version = "0.1.0", optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +target-dep = { version = "0.1.0", optional = true } + +[features] +target-dep = ["dep:target-dep"] +"#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("fix --edition --allow-no-vcs") + .masquerade_as_nightly_cargo(&["edition2024"]) + .with_stderr( + "\ +[MIGRATING] Cargo.toml from 2021 edition to 2024 +[FIXED] Cargo.toml (2 fixes) +[UPDATING] `dummy-registry` index +[LOCKING] 4 packages to latest compatible versions +[CHECKING] foo v0.1.0 ([CWD]) +[MIGRATING] src/lib.rs from 2021 edition to 2024 +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); + assert_eq!( + p.read_file("Cargo.toml"), + r#" +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +[dependencies] +bar = { version = "0.1.0", optional = true } + +[build-dependencies] +baz = { version = "0.1.0", optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +target-dep = { version = "0.1.0", optional = true } + +[features] +target-dep = ["dep:target-dep"] +bar = ["dep:bar"] +baz = ["dep:baz"] +"# + ); +}