From 19a9579031d5125c31e3ce090687b83dc173e887 Mon Sep 17 00:00:00 2001 From: Alex Tokarev Date: Fri, 28 Feb 2020 21:55:18 +0300 Subject: [PATCH] Warn on missing required-features Co-authored-by: Eric Huss --- src/cargo/ops/cargo_compile.rs | 73 +++++++++++++++++++++++++++++++++- tests/testsuite/bench.rs | 3 ++ tests/testsuite/features.rs | 59 +++++++++++++++++++++++---- 3 files changed, 126 insertions(+), 9 deletions(-) diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 5e9a764346f..b8dbc211d72 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -34,7 +34,7 @@ use crate::core::compiler::{DefaultExecutor, Executor, UnitInterner}; use crate::core::profiles::{Profiles, UnitFor}; use crate::core::resolver::features::{self, FeaturesFor}; use crate::core::resolver::{HasDevUnits, Resolve, ResolveOpts}; -use crate::core::{Package, PackageSet, Target}; +use crate::core::{FeatureValue, Package, PackageSet, Shell, Summary, Target}; use crate::core::{PackageId, PackageIdSpec, TargetKind, Workspace}; use crate::ops; use crate::ops::resolve::WorkspaceResolve; @@ -417,6 +417,7 @@ pub fn create_bcx<'a, 'cfg>( &build_config.requested_kinds, build_config.mode, &resolve, + &workspace_resolve, &resolved_features, &pkg_set, &profiles, @@ -701,6 +702,7 @@ fn generate_targets( requested_kinds: &[CompileKind], mode: CompileMode, resolve: &Resolve, + workspace_resolve: &Option, resolved_features: &features::ResolvedFeatures, package_set: &PackageSet<'_>, profiles: &Profiles, @@ -929,6 +931,13 @@ fn generate_targets( { let unavailable_features = match target.required_features() { Some(rf) => { + warn_on_missing_features( + workspace_resolve, + rf, + pkg.summary(), + &mut config.shell(), + )?; + let features = features_map.entry(pkg).or_insert_with(|| { resolve_all_features(resolve, resolved_features, package_set, pkg.package_id()) }); @@ -958,6 +967,68 @@ fn generate_targets( Ok(units.into_iter().collect()) } +fn warn_on_missing_features( + resolve: &Option, + required_features: &[String], + summary: &Summary, + shell: &mut Shell, +) -> CargoResult<()> { + let resolve = match resolve { + None => return Ok(()), + Some(resolve) => resolve, + }; + + for feature in required_features { + match FeatureValue::new(feature.into(), summary) { + // No need to do anything here, since the feature must exist to be parsed as such + FeatureValue::Feature(_) => {} + // Possibly mislabeled feature that was not found + FeatureValue::Crate(krate) => { + if !summary + .dependencies() + .iter() + .any(|dep| dep.name_in_toml() == krate && dep.is_optional()) + { + shell.warn(format!( + "feature `{}` is not present in [features] section.", + krate + ))?; + } + } + // Handling of dependent_crate/dependent_crate_feature syntax + FeatureValue::CrateFeature(krate, feature) => { + match resolve + .deps(summary.package_id()) + .find(|(_dep_id, deps)| deps.iter().any(|dep| dep.name_in_toml() == krate)) + { + Some((dep_id, _deps)) => { + let dep_summary = resolve.summary(dep_id); + if !dep_summary.features().contains_key(&feature) + && !dep_summary + .dependencies() + .iter() + .any(|dep| dep.name_in_toml() == feature && dep.is_optional()) + { + shell.warn(format!( + "feature `{}` does not exist in package `{}`.", + feature, dep_id + ))?; + } + } + None => { + shell.warn(format!( + "dependency `{}` specified in required-features as `{}/{}` \ + does not exist.", + krate, krate, feature + ))?; + } + } + } + } + } + Ok(()) +} + /// Gets all of the features enabled for a package, plus its dependencies' /// features. /// diff --git a/tests/testsuite/bench.rs b/tests/testsuite/bench.rs index b4d7de19aa8..b1b9ed28f3e 100644 --- a/tests/testsuite/bench.rs +++ b/tests/testsuite/bench.rs @@ -595,6 +595,9 @@ fn bench_autodiscover_2015() { version = "0.0.1" authors = [] edition = "2015" + + [features] + magic = [] [[bench]] name = "bench_magic" diff --git a/tests/testsuite/features.rs b/tests/testsuite/features.rs index d532ae4973a..b75c4829191 100644 --- a/tests/testsuite/features.rs +++ b/tests/testsuite/features.rs @@ -291,7 +291,7 @@ fn invalid9() { .build(); p.cargo("build --features bar") -.with_stderr( + .with_stderr( "\ error: Package `foo v0.0.1 ([..])` does not have feature `bar`. It has a required dependency with that name, but only optional dependencies can be used as features. ", @@ -1514,14 +1514,14 @@ fn namespaced_shadowed_dep() { .build(); p.cargo("build").masquerade_as_nightly_cargo().with_status(101).with_stderr( - "\ + "\ [ERROR] failed to parse manifest at `[..]` Caused by: Feature `baz` includes the optional dependency of the same name, but this is left implicit in the features included by this feature. Consider adding `crate:baz` to this feature's requirements. ", - ) + ) .run(); } @@ -1550,7 +1550,7 @@ fn namespaced_shadowed_non_optional() { .build(); p.cargo("build").masquerade_as_nightly_cargo().with_status(101).with_stderr( - "\ + "\ [ERROR] failed to parse manifest at `[..]` Caused by: @@ -1558,7 +1558,7 @@ Caused by: Additionally, the dependency must be marked as optional to be included in the feature definition. Consider adding `crate:baz` to this feature's requirements and marking the dependency as `optional = true` ", - ) + ) .run(); } @@ -1587,15 +1587,14 @@ fn namespaced_implicit_non_optional() { .build(); p.cargo("build").masquerade_as_nightly_cargo().with_status(101).with_stderr( - "\ + "\ [ERROR] failed to parse manifest at `[..]` Caused by: Feature `bar` includes `baz` which is not defined as a feature. A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition ", - ).run( - ); + ).run(); } #[cargo_test] @@ -2190,3 +2189,47 @@ fn registry_summary_order_doesnt_matter() { .with_stdout("it works") .run(); } + +#[cargo_test] +fn nonexistent_required_features() { + Package::new("required_dependency", "0.1.0") + .feature("simple", &[]) + .publish(); + Package::new("optional_dependency", "0.2.0") + .feature("optional", &[]) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.1.0" + [features] + existing = [] + fancy = ["optional_dependency"] + [dependencies] + required_dependency = { version = "0.1", optional = false} + optional_dependency = { version = "0.2", optional = true} + [[example]] + name = "ololo" + required-features = ["not_present", + "existing", + "fancy", + "required_dependency/not_existing", + "required_dependency/simple", + "optional_dependency/optional", + "not_specified_dependency/some_feature"]"#, + ) + .file("src/main.rs", "fn main() {}") + .file("examples/ololo.rs", "fn main() {}") + .build(); + + p.cargo("build --examples") + .with_stderr_contains( + r#"warning: feature `not_present` is not present in [features] section. +warning: feature `not_existing` does not exist in package `required_dependency v0.1.0`. +warning: dependency `not_specified_dependency` specified in required-features as `not_specified_dependency/some_feature` does not exist."#, + ) + .run(); +}