Skip to main content

xtask/tasks/fmt/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4mod lints;
5mod rustfmt;
6mod verify_flowey;
7
8use crate::Xtask;
9use anyhow::Context;
10use clap::Parser;
11use heck::ToKebabCase;
12
13/// Xtask to run various repo-specific formatting checks
14#[derive(Parser)]
15#[clap(about = "Run various formatting checks")]
16pub struct Fmt {
17    /// Attempt to fix any formatting issues
18    ///
19    /// NOTE: setting this flag disables pass-level parallelism
20    #[clap(long)]
21    fix: bool,
22
23    /// Don't run passes in parallel (avoiding potentially interweaved output)
24    #[clap(long)]
25    no_parallel: bool,
26
27    /// Only run checks on files that are currently diffed
28    #[clap(long)]
29    only_diffed: bool,
30
31    /// Run only certain formatting passes
32    #[clap(long)]
33    pass: Vec<PassName>,
34}
35
36/// Common trait implemented by all Fmt passes.
37pub trait FmtPass {
38    /// Run the pass.
39    ///
40    /// For consistency and simplicity, `FmtPass` implementations are allowed to
41    /// assume that they are being run from the root of the repo's filesystem.
42    fn run(self, ctx: FmtCtx) -> anyhow::Result<()>;
43}
44
45#[derive(Clone)]
46pub struct FmtCtx {
47    ctx: crate::XtaskCtx,
48    fix: bool,
49    only_diffed: bool,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
53enum PassName {
54    // Keep Rustfmt first since some lints may depend on proper formatting
55    Rustfmt,
56    Lints,
57    VerifyFuzzers,
58    VerifyFlowey,
59}
60
61impl Xtask for Fmt {
62    fn run(self, ctx: crate::XtaskCtx) -> anyhow::Result<()> {
63        let tasks: Vec<Box<dyn FnOnce() -> anyhow::Result<()> + Send>> = {
64            fn wrapper(
65                ctx: &FmtCtx,
66                name: String,
67                func: impl FnOnce(FmtCtx) -> anyhow::Result<()> + Send + 'static,
68            ) -> Box<dyn FnOnce() -> anyhow::Result<()> + Send> {
69                let ctx = ctx.clone();
70
71                Box::new(move || {
72                    let start_time = std::time::Instant::now();
73                    log::info!("[checking] {}", name);
74                    let res = func(ctx).context(format!("while running {name}"));
75                    log::info!(
76                        "[complete] {} ({:.2?})",
77                        name,
78                        std::time::Instant::now() - start_time
79                    );
80                    res
81                })
82            }
83
84            let passes = if !self.pass.is_empty() {
85                let mut passes = self.pass.clone();
86                passes.sort();
87                passes.dedup_by(|a, b| a == b);
88                passes
89            } else {
90                // Run all of them by default.
91                // Run rustfmt first since lints may depend on proper formatting
92                vec![
93                    PassName::Rustfmt,
94                    PassName::Lints,
95                    PassName::VerifyFuzzers,
96                    PassName::VerifyFlowey,
97                ]
98            };
99
100            let ctx = FmtCtx {
101                ctx,
102                fix: self.fix,
103                only_diffed: self.only_diffed,
104            };
105
106            passes
107                .into_iter()
108                .map(|pass| {
109                    let name = format!("{:?}", pass).to_kebab_case();
110                    match pass {
111                        PassName::Rustfmt => {
112                            wrapper(&ctx, name, move |ctx| rustfmt::Rustfmt.run(ctx))
113                        }
114                        PassName::Lints => wrapper(&ctx, name, move |ctx| lints::Lints.run(ctx)),
115                        PassName::VerifyFuzzers => wrapper(&ctx, name, {
116                            move |ctx| crate::tasks::fuzz::VerifyFuzzers.run(ctx.ctx)
117                        }),
118                        PassName::VerifyFlowey => wrapper(&ctx, name, {
119                            move |ctx| verify_flowey::VerifyFlowey.run(ctx)
120                        }),
121                    }
122                })
123                .collect()
124        };
125
126        let results: Vec<_> = if self.fix || self.no_parallel {
127            tasks.into_iter().map(|f| (f)()).collect()
128        } else {
129            tasks
130                .into_iter()
131                .map(std::thread::spawn)
132                .collect::<Vec<_>>()
133                .into_iter()
134                .map(|j| j.join().unwrap())
135                .collect()
136        };
137
138        for res in results.iter() {
139            if let Err(e) = res {
140                log::error!("{:#}", e);
141            }
142        }
143
144        if results.iter().any(|res| res.is_err()) && !self.fix {
145            log::error!(
146                "run `cargo xtask fmt{}{} --fix`",
147                if self.only_diffed {
148                    " --only-diffed"
149                } else {
150                    ""
151                },
152                if !self.pass.is_empty() {
153                    self.pass
154                        .into_iter()
155                        .map(|pass| format!(" --pass {}", format!("{:?}", pass).to_kebab_case()))
156                        .collect::<Vec<_>>()
157                        .join("")
158                } else {
159                    "".into()
160                }
161            );
162            Err(anyhow::anyhow!("found formatting errors"))
163        } else {
164            Ok(())
165        }
166    }
167}