xtask/
main.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! OpenVMM repo-specific automation.
5//!
6//! If you're thinking of writing a bash script, write an xtask instead!
7//!
8//! Follows the xtask workflow/convention, as described at
9//! <https://github.com/matklad/cargo-xtask>
10
11#![forbid(unsafe_code)]
12
13use anyhow::Context;
14use clap::Parser;
15use clap::Subcommand;
16use std::path::Path;
17use std::path::PathBuf;
18
19mod completions;
20pub mod fs_helpers;
21pub mod shell;
22pub mod tasks;
23
24/// Default location to maintain a `xtask-path` file
25///
26/// This file contains a fully-resolved path to the actual `xtask` binary,
27/// allowing other tooling (e.g: git hook) to invoke the xtask without having to
28/// go through `cargo`.
29pub const XTASK_PATH_FILE: &str = "./target/xtask-path";
30
31/// Common context passed into every Xtask
32#[derive(Clone)]
33pub struct XtaskCtx {
34    /// Project root directory
35    pub root: PathBuf,
36    /// xtask is running within a hook
37    pub in_git_hook: bool,
38    /// xtask was invoked as part of a "run on save" operation
39    pub in_run_on_save: bool,
40}
41
42/// Common trait implemented by all Xtask subcommands.
43pub trait Xtask: Parser {
44    /// Run the Xtask.
45    ///
46    /// For consistency and simplicity, `Xtask` implementations are allowed to
47    /// assume that they are being run from the root of the repo's filesystem.
48    /// Callers of `Xtask::run` should take care to ensure
49    /// [`std::env::set_current_dir`] was called prior to invoking `Xtask::run`.
50    fn run(self, ctx: XtaskCtx) -> anyhow::Result<()>;
51}
52
53#[derive(Parser)]
54#[clap(name = "xtask", about = "OpenVMM repo automation")]
55struct Cli {
56    #[clap(subcommand)]
57    command: Commands,
58
59    /// Specify a custom project root directory.
60    ///
61    /// Can be used to ensure consistent formatting between the OpenVMM base
62    /// repo, and any custom out-of-tree overlay repos.
63    #[clap(long)]
64    custom_root: Option<PathBuf>,
65
66    /// Signal that this `xtask` is being invoked as part of a "run on save"
67    /// operation.
68    ///
69    /// When set, certain tasks may choose to skip certain expensive checks in
70    /// order to execute as quickly as possible.
71    ///
72    /// e.g: instead of calling `cargo run` in order to execute a project-local
73    /// tool, `xtask` may instead attempt to find and use an existing pre-built
74    /// binary, if one is available. This will be faster, but may run the risk
75    /// of executing a slightly stale binary.
76    #[clap(long)]
77    run_on_save: bool,
78}
79
80#[derive(Subcommand)]
81enum Commands {
82    #[clap(hide = true)]
83    Hook(tasks::RunGitHook),
84    #[clap(hide = true)]
85    Complete(clap_dyn_complete::Complete),
86    Completions(completions::Completions),
87
88    Clean(tasks::Clean),
89    Fmt(tasks::Fmt),
90    Fuzz(tasks::Fuzz),
91    GuestTest(tasks::GuestTest),
92    InstallGitHooks(tasks::InstallGitHooks),
93    VerifySize(tasks::VerifySize),
94}
95
96fn main() {
97    ci_logger::init("XTASK_LOG").unwrap();
98
99    if let Err(e) = try_main() {
100        log::error!("Error: {:#}", e);
101        std::process::exit(-1);
102    }
103}
104
105fn try_main() -> anyhow::Result<()> {
106    let cli = Cli::parse();
107
108    let orig_root = Path::new(&env!("CARGO_MANIFEST_DIR"))
109        .ancestors()
110        .nth(1)
111        .unwrap()
112        .to_path_buf();
113
114    let root = cli
115        .custom_root
116        .map(std::path::absolute)
117        .transpose()?
118        .unwrap_or(orig_root.clone());
119
120    // for consistency, always run xtasks as though they were run from the root
121    std::env::set_current_dir(&root)?;
122
123    // drop the path to the xtask binary in an easy-to-find place. this gets
124    // used by the pre-commit hook, as well as the fmt-on-save dev-flow to avoid
125    // rebuilding the xtask.
126    if let Ok(path) = std::env::current_exe() {
127        if let Err(e) = fs_err::write(orig_root.join(XTASK_PATH_FILE), path.display().to_string()) {
128            log::debug!("Unable to create XTASK_PATH_FILE: {:#}", e)
129        }
130    }
131
132    if !matches!(cli.command, Commands::Complete(..)) {
133        tasks::update_hooks(&root).context("failed to update git hooks")?;
134    }
135
136    let ctx = XtaskCtx {
137        root,
138        in_git_hook: matches!(cli.command, Commands::Hook(..)),
139        in_run_on_save: cli.run_on_save,
140    };
141
142    match cli.command {
143        Commands::Hook(task) => task.run(ctx),
144        Commands::Completions(task) => task.run(),
145        Commands::Complete(task) => {
146            futures::executor::block_on(task.println_to_stub_script::<Cli>(
147                Some("cargo"),
148                completions::XtaskCompleteFactory { ctx },
149            ));
150            Ok(())
151        }
152
153        Commands::Clean(task) => task.run(ctx),
154        Commands::Fmt(task) => task.run(ctx),
155        Commands::Fuzz(task) => task.run(ctx),
156        Commands::GuestTest(task) => task.run(ctx),
157        Commands::InstallGitHooks(task) => task.run(ctx),
158        Commands::VerifySize(task) => task.run(ctx),
159    }
160}