bootstrap/core/build_steps/
clippy.rs

1//! Implementation of running clippy on the compiler, standard library and various tools.
2
3use super::compile::{run_cargo, rustc_cargo, std_cargo};
4use super::tool::{SourceType, prepare_tool_cargo};
5use super::{check, compile};
6use crate::builder::{Builder, ShouldRun};
7use crate::core::build_steps::compile::std_crates_for_run_make;
8use crate::core::builder;
9use crate::core::builder::{Alias, Kind, RunConfig, Step, crate_description};
10use crate::utils::build_stamp::{self, BuildStamp};
11use crate::{Mode, Subcommand, TargetSelection};
12
13/// Disable the most spammy clippy lints
14const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[
15    "many_single_char_names", // there are a lot in stdarch
16    "collapsible_if",
17    "type_complexity",
18    "missing_safety_doc", // almost 3K warnings
19    "too_many_arguments",
20    "needless_lifetimes", // people want to keep the lifetimes
21    "wrong_self_convention",
22];
23
24fn lint_args(builder: &Builder<'_>, config: &LintConfig, ignored_rules: &[&str]) -> Vec<String> {
25    fn strings<'a>(arr: &'a [&str]) -> impl Iterator<Item = String> + 'a {
26        arr.iter().copied().map(String::from)
27    }
28
29    let Subcommand::Clippy { fix, allow_dirty, allow_staged, .. } = &builder.config.cmd else {
30        unreachable!("clippy::lint_args can only be called from `clippy` subcommands.");
31    };
32
33    let mut args = vec![];
34    if *fix {
35        #[rustfmt::skip]
36            args.extend(strings(&[
37                "--fix", "-Zunstable-options",
38                // FIXME: currently, `--fix` gives an error while checking tests for libtest,
39                // possibly because libtest is not yet built in the sysroot.
40                // As a workaround, avoid checking tests and benches when passed --fix.
41                "--lib", "--bins", "--examples",
42            ]));
43
44        if *allow_dirty {
45            args.push("--allow-dirty".to_owned());
46        }
47
48        if *allow_staged {
49            args.push("--allow-staged".to_owned());
50        }
51    }
52
53    args.extend(strings(&["--"]));
54
55    if config.deny.is_empty() && config.forbid.is_empty() {
56        args.extend(strings(&["--cap-lints", "warn"]));
57    }
58
59    let all_args = std::env::args().collect::<Vec<_>>();
60    args.extend(get_clippy_rules_in_order(&all_args, config));
61
62    args.extend(ignored_rules.iter().map(|lint| format!("-Aclippy::{}", lint)));
63    args.extend(builder.config.free_args.clone());
64    args
65}
66
67/// We need to keep the order of the given clippy lint rules before passing them.
68/// Since clap doesn't offer any useful interface for this purpose out of the box,
69/// we have to handle it manually.
70pub fn get_clippy_rules_in_order(all_args: &[String], config: &LintConfig) -> Vec<String> {
71    let mut result = vec![];
72
73    for (prefix, item) in
74        [("-A", &config.allow), ("-D", &config.deny), ("-W", &config.warn), ("-F", &config.forbid)]
75    {
76        item.iter().for_each(|v| {
77            let rule = format!("{prefix}{v}");
78            // Arguments added by bootstrap in LintConfig won't show up in the all_args list, so
79            // put them at the end of the command line.
80            let position = all_args.iter().position(|t| t == &rule || t == v).unwrap_or(usize::MAX);
81            result.push((position, rule));
82        });
83    }
84
85    result.sort_by_key(|&(position, _)| position);
86    result.into_iter().map(|v| v.1).collect()
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Hash)]
90pub struct LintConfig {
91    pub allow: Vec<String>,
92    pub warn: Vec<String>,
93    pub deny: Vec<String>,
94    pub forbid: Vec<String>,
95}
96
97impl LintConfig {
98    fn new(builder: &Builder<'_>) -> Self {
99        match builder.config.cmd.clone() {
100            Subcommand::Clippy { allow, deny, warn, forbid, .. } => {
101                Self { allow, warn, deny, forbid }
102            }
103            _ => unreachable!("LintConfig can only be called from `clippy` subcommands."),
104        }
105    }
106
107    fn merge(&self, other: &Self) -> Self {
108        let merged = |self_attr: &[String], other_attr: &[String]| -> Vec<String> {
109            self_attr.iter().cloned().chain(other_attr.iter().cloned()).collect()
110        };
111        // This is written this way to ensure we get a compiler error if we add a new field.
112        Self {
113            allow: merged(&self.allow, &other.allow),
114            warn: merged(&self.warn, &other.warn),
115            deny: merged(&self.deny, &other.deny),
116            forbid: merged(&self.forbid, &other.forbid),
117        }
118    }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122pub struct Std {
123    pub target: TargetSelection,
124    config: LintConfig,
125    /// Whether to lint only a subset of crates.
126    crates: Vec<String>,
127}
128
129impl Step for Std {
130    type Output = ();
131    const DEFAULT: bool = true;
132
133    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
134        run.crate_or_deps("sysroot").path("library")
135    }
136
137    fn make_run(run: RunConfig<'_>) {
138        let crates = std_crates_for_run_make(&run);
139        let config = LintConfig::new(run.builder);
140        run.builder.ensure(Std { target: run.target, config, crates });
141    }
142
143    fn run(self, builder: &Builder<'_>) {
144        builder.require_submodule("library/stdarch", None);
145
146        let target = self.target;
147        let compiler = builder.compiler(builder.top_stage, builder.config.build);
148
149        let mut cargo = builder::Cargo::new(
150            builder,
151            compiler,
152            Mode::Std,
153            SourceType::InTree,
154            target,
155            Kind::Clippy,
156        );
157
158        std_cargo(builder, target, compiler.stage, &mut cargo);
159
160        for krate in &*self.crates {
161            cargo.arg("-p").arg(krate);
162        }
163
164        let _guard =
165            builder.msg_clippy(format_args!("library{}", crate_description(&self.crates)), target);
166
167        run_cargo(
168            builder,
169            cargo,
170            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
171            &build_stamp::libstd_stamp(builder, compiler, target),
172            vec![],
173            true,
174            false,
175        );
176    }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Hash)]
180pub struct Rustc {
181    pub target: TargetSelection,
182    config: LintConfig,
183    /// Whether to lint only a subset of crates.
184    crates: Vec<String>,
185}
186
187impl Step for Rustc {
188    type Output = ();
189    const ONLY_HOSTS: bool = true;
190    const DEFAULT: bool = true;
191
192    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
193        run.crate_or_deps("rustc-main").path("compiler")
194    }
195
196    fn make_run(run: RunConfig<'_>) {
197        let crates = run.make_run_crates(Alias::Compiler);
198        let config = LintConfig::new(run.builder);
199        run.builder.ensure(Rustc { target: run.target, config, crates });
200    }
201
202    /// Lints the compiler.
203    ///
204    /// This will lint the compiler for a particular stage of the build using
205    /// the `compiler` targeting the `target` architecture.
206    fn run(self, builder: &Builder<'_>) {
207        let compiler = builder.compiler(builder.top_stage, builder.config.build);
208        let target = self.target;
209
210        if compiler.stage != 0 {
211            // If we're not in stage 0, then we won't have a std from the beta
212            // compiler around. That means we need to make sure there's one in
213            // the sysroot for the compiler to find. Otherwise, we're going to
214            // fail when building crates that need to generate code (e.g., build
215            // scripts and their dependencies).
216            builder.ensure(compile::Std::new(compiler, compiler.host));
217            builder.ensure(compile::Std::new(compiler, target));
218        } else {
219            builder.ensure(check::Std::new(target).build_kind(Some(Kind::Check)));
220        }
221
222        let mut cargo = builder::Cargo::new(
223            builder,
224            compiler,
225            Mode::Rustc,
226            SourceType::InTree,
227            target,
228            Kind::Clippy,
229        );
230
231        rustc_cargo(builder, &mut cargo, target, &compiler, &self.crates);
232
233        // Explicitly pass -p for all compiler crates -- this will force cargo
234        // to also lint the tests/benches/examples for these crates, rather
235        // than just the leaf crate.
236        for krate in &*self.crates {
237            cargo.arg("-p").arg(krate);
238        }
239
240        let _guard =
241            builder.msg_clippy(format_args!("compiler{}", crate_description(&self.crates)), target);
242
243        run_cargo(
244            builder,
245            cargo,
246            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
247            &build_stamp::librustc_stamp(builder, compiler, target),
248            vec![],
249            true,
250            false,
251        );
252    }
253}
254
255macro_rules! lint_any {
256    ($(
257        $name:ident, $path:expr, $readable_name:expr
258        $(,lint_by_default = $lint_by_default:expr)*
259        ;
260    )+) => {
261        $(
262
263        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
264        pub struct $name {
265            pub target: TargetSelection,
266            config: LintConfig,
267        }
268
269        impl Step for $name {
270            type Output = ();
271            const DEFAULT: bool = if false $(|| $lint_by_default)* { true } else { false };
272
273            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
274                run.path($path)
275            }
276
277            fn make_run(run: RunConfig<'_>) {
278                let config = LintConfig::new(run.builder);
279                run.builder.ensure($name {
280                    target: run.target,
281                    config,
282                });
283            }
284
285            fn run(self, builder: &Builder<'_>) -> Self::Output {
286                let compiler = builder.compiler(builder.top_stage, builder.config.build);
287                let target = self.target;
288
289                builder.ensure(check::Rustc::new(target, builder).build_kind(Some(Kind::Check)));
290
291                let cargo = prepare_tool_cargo(
292                    builder,
293                    compiler,
294                    Mode::ToolRustc,
295                    target,
296                    Kind::Clippy,
297                    $path,
298                    SourceType::InTree,
299                    &[],
300                );
301
302                let _guard = builder.msg_tool(
303                    Kind::Clippy,
304                    Mode::ToolRustc,
305                    $readable_name,
306                    compiler.stage,
307                    &compiler.host,
308                    &target,
309                );
310
311                let stringified_name = stringify!($name).to_lowercase();
312                let stamp = BuildStamp::new(&builder.cargo_out(compiler, Mode::ToolRustc, target))
313                    .with_prefix(&format!("{}-check", stringified_name));
314
315                run_cargo(
316                    builder,
317                    cargo,
318                    lint_args(builder, &self.config, &[]),
319                    &stamp,
320                    vec![],
321                    true,
322                    false,
323                );
324            }
325        }
326        )+
327    }
328}
329
330lint_any!(
331    Bootstrap, "src/bootstrap", "bootstrap";
332    BuildHelper, "src/build_helper", "build_helper";
333    BuildManifest, "src/tools/build-manifest", "build-manifest";
334    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri";
335    Clippy, "src/tools/clippy", "clippy";
336    CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata";
337    CodegenGcc, "compiler/rustc_codegen_gcc", "rustc-codegen-gcc";
338    Compiletest, "src/tools/compiletest", "compiletest";
339    CoverageDump, "src/tools/coverage-dump", "coverage-dump";
340    Jsondocck, "src/tools/jsondocck", "jsondocck";
341    Jsondoclint, "src/tools/jsondoclint", "jsondoclint";
342    LintDocs, "src/tools/lint-docs", "lint-docs";
343    LlvmBitcodeLinker, "src/tools/llvm-bitcode-linker", "llvm-bitcode-linker";
344    Miri, "src/tools/miri", "miri";
345    MiroptTestTools, "src/tools/miropt-test-tools", "miropt-test-tools";
346    OptDist, "src/tools/opt-dist", "opt-dist";
347    RemoteTestClient, "src/tools/remote-test-client", "remote-test-client";
348    RemoteTestServer, "src/tools/remote-test-server", "remote-test-server";
349    Rls, "src/tools/rls", "rls";
350    RustAnalyzer, "src/tools/rust-analyzer", "rust-analyzer";
351    Rustdoc, "src/librustdoc", "clippy";
352    Rustfmt, "src/tools/rustfmt", "rustfmt";
353    RustInstaller, "src/tools/rust-installer", "rust-installer";
354    Tidy, "src/tools/tidy", "tidy";
355    TestFloatParse, "src/etc/test-float-parse", "test-float-parse";
356);
357
358#[derive(Debug, Clone, PartialEq, Eq, Hash)]
359pub struct CI {
360    target: TargetSelection,
361    config: LintConfig,
362}
363
364impl Step for CI {
365    type Output = ();
366    const DEFAULT: bool = false;
367
368    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
369        run.alias("ci")
370    }
371
372    fn make_run(run: RunConfig<'_>) {
373        let config = LintConfig::new(run.builder);
374        run.builder.ensure(CI { target: run.target, config });
375    }
376
377    fn run(self, builder: &Builder<'_>) -> Self::Output {
378        builder.ensure(Bootstrap {
379            target: self.target,
380            config: self.config.merge(&LintConfig {
381                allow: vec![],
382                warn: vec![],
383                deny: vec!["warnings".into()],
384                forbid: vec![],
385            }),
386        });
387        let library_clippy_cfg = LintConfig {
388            allow: vec!["clippy::all".into()],
389            warn: vec![],
390            deny: vec![
391                "clippy::correctness".into(),
392                "clippy::char_lit_as_u8".into(),
393                "clippy::four_forward_slashes".into(),
394                "clippy::needless_bool".into(),
395                "clippy::needless_bool_assign".into(),
396                "clippy::non_minimal_cfg".into(),
397                "clippy::print_literal".into(),
398                "clippy::same_item_push".into(),
399                "clippy::single_char_add_str".into(),
400                "clippy::to_string_in_format_args".into(),
401            ],
402            forbid: vec![],
403        };
404        builder.ensure(Std {
405            target: self.target,
406            config: self.config.merge(&library_clippy_cfg),
407            crates: vec![],
408        });
409
410        let compiler_clippy_cfg = LintConfig {
411            allow: vec!["clippy::all".into()],
412            warn: vec![],
413            deny: vec![
414                "clippy::correctness".into(),
415                "clippy::char_lit_as_u8".into(),
416                "clippy::clone_on_ref_ptr".into(),
417                "clippy::format_in_format_args".into(),
418                "clippy::four_forward_slashes".into(),
419                "clippy::needless_bool".into(),
420                "clippy::needless_bool_assign".into(),
421                "clippy::non_minimal_cfg".into(),
422                "clippy::print_literal".into(),
423                "clippy::same_item_push".into(),
424                "clippy::single_char_add_str".into(),
425                "clippy::to_string_in_format_args".into(),
426            ],
427            forbid: vec![],
428        };
429        builder.ensure(Rustc {
430            target: self.target,
431            config: self.config.merge(&compiler_clippy_cfg),
432            crates: vec![],
433        });
434
435        let rustc_codegen_gcc = LintConfig {
436            allow: vec![],
437            warn: vec![],
438            deny: vec!["warnings".into()],
439            forbid: vec![],
440        };
441        builder.ensure(CodegenGcc {
442            target: self.target,
443            config: self.config.merge(&rustc_codegen_gcc),
444        });
445    }
446}