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