From bffb7b7f9759aa33c7359628a564c1d0425fb447 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 14:16:39 +0100 Subject: [PATCH 1/6] Make `MetaItem::from_tokens` public --- compiler/rustc_ast/src/attr/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 369fe12539fa8..94b138be11dab 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -481,7 +481,7 @@ impl MetaItem { } } - fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { + pub fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option { // FIXME: Share code with `parse_path`. let tt = iter.next().map(|tt| TokenTree::uninterpolate(tt)); let path = match tt.as_deref() { From bf79ff184ef4076403bf3bbe1c5e05b00b546c13 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 12:29:15 +0100 Subject: [PATCH 2/6] Add new `misleading_cfg_in_build_script` rustc lint --- .../src/early/diagnostics/check_cfg.rs | 2 +- compiler/rustc_lint/src/lib.rs | 3 + .../src/misleading_cfg_in_build_script.rs | 290 ++++++++++++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_lint/src/misleading_cfg_in_build_script.rs diff --git a/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs index 9fcabfa623d80..2d2daab635de8 100644 --- a/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs +++ b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs @@ -66,7 +66,7 @@ fn cargo_help_sub( inst: &impl Fn(EscapeQuotes) -> String, ) -> lints::UnexpectedCfgCargoHelp { // We don't want to suggest the `build.rs` way to expected cfgs if we are already in a - // `build.rs`. We therefor do a best effort check (looking if the `--crate-name` is + // `build.rs`. We therefore do a best effort check (looking if the `--crate-name` is // `build_script_build`) to try to figure out if we are building a Cargo build script let unescaped = &inst(EscapeQuotes::No); diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 9fa5501433453..f33a6a430e8c5 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -57,6 +57,7 @@ pub mod lifetime_syntax; mod lints; mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; +mod misleading_cfg_in_build_script; mod multiple_supertrait_upcastable; mod non_ascii_idents; mod non_fmt_panic; @@ -101,6 +102,7 @@ use let_underscore::*; use lifetime_syntax::*; use macro_expr_fragment_specifier_2024_migration::*; use map_unit_fn::*; +use misleading_cfg_in_build_script::MisleadingCfgInBuildScript; use multiple_supertrait_upcastable::*; use non_ascii_idents::*; use non_fmt_panic::NonPanicFmt; @@ -155,6 +157,7 @@ early_lint_methods!( pub BuiltinCombinedPreExpansionLintPass, [ KeywordIdents: KeywordIdents, + MisleadingCfgInBuildScript: MisleadingCfgInBuildScript, ] ] ); diff --git a/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs new file mode 100644 index 0000000000000..435b65184e7f7 --- /dev/null +++ b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs @@ -0,0 +1,290 @@ +use rustc_ast::ast::{Attribute, MacCall}; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{MetaItem, MetaItemInner, MetaItemKind}; +use rustc_errors::{Applicability, DiagDecorator}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{Span, Symbol, sym}; + +use crate::{EarlyContext, EarlyLintPass, LintContext}; + +declare_lint! { + /// Checks for usage of `#[cfg]`/`#[cfg_attr]`/`cfg!()` in `build.rs` scripts. + /// + /// ### Explanation + /// + /// It checks the `cfg` values for the *host*, not the target. For example, `cfg!(windows)` is + /// true when compiling on Windows, so it will give the wrong answer if you are cross compiling. + /// This is because build scripts run on the machine performing compilation, rather than on the + /// target. + /// + /// ### Example + /// + /// ```rust,ignore (can only be run in cargo build scripts) + /// if cfg!(windows) {} + /// ``` + /// + /// Use instead: + /// + /// ```rust + /// if std::env::var("CARGO_CFG_WINDOWS").is_ok() {} + /// ``` + pub MISLEADING_CFG_IN_BUILD_SCRIPT, + Allow, + "use of host configs in `build.rs` scripts" +} + +declare_lint_pass!(MisleadingCfgInBuildScript => [MISLEADING_CFG_IN_BUILD_SCRIPT]); + +/// Represents the AST of `cfg` attribute and `cfg!` macro. +#[derive(Debug)] +enum CfgAst { + /// Represents an OS family such as "unix" or "windows". + OsFamily(Symbol), + /// The `any()` attribute. + Any(Vec), + /// The `all()` attribute. + All(Vec), + /// The `not()` attribute. + Not(Box), + /// Any `target_* = ""` key/value attribute. + TargetKeyValue(Symbol, Symbol), + /// the `feature = ""` attribute with its associated value. + Feature(Symbol), +} + +impl CfgAst { + fn has_only_features(&self) -> bool { + match self { + Self::OsFamily(_) | Self::TargetKeyValue(_, _) => false, + Self::Any(v) | Self::All(v) => v.is_empty() || v.iter().all(CfgAst::has_only_features), + Self::Not(v) => v.has_only_features(), + Self::Feature(_) => true, + } + } + + fn generate_replacement(&self) -> String { + self.generate_replacement_inner(true, false) + } + + fn generate_replacement_inner(&self, is_top_level: bool, parent_is_not: bool) -> String { + match self { + Self::OsFamily(os) => format!( + "std::env::var(\"CARGO_CFG_{}\"){}", + os.as_str().to_uppercase(), + if parent_is_not { ".is_err()" } else { ".is_ok()" }, + ), + Self::TargetKeyValue(cfg_target, s) => format!( + "{}std::env::var(\"CARGO_CFG_{}\").unwrap_or_default() == \"{s}\"", + if parent_is_not { "!" } else { "" }, + cfg_target.as_str().to_uppercase(), + ), + Self::Any(v) => { + if v.is_empty() { + if parent_is_not { "true" } else { "false" }.to_string() + } else if v.len() == 1 { + v[0].generate_replacement_inner(is_top_level, parent_is_not) + } else { + format!( + "{not}{open_paren}{cond}{closing_paren}", + not = if parent_is_not { "!" } else { "" }, + open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, + cond = v + .iter() + .map(|i| i.generate_replacement_inner(false, false)) + .collect::>() + .join(" || "), + closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, + ) + } + } + Self::All(v) => { + if v.is_empty() { + if parent_is_not { "false" } else { "true" }.to_string() + } else if v.len() == 1 { + v[0].generate_replacement_inner(is_top_level, parent_is_not) + } else { + format!( + "{not}{open_paren}{cond}{closing_paren}", + not = if parent_is_not { "!" } else { "" }, + open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, + cond = v + .iter() + .map(|i| i.generate_replacement_inner(false, false)) + .collect::>() + .join(" && "), + closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, + ) + } + } + Self::Not(i) => i.generate_replacement_inner(is_top_level, true), + Self::Feature(s) => format!( + "cfg!({}feature = {s}{})", + if parent_is_not { "not(" } else { "" }, + if parent_is_not { ")" } else { "" }, + ), + } + } +} + +fn parse_meta_item(meta: MetaItem, has_unknown: &mut bool, out: &mut Vec) { + let Some(name) = meta.name() else { + *has_unknown = true; + return; + }; + match meta.kind { + MetaItemKind::Word => { + if [sym::windows, sym::unix].contains(&name) { + out.push(CfgAst::OsFamily(name)); + return; + } + } + MetaItemKind::NameValue(item) => { + if name == sym::feature { + out.push(CfgAst::Feature(item.symbol)); + return; + } else if name.as_str().starts_with("target_") { + out.push(CfgAst::TargetKeyValue(name, item.symbol)); + return; + } + } + MetaItemKind::List(item) => { + if !*has_unknown && [sym::any, sym::not, sym::all].contains(&name) { + let mut sub_out = Vec::new(); + + for sub in item { + if let MetaItemInner::MetaItem(item) = sub { + parse_meta_item(item, has_unknown, &mut sub_out); + if *has_unknown { + return; + } + } + } + if name == sym::any { + out.push(CfgAst::Any(sub_out)); + return; + } else if name == sym::all { + out.push(CfgAst::All(sub_out)); + return; + // `not()` can only have one argument. + } else if sub_out.len() == 1 { + out.push(CfgAst::Not(Box::new(sub_out.pop().unwrap()))); + return; + } + } + } + } + *has_unknown = true; +} + +fn parse_macro_args(tokens: &TokenStream, has_unknown: &mut bool, out: &mut Vec) { + if let Some(meta) = MetaItem::from_tokens(&mut tokens.iter()) { + parse_meta_item(meta, has_unknown, out); + } +} + +fn get_invalid_cfg_attrs(attr: &MetaItem, spans: &mut Vec, has_unknown: &mut bool) { + let Some(ident) = attr.ident() else { return }; + if [sym::unix, sym::windows].contains(&ident.name) { + spans.push(attr.span); + } else if attr.value_str().is_some() && ident.name.as_str().starts_with("target_") { + spans.push(attr.span); + } else if let Some(sub_attrs) = attr.meta_item_list() { + for sub_attr in sub_attrs { + if let Some(meta) = sub_attr.meta_item() { + get_invalid_cfg_attrs(meta, spans, has_unknown); + } + } + } else { + *has_unknown = true; + } +} + +fn is_build_script(cx: &EarlyContext<'_>) -> bool { + rustc_session::utils::was_invoked_from_cargo() + && cx.sess().opts.crate_name.as_deref() == Some("build_script_build") +} + +const ERROR_MESSAGE: &str = "target-based cfg should be avoided in build scripts"; + +impl EarlyLintPass for MisleadingCfgInBuildScript { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { + if !is_build_script(cx) { + return; + } + + let mut spans = Vec::new(); + let mut has_unknown = false; + match attr.name() { + Some(sym::cfg) if let Some(meta) = attr.meta() => { + get_invalid_cfg_attrs(&meta, &mut spans, &mut has_unknown); + } + Some(sym::cfg_attr) + if let Some(sub_attrs) = attr.meta_item_list() + && let Some(meta) = sub_attrs.first().and_then(|a| a.meta_item()) => + { + get_invalid_cfg_attrs(meta, &mut spans, &mut has_unknown); + } + _ => return, + } + if !spans.is_empty() { + if has_unknown { + // If the `cfg`/`cfg_attr` attribute contains not only invalid items, we display + // spans of all invalid items. + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + spans, + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE); + }), + ); + } else { + // No "good" item in the `cfg`/`cfg_attr` attribute so we can use the span of the + // whole attribute directly. + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + attr.span, + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE); + }), + ); + } + } + } + + fn check_mac(&mut self, cx: &EarlyContext<'_>, call: &MacCall) { + if !is_build_script(cx) { + return; + } + + if call.path.segments.len() == 1 && call.path.segments[0].ident.name == sym::cfg { + let mut ast = Vec::new(); + let mut has_unknown = false; + parse_macro_args(&call.args.tokens, &mut has_unknown, &mut ast); + if !has_unknown && ast.len() > 1 { + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + call.span(), + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE); + }), + ); + } else if let Some(ast) = ast.get(0) + && !ast.has_only_features() + { + let span = call.span(); + cx.emit_span_lint( + MISLEADING_CFG_IN_BUILD_SCRIPT, + span, + DiagDecorator(|diag| { + diag.primary_message(ERROR_MESSAGE).span_suggestion( + span, + "use cargo environment variables if possible", + ast.generate_replacement(), + Applicability::MaybeIncorrect, + ); + }), + ); + } + } + } +} From 1d098a2c62a2acbe5cc56c5e65d4b264fc279cc4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 14:35:39 +0100 Subject: [PATCH 3/6] Add ui test for new `misleading_cfg_in_build_script` lint --- ...misleading_cfg_in_build_script-no-cargo.rs | 36 +++++++++ .../ui/lint/misleading_cfg_in_build_script.rs | 49 ++++++++++++ .../misleading_cfg_in_build_script.stderr | 74 +++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs create mode 100644 tests/ui/lint/misleading_cfg_in_build_script.rs create mode 100644 tests/ui/lint/misleading_cfg_in_build_script.stderr diff --git a/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs b/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs new file mode 100644 index 0000000000000..42cdcc83601a3 --- /dev/null +++ b/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs @@ -0,0 +1,36 @@ +// This test ensures that the `misleading_cfg_in_build_script` is not emitted if not +// in a cargo `build.rs` script. +// +//@ no-auto-check-cfg +//@ check-pass + +#![deny(misleading_cfg_in_build_script)] +#![allow(dead_code)] + +#[cfg(windows)] +fn unused_windows_fn() {} +#[cfg(not(windows))] +fn unused_not_windows_fn() {} +#[cfg(any(windows, feature = "yellow", unix))] +fn pink() {} + +#[cfg(feature = "green")] +fn pink() {} +#[cfg(bob)] +fn bob() {} + +fn main() { + if cfg!(windows) {} + if cfg!(not(windows)) {} + if cfg!(target_os = "freebsd") {} + if cfg!(any(target_os = "freebsd", windows)) {} + if cfg!(not(any(target_os = "freebsd", windows))) {} + if cfg!(all(target_os = "freebsd", windows)) {} + if cfg!(not(all(target_os = "freebsd", windows))) {} + if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} + + if cfg!(any()) {} + if cfg!(all()) {} + if cfg!(feature = "blue") {} + if cfg!(bob) {} +} diff --git a/tests/ui/lint/misleading_cfg_in_build_script.rs b/tests/ui/lint/misleading_cfg_in_build_script.rs new file mode 100644 index 0000000000000..75df4addec0ff --- /dev/null +++ b/tests/ui/lint/misleading_cfg_in_build_script.rs @@ -0,0 +1,49 @@ +// This test checks the `cfg()` attributes/macros in cargo build scripts. +// +//@ no-auto-check-cfg +//@ rustc-env:CARGO_CRATE_NAME=build_script_build +//@ compile-flags:--crate-name=build_script_build + +#![deny(misleading_cfg_in_build_script)] +#![allow(dead_code)] + +#[cfg(windows)] +//~^ ERROR: misleading_cfg_in_build_script +fn unused_windows_fn() {} +#[cfg(not(windows))] +//~^ ERROR: misleading_cfg_in_build_script +fn unused_not_windows_fn() {} +#[cfg(any(windows, feature = "yellow", unix))] +//~^ ERROR: misleading_cfg_in_build_script +fn pink() {} + +// Should not lint. +#[cfg(feature = "green")] +fn pink() {} +#[cfg(bob)] +fn bob() {} + +fn main() { + if cfg!(windows) {} + //~^ ERROR: misleading_cfg_in_build_script + if cfg!(not(windows)) {} + //~^ ERROR: misleading_cfg_in_build_script + if cfg!(target_os = "freebsd") {} + //~^ ERROR: misleading_cfg_in_build_script + if cfg!(any(target_os = "freebsd", windows)) {} + //~^ ERROR: misleading_cfg_in_build_script + if cfg!(not(any(target_os = "freebsd", windows))) {} + //~^ ERROR: misleading_cfg_in_build_script + if cfg!(all(target_os = "freebsd", windows)) {} + //~^ ERROR: misleading_cfg_in_build_script + if cfg!(not(all(target_os = "freebsd", windows))) {} + //~^ ERROR: misleading_cfg_in_build_script + if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} + //~^ ERROR: misleading_cfg_in_build_script + + // Should not warn. + if cfg!(any()) {} + if cfg!(all()) {} + if cfg!(feature = "blue") {} + if cfg!(bob) {} +} diff --git a/tests/ui/lint/misleading_cfg_in_build_script.stderr b/tests/ui/lint/misleading_cfg_in_build_script.stderr new file mode 100644 index 0000000000000..7152d40feafd9 --- /dev/null +++ b/tests/ui/lint/misleading_cfg_in_build_script.stderr @@ -0,0 +1,74 @@ +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:10:1 + | +LL | #[cfg(windows)] + | ^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/misleading_cfg_in_build_script.rs:7:9 + | +LL | #![deny(misleading_cfg_in_build_script)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:13:1 + | +LL | #[cfg(not(windows))] + | ^^^^^^^^^^^^^^^^^^^^ + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:16:11 + | +LL | #[cfg(any(windows, feature = "yellow", unix))] + | ^^^^^^^ ^^^^ + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:27:8 + | +LL | if cfg!(windows) {} + | ^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_ok()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:29:8 + | +LL | if cfg!(not(windows)) {} + | ^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_err()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:31:8 + | +LL | if cfg!(target_os = "freebsd") {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd"` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:33:8 + | +LL | if cfg!(any(target_os = "freebsd", windows)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:35:8 + | +LL | if cfg!(not(any(target_os = "freebsd", windows))) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok())` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:37:8 + | +LL | if cfg!(all(target_os = "freebsd", windows)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok()` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:39:8 + | +LL | if cfg!(not(all(target_os = "freebsd", windows))) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok())` + +error: target-based cfg should be avoided in build scripts + --> $DIR/misleading_cfg_in_build_script.rs:41:8 + | +LL | if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && (std::env::var("CARGO_CFG_WINDOWS").is_ok() || cfg!(not(feature = red)))` + +error: aborting due to 11 previous errors + From 7f33fa076aa12ac88576c5f532174da040dbc870 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 11 Mar 2026 23:02:39 +0100 Subject: [PATCH 4/6] Add `misleading_cfg_in_build_script` to the list of lints which cannot have a compiler output generated by the lint-docs script --- src/tools/lint-docs/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index f7d487333e32b..739de9e801a59 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -332,6 +332,7 @@ impl<'a> LintExtractor<'a> { if matches!( lint.name.as_str(), "unused_features" // broken lint + | "misleading_cfg_in_build_script" // only run in cargo build scripts ) { return Ok(()); } From 395e9b56658bb56149c7ccacc0170b2e27fe51f9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 19 Mar 2026 14:33:31 +0100 Subject: [PATCH 5/6] Remove `is_build_script` check for `misleading_cfg_in_build_script` rustc lint --- .../src/misleading_cfg_in_build_script.rs | 15 +------- ...misleading_cfg_in_build_script-no-cargo.rs | 36 ------------------- .../ui/lint/misleading_cfg_in_build_script.rs | 2 -- .../misleading_cfg_in_build_script.stderr | 24 ++++++------- 4 files changed, 13 insertions(+), 64 deletions(-) delete mode 100644 tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs diff --git a/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs index 435b65184e7f7..fce3c017ec2a7 100644 --- a/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs +++ b/compiler/rustc_lint/src/misleading_cfg_in_build_script.rs @@ -19,7 +19,7 @@ declare_lint! { /// /// ### Example /// - /// ```rust,ignore (can only be run in cargo build scripts) + /// ```rust,ignore (should only be run in cargo build scripts) /// if cfg!(windows) {} /// ``` /// @@ -199,19 +199,10 @@ fn get_invalid_cfg_attrs(attr: &MetaItem, spans: &mut Vec, has_unknown: &m } } -fn is_build_script(cx: &EarlyContext<'_>) -> bool { - rustc_session::utils::was_invoked_from_cargo() - && cx.sess().opts.crate_name.as_deref() == Some("build_script_build") -} - const ERROR_MESSAGE: &str = "target-based cfg should be avoided in build scripts"; impl EarlyLintPass for MisleadingCfgInBuildScript { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { - if !is_build_script(cx) { - return; - } - let mut spans = Vec::new(); let mut has_unknown = false; match attr.name() { @@ -252,10 +243,6 @@ impl EarlyLintPass for MisleadingCfgInBuildScript { } fn check_mac(&mut self, cx: &EarlyContext<'_>, call: &MacCall) { - if !is_build_script(cx) { - return; - } - if call.path.segments.len() == 1 && call.path.segments[0].ident.name == sym::cfg { let mut ast = Vec::new(); let mut has_unknown = false; diff --git a/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs b/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs deleted file mode 100644 index 42cdcc83601a3..0000000000000 --- a/tests/ui/lint/misleading_cfg_in_build_script-no-cargo.rs +++ /dev/null @@ -1,36 +0,0 @@ -// This test ensures that the `misleading_cfg_in_build_script` is not emitted if not -// in a cargo `build.rs` script. -// -//@ no-auto-check-cfg -//@ check-pass - -#![deny(misleading_cfg_in_build_script)] -#![allow(dead_code)] - -#[cfg(windows)] -fn unused_windows_fn() {} -#[cfg(not(windows))] -fn unused_not_windows_fn() {} -#[cfg(any(windows, feature = "yellow", unix))] -fn pink() {} - -#[cfg(feature = "green")] -fn pink() {} -#[cfg(bob)] -fn bob() {} - -fn main() { - if cfg!(windows) {} - if cfg!(not(windows)) {} - if cfg!(target_os = "freebsd") {} - if cfg!(any(target_os = "freebsd", windows)) {} - if cfg!(not(any(target_os = "freebsd", windows))) {} - if cfg!(all(target_os = "freebsd", windows)) {} - if cfg!(not(all(target_os = "freebsd", windows))) {} - if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} - - if cfg!(any()) {} - if cfg!(all()) {} - if cfg!(feature = "blue") {} - if cfg!(bob) {} -} diff --git a/tests/ui/lint/misleading_cfg_in_build_script.rs b/tests/ui/lint/misleading_cfg_in_build_script.rs index 75df4addec0ff..e7c840d596ac5 100644 --- a/tests/ui/lint/misleading_cfg_in_build_script.rs +++ b/tests/ui/lint/misleading_cfg_in_build_script.rs @@ -1,8 +1,6 @@ // This test checks the `cfg()` attributes/macros in cargo build scripts. // //@ no-auto-check-cfg -//@ rustc-env:CARGO_CRATE_NAME=build_script_build -//@ compile-flags:--crate-name=build_script_build #![deny(misleading_cfg_in_build_script)] #![allow(dead_code)] diff --git a/tests/ui/lint/misleading_cfg_in_build_script.stderr b/tests/ui/lint/misleading_cfg_in_build_script.stderr index 7152d40feafd9..e2c75999db929 100644 --- a/tests/ui/lint/misleading_cfg_in_build_script.stderr +++ b/tests/ui/lint/misleading_cfg_in_build_script.stderr @@ -1,71 +1,71 @@ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:10:1 + --> $DIR/misleading_cfg_in_build_script.rs:8:1 | LL | #[cfg(windows)] | ^^^^^^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/misleading_cfg_in_build_script.rs:7:9 + --> $DIR/misleading_cfg_in_build_script.rs:5:9 | LL | #![deny(misleading_cfg_in_build_script)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:13:1 + --> $DIR/misleading_cfg_in_build_script.rs:11:1 | LL | #[cfg(not(windows))] | ^^^^^^^^^^^^^^^^^^^^ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:16:11 + --> $DIR/misleading_cfg_in_build_script.rs:14:11 | LL | #[cfg(any(windows, feature = "yellow", unix))] | ^^^^^^^ ^^^^ error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:27:8 + --> $DIR/misleading_cfg_in_build_script.rs:25:8 | LL | if cfg!(windows) {} | ^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_ok()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:29:8 + --> $DIR/misleading_cfg_in_build_script.rs:27:8 | LL | if cfg!(not(windows)) {} | ^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_WINDOWS").is_err()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:31:8 + --> $DIR/misleading_cfg_in_build_script.rs:29:8 | LL | if cfg!(target_os = "freebsd") {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd"` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:33:8 + --> $DIR/misleading_cfg_in_build_script.rs:31:8 | LL | if cfg!(any(target_os = "freebsd", windows)) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:35:8 + --> $DIR/misleading_cfg_in_build_script.rs:33:8 | LL | if cfg!(not(any(target_os = "freebsd", windows))) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" || std::env::var("CARGO_CFG_WINDOWS").is_ok())` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:37:8 + --> $DIR/misleading_cfg_in_build_script.rs:35:8 | LL | if cfg!(all(target_os = "freebsd", windows)) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok()` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:39:8 + --> $DIR/misleading_cfg_in_build_script.rs:37:8 | LL | if cfg!(not(all(target_os = "freebsd", windows))) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `!(std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && std::env::var("CARGO_CFG_WINDOWS").is_ok())` error: target-based cfg should be avoided in build scripts - --> $DIR/misleading_cfg_in_build_script.rs:41:8 + --> $DIR/misleading_cfg_in_build_script.rs:39:8 | LL | if cfg!(all(target_os = "freebsd", any(windows, not(feature = "red")))) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use cargo environment variables if possible: `std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "freebsd" && (std::env::var("CARGO_CFG_WINDOWS").is_ok() || cfg!(not(feature = red)))` From a3c37691f1dbbec439acacedab5c0d42713aeb42 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 10 Apr 2026 12:03:34 +0200 Subject: [PATCH 6/6] Move `misleading_cfg_in_build_script` directly into attribute parsing --- .../rustc_attr_parsing/src/attributes/cfg.rs | 150 +++++++++- compiler/rustc_attr_parsing/src/errors.rs | 12 + compiler/rustc_attr_parsing/src/lib.rs | 1 + compiler/rustc_builtin_macros/src/cfg.rs | 4 +- compiler/rustc_lint/src/lib.rs | 3 - .../src/misleading_cfg_in_build_script.rs | 277 ------------------ compiler/rustc_lint_defs/src/builtin.rs | 27 ++ 7 files changed, 188 insertions(+), 286 deletions(-) delete mode 100644 compiler/rustc_lint/src/misleading_cfg_in_build_script.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 84c83be8b4a5d..dcd67e70b5e7e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -3,7 +3,7 @@ use std::convert::identity; use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{AttrItem, Attribute, LitKind, ast, token}; -use rustc_errors::{Applicability, PResult, msg}; +use rustc_errors::{Applicability, Diagnostic, PResult, msg}; use rustc_feature::{ AttrSuggestionStyle, AttributeTemplate, Features, GatedCfg, find_gated_cfg, template, }; @@ -85,9 +85,151 @@ pub fn parse_cfg( parse_cfg_entry(cx, single).ok() } +fn generate_cfg_replacement(entry: &CfgEntry) -> String { + generate_cfg_replacement_inner(entry, true, false) +} + +fn generate_cfg_replacement_inner(entry: &CfgEntry, is_top_level: bool, parent_is_not: bool) -> String { + match entry { + CfgEntry::NameValue { name, value, .. } => { + let name = name.as_str(); + match value { + Some(value) => format!( + "{}std::env::var(\"CARGO_CFG_{}\").unwrap_or_default() == \"{value}\"", + if parent_is_not { "!" } else { "" }, + name.to_uppercase(), + ), + None => format!( + "std::env::var(\"CARGO_CFG_{}\"){}", + name.to_uppercase(), + if parent_is_not { ".is_err()" } else { ".is_ok()" }, + ), + } + } + CfgEntry::Any(entries, _) => { + match entries.as_slice() { + [] => if parent_is_not { "true" } else { "false" }.to_string(), + [entry] => generate_cfg_replacement_inner(&entry, is_top_level, parent_is_not), + _ => format!( + "{not}{open_paren}{cond}{closing_paren}", + not = if parent_is_not { "!" } else { "" }, + open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, + cond = entries + .iter() + .map(|cfg| generate_cfg_replacement_inner(cfg, false, false)) + .collect::>() + .join(" || "), + closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, + ), + } + } + CfgEntry::All(entries, _) => { + match entries.as_slice() { + [] => if parent_is_not { "false" } else { "true" }.to_string(), + [entry] => generate_cfg_replacement_inner(&entry, is_top_level, parent_is_not), + _ => format!( + "{not}{open_paren}{cond}{closing_paren}", + not = if parent_is_not { "!" } else { "" }, + open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, + cond = entries + .iter() + .map(|cfg| generate_cfg_replacement_inner(cfg, false, false)) + .collect::>() + .join(" && "), + closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, + ), + } + } + CfgEntry::Not(cfg, _) => generate_cfg_replacement_inner(cfg, is_top_level, true), + _ => String::new(), + } +} + +fn misleading_cfgs(entry: &CfgEntry, spans: &mut Vec, has_ok_cfgs: &mut bool) { + match entry { + CfgEntry::All(entries, _) | CfgEntry::Any(entries, _) => { + for entry in entries { + misleading_cfgs(entry, spans, has_ok_cfgs); + } + } + CfgEntry::Not(entry, _) => misleading_cfgs(entry, spans, has_ok_cfgs), + CfgEntry::Bool(..) | CfgEntry::Version(..) => { + *has_ok_cfgs = true; + } + CfgEntry::NameValue { name, value, span } => { + match value { + Some(_) => { + let name = name.as_str(); + if name.starts_with("target_") { + spans.push(*span); + } else { + *has_ok_cfgs = true; + } + } + None => { + if [sym::windows, sym::unix].contains(&name) { + spans.push(*span); + } else { + *has_ok_cfgs = true; + } + } + } + } + } +} + +fn check_unexpected_cfgs(cx: &mut AcceptContext<'_, '_, S>, entry: &CfgEntry, _span: Span, is_cfg_macro: bool) { + let mut spans = Vec::new(); + let mut has_ok_cfgs = false; + misleading_cfgs(entry, &mut spans, &mut has_ok_cfgs); + if spans.is_empty() { + return; + } + if !is_cfg_macro || has_ok_cfgs { + cx.emit_dyn_lint( + UNEXPECTED_CFGS, + |dcx, level| crate::errors::UnexpectedCfg { + span: None, + suggestion_message: "", + }.into_diag(dcx, level), + spans, + ); + return; + } + let replacement = generate_cfg_replacement(entry); + // The span including the `cfg!()` macro. + let span = cx.attr_span; + cx.emit_dyn_lint( + UNEXPECTED_CFGS, + move |dcx, level| crate::errors::UnexpectedCfg { + span: Some(span), + suggestion_message: &replacement, + }.into_diag(dcx, level), + span, + ); +} + +pub fn parse_cfg_entry_macro( + cx: &mut AcceptContext<'_, '_, S>, + item: &MetaItemOrLitParser, +) -> Result { + let entry = parse_cfg_entry_inner(cx, item)?; + check_unexpected_cfgs(cx, &entry, item.span(), true); + Ok(entry) +} + pub fn parse_cfg_entry( cx: &mut AcceptContext<'_, '_, S>, item: &MetaItemOrLitParser, +) -> Result { + let entry = parse_cfg_entry_inner(cx, item)?; + check_unexpected_cfgs(cx, &entry, item.span(), false); + Ok(entry) +} + +fn parse_cfg_entry_inner( + cx: &mut AcceptContext<'_, '_, S>, + item: &MetaItemOrLitParser, ) -> Result { Ok(match item { MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() { @@ -96,14 +238,14 @@ pub fn parse_cfg_entry( let Some(single) = list.single() else { return Err(cx.adcx().expected_single_argument(list.span, list.len())); }; - CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span) + CfgEntry::Not(Box::new(parse_cfg_entry_inner(cx, single)?), list.span) } Some(sym::any) => CfgEntry::Any( - list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(), + list.mixed().flat_map(|sub_item| parse_cfg_entry_inner(cx, sub_item)).collect(), list.span, ), Some(sym::all) => CfgEntry::All( - list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(), + list.mixed().flat_map(|sub_item| parse_cfg_entry_inner(cx, sub_item)).collect(), list.span, ), Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?, diff --git a/compiler/rustc_attr_parsing/src/errors.rs b/compiler/rustc_attr_parsing/src/errors.rs index 96a4c473c3ae2..1b80e64de5454 100644 --- a/compiler/rustc_attr_parsing/src/errors.rs +++ b/compiler/rustc_attr_parsing/src/errors.rs @@ -321,3 +321,15 @@ pub(crate) struct IncorrectDoNotRecommendLocation { #[label("not a trait implementation")] pub target_span: Span, } + +#[derive(Diagnostic)] +#[diag("target-based cfg should be avoided in build scripts")] +pub(crate) struct UnexpectedCfg<'a> { + #[suggestion( + "use cargo environment variables if possible", + code = "{suggestion_message}", + applicability = "maybe-incorrect", + )] + pub span: Option, + pub suggestion_message: &'a str, +} diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 73618dbfbf30c..8caa5fff81ba9 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -109,6 +109,7 @@ pub mod validate_attr; pub use attributes::AttributeSafety; pub use attributes::cfg::{ CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg, parse_cfg_attr, parse_cfg_entry, + parse_cfg_entry_macro, }; pub use attributes::cfg_select::*; pub use attributes::util::{is_builtin_attr, parse_version}; diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index 2872cff0fdc7a..892f7326c4e7b 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -7,7 +7,7 @@ use rustc_ast::{AttrStyle, token}; use rustc_attr_parsing::parser::{AllowExprMetavar, MetaItemOrLitParser}; use rustc_attr_parsing::{ self as attr, AttributeParser, AttributeSafety, CFG_TEMPLATE, ParsedDescription, ShouldEmit, - parse_cfg_entry, + parse_cfg_entry_macro, }; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; use rustc_hir::attrs::CfgEntry; @@ -63,7 +63,7 @@ fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result [MISLEADING_CFG_IN_BUILD_SCRIPT]); - -/// Represents the AST of `cfg` attribute and `cfg!` macro. -#[derive(Debug)] -enum CfgAst { - /// Represents an OS family such as "unix" or "windows". - OsFamily(Symbol), - /// The `any()` attribute. - Any(Vec), - /// The `all()` attribute. - All(Vec), - /// The `not()` attribute. - Not(Box), - /// Any `target_* = ""` key/value attribute. - TargetKeyValue(Symbol, Symbol), - /// the `feature = ""` attribute with its associated value. - Feature(Symbol), -} - -impl CfgAst { - fn has_only_features(&self) -> bool { - match self { - Self::OsFamily(_) | Self::TargetKeyValue(_, _) => false, - Self::Any(v) | Self::All(v) => v.is_empty() || v.iter().all(CfgAst::has_only_features), - Self::Not(v) => v.has_only_features(), - Self::Feature(_) => true, - } - } - - fn generate_replacement(&self) -> String { - self.generate_replacement_inner(true, false) - } - - fn generate_replacement_inner(&self, is_top_level: bool, parent_is_not: bool) -> String { - match self { - Self::OsFamily(os) => format!( - "std::env::var(\"CARGO_CFG_{}\"){}", - os.as_str().to_uppercase(), - if parent_is_not { ".is_err()" } else { ".is_ok()" }, - ), - Self::TargetKeyValue(cfg_target, s) => format!( - "{}std::env::var(\"CARGO_CFG_{}\").unwrap_or_default() == \"{s}\"", - if parent_is_not { "!" } else { "" }, - cfg_target.as_str().to_uppercase(), - ), - Self::Any(v) => { - if v.is_empty() { - if parent_is_not { "true" } else { "false" }.to_string() - } else if v.len() == 1 { - v[0].generate_replacement_inner(is_top_level, parent_is_not) - } else { - format!( - "{not}{open_paren}{cond}{closing_paren}", - not = if parent_is_not { "!" } else { "" }, - open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, - cond = v - .iter() - .map(|i| i.generate_replacement_inner(false, false)) - .collect::>() - .join(" || "), - closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, - ) - } - } - Self::All(v) => { - if v.is_empty() { - if parent_is_not { "false" } else { "true" }.to_string() - } else if v.len() == 1 { - v[0].generate_replacement_inner(is_top_level, parent_is_not) - } else { - format!( - "{not}{open_paren}{cond}{closing_paren}", - not = if parent_is_not { "!" } else { "" }, - open_paren = if !parent_is_not && is_top_level { "" } else { "(" }, - cond = v - .iter() - .map(|i| i.generate_replacement_inner(false, false)) - .collect::>() - .join(" && "), - closing_paren = if !parent_is_not && is_top_level { "" } else { ")" }, - ) - } - } - Self::Not(i) => i.generate_replacement_inner(is_top_level, true), - Self::Feature(s) => format!( - "cfg!({}feature = {s}{})", - if parent_is_not { "not(" } else { "" }, - if parent_is_not { ")" } else { "" }, - ), - } - } -} - -fn parse_meta_item(meta: MetaItem, has_unknown: &mut bool, out: &mut Vec) { - let Some(name) = meta.name() else { - *has_unknown = true; - return; - }; - match meta.kind { - MetaItemKind::Word => { - if [sym::windows, sym::unix].contains(&name) { - out.push(CfgAst::OsFamily(name)); - return; - } - } - MetaItemKind::NameValue(item) => { - if name == sym::feature { - out.push(CfgAst::Feature(item.symbol)); - return; - } else if name.as_str().starts_with("target_") { - out.push(CfgAst::TargetKeyValue(name, item.symbol)); - return; - } - } - MetaItemKind::List(item) => { - if !*has_unknown && [sym::any, sym::not, sym::all].contains(&name) { - let mut sub_out = Vec::new(); - - for sub in item { - if let MetaItemInner::MetaItem(item) = sub { - parse_meta_item(item, has_unknown, &mut sub_out); - if *has_unknown { - return; - } - } - } - if name == sym::any { - out.push(CfgAst::Any(sub_out)); - return; - } else if name == sym::all { - out.push(CfgAst::All(sub_out)); - return; - // `not()` can only have one argument. - } else if sub_out.len() == 1 { - out.push(CfgAst::Not(Box::new(sub_out.pop().unwrap()))); - return; - } - } - } - } - *has_unknown = true; -} - -fn parse_macro_args(tokens: &TokenStream, has_unknown: &mut bool, out: &mut Vec) { - if let Some(meta) = MetaItem::from_tokens(&mut tokens.iter()) { - parse_meta_item(meta, has_unknown, out); - } -} - -fn get_invalid_cfg_attrs(attr: &MetaItem, spans: &mut Vec, has_unknown: &mut bool) { - let Some(ident) = attr.ident() else { return }; - if [sym::unix, sym::windows].contains(&ident.name) { - spans.push(attr.span); - } else if attr.value_str().is_some() && ident.name.as_str().starts_with("target_") { - spans.push(attr.span); - } else if let Some(sub_attrs) = attr.meta_item_list() { - for sub_attr in sub_attrs { - if let Some(meta) = sub_attr.meta_item() { - get_invalid_cfg_attrs(meta, spans, has_unknown); - } - } - } else { - *has_unknown = true; - } -} - -const ERROR_MESSAGE: &str = "target-based cfg should be avoided in build scripts"; - -impl EarlyLintPass for MisleadingCfgInBuildScript { - fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { - let mut spans = Vec::new(); - let mut has_unknown = false; - match attr.name() { - Some(sym::cfg) if let Some(meta) = attr.meta() => { - get_invalid_cfg_attrs(&meta, &mut spans, &mut has_unknown); - } - Some(sym::cfg_attr) - if let Some(sub_attrs) = attr.meta_item_list() - && let Some(meta) = sub_attrs.first().and_then(|a| a.meta_item()) => - { - get_invalid_cfg_attrs(meta, &mut spans, &mut has_unknown); - } - _ => return, - } - if !spans.is_empty() { - if has_unknown { - // If the `cfg`/`cfg_attr` attribute contains not only invalid items, we display - // spans of all invalid items. - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - spans, - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE); - }), - ); - } else { - // No "good" item in the `cfg`/`cfg_attr` attribute so we can use the span of the - // whole attribute directly. - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - attr.span, - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE); - }), - ); - } - } - } - - fn check_mac(&mut self, cx: &EarlyContext<'_>, call: &MacCall) { - if call.path.segments.len() == 1 && call.path.segments[0].ident.name == sym::cfg { - let mut ast = Vec::new(); - let mut has_unknown = false; - parse_macro_args(&call.args.tokens, &mut has_unknown, &mut ast); - if !has_unknown && ast.len() > 1 { - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - call.span(), - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE); - }), - ); - } else if let Some(ast) = ast.get(0) - && !ast.has_only_features() - { - let span = call.span(); - cx.emit_span_lint( - MISLEADING_CFG_IN_BUILD_SCRIPT, - span, - DiagDecorator(|diag| { - diag.primary_message(ERROR_MESSAGE).span_suggestion( - span, - "use cargo environment variables if possible", - ast.generate_replacement(), - Applicability::MaybeIncorrect, - ); - }), - ); - } - } - } -} diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index b027872dd99cc..778039b51cc40 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -73,6 +73,7 @@ declare_lint_pass! { MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, META_VARIABLE_MISUSE, + MISLEADING_CFG_IN_BUILD_SCRIPT, MISPLACED_DIAGNOSTIC_ATTRIBUTES, MISSING_ABI, MISSING_UNSAFE_ON_EXTERN, @@ -5695,3 +5696,29 @@ declare_lint! { report_in_deps: false, }; } + +declare_lint! { + /// Checks for usage of `#[cfg]`/`#[cfg_attr]`/`cfg!()` in `build.rs` scripts. + /// + /// ### Explanation + /// + /// It checks the `cfg` values for the *host*, not the target. For example, `cfg!(windows)` is + /// true when compiling on Windows, so it will give the wrong answer if you are cross compiling. + /// This is because build scripts run on the machine performing compilation, rather than on the + /// target. + /// + /// ### Example + /// + /// ```rust,ignore (should only be run in cargo build scripts) + /// if cfg!(windows) {} + /// ``` + /// + /// Use instead: + /// + /// ```rust + /// if std::env::var("CARGO_CFG_WINDOWS").is_ok() {} + /// ``` + pub MISLEADING_CFG_IN_BUILD_SCRIPT, + Allow, + "use of host configs in `build.rs` scripts" +}