perseus_cli/
tinker.rs

1use crate::cmd::{cfg_spinner, run_stage};
2use crate::errors::*;
3use crate::install::Tools;
4use crate::parse::Opts;
5use crate::thread::{spawn_thread, ThreadHandle};
6use console::{style, Emoji};
7use indicatif::{MultiProgress, ProgressBar};
8use std::path::PathBuf;
9
10// Emojis for stages
11static TINKERING: Emoji<'_, '_> = Emoji("🔧", ""); // TODO
12
13/// Returns the exit code if it's non-zero.
14macro_rules! handle_exit_code {
15    ($code:expr) => {
16        let (_, _, code) = $code;
17        if code != 0 {
18            return ::std::result::Result::Ok(code);
19        }
20    };
21}
22
23/// Actually tinkers the engine, program arguments having been interpreted.
24/// This needs to know how many steps there are in total and takes a
25/// `MultiProgress` to interact with so it can be used truly atomically. This
26/// returns handles for waiting on the component threads so we can use it
27/// composably.
28#[allow(clippy::type_complexity)]
29pub fn tinker_internal(
30    dir: PathBuf,
31    spinners: &MultiProgress,
32    num_steps: u8,
33    tools: &Tools,
34    global_opts: &Opts,
35) -> Result<
36    ThreadHandle<impl FnOnce() -> Result<i32, ExecutionError>, Result<i32, ExecutionError>>,
37    Error,
38> {
39    let tools = tools.clone();
40    let Opts {
41        cargo_engine_args,
42        verbose,
43        ..
44    } = global_opts.clone();
45
46    // Tinkering message
47    let tk_msg = format!(
48        "{} {} Running plugin tinkers",
49        style(format!("[1/{}]", num_steps)).bold().dim(),
50        TINKERING
51    );
52
53    // We make sure to add them at the top (other spinners may have already been
54    // instantiated)
55    let tk_spinner = spinners.insert(0, ProgressBar::new_spinner());
56    let tk_spinner = cfg_spinner(tk_spinner, &tk_msg);
57    let tk_target = dir;
58    let tk_thread = spawn_thread(
59        move || {
60            handle_exit_code!(run_stage(
61                vec![&format!("{} run {}", tools.cargo_engine, cargo_engine_args)],
62                &tk_target,
63                &tk_spinner,
64                &tk_msg,
65                vec![
66                    ("PERSEUS_ENGINE_OPERATION", "tinker"),
67                    ("CARGO_TARGET_DIR", "dist/target_engine"),
68                    ("RUSTFLAGS", "--cfg=engine"),
69                    ("CARGO_TERM_COLOR", "always")
70                ],
71                verbose,
72            )?);
73
74            Ok(0)
75        },
76        global_opts.sequential,
77    );
78
79    Ok(tk_thread)
80}
81
82/// Runs plugin tinkers on the engine and returns an exit code. This doesn't
83/// have a release mode because tinkers should be applied in development to work
84/// in both development and production.
85pub fn tinker(dir: PathBuf, tools: &Tools, global_opts: &Opts) -> Result<i32, Error> {
86    let spinners = MultiProgress::new();
87
88    let tk_thread = tinker_internal(dir, &spinners, 1, tools, global_opts)?;
89    let tk_res = tk_thread
90        .join()
91        .map_err(|_| ExecutionError::ThreadWaitFailed)??;
92    if tk_res != 0 {
93        return Ok(tk_res);
94    }
95
96    // We've handled errors in the component threads, so the exit code is now zero
97    Ok(0)
98}