assemble_core/startup/
invocation.rs

1//! Handles standard invoking and monitoring builds
2
3use crate::logging::{ConsoleMode, LoggingArgs};
4use crate::plugins::PluginManager;
5use crate::prelude::listeners::TaskExecutionGraphListener;
6use crate::prelude::{PluginAware, SettingsAware};
7use std::backtrace::Backtrace;
8
9use crate::project::ProjectResult;
10use crate::startup::execution_graph::ExecutionGraph;
11use crate::startup::listeners::{BuildListener, Listener, TaskExecutionListener};
12use crate::version::{version, Version};
13
14use itertools::Itertools;
15use log::Level;
16use once_cell::sync::OnceCell;
17use parking_lot::RwLock;
18use std::collections::HashMap;
19use std::env::current_dir;
20use std::fmt::Debug;
21use std::ops::{Deref, DerefMut};
22use std::path::{Path, PathBuf};
23use std::sync::Arc;
24
25/// Provides a wrapper around the assemble instance that's running this build.
26#[derive(Debug)]
27pub struct Assemble {
28    plugins: PluginManager<Assemble>,
29    task_listeners: Vec<Box<dyn TaskExecutionListener>>,
30    task_graph_listeners: Vec<Box<dyn TaskExecutionGraphListener>>,
31    build_listeners: Vec<Box<dyn BuildListener>>,
32    version: Version,
33    start_parameter: StartParameter,
34    graph: RwLock<OnceCell<ExecutionGraph>>,
35}
36
37impl Assemble {
38    /// Create a new assemble instance
39    pub fn new(start: StartParameter) -> Self {
40        Self {
41            plugins: PluginManager::new(),
42            task_listeners: vec![],
43            task_graph_listeners: vec![],
44            build_listeners: vec![],
45            version: version(),
46            start_parameter: start,
47            graph: Default::default(),
48        }
49    }
50
51    /// Makes the execution graph available
52    pub fn set_execution_graph(&mut self, graph: &ExecutionGraph) -> ProjectResult {
53        self.graph
54            .write()
55            .set(graph.clone())
56            .expect("execution graph already set");
57        for listener in &mut self.task_graph_listeners {
58            listener.graph_ready(graph)?;
59        }
60        Ok(())
61    }
62
63    /// Add a listener to the inner freight
64    pub fn add_listener<T: Listener<Listened = Self>>(&mut self, listener: T) -> ProjectResult {
65        listener.add_listener(self)
66    }
67
68    pub fn add_task_execution_listener<T: TaskExecutionListener + 'static>(
69        &mut self,
70        listener: T,
71    ) -> ProjectResult {
72        self.task_listeners.push(Box::new(listener));
73        Ok(())
74    }
75
76    pub fn add_task_execution_graph_listener<T: TaskExecutionGraphListener + 'static>(
77        &mut self,
78        mut listener: T,
79    ) -> ProjectResult {
80        if let Some(graph) = self.graph.read().get() {
81            listener.graph_ready(graph)
82        } else {
83            self.task_graph_listeners.push(Box::new(listener));
84            Ok(())
85        }
86    }
87
88    pub fn add_build_listener<T: BuildListener + 'static>(&mut self, listener: T) -> ProjectResult {
89        self.build_listeners.push(Box::new(listener));
90        Ok(())
91    }
92
93    pub fn settings_evaluated<S: SettingsAware>(&mut self, settings: S) -> ProjectResult {
94        trace!("running settings evaluated method in build listeners");
95        settings.with_settings(|settings| {
96            self.build_listeners
97                .iter_mut()
98                .map(|b| b.settings_evaluated(&settings))
99                .collect::<ProjectResult>()
100        })
101    }
102
103    /// Gets the current version of assemble
104    pub fn assemble_version(&self) -> &Version {
105        &self.version
106    }
107
108    /// Gets the start parameters used to start this build
109    pub fn start_parameter(&self) -> &StartParameter {
110        &self.start_parameter
111    }
112    pub fn current_dir(&self) -> &Path {
113        self.start_parameter().current_dir()
114    }
115
116    pub fn project_dir(&self) -> PathBuf {
117        self.start_parameter().project_dir()
118    }
119
120    pub fn properties(&self) -> &HashMap<String, Option<String>> {
121        &self.start_parameter.properties
122    }
123}
124
125impl PluginAware for Assemble {
126    fn plugin_manager(&self) -> &PluginManager<Self> {
127        &self.plugins
128    }
129
130    fn plugin_manager_mut(&mut self) -> &mut PluginManager<Self> {
131        &mut self.plugins
132    }
133}
134
135impl Default for Assemble {
136    fn default() -> Self {
137        Assemble::new(StartParameter::new())
138    }
139}
140
141/// A type that's aware it's part of an assemble build
142pub trait AssembleAware {
143    /// Get the assemble instance this value is aware of.
144    fn with_assemble<F, R>(&self, func: F) -> R
145    where
146        F: FnOnce(&Assemble) -> R;
147
148    /// Get the assemble instance this value is aware of as a mutable reference
149    fn with_assemble_mut<F, R>(&mut self, func: F) -> R
150    where
151        F: FnOnce(&mut Assemble) -> R;
152
153    fn start_parameter(&self) -> StartParameter {
154        self.with_assemble(|asm| asm.start_parameter.clone())
155    }
156}
157
158impl AssembleAware for Assemble {
159    /// Gets this [`Assemble`](Assemble) instance.
160    fn with_assemble<F, R>(&self, func: F) -> R
161    where
162        F: FnOnce(&Assemble) -> R,
163    {
164        (func)(self)
165    }
166
167    fn with_assemble_mut<F, R>(&mut self, func: F) -> R
168    where
169        F: FnOnce(&mut Assemble) -> R,
170    {
171        (func)(self)
172    }
173}
174
175impl AssembleAware for Arc<RwLock<Assemble>> {
176    fn with_assemble<F, R>(&self, func: F) -> R
177    where
178        F: FnOnce(&Assemble) -> R,
179    {
180        (func)(self.read().deref())
181    }
182
183    fn with_assemble_mut<F, R>(&mut self, func: F) -> R
184    where
185        F: FnOnce(&mut Assemble) -> R,
186    {
187        (func)(self.write().deref_mut())
188    }
189}
190
191/// The start parameters define the configuration used by an assemble instance to execute a build.
192///
193/// Generally corresponds to the command line options for assemble.
194#[derive(Debug, Clone)]
195pub struct StartParameter {
196    current_dir: PathBuf,
197    logging: LoggingArgs,
198    mode: ConsoleMode,
199    project_dir: Option<PathBuf>,
200    properties: HashMap<String, Option<String>>,
201    task_requests: Vec<String>,
202    workers: usize,
203    backtrace: BacktraceEmit,
204    rerun_tasks: bool,
205}
206
207/// The mechanism to emit the backtrace at
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub enum BacktraceEmit {
210    None,
211    Short,
212    Long,
213}
214
215impl BacktraceEmit {
216    pub fn emit(&self, level: Level, backtrace: &Backtrace) {
217        let lines: Vec<String> = match self {
218            BacktraceEmit::None => return,
219            BacktraceEmit::Short => {
220                let mut bt = backtrace
221                    .to_string()
222                    .lines()
223                    .skip_while(|line| !line.contains("assemble_core::error::PayloadError"))
224                    .map(|s| s.to_string())
225                    .collect::<Vec<_>>();
226
227                if let Some(pos) = bt
228                    .iter()
229                    .position(|p| p.contains("assemble::main"))
230                    .map(|s| s + 2)
231                {
232                    bt.drain(pos..);
233                }
234
235                bt.into_iter()
236                    .tuples::<(_, _)>()
237                    .map(|(frame, location)| {
238                        if location.contains("/rustc/") {
239                            vec!["\t... <hidden>".to_string()]
240                        } else {
241                            vec![frame, location]
242                        }
243                    })
244                    .flatten()
245                    .fold(vec![], |mut acc, line| {
246                        let should_push = if line.trim().starts_with("...") {
247                            if let Some(last) = acc.last() {
248                                if !last.trim().starts_with("...") {
249                                    true
250                                } else {
251                                    false
252                                }
253                            } else {
254                                true
255                            }
256                        } else {
257                            true
258                        };
259                        if should_push {
260                            acc.push(line);
261                        }
262                        acc
263                    })
264            }
265            BacktraceEmit::Long => backtrace
266                .to_string()
267                .lines()
268                .map(|s| s.to_string())
269                .collect(),
270        };
271
272        for line in lines {
273            log!(level, "{}", line);
274        }
275    }
276}
277
278impl StartParameter {
279    /// Creates a new instance of a start parameter with only default settings
280    pub fn new() -> Self {
281        Self {
282            current_dir: current_dir().expect("no valid current working directory"),
283            logging: LoggingArgs::default(),
284            mode: ConsoleMode::Auto,
285            project_dir: None,
286            properties: HashMap::new(),
287            task_requests: vec![],
288            workers: 0,
289            backtrace: BacktraceEmit::None,
290            rerun_tasks: false,
291        }
292    }
293
294    /// Gets the current directory of the start parameter, used to select the default project and
295    /// find the settings file.
296    pub fn current_dir(&self) -> &Path {
297        &self.current_dir
298    }
299
300    /// The console mode to use
301    pub fn mode(&self) -> ConsoleMode {
302        self.mode
303    }
304
305    /// The project directory to find the default project. If not set, defaults to the same
306    /// as the current dir.
307    pub fn project_dir(&self) -> PathBuf {
308        self.project_dir
309            .as_ref()
310            .unwrap_or(&self.current_dir)
311            .clone()
312    }
313
314    /// The project properties set for this build
315    pub fn properties(&self) -> &HashMap<String, Option<String>> {
316        &self.properties
317    }
318
319    /// A mutable reference to the project properties for this build
320    pub fn properties_mut(&mut self) -> &mut HashMap<String, Option<String>> {
321        &mut self.properties
322    }
323
324    /// Gets whether the backtrace should be emitted
325    pub fn backtrace(&self) -> BacktraceEmit {
326        self.backtrace
327    }
328
329    /// the task requests used to build this project. Contains both task names
330    /// and args for said tasks
331    pub fn task_requests(&self) -> &[String] {
332        &self.task_requests
333    }
334
335    /// the task requests used to build this project. Contains both task names
336    /// and args for said tasks
337    pub fn task_requests_mut(&mut self) -> &mut Vec<String> {
338        &mut self.task_requests
339    }
340
341    /// Adds the task requests to this start parameter
342    pub fn with_task_requests<S: AsRef<str>, I: IntoIterator<Item = S>>(mut self, iter: I) -> Self {
343        self.task_requests_mut()
344            .extend(iter.into_iter().map(|s| s.as_ref().to_string()));
345        self
346    }
347
348    /// Whether to rerun all tasks
349    pub fn is_rerun_tasks(&self) -> bool {
350        self.rerun_tasks
351    }
352
353    /// Force all tasks to be re-ran
354    pub fn rerun_tasks(&mut self) {
355        self.rerun_tasks = true;
356    }
357
358    /// Set the current directory
359    pub fn set_current_dir<P: AsRef<Path>>(&mut self, current_dir: P) {
360        self.current_dir = current_dir.as_ref().to_path_buf();
361    }
362    /// The level filter to log
363    pub fn set_logging(&mut self, log_level: LoggingArgs) {
364        self.logging = log_level;
365    }
366
367    /// Sets the console mode
368    pub fn set_mode(&mut self, mode: ConsoleMode) {
369        self.mode = mode;
370    }
371
372    /// Sets the project directory used to find the default project
373    pub fn set_project_dir<P: AsRef<Path>>(&mut self, project_dir: P) {
374        self.project_dir = Some(project_dir.as_ref().to_path_buf());
375    }
376
377    pub fn set_backtrace(&mut self, backtrace: BacktraceEmit) {
378        self.backtrace = backtrace;
379    }
380
381    pub fn workers(&self) -> usize {
382        self.workers
383    }
384
385    pub fn set_workers(&mut self, workers: usize) {
386        self.workers = workers;
387    }
388    pub fn logging(&self) -> &LoggingArgs {
389        &self.logging
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    #[test]
398    fn get_assemble_version() {
399        let assemble = Assemble::default();
400        println!("assemble: {:#?}", assemble);
401        assert_eq!(assemble.assemble_version(), &version());
402    }
403}