diff --git a/Cargo.lock b/Cargo.lock index 4106030a1d9..908bddb3197 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,7 +312,7 @@ dependencies = [ "cargo-credential-libsecret", "cargo-credential-macos-keychain", "cargo-credential-wincred", - "cargo-platform 0.2.0", + "cargo-platform 0.2.1", "cargo-test-support", "cargo-util", "cargo-util-schemas", @@ -439,7 +439,7 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.2.0" +version = "0.2.1" dependencies = [ "serde", ] @@ -3371,7 +3371,7 @@ name = "resolver-tests" version = "0.0.0" dependencies = [ "cargo", - "cargo-platform 0.2.0", + "cargo-platform 0.2.1", "cargo-util", "cargo-util-schemas", "proptest", diff --git a/crates/cargo-platform/Cargo.toml b/crates/cargo-platform/Cargo.toml index 778a7cf6742..eb75c2b411e 100644 --- a/crates/cargo-platform/Cargo.toml +++ b/crates/cargo-platform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-platform" -version = "0.2.0" +version = "0.2.1" edition.workspace = true license.workspace = true rust-version.workspace = true diff --git a/crates/cargo-platform/src/cfg.rs b/crates/cargo-platform/src/cfg.rs index 79aaa02c23f..49c289acd8f 100644 --- a/crates/cargo-platform/src/cfg.rs +++ b/crates/cargo-platform/src/cfg.rs @@ -149,6 +149,28 @@ impl CfgExpr { CfgExpr::Value(ref e) => cfg.contains(e), } } + + /// Walk over all the `Cfg`s of the given `CfgExpr`, recursing into `not(...)`, `all(...)`, + /// `any(...)` and stopping at the first error and returning that error. + pub fn walk(&self, mut f: impl FnMut(&Cfg) -> Result<(), E>) -> Result<(), E> { + fn walk_inner( + cfg_expr: &CfgExpr, + f: &mut impl FnMut(&Cfg) -> Result<(), E>, + ) -> Result<(), E> { + match *cfg_expr { + CfgExpr::Not(ref e) => walk_inner(e, &mut *f), + CfgExpr::All(ref e) | CfgExpr::Any(ref e) => { + for e in e { + let _ = walk_inner(e, &mut *f)?; + } + Ok(()) + } + CfgExpr::Value(ref e) => f(e), + } + } + + walk_inner(self, &mut f) + } } impl FromStr for CfgExpr { diff --git a/crates/cargo-platform/src/check_cfg.rs b/crates/cargo-platform/src/check_cfg.rs new file mode 100644 index 00000000000..d91f2ea699c --- /dev/null +++ b/crates/cargo-platform/src/check_cfg.rs @@ -0,0 +1,98 @@ +//! check-cfg + +use std::{ + collections::{HashMap, HashSet}, + error::Error, + fmt::Display, +}; + +/// Check Config (aka `--check-cfg`/`--print=check-cfg` representation) +#[derive(Debug, Default, Clone)] +pub struct CheckCfg { + /// Is `--check-cfg` activated + pub exhaustive: bool, + /// List of expected cfgs + pub expecteds: HashMap, +} + +/// List of expected check-cfg values +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExpectedValues { + /// List of expected values + /// + /// - `#[cfg(foo)]` value is `None` + /// - `#[cfg(foo = "")]` value is `Some("")` + /// - `#[cfg(foo = "bar")]` value is `Some("bar")` + Some(HashSet>), + /// All values expected + Any, +} + +/// Error when parse a line from `--print=check-cfg` +#[derive(Debug)] +#[non_exhaustive] +pub struct PrintCheckCfgParsingError; + +impl Error for PrintCheckCfgParsingError {} + +impl Display for PrintCheckCfgParsingError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("error when parsing a `--print=check-cfg` line") + } +} + +impl CheckCfg { + /// Parse a line from `--print=check-cfg` + pub fn parse_print_check_cfg_line( + &mut self, + line: &str, + ) -> Result<(), PrintCheckCfgParsingError> { + if line == "any()=any()" || line == "any()" { + self.exhaustive = false; + return Ok(()); + } + + let mut value: HashSet> = HashSet::default(); + let mut value_any_specified = false; + let name: String; + + if let Some((n, val)) = line.split_once('=') { + name = n.to_string(); + + if val == "any()" { + value_any_specified = true; + } else if val.is_empty() { + // no value, nothing to add + } else if let Some(val) = maybe_quoted_value(val) { + value.insert(Some(val.to_string())); + } else { + // missing quotes and non-empty + return Err(PrintCheckCfgParsingError); + } + } else { + name = line.to_string(); + value.insert(None); + } + + self.expecteds + .entry(name) + .and_modify(|v| match v { + ExpectedValues::Some(_) if value_any_specified => *v = ExpectedValues::Any, + ExpectedValues::Some(v) => v.extend(value.clone()), + ExpectedValues::Any => {} + }) + .or_insert_with(|| { + if value_any_specified { + ExpectedValues::Any + } else { + ExpectedValues::Some(value) + } + }); + Ok(()) + } +} + +fn maybe_quoted_value<'a>(v: &'a str) -> Option<&'a str> { + // strip "" around the value, e.g. "linux" -> linux + v.strip_prefix('"').and_then(|v| v.strip_suffix('"')) +} diff --git a/crates/cargo-platform/src/lib.rs b/crates/cargo-platform/src/lib.rs index f91b61708bb..fafef219b69 100644 --- a/crates/cargo-platform/src/lib.rs +++ b/crates/cargo-platform/src/lib.rs @@ -15,10 +15,12 @@ use std::str::FromStr; use std::{fmt, path::Path}; mod cfg; +mod check_cfg; mod error; use cfg::KEYWORDS; pub use cfg::{Cfg, CfgExpr, Ident}; +pub use check_cfg::{CheckCfg, ExpectedValues}; pub use error::{ParseError, ParseErrorKind}; /// Platform definition. diff --git a/crates/cargo-platform/tests/test_print_check_cfg.rs b/crates/cargo-platform/tests/test_print_check_cfg.rs new file mode 100644 index 00000000000..aa49a7f3089 --- /dev/null +++ b/crates/cargo-platform/tests/test_print_check_cfg.rs @@ -0,0 +1,123 @@ +use cargo_platform::{CheckCfg, ExpectedValues}; +use std::collections::HashSet; + +#[test] +fn print_check_cfg_none() { + let mut check_cfg = CheckCfg::default(); + + check_cfg.parse_print_check_cfg_line("cfg_a").unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_a").unwrap(), + ExpectedValues::Some(HashSet::from([None])) + ); +} + +#[test] +fn print_check_cfg_empty() { + let mut check_cfg = CheckCfg::default(); + + check_cfg.parse_print_check_cfg_line("cfg_b=").unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_b").unwrap(), + ExpectedValues::Some(HashSet::from([])) + ); +} + +#[test] +fn print_check_cfg_any() { + let mut check_cfg = CheckCfg::default(); + + check_cfg.parse_print_check_cfg_line("cfg_c=any()").unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_c").unwrap(), + ExpectedValues::Any + ); + + check_cfg.parse_print_check_cfg_line("cfg_c").unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_c").unwrap(), + ExpectedValues::Any + ); +} + +#[test] +fn print_check_cfg_value() { + let mut check_cfg = CheckCfg::default(); + + check_cfg + .parse_print_check_cfg_line("cfg_d=\"test\"") + .unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_d").unwrap(), + ExpectedValues::Some(HashSet::from([Some("test".to_string())])) + ); + + check_cfg + .parse_print_check_cfg_line("cfg_d=\"tmp\"") + .unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_d").unwrap(), + ExpectedValues::Some(HashSet::from([ + Some("test".to_string()), + Some("tmp".to_string()) + ])) + ); +} + +#[test] +fn print_check_cfg_none_and_value() { + let mut check_cfg = CheckCfg::default(); + + check_cfg.parse_print_check_cfg_line("cfg").unwrap(); + check_cfg.parse_print_check_cfg_line("cfg=\"foo\"").unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg").unwrap(), + ExpectedValues::Some(HashSet::from([None, Some("foo".to_string())])) + ); +} + +#[test] +fn print_check_cfg_quote_in_value() { + let mut check_cfg = CheckCfg::default(); + + check_cfg + .parse_print_check_cfg_line("cfg_e=\"quote_in_value\"\"") + .unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_e").unwrap(), + ExpectedValues::Some(HashSet::from([Some("quote_in_value\"".to_string())])) + ); +} + +#[test] +fn print_check_cfg_value_and_any() { + let mut check_cfg = CheckCfg::default(); + + // having both a value and `any()` shouldn't be possible but better + // handle this correctly anyway + + check_cfg + .parse_print_check_cfg_line("cfg_1=\"foo\"") + .unwrap(); + check_cfg.parse_print_check_cfg_line("cfg_1=any()").unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_1").unwrap(), + ExpectedValues::Any + ); + + check_cfg.parse_print_check_cfg_line("cfg_2=any()").unwrap(); + check_cfg + .parse_print_check_cfg_line("cfg_2=\"foo\"") + .unwrap(); + assert_eq!( + *check_cfg.expecteds.get("cfg_2").unwrap(), + ExpectedValues::Any + ); +} + +#[test] +#[should_panic] +fn print_check_cfg_missing_quote_value() { + let mut check_cfg = CheckCfg::default(); + check_cfg.parse_print_check_cfg_line("foo=bar").unwrap(); +} diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 209d88a02d2..e51b2aac242 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -14,7 +14,7 @@ use crate::util::context::{GlobalContext, StringList, TargetConfig}; use crate::util::interning::InternedString; use crate::util::{CargoResult, Rustc}; use anyhow::Context as _; -use cargo_platform::{Cfg, CfgExpr}; +use cargo_platform::{Cfg, CfgExpr, CheckCfg}; use cargo_util::{paths, ProcessBuilder}; use serde::{Deserialize, Serialize}; use std::cell::RefCell; @@ -43,6 +43,8 @@ pub struct TargetInfo { crate_types: RefCell>>, /// `cfg` information extracted from `rustc --print=cfg`. cfg: Vec, + /// `check-cfg` informations extracted from `rustc --print=check-cfg`. + check_cfg: Option, /// `supports_std` information extracted from `rustc --print=target-spec-json` pub supports_std: Option, /// Supported values for `-Csplit-debuginfo=` flag, queried from rustc @@ -208,6 +210,14 @@ impl TargetInfo { process.arg("--print=crate-name"); // `___` as a delimiter. process.arg("--print=cfg"); + if gctx.cli_unstable().check_target_cfgs { + process.arg("--print=crate-name"); // `___` as a delimiter. + process.arg("--print=check-cfg"); + + process.arg("--check-cfg=cfg()"); // otherwise `--print=check-cfg` won't output + process.arg("-Zunstable-options"); // required by `--print=check-cfg` + } + // parse_crate_type() relies on "unsupported/unknown crate type" error message, // so make warnings always emitted as warnings. process.arg("-Wwarnings"); @@ -261,16 +271,42 @@ impl TargetInfo { res }; - let cfg = lines - .map(|line| Ok(Cfg::from_str(line)?)) - .filter(TargetInfo::not_user_specific_cfg) - .collect::>>() - .with_context(|| { - format!( - "failed to parse the cfg from `rustc --print=cfg`, got:\n{}", - output - ) - })?; + let cfg = { + let mut res = Vec::new(); + for line in &mut lines { + // HACK: abuse `--print=crate-name` to use `___` as a delimiter. + if line == "___" { + break; + } + + let cfg = Cfg::from_str(line).with_context(|| { + format!( + "failed to parse the cfg from `rustc --print=cfg`, got:\n{}", + output + ) + })?; + if TargetInfo::not_user_specific_cfg(&cfg) { + res.push(cfg); + } + } + res + }; + + let check_cfg = if gctx.cli_unstable().check_target_cfgs { + let mut check_cfg = CheckCfg::default(); + check_cfg.exhaustive = true; + + for line in lines { + check_cfg + .parse_print_check_cfg_line(line) + .with_context(|| { + format!("unable to parse a line from `--print=check-cfg`") + })?; + } + Some(check_cfg) + } else { + None + }; // recalculate `rustflags` from above now that we have `cfg` // information @@ -356,14 +392,15 @@ impl TargetInfo { )? .into(), cfg, + check_cfg, supports_std, support_split_debuginfo, }); } } - fn not_user_specific_cfg(cfg: &CargoResult) -> bool { - if let Ok(Cfg::Name(cfg_name)) = cfg { + fn not_user_specific_cfg(cfg: &Cfg) -> bool { + if let Cfg::Name(cfg_name) = cfg { // This should also include "debug_assertions", but it causes // regressions. Maybe some day in the distant future it can be // added (and possibly change the warning to an error). @@ -374,11 +411,57 @@ impl TargetInfo { true } + /// The [`CheckCfg`] settings with extra arguments passed. + pub fn check_cfg_with_extra_args( + &self, + gctx: &GlobalContext, + rustc: &Rustc, + extra_args: &[String], + ) -> CargoResult { + let mut process = rustc.workspace_process(); + + apply_env_config(gctx, &mut process)?; + process + .arg("-") + .arg("--print=check-cfg") + .arg("--check-cfg=cfg()") + .arg("-Zunstable-options") + .args(&self.rustflags) + .args(extra_args) + .env_remove("RUSTC_LOG"); + + // Removes `FD_CLOEXEC` set by `jobserver::Client` to pass jobserver + // as environment variables specify. + if let Some(client) = gctx.jobserver_from_env() { + process.inherit_jobserver(client); + } + + let (output, _error) = rustc + .cached_output(&process, 0) + .with_context(|| "failed to run `rustc` to learn about check-cfg information")?; + + let lines = output.lines(); + let mut check_cfg = CheckCfg::default(); + check_cfg.exhaustive = true; + + for line in lines { + check_cfg + .parse_print_check_cfg_line(line) + .with_context(|| format!("unable to parse a line from `--print=check-cfg`"))?; + } + Ok(check_cfg) + } + /// All the target [`Cfg`] settings. pub fn cfg(&self) -> &[Cfg] { &self.cfg } + /// The [`CheckCfg`] settings. + pub fn check_cfg(&self) -> &Option { + &self.check_cfg + } + /// Returns the list of file types generated by the given crate type. /// /// Returns `None` if the target does not support the given crate type. diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 152d308d832..a9e6a7fc2fc 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -765,6 +765,7 @@ unstable_cli_options!( #[serde(deserialize_with = "deserialize_comma_separated_list")] build_std_features: Option> = ("Configure features enabled for the standard library itself when building the standard library"), cargo_lints: bool = ("Enable the `[lints.cargo]` table"), + check_target_cfgs: bool = ("Enable unexpected cfgs checking in `[target.'cfg(...)']` tables"), checksum_freshness: bool = ("Use a checksum to determine if output is fresh rather than filesystem mtime"), codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"), config_include: bool = ("Enable the `include` key in config files"), @@ -1271,6 +1272,7 @@ impl CliUnstable { "build-std" => self.build_std = Some(parse_list(v)), "build-std-features" => self.build_std_features = Some(parse_list(v)), "cargo-lints" => self.cargo_lints = parse_empty(k, v)?, + "check-target-cfgs" => self.check_target_cfgs = parse_empty(k, v)?, "codegen-backend" => self.codegen_backend = parse_empty(k, v)?, "config-include" => self.config_include = parse_empty(k, v)?, "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?, diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 50b1de570e8..0c17c7c3fb7 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -25,7 +25,7 @@ use crate::util::context::FeatureUnification; use crate::util::edit_distance; use crate::util::errors::{CargoResult, ManifestError}; use crate::util::interning::InternedString; -use crate::util::lints::{analyze_cargo_lints_table, check_im_a_teapot}; +use crate::util::lints::{analyze_cargo_lints_table, check_im_a_teapot, unexpected_target_cfgs}; use crate::util::toml::{read_manifest, InheritableFields}; use crate::util::{ context::CargoResolverConfig, context::ConfigRelativePath, context::IncompatibleRustVersions, @@ -1240,6 +1240,10 @@ impl<'gctx> Workspace<'gctx> { .get("cargo") .cloned() .unwrap_or(manifest::TomlToolLints::default()); + let rust_lints = toml_lints + .get("rust") + .cloned() + .unwrap_or(manifest::TomlToolLints::default()); let ws_contents = match self.root_maybe() { MaybePackage::Package(pkg) => pkg.manifest().contents(), @@ -1261,6 +1265,7 @@ impl<'gctx> Workspace<'gctx> { self.gctx, )?; check_im_a_teapot(pkg, &path, &cargo_lints, &mut error_count, self.gctx)?; + unexpected_target_cfgs(self, pkg, &path, &rust_lints, &mut error_count, self.gctx)?; if error_count > 0 { Err(crate::util::errors::AlreadyPrintedError::new(anyhow!( "encountered {error_count} errors(s) while running lints" diff --git a/src/cargo/util/lints.rs b/src/cargo/util/lints.rs index 99531bfbd7b..a5e8d6ffebc 100644 --- a/src/cargo/util/lints.rs +++ b/src/cargo/util/lints.rs @@ -1,15 +1,18 @@ -use crate::core::{Edition, Feature, Features, Manifest, Package}; +use crate::core::compiler::{CompileKind, TargetInfo}; +use crate::core::{Edition, Feature, Features, Manifest, Package, Workspace}; use crate::{CargoResult, GlobalContext}; use annotate_snippets::{Level, Snippet}; +use cargo_platform::{Cfg, ExpectedValues, Platform}; use cargo_util_schemas::manifest::{TomlLintLevel, TomlToolLints}; use pathdiff::diff_paths; +use std::borrow::Cow; use std::fmt::Display; use std::ops::Range; use std::path::Path; use toml_edit::ImDocument; const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE]; -pub const LINTS: &[Lint] = &[IM_A_TEAPOT, UNKNOWN_LINTS]; +pub const LINTS: &[Lint] = &[IM_A_TEAPOT, UNEXPECTED_CFGS, UNKNOWN_LINTS]; pub fn analyze_cargo_lints_table( pkg: &Package, @@ -605,6 +608,157 @@ fn output_unknown_lints( Ok(()) } +// FIXME: This lint is only used for the Cargo infra, it's actually defined in rustc +// it-self, which is broken is several ways, as it doesn't take into account +// `rustc` flags ('via `RUSTFLAGS`), nor the possible `rustc` lints groups, ... +const UNEXPECTED_CFGS: Lint = Lint { + name: "unexpected_cfgs", + desc: "lint on unexpected target cfgs", + groups: &[], + default_level: LintLevel::Warn, + edition_lint_opts: None, + feature_gate: None, + docs: None, +}; + +pub fn unexpected_target_cfgs( + ws: &Workspace<'_>, + pkg: &Package, + path: &Path, + rust_lints: &TomlToolLints, + error_count: &mut usize, + gctx: &GlobalContext, +) -> CargoResult<()> { + let manifest = pkg.manifest(); + + let (lint_level, _lint_reason) = + UNEXPECTED_CFGS.level(rust_lints, manifest.edition(), manifest.unstable_features()); + + if lint_level == LintLevel::Allow { + return Ok(()); + } + + let rustc = gctx.load_global_rustc(Some(ws))?; + // FIXME: While it doesn't doesn't really matter for `--print=check-cfg`, we should + // still pass the actual requested targets instead of an empty array so that the + // target info can always be de-duplicated. + // FIXME: Move the target info creation before any linting is done and re-use it for + // all the packages. + let compile_kinds = CompileKind::from_requested_targets(gctx, &[])?; + let target_info = TargetInfo::new(gctx, &compile_kinds, &rustc, CompileKind::Host)?; + + let Some(global_check_cfg) = target_info.check_cfg() else { + return Ok(()); + }; + + // If we have extra `--check-cfg` args comming from the lints config, we need to + // refetch the `--print=check-cfg` with those extra args. + let lint_rustflags = pkg.manifest().lint_rustflags(); + let check_cfg = if lint_rustflags.iter().any(|a| a == "--check-cfg") { + Some(target_info.check_cfg_with_extra_args(gctx, &rustc, lint_rustflags)?) + } else { + None + }; + let check_cfg = check_cfg.as_ref().unwrap_or(&global_check_cfg); + + if !check_cfg.exhaustive { + return Ok(()); + } + + for dep in pkg.summary().dependencies() { + let Some(platform) = dep.platform() else { + continue; + }; + let Platform::Cfg(cfg_expr) = platform else { + continue; + }; + + cfg_expr.walk(|cfg| -> CargoResult<()> { + let (name, value) = match cfg { + Cfg::Name(name) => (name, None), + Cfg::KeyPair(name, value) => (name, Some(value.to_string())), + }; + + match check_cfg.expecteds.get(name.as_str()) { + Some(ExpectedValues::Some(values)) if !values.contains(&value) => { + let level = lint_level.to_diagnostic_level(); + if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny { + *error_count += 1; + } + + let value = match value { + Some(value) => Cow::from(format!("`{value}`")), + None => Cow::from("(none)"), + }; + + // Retrieving the span can fail if our pretty-priting doesn't match what the + // user wrote: `unix="foo"` and `unix = "foo"`, for that reason we don't fail + // and just produce a slightly worth diagnostic. + if let Some(span) = get_span(manifest.document(), &["target", &*format!("cfg({cfg_expr})")], false) { + let manifest_path = rel_cwd_manifest_path(path, gctx); + let title = format!( + "unexpected `cfg` condition value: {value} for `{cfg}`" + ); + let diag = level.title(&*title).snippet( + Snippet::source(manifest.contents()) + .origin(&manifest_path) + .annotation(level.span(span)) + .fold(true) + ); + gctx.shell().print_message(diag)?; + } else { + let title = format!( + "unexpected `cfg` condition value: {value} for `{cfg}` in `[target.'cfg({cfg_expr})']`" + ); + let help_where = format!("occurred in `{path}`", path = path.display()); + let diag = level.title(&*title).footer(Level::Help.title(&*help_where)); + gctx.shell().print_message(diag)?; + } + } + None => { + let level = lint_level.to_diagnostic_level(); + if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny { + *error_count += 1; + } + + let for_cfg = match value { + Some(_value) => Cow::from(format!(" for `{cfg}`")), + None => Cow::from(""), + }; + + // Retrieving the span can fail if our pretty-priting doesn't match what the + // user wrote: `unix="foo"` and `unix = "foo"`, for that reason we don't fail + // and just produce a slightly worth diagnostic. + if let Some(span) = get_span(manifest.document(), &["target", &*format!("cfg({cfg_expr})")], false) { + let manifest_path = rel_cwd_manifest_path(path, gctx); + let title = format!( + "unexpected `cfg` condition name: {name}{for_cfg}" + ); + let diag = level.title(&*title).snippet( + Snippet::source(manifest.contents()) + .origin(&manifest_path) + .annotation(level.span(span)) + .fold(true) + ); + gctx.shell().print_message(diag)?; + } else { + let title = format!( + "unexpected `cfg` condition name: {name}{for_cfg} in `[target.'cfg({cfg_expr})']`" + ); + let help_where = format!("occurred in `{path}`", path = path.display()); + let diag = level.title(&*title).footer(Level::Help.title(&*help_where)); + gctx.shell().print_message(diag)?; + } + } + _ => { /* not unexpected */ } + } + Ok(()) + })?; + } + + Ok(()) +} + #[cfg(test)] mod tests { use itertools::Itertools; diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index abcf0faf355..2df41c7c77f 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -125,6 +125,7 @@ Each new feature described below should explain how to use it. * [native-completions](#native-completions) --- Move cargo shell completions to native completions. * [warnings](#warnings) --- controls warning behavior; options for allowing or denying warnings. * [Package message format](#package-message-format) --- Message format for `cargo package`. + * [check-target-cfgs](#check-target-cfgs) --- Allows checking unexpected cfgs in `[target.'cfg(...)']` ## allow-features @@ -1913,6 +1914,28 @@ The `-Z rustdoc-depinfo` flag leverages rustdoc's dep-info files to determine whether documentations are required to re-generate. This can be combined with `-Z checksum-freshness` to detect checksum changes rather than file mtime. +## check-target-cfgs + +* Tracking Issue: [#00000](https://github.com/rust-lang/cargo/issues/00000) + +This feature checks for unexpected cfgs in `[target.'cfg(...)']` entries, based +on `rustc --print=check-cfg`. + +```sh +cargo check -Zcargo-lints -Zcheck-target-cfgs +``` + +It follows the lint Rust `unexpected_cfgs` lint configuration: + +```toml +[target.'cfg(foo)'.dependencies] +cfg-if = "1.0" + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(foo)'] +``` + # Stabilized and removed features ## Compile progress diff --git a/tests/testsuite/cargo/z_help/stdout.term.svg b/tests/testsuite/cargo/z_help/stdout.term.svg index c152f663f20..cb440d7214e 100644 --- a/tests/testsuite/cargo/z_help/stdout.term.svg +++ b/tests/testsuite/cargo/z_help/stdout.term.svg @@ -1,4 +1,4 @@ - +