1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#![recursion_limit = "1024"]

mod async_utils;
mod clean;
mod cli;
mod config;
mod domain;
mod engine;
mod fs;
mod run_script;
mod work_dir;

use anyhow::{Context, Result};
use async_ctrlc::CtrlC;
use async_std::channel::{self, Receiver};
use async_std::path::PathBuf;
use async_std::task;
use clean::clean_target_output_paths;
use config::{ir, yaml};
use domain::TargetId;
use engine::{incremental::storage::delete_saved_env_state, TargetActors};
use work_dir::remove_work_dir;

#[cfg(all(not(target_env = "msvc"), target_pointer_width = "64"))]
use jemallocator::Jemalloc;

#[cfg(all(not(target_env = "msvc"), target_pointer_width = "64"))]
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

pub static DEFAULT_CHANNEL_CAP: usize = 64;

fn main() -> Result<()> {
    let arg_matches = cli::get_app().get_matches();

    stderrlog::new()
        .module(module_path!())
        .verbosity(arg_matches.occurrences_of(cli::arg::VERBOSITY) as usize + 2)
        .init()
        .unwrap();

    let root_project_dir =
        std::path::PathBuf::from(arg_matches.value_of(cli::arg::PROJECT_DIR).unwrap());
    let config = yaml::Config::load(&root_project_dir)?;
    let project_dirs = config.get_project_dirs();
    let config: ir::Config = config.into();
    let all_target_names = config.list_all_available_target_names();

    let arg_matches = cli::get_app()
        .mut_arg(cli::arg::TARGETS, |arg| {
            arg.possible_values(
                &all_target_names
                    .iter()
                    .map(String::as_str)
                    .collect::<Vec<_>>(),
            )
            .required_unless(cli::arg::CLEAN)
        })
        .get_matches();

    let requested_targets = arg_matches.values_of_lossy(cli::arg::TARGETS);

    let root_target_ids = if let Some(requested_targets) = &requested_targets {
        TargetId::try_parse_many(requested_targets, &config.root_project_name).unwrap()
    } else {
        config.list_all_targets()
    };

    let targets = config.try_into_domain_targets(&root_target_ids)?;

    task::block_on(async {
        if arg_matches.is_present(cli::arg::CLEAN) {
            if requested_targets.is_some() {
                for target in targets.values() {
                    delete_saved_env_state(target.metadata()).await?;
                }
            } else {
                for project_dir in project_dirs {
                    let project_dir: PathBuf = project_dir.into();
                    remove_work_dir(&project_dir).await?;
                }
            }

            for target in targets.values() {
                clean_target_output_paths(target).await?;
            }
        }

        if requested_targets.is_some() {
            let watch_option = arg_matches.is_present(cli::arg::WATCH).into();
            let termination_events = terminate_on_ctrlc()?;

            let (target_actor_output_sender, target_actor_output_events) =
                channel::bounded(crate::DEFAULT_CHANNEL_CAP);
            let mut target_actors =
                TargetActors::new(targets, target_actor_output_sender, watch_option);

            let result = engine::run(
                root_target_ids,
                watch_option,
                &mut target_actors,
                termination_events,
                target_actor_output_events,
            )
            .await;

            target_actors.terminate().await;

            result?;
        }

        Ok(())
    })
}

fn terminate_on_ctrlc() -> Result<Receiver<TerminationMessage>> {
    let (termination_sender, termination_events) = channel::bounded(1);
    let ctrlc = CtrlC::new().with_context(|| "Failed to set Ctrl-C handler")?;

    task::spawn(async move {
        ctrlc.await;
        log::debug!("Ctrl-C received");
        let _ = termination_sender.send(TerminationMessage).await;
    });

    Ok(termination_events)
}

pub struct TerminationMessage;