Skip to main content

bootstrap/core/build_steps/
clippy.rs

1//! Implementation of running clippy on the compiler, standard library and various tools.
2//!
3//! This serves a double purpose:
4//! - The first is to run Clippy itself on in-tree code, in order to test and dogfood it.
5//! - The second is to actually lint the in-tree codebase on CI, with a hard-coded set of rules,
6//!   which is performed by the `x clippy ci` command.
7//!
8//! In order to prepare a build compiler for running clippy, use the
9//! [prepare_compiler_for_check] function. That prepares a
10//! compiler and a standard library
11//! for running Clippy. The second part (actually building Clippy) is performed inside
12//! [Builder::cargo_clippy_cmd]. It would be nice if this was more explicit, and we actually had
13//! to pass a prebuilt Clippy from the outside when running `cargo clippy`, but that would be
14//! (as usual) a massive undertaking/refactoring.
15
16use super::compile::{ArtifactKeepMode, run_cargo, rustc_cargo, std_cargo};
17use super::tool::{SourceType, prepare_tool_cargo};
18use crate::builder::{Builder, ShouldRun};
19use crate::core::build_steps::check::{CompilerForCheck, prepare_compiler_for_check};
20use crate::core::build_steps::compile::std_crates_for_run_make;
21use crate::core::builder;
22use crate::core::builder::{Alias, Kind, RunConfig, Step, StepMetadata, crate_description};
23use crate::utils::build_stamp::{self, BuildStamp};
24use crate::{Compiler, Mode, Subcommand, TargetSelection, exit};
25
26/// Disable the most spammy clippy lints
27const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[
28    "many_single_char_names", // there are a lot in stdarch
29    "collapsible_if",
30    "type_complexity",
31    "missing_safety_doc", // almost 3K warnings
32    "too_many_arguments",
33    "needless_lifetimes", // people want to keep the lifetimes
34    "wrong_self_convention",
35    "approx_constant", // libcore is what defines those
36];
37
38fn lint_args(builder: &Builder<'_>, config: &LintConfig, ignored_rules: &[&str]) -> Vec<String> {
39    fn strings<'a>(arr: &'a [&str]) -> impl Iterator<Item = String> + 'a {
40        arr.iter().copied().map(String::from)
41    }
42
43    let Subcommand::Clippy { fix, allow_dirty, allow_staged, .. } = &builder.config.cmd else {
44        unreachable!("clippy::lint_args can only be called from `clippy` subcommands.");
45    };
46
47    let mut args = vec![];
48    if *fix {
49        #[rustfmt::skip]
50            args.extend(strings(&[
51                "--fix", "-Zunstable-options",
52                // FIXME: currently, `--fix` gives an error while checking tests for libtest,
53                // possibly because libtest is not yet built in the sysroot.
54                // As a workaround, avoid checking tests and benches when passed --fix.
55                "--lib", "--bins", "--examples",
56            ]));
57
58        if *allow_dirty {
59            args.push("--allow-dirty".to_owned());
60        }
61
62        if *allow_staged {
63            args.push("--allow-staged".to_owned());
64        }
65    }
66
67    args.extend(strings(&["--"]));
68
69    if config.deny.is_empty() && config.forbid.is_empty() {
70        args.extend(strings(&["--cap-lints", "warn"]));
71    }
72
73    let all_args = std::env::args().collect::<Vec<_>>();
74    args.extend(get_clippy_rules_in_order(&all_args, config));
75
76    args.extend(ignored_rules.iter().map(|lint| format!("-Aclippy::{lint}")));
77    args.extend(builder.config.free_args.clone());
78    args
79}
80
81/// We need to keep the order of the given clippy lint rules before passing them.
82/// Since clap doesn't offer any useful interface for this purpose out of the box,
83/// we have to handle it manually.
84pub fn get_clippy_rules_in_order(all_args: &[String], config: &LintConfig) -> Vec<String> {
85    let mut result = vec![];
86
87    for (prefix, item) in
88        [("-A", &config.allow), ("-D", &config.deny), ("-W", &config.warn), ("-F", &config.forbid)]
89    {
90        item.iter().for_each(|v| {
91            let rule = format!("{prefix}{v}");
92            // Arguments added by bootstrap in LintConfig won't show up in the all_args list, so
93            // put them at the end of the command line.
94            let position = all_args.iter().position(|t| t == &rule || t == v).unwrap_or(usize::MAX);
95            result.push((position, rule));
96        });
97    }
98
99    result.sort_by_key(|&(position, _)| position);
100    result.into_iter().map(|v| v.1).collect()
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Hash)]
104pub struct LintConfig {
105    pub allow: Vec<String>,
106    pub warn: Vec<String>,
107    pub deny: Vec<String>,
108    pub forbid: Vec<String>,
109}
110
111impl LintConfig {
112    fn new(builder: &Builder<'_>) -> Self {
113        match builder.config.cmd.clone() {
114            Subcommand::Clippy { allow, deny, warn, forbid, .. } => {
115                Self { allow, warn, deny, forbid }
116            }
117            _ => unreachable!("LintConfig can only be called from `clippy` subcommands."),
118        }
119    }
120
121    fn merge(&self, other: &Self) -> Self {
122        let merged = |self_attr: &[String], other_attr: &[String]| -> Vec<String> {
123            self_attr.iter().cloned().chain(other_attr.iter().cloned()).collect()
124        };
125        // This is written this way to ensure we get a compiler error if we add a new field.
126        Self {
127            allow: merged(&self.allow, &other.allow),
128            warn: merged(&self.warn, &other.warn),
129            deny: merged(&self.deny, &other.deny),
130            forbid: merged(&self.forbid, &other.forbid),
131        }
132    }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct Std {
137    build_compiler: Compiler,
138    target: TargetSelection,
139    config: LintConfig,
140    /// Whether to lint only a subset of crates.
141    crates: Vec<String>,
142}
143
144impl Std {
145    fn new(
146        builder: &Builder<'_>,
147        target: TargetSelection,
148        config: LintConfig,
149        crates: Vec<String>,
150    ) -> Self {
151        Self {
152            build_compiler: builder.compiler(builder.top_stage, builder.host_target),
153            target,
154            config,
155            crates,
156        }
157    }
158
159    fn from_build_compiler(
160        build_compiler: Compiler,
161        target: TargetSelection,
162        config: LintConfig,
163        crates: Vec<String>,
164    ) -> Self {
165        Self { build_compiler, target, config, crates }
166    }
167}
168
169impl Step for Std {
170    type Output = ();
171
172    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
173        run.crate_or_deps("sysroot").path("library")
174    }
175
176    fn is_default_step(_builder: &Builder<'_>) -> bool {
177        true
178    }
179
180    fn make_run(run: RunConfig<'_>) {
181        let crates = std_crates_for_run_make(&run);
182        let config = LintConfig::new(run.builder);
183        run.builder.ensure(Std::new(run.builder, run.target, config, crates));
184    }
185
186    fn run(self, builder: &Builder<'_>) {
187        let target = self.target;
188        let build_compiler = self.build_compiler;
189
190        let mut cargo = builder::Cargo::new(
191            builder,
192            build_compiler,
193            Mode::Std,
194            SourceType::InTree,
195            target,
196            Kind::Clippy,
197        );
198
199        std_cargo(builder, target, &mut cargo, &self.crates);
200
201        let _guard = builder.msg(
202            Kind::Clippy,
203            format_args!("library{}", crate_description(&self.crates)),
204            Mode::Std,
205            build_compiler,
206            target,
207        );
208
209        run_cargo(
210            builder,
211            cargo,
212            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
213            &build_stamp::libstd_stamp(builder, build_compiler, target),
214            vec![],
215            ArtifactKeepMode::OnlyRmeta,
216        );
217    }
218
219    fn metadata(&self) -> Option<StepMetadata> {
220        Some(StepMetadata::clippy("std", self.target).built_by(self.build_compiler))
221    }
222}
223
224/// Lints the compiler.
225///
226/// This will build Clippy with the `build_compiler` and use it to lint
227/// in-tree rustc.
228#[derive(Debug, Clone, PartialEq, Eq, Hash)]
229pub struct Rustc {
230    build_compiler: CompilerForCheck,
231    target: TargetSelection,
232    config: LintConfig,
233    /// Whether to lint only a subset of crates.
234    crates: Vec<String>,
235}
236
237impl Rustc {
238    fn new(
239        builder: &Builder<'_>,
240        target: TargetSelection,
241        config: LintConfig,
242        crates: Vec<String>,
243    ) -> Self {
244        Self {
245            build_compiler: prepare_compiler_for_check(builder, target, Mode::Rustc),
246            target,
247            config,
248            crates,
249        }
250    }
251}
252
253impl Step for Rustc {
254    type Output = ();
255    const IS_HOST: bool = true;
256
257    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
258        run.crate_or_deps("rustc-main").path("compiler")
259    }
260
261    fn is_default_step(_builder: &Builder<'_>) -> bool {
262        true
263    }
264
265    fn make_run(run: RunConfig<'_>) {
266        let builder = run.builder;
267        let crates = run.make_run_crates(Alias::Compiler);
268        let config = LintConfig::new(run.builder);
269        run.builder.ensure(Rustc::new(builder, run.target, config, crates));
270    }
271
272    fn run(self, builder: &Builder<'_>) {
273        let build_compiler = self.build_compiler.build_compiler();
274        let target = self.target;
275
276        let mut cargo = builder::Cargo::new(
277            builder,
278            build_compiler,
279            Mode::Rustc,
280            SourceType::InTree,
281            target,
282            Kind::Clippy,
283        );
284
285        rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
286        self.build_compiler.configure_cargo(&mut cargo);
287
288        // Explicitly pass -p for all compiler crates -- this will force cargo
289        // to also lint the tests/benches/examples for these crates, rather
290        // than just the leaf crate.
291        for krate in &*self.crates {
292            cargo.arg("-p").arg(krate);
293        }
294
295        let _guard = builder.msg(
296            Kind::Clippy,
297            format_args!("compiler{}", crate_description(&self.crates)),
298            Mode::Rustc,
299            build_compiler,
300            target,
301        );
302
303        run_cargo(
304            builder,
305            cargo,
306            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
307            &build_stamp::librustc_stamp(builder, build_compiler, target),
308            vec![],
309            ArtifactKeepMode::OnlyRmeta,
310        );
311    }
312
313    fn metadata(&self) -> Option<StepMetadata> {
314        Some(
315            StepMetadata::clippy("rustc", self.target)
316                .built_by(self.build_compiler.build_compiler()),
317        )
318    }
319}
320
321#[derive(Debug, Clone, Hash, PartialEq, Eq)]
322pub struct CodegenGcc {
323    build_compiler: CompilerForCheck,
324    target: TargetSelection,
325    config: LintConfig,
326}
327
328impl CodegenGcc {
329    fn new(builder: &Builder<'_>, target: TargetSelection, config: LintConfig) -> Self {
330        Self {
331            build_compiler: prepare_compiler_for_check(builder, target, Mode::Codegen),
332            target,
333            config,
334        }
335    }
336}
337
338impl Step for CodegenGcc {
339    type Output = ();
340
341    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
342        run.alias("rustc_codegen_gcc")
343    }
344
345    fn make_run(run: RunConfig<'_>) {
346        let builder = run.builder;
347        let config = LintConfig::new(builder);
348        builder.ensure(CodegenGcc::new(builder, run.target, config));
349    }
350
351    fn run(self, builder: &Builder<'_>) -> Self::Output {
352        let build_compiler = self.build_compiler.build_compiler();
353        let target = self.target;
354
355        let mut cargo = prepare_tool_cargo(
356            builder,
357            build_compiler,
358            Mode::Codegen,
359            target,
360            Kind::Clippy,
361            "compiler/rustc_codegen_gcc",
362            SourceType::InTree,
363            &[],
364        );
365        self.build_compiler.configure_cargo(&mut cargo);
366
367        let _guard = builder.msg(
368            Kind::Clippy,
369            "rustc_codegen_gcc",
370            Mode::ToolRustcPrivate,
371            build_compiler,
372            target,
373        );
374
375        let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, Mode::Codegen, target))
376            .with_prefix("rustc_codegen_gcc-check");
377
378        let args = lint_args(builder, &self.config, &[]);
379        run_cargo(builder, cargo, args.clone(), &stamp, vec![], ArtifactKeepMode::OnlyRmeta);
380
381        // Same but we disable the features enabled by default.
382        let mut cargo = prepare_tool_cargo(
383            builder,
384            build_compiler,
385            Mode::Codegen,
386            target,
387            Kind::Clippy,
388            "compiler/rustc_codegen_gcc",
389            SourceType::InTree,
390            &[],
391        );
392        self.build_compiler.configure_cargo(&mut cargo);
393        println!("Now running clippy on `rustc_codegen_gcc` with `--no-default-features`");
394        cargo.arg("--no-default-features");
395        run_cargo(builder, cargo, args, &stamp, vec![], ArtifactKeepMode::OnlyRmeta);
396    }
397
398    fn metadata(&self) -> Option<StepMetadata> {
399        Some(
400            StepMetadata::clippy("rustc_codegen_gcc", self.target)
401                .built_by(self.build_compiler.build_compiler()),
402        )
403    }
404}
405
406macro_rules! lint_any {
407    ($(
408        $name:ident,
409        $path:expr,
410        $readable_name:expr,
411        $mode:expr
412        $(, lint_by_default = $lint_by_default:expr )?
413        ;
414    )+) => {
415        $(
416
417        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
418        pub struct $name {
419            build_compiler: CompilerForCheck,
420            target: TargetSelection,
421            config: LintConfig,
422        }
423
424        impl Step for $name {
425            type Output = ();
426
427            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
428                run.path($path)
429            }
430
431            fn is_default_step(_builder: &Builder<'_>) -> bool {
432                false $( || const { $lint_by_default } )?
433            }
434
435            fn make_run(run: RunConfig<'_>) {
436                let config = LintConfig::new(run.builder);
437                run.builder.ensure($name {
438                    build_compiler: prepare_compiler_for_check(run.builder, run.target, $mode),
439                    target: run.target,
440                    config,
441                });
442            }
443
444            fn run(self, builder: &Builder<'_>) -> Self::Output {
445                let build_compiler = self.build_compiler.build_compiler();
446                let target = self.target;
447                let mut cargo = prepare_tool_cargo(
448                    builder,
449                    build_compiler,
450                    $mode,
451                    target,
452                    Kind::Clippy,
453                    $path,
454                    SourceType::InTree,
455                    &[],
456                );
457                self.build_compiler.configure_cargo(&mut cargo);
458
459                let _guard = builder.msg(
460                    Kind::Clippy,
461                    $readable_name,
462                    $mode,
463                    build_compiler,
464                    target,
465                );
466
467                let stringified_name = stringify!($name).to_lowercase();
468                let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, $mode, target))
469                    .with_prefix(&format!("{}-check", stringified_name));
470
471                run_cargo(
472                    builder,
473                    cargo,
474                    lint_args(builder, &self.config, &[]),
475                    &stamp,
476                    vec![],
477                    ArtifactKeepMode::OnlyRmeta
478                );
479            }
480
481            fn metadata(&self) -> Option<StepMetadata> {
482                Some(StepMetadata::clippy($readable_name, self.target).built_by(self.build_compiler.build_compiler()))
483            }
484        }
485        )+
486    }
487}
488
489// Note: we use ToolTarget instead of ToolBootstrap here, to allow linting in-tree host tools
490// using the in-tree Clippy. Because Mode::ToolBootstrap would always use stage 0 rustc/Clippy.
491lint_any!(
492    Bootstrap, "src/bootstrap", "bootstrap", Mode::ToolTarget;
493    BuildHelper, "src/build_helper", "build_helper", Mode::ToolTarget;
494    BuildManifest, "src/tools/build-manifest", "build-manifest", Mode::ToolTarget;
495    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", Mode::ToolRustcPrivate;
496    Clippy, "src/tools/clippy", "clippy", Mode::ToolRustcPrivate;
497    CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata", Mode::ToolTarget;
498    Compiletest, "src/tools/compiletest", "compiletest", Mode::ToolTarget;
499    CoverageDump, "src/tools/coverage-dump", "coverage-dump", Mode::ToolTarget;
500    Jsondocck, "src/tools/jsondocck", "jsondocck", Mode::ToolTarget;
501    Jsondoclint, "src/tools/jsondoclint", "jsondoclint", Mode::ToolTarget;
502    LintDocs, "src/tools/lint-docs", "lint-docs", Mode::ToolTarget;
503    LlvmBitcodeLinker, "src/tools/llvm-bitcode-linker", "llvm-bitcode-linker", Mode::ToolTarget;
504    Miri, "src/tools/miri", "miri", Mode::ToolRustcPrivate;
505    MiroptTestTools, "src/tools/miropt-test-tools", "miropt-test-tools", Mode::ToolTarget;
506    OptDist, "src/tools/opt-dist", "opt-dist", Mode::ToolTarget;
507    RemoteTestClient, "src/tools/remote-test-client", "remote-test-client", Mode::ToolTarget;
508    RemoteTestServer, "src/tools/remote-test-server", "remote-test-server", Mode::ToolTarget;
509    RustAnalyzer, "src/tools/rust-analyzer", "rust-analyzer", Mode::ToolRustcPrivate;
510    Rustdoc, "src/librustdoc", "clippy", Mode::ToolRustcPrivate;
511    Rustfmt, "src/tools/rustfmt", "rustfmt", Mode::ToolRustcPrivate;
512    RustInstaller, "src/tools/rust-installer", "rust-installer", Mode::ToolTarget;
513    Tidy, "src/tools/tidy", "tidy", Mode::ToolTarget;
514    TestFloatParse, "src/tools/test-float-parse", "test-float-parse", Mode::ToolStd;
515);
516
517/// Runs Clippy on in-tree sources of selected projects using in-tree CLippy.
518#[derive(Debug, Clone, PartialEq, Eq, Hash)]
519pub struct CI {
520    target: TargetSelection,
521    config: LintConfig,
522}
523
524impl Step for CI {
525    type Output = ();
526
527    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
528        run.alias("ci")
529    }
530
531    fn is_default_step(_builder: &Builder<'_>) -> bool {
532        false
533    }
534
535    fn make_run(run: RunConfig<'_>) {
536        let config = LintConfig::new(run.builder);
537        run.builder.ensure(CI { target: run.target, config });
538    }
539
540    fn run(self, builder: &Builder<'_>) -> Self::Output {
541        if builder.top_stage != 2 {
542            eprintln!("ERROR: `x clippy ci` should always be executed with --stage 2");
543            exit!(1);
544        }
545
546        // We want to check in-tree source using in-tree clippy. However, if we naively did
547        // a stage 2 `x clippy ci`, it would *build* a stage 2 rustc, in order to lint stage 2
548        // std, which is wasteful.
549        // So we want to lint stage 2 [bootstrap/rustc/...], but only stage 1 std rustc_codegen_gcc.
550        // We thus construct the compilers in this step manually, to optimize the number of
551        // steps that get built.
552
553        builder.ensure(Bootstrap {
554            // This will be the stage 1 compiler
555            build_compiler: prepare_compiler_for_check(builder, self.target, Mode::ToolTarget),
556            target: self.target,
557            config: self.config.merge(&LintConfig {
558                allow: vec![],
559                warn: vec![],
560                deny: vec!["warnings".into()],
561                forbid: vec![],
562            }),
563        });
564
565        let library_clippy_cfg = LintConfig {
566            allow: vec!["clippy::all".into()],
567            warn: vec![],
568            deny: vec![
569                "clippy::correctness".into(),
570                "clippy::char_lit_as_u8".into(),
571                "clippy::four_forward_slashes".into(),
572                "clippy::needless_bool".into(),
573                "clippy::needless_bool_assign".into(),
574                "clippy::non_minimal_cfg".into(),
575                "clippy::print_literal".into(),
576                "clippy::same_item_push".into(),
577                "clippy::single_char_add_str".into(),
578                "clippy::to_string_in_format_args".into(),
579                "clippy::unconditional_recursion".into(),
580            ],
581            forbid: vec![],
582        };
583        builder.ensure(Std::from_build_compiler(
584            // This will be the stage 1 compiler, to avoid building rustc stage 2 just to lint std
585            builder.compiler(1, self.target),
586            self.target,
587            self.config.merge(&library_clippy_cfg),
588            vec![],
589        ));
590
591        let compiler_clippy_cfg = LintConfig {
592            allow: vec!["clippy::all".into()],
593            warn: vec![],
594            deny: vec![
595                "clippy::correctness".into(),
596                "clippy::char_lit_as_u8".into(),
597                "clippy::clone_on_ref_ptr".into(),
598                "clippy::format_in_format_args".into(),
599                "clippy::four_forward_slashes".into(),
600                "clippy::needless_bool".into(),
601                "clippy::needless_bool_assign".into(),
602                "clippy::non_minimal_cfg".into(),
603                "clippy::print_literal".into(),
604                "clippy::same_item_push".into(),
605                "clippy::single_char_add_str".into(),
606                "clippy::to_string_in_format_args".into(),
607                "clippy::unconditional_recursion".into(),
608                "clippy::mem_replace_with_default".into(),
609            ],
610            forbid: vec![],
611        };
612        // This will lint stage 2 rustc using stage 1 Clippy
613        builder.ensure(Rustc::new(
614            builder,
615            self.target,
616            self.config.merge(&compiler_clippy_cfg),
617            vec![],
618        ));
619
620        let rustc_codegen_gcc = LintConfig {
621            allow: vec![],
622            warn: vec![],
623            deny: vec!["warnings".into()],
624            forbid: vec![],
625        };
626        // This will check stage 2 rustc
627        builder.ensure(CodegenGcc::new(
628            builder,
629            self.target,
630            self.config.merge(&rustc_codegen_gcc),
631        ));
632    }
633}