Skip to main content

tracel_xtask/commands/
check.rs

1use anyhow::Ok;
2use strum::IntoEnumIterator;
3
4use crate::{
5    commands::WARN_IGNORED_EXCLUDE_AND_ONLY_ARGS,
6    endgroup, group,
7    prelude::{Context, Environment},
8    utils::{
9        cargo::ensure_cargo_crate_is_installed,
10        process::{run_process, run_process_for_package, run_process_for_workspace},
11        workspace::{WorkspaceMemberType, get_workspace_members},
12    },
13    versions::TYPOS_VERSION,
14};
15
16use super::Target;
17
18#[tracel_xtask_macros::declare_command_args(Target, CheckSubCommand)]
19pub struct CheckCmdArgs {}
20
21pub fn handle_command(args: CheckCmdArgs, _env: Environment, _ctx: Context) -> anyhow::Result<()> {
22    if args.target == Target::Workspace && (!args.exclude.is_empty() || !args.only.is_empty()) {
23        warn!("{WARN_IGNORED_EXCLUDE_AND_ONLY_ARGS}");
24    }
25
26    match args.get_command() {
27        CheckSubCommand::Audit => {
28            let res = run_audit();
29            if res.is_err() && args.ignore_audit {
30                warn!("Ignoring audit error because of '--ignore-audit' flag.");
31                Ok(())
32            } else {
33                res
34            }
35        }
36        CheckSubCommand::Format => run_format(&args.target, &args.exclude, &args.only),
37        CheckSubCommand::Lint => run_lint(
38            &args.target,
39            &args.exclude,
40            &args.only,
41            &args.features,
42            args.no_default_features,
43        ),
44        CheckSubCommand::Typos => {
45            let res = run_typos();
46            if res.is_err() && args.ignore_typos {
47                warn!("Ignoring typos error because of '--ignore-typos' flag.");
48                Ok(())
49            } else {
50                res
51            }
52        }
53        CheckSubCommand::All => CheckSubCommand::iter()
54            .filter(|c| *c != CheckSubCommand::All)
55            .try_for_each(|c| {
56                handle_command(
57                    CheckCmdArgs {
58                        command: Some(c),
59                        target: args.target.clone(),
60                        exclude: args.exclude.clone(),
61                        only: args.only.clone(),
62                        ignore_audit: args.ignore_audit,
63                        ignore_typos: args.ignore_typos,
64                        features: args.features.clone(),
65                        no_default_features: args.no_default_features,
66                    },
67                    _env.clone(),
68                    _ctx.clone(),
69                )
70            }),
71    }
72}
73
74fn run_audit() -> anyhow::Result<()> {
75    group!("Audit Rust Dependencies");
76    ensure_cargo_crate_is_installed("cargo-audit", Some("fix"), None, false)?;
77    run_process(
78        "cargo",
79        &["audit", "-q", "--color", "always"],
80        None,
81        None,
82        "Audit check execution failed",
83    )?;
84    endgroup!();
85    Ok(())
86}
87
88fn run_format(target: &Target, excluded: &[String], only: &[String]) -> anyhow::Result<()> {
89    match target {
90        Target::Workspace => {
91            group!("Format Workspace");
92            run_process_for_workspace(
93                "cargo",
94                &["fmt", "--check"],
95                &[],
96                None,
97                None,
98                "Workspace format failed",
99                None,
100                None,
101            )?;
102            endgroup!();
103        }
104        Target::Crates | Target::Examples => {
105            let members = match target {
106                Target::Crates => get_workspace_members(WorkspaceMemberType::Crate),
107                Target::Examples => get_workspace_members(WorkspaceMemberType::Example),
108                _ => unreachable!(),
109            };
110
111            for member in members {
112                group!("Format: {}", member.name);
113                run_process_for_package(
114                    "cargo",
115                    &member.name,
116                    &["fmt", "--check", "-p", &member.name],
117                    excluded,
118                    only,
119                    &format!("Format check execution failed for {}", &member.name),
120                    None,
121                    None,
122                )?;
123                endgroup!();
124            }
125        }
126        Target::AllPackages => {
127            Target::iter()
128                .filter(|t| *t != Target::AllPackages && *t != Target::Workspace)
129                .try_for_each(|t| run_format(&t, excluded, only))?;
130        }
131    }
132    Ok(())
133}
134
135fn run_lint(
136    target: &Target,
137    excluded: &[String],
138    only: &[String],
139    features: &[String],
140    no_default_features: bool,
141) -> anyhow::Result<()> {
142    match target {
143        Target::Workspace => {
144            group!("Lint Workspace");
145            let mut cmd_args = vec!["clippy", "--no-deps", "--color=always"];
146
147            if no_default_features {
148                cmd_args.push("--no-default-features");
149            }
150
151            let features_str = features.join(",");
152            if !features.is_empty() {
153                cmd_args.push("--features");
154                cmd_args.push(&features_str);
155            }
156
157            cmd_args.extend(&["--", "--deny", "warnings"]);
158
159            run_process_for_workspace(
160                "cargo",
161                &cmd_args,
162                &[],
163                None,
164                None,
165                "Workspace lint failed",
166                None,
167                None,
168            )?;
169            endgroup!();
170        }
171        Target::Crates | Target::Examples => {
172            let members = match target {
173                Target::Crates => get_workspace_members(WorkspaceMemberType::Crate),
174                Target::Examples => get_workspace_members(WorkspaceMemberType::Example),
175                _ => unreachable!(),
176            };
177
178            for member in members {
179                group!("Lint: {}", member.name);
180                let mut cmd_args =
181                    vec!["clippy", "--no-deps", "--color=always", "-p", &member.name];
182
183                if no_default_features {
184                    cmd_args.push("--no-default-features");
185                }
186
187                let features_str = features.join(",");
188                if !features.is_empty() {
189                    cmd_args.push("--features");
190                    cmd_args.push(&features_str);
191                }
192
193                cmd_args.extend(&["--", "--deny", "warnings"]);
194
195                run_process_for_package(
196                    "cargo",
197                    &member.name,
198                    &cmd_args,
199                    excluded,
200                    only,
201                    &format!("Lint fix execution failed for {}", &member.name),
202                    None,
203                    None,
204                )?;
205                endgroup!();
206            }
207        }
208        Target::AllPackages => {
209            Target::iter()
210                .filter(|t| *t != Target::AllPackages && *t != Target::Workspace)
211                .try_for_each(|t| run_lint(&t, excluded, only, features, no_default_features))?;
212        }
213    }
214    Ok(())
215}
216
217fn run_typos() -> anyhow::Result<()> {
218    if std::env::var("CI").is_err() {
219        ensure_cargo_crate_is_installed("typos-cli", None, Some(TYPOS_VERSION), false)?;
220    }
221    group!("Typos");
222    run_process(
223        "typos",
224        // default without any args if better than '--diff --colors always'
225        &[],
226        None,
227        None,
228        "Typos check execution failed",
229    )?;
230    endgroup!();
231    Ok(())
232}