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