Skip to main content

xtask/
main.rs

1#![deny(clippy::all)]
2#![warn(clippy::nursery, clippy::pedantic)]
3
4use std::env;
5
6use anyhow::Result;
7
8mod commands;
9mod coverage;
10mod dev;
11mod dist;
12mod fixup;
13mod utils;
14
15const HELP: &str = "\
16cargo xtask - helper scripts for running common project tasks
17
18USAGE:
19    cargo xtask [OPTIONS] [TASK]...
20
21OPTIONS:
22    --cross                Uses cross for cross-compilation instead of cargo
23    -i, --ignore-missing   Ignores any missing tools; only warns during fixup
24    --target <TRIPLE>      Sets the target triple that dist will build
25    -h, --help             Prints help information
26
27TASKS:
28    check                  Watch for file changes and auto-trigger clippy linting
29    coverage               Generate and print a code coverage report summary
30    coverage.html          Generate and open an HTML code coverage report
31    dist                   Package project assets into distributable artifacts
32    doc                    Watch for file changes and auto-trigger doc generation
33    fixup                  Run all fixup xtasks, editing files in-place
34    fixup.github-actions   Lint CI YAML files using actionlint static checker
35    fixup.markdown         Format Markdown files in-place
36    fixup.rust             Fix lints and format Rust files in-place
37    fixup.spelling         Fix common misspellings across all files in-place
38    install                Install required Rust components and Cargo dependencies
39    test                   Run all tests/doctests and generate Insta snapshots
40";
41
42enum Task {
43    Check,
44    Coverage,
45    CoverageHtml,
46    Dist,
47    Doc,
48    Fixup,
49    FixupGithubActions,
50    FixupMarkdown,
51    FixupRust,
52    FixupSpelling,
53    Install,
54    Test,
55}
56
57pub struct Config {
58    cross: bool,
59    run_tasks: Vec<Task>,
60    ignore_missing_commands: bool,
61    target: Option<String>,
62}
63
64fn main() -> Result<()> {
65    // print help when no arguments are given
66    if env::args().len() == 1 {
67        print!("{HELP}");
68        std::process::exit(1);
69    }
70
71    let config = parse_args()?;
72    for task in &config.run_tasks {
73        match task {
74            Task::Check => dev::watch_clippy(&config)?,
75            Task::Coverage => coverage::report_summary(&config)?,
76            Task::CoverageHtml => coverage::html_report(&config)?,
77            Task::Dist => dist::dist(&config)?,
78            Task::Doc => dev::watch_doc(&config)?,
79            Task::Fixup => fixup::everything(&config)?,
80            Task::FixupGithubActions => fixup::github_actions(&config)?,
81            Task::FixupMarkdown => fixup::markdown(&config)?,
82            Task::FixupRust => fixup::rust(&config)?,
83            Task::FixupSpelling => fixup::spelling(&config)?,
84            Task::Install => dev::install_rust_deps(&config)?,
85            Task::Test => dev::test_with_snapshots(&config)?,
86        }
87    }
88
89    Ok(())
90}
91
92fn parse_args() -> Result<Config> {
93    use lexopt::prelude::*;
94
95    // default config values
96    let mut cross = false;
97    let mut ignore_missing_commands = false;
98    let mut target: Option<String> = None;
99    let mut run_tasks = Vec::new();
100
101    let mut parser = lexopt::Parser::from_env();
102    while let Some(arg) = parser.next()? {
103        match arg {
104            Short('h') | Long("help") => {
105                print!("{HELP}");
106                std::process::exit(0);
107            }
108            Long("cross") => {
109                cross = true;
110            }
111            Short('i') | Long("ignore-missing") => {
112                ignore_missing_commands = true;
113            }
114            Long("target") => {
115                target = Some(parser.value()?.string()?);
116            }
117            Value(value) => {
118                let value = value.string()?;
119                let task = match value.as_str() {
120                    "check" => Task::Check,
121                    "coverage" => Task::Coverage,
122                    "coverage.html" => Task::CoverageHtml,
123                    "dist" => Task::Dist,
124                    "doc" => Task::Doc,
125                    "fixup" => Task::Fixup,
126                    "fixup.github-actions" => Task::FixupGithubActions,
127                    "fixup.markdown" => Task::FixupMarkdown,
128                    "fixup.rust" => Task::FixupRust,
129                    "fixup.spelling" => Task::FixupSpelling,
130                    "install" => Task::Install,
131                    "test" => Task::Test,
132                    value => {
133                        anyhow::bail!("unknown task '{value}'");
134                    }
135                };
136                run_tasks.push(task);
137            }
138            _ => anyhow::bail!(arg.unexpected()),
139        }
140    }
141
142    if run_tasks.is_empty() {
143        anyhow::bail!("no task given");
144    }
145
146    Ok(Config {
147        cross,
148        run_tasks,
149        ignore_missing_commands,
150        target,
151    })
152}