perseus_cli/
check.rs

1use crate::{
2    cmd::{cfg_spinner, run_stage},
3    errors::*,
4    parse::{CheckOpts, Opts},
5    thread::{spawn_thread, ThreadHandle},
6    Tools,
7};
8use console::{style, Emoji};
9use indicatif::{MultiProgress, ProgressBar};
10use std::path::PathBuf;
11
12// Emoji for stages
13static CHECKING_ENGINE: Emoji<'_, '_> = Emoji("🦾", "");
14static CHECKING_BROWSER: Emoji<'_, '_> = Emoji("🌐", "");
15static GENERATING: Emoji<'_, '_> = Emoji("🔨", "");
16
17/// Returns the exit code if it's non-zero.
18macro_rules! handle_exit_code {
19    ($code:expr) => {
20        let (_, _, code) = $code;
21        if code != 0 {
22            return ::std::result::Result::Ok(code);
23        }
24    };
25}
26
27/// Checks the user's app by checking their code and building it. This will
28/// first run `cargo check`, and then `cargo check --target
29/// wasm32-unknown-unknown`, so we can error quickly on compilation errors.
30/// If those both succeed, then we'll actually try to generate build artifacts,
31/// which is the only other place a Perseus can reasonably fail at build-time.
32pub fn check(
33    dir: PathBuf,
34    check_opts: &CheckOpts,
35    tools: &Tools,
36    global_opts: &Opts,
37) -> Result<i32, ExecutionError> {
38    // First, run `cargo check`
39    let spinners = MultiProgress::new();
40    let num_steps = if check_opts.generate { 3 } else { 2 };
41
42    let (engine_thread, browser_thread) =
43        cargo_check(dir.clone(), &spinners, num_steps, tools, global_opts)?;
44    let engine_res = engine_thread
45        .join()
46        .map_err(|_| ExecutionError::ThreadWaitFailed)??;
47    if engine_res != 0 {
48        return Ok(engine_res);
49    }
50    let browser_res = browser_thread
51        .join()
52        .map_err(|_| ExecutionError::ThreadWaitFailed)??;
53    if browser_res != 0 {
54        return Ok(browser_res);
55    }
56
57    // If that worked, generate static artifacts (if we've been told to)
58    if check_opts.generate {
59        let generation_res =
60            run_static_generation(dir, &MultiProgress::new(), num_steps, tools, global_opts)?;
61        Ok(generation_res)
62    } else {
63        Ok(0)
64    }
65}
66
67/// Runs `cargo check` for both the engine-side and the browser-side
68/// simultaneously, returning handles to the underlying threads. This will
69/// create progress bars as appropriate.
70#[allow(clippy::type_complexity)]
71fn cargo_check(
72    dir: PathBuf,
73    spinners: &MultiProgress,
74    num_steps: u8,
75    tools: &Tools,
76    global_opts: &Opts,
77) -> Result<
78    (
79        ThreadHandle<impl FnOnce() -> Result<i32, ExecutionError>, Result<i32, ExecutionError>>,
80        ThreadHandle<impl FnOnce() -> Result<i32, ExecutionError>, Result<i32, ExecutionError>>,
81    ),
82    ExecutionError,
83> {
84    // We need to own this for the threads
85    let tools = tools.clone();
86    let Opts {
87        cargo_engine_args,
88        cargo_browser_args,
89        verbose,
90        ..
91    } = global_opts.clone();
92
93    let engine_msg = format!(
94        "{} {} Checking your app's engine-side",
95        style(format!("[1/{}]", num_steps)).bold().dim(),
96        CHECKING_ENGINE
97    );
98    let browser_msg = format!(
99        "{} {} Checking your app's browser-side",
100        style(format!("[2/{}]", num_steps)).bold().dim(),
101        CHECKING_BROWSER
102    );
103
104    // We parallelize the first two spinners
105    let engine_spinner = spinners.insert(0, ProgressBar::new_spinner());
106    let engine_spinner = cfg_spinner(engine_spinner, &engine_msg);
107    let engine_dir = dir.clone();
108    let browser_spinner = spinners.insert(1, ProgressBar::new_spinner());
109    let browser_spinner = cfg_spinner(browser_spinner, &browser_msg);
110    let browser_dir = dir;
111    let cargo_engine_exec = tools.cargo_engine.clone();
112    let engine_thread = spawn_thread(
113        move || {
114            handle_exit_code!(run_stage(
115                vec![&format!(
116                    "{} check {}",
117                    cargo_engine_exec, cargo_engine_args
118                )],
119                &engine_dir,
120                &engine_spinner,
121                &engine_msg,
122                vec![
123                    // We still need this for checking, because otherwise we can't check the engine
124                    // and the browser simultaneously (different targets, so no
125                    // commonalities gained by one directory)
126                    ("CARGO_TARGET_DIR", "dist/target_engine"),
127                    ("RUSTFLAGS", "--cfg=engine"),
128                    ("CARGO_TERM_COLOR", "always")
129                ],
130                verbose,
131            )?);
132
133            Ok(0)
134        },
135        global_opts.sequential,
136    );
137    let browser_thread = spawn_thread(
138        move || {
139            handle_exit_code!(run_stage(
140                vec![&format!(
141                    "{} check --target wasm32-unknown-unknown {}",
142                    tools.cargo_browser, cargo_browser_args
143                )],
144                &browser_dir,
145                &browser_spinner,
146                &browser_msg,
147                vec![
148                    ("CARGO_TARGET_DIR", "dist/target_wasm"),
149                    ("RUSTFLAGS", "--cfg=client"),
150                    ("CARGO_TERM_COLOR", "always")
151                ],
152                verbose,
153            )?);
154
155            Ok(0)
156        },
157        global_opts.sequential,
158    );
159
160    Ok((engine_thread, browser_thread))
161}
162
163#[allow(clippy::type_complexity)]
164fn run_static_generation(
165    dir: PathBuf,
166    spinners: &MultiProgress,
167    num_steps: u8,
168    tools: &Tools,
169    global_opts: &Opts,
170) -> Result<i32, ExecutionError> {
171    let Opts {
172        cargo_engine_args,
173        verbose,
174        ..
175    } = global_opts.clone();
176
177    let msg = format!(
178        "{} {} Checking your app's page generation",
179        style(format!("[3/{}]", num_steps)).bold().dim(),
180        GENERATING
181    );
182
183    // We parallelize the first two spinners
184    let spinner = spinners.insert(0, ProgressBar::new_spinner());
185    let spinner = cfg_spinner(spinner, &msg);
186
187    handle_exit_code!(run_stage(
188        vec![&format!("{} run {}", tools.cargo_engine, cargo_engine_args)],
189        &dir,
190        &spinner,
191        &msg,
192        vec![
193            ("PERSEUS_ENGINE_OPERATION", "build"),
194            ("CARGO_TARGET_DIR", "dist/target_engine"),
195            ("RUSTFLAGS", "--cfg=engine"),
196            ("CARGO_TERM_COLOR", "always")
197        ],
198        verbose,
199    )?);
200
201    Ok(0)
202}