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