1mod lints;
5mod rustfmt;
6mod verify_flowey;
7
8use crate::Xtask;
9use anyhow::Context;
10use clap::Parser;
11use heck::ToKebabCase;
12
13#[derive(Parser)]
15#[clap(about = "Run various formatting checks")]
16pub struct Fmt {
17 #[clap(long)]
21 fix: bool,
22
23 #[clap(long)]
25 no_parallel: bool,
26
27 #[clap(long)]
29 only_diffed: bool,
30
31 #[clap(long)]
33 pass: Vec<PassName>,
34}
35
36pub trait FmtPass {
38 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 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 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}