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