assemble_core/task/
lazy_task.rs

1use std::any::type_name;
2use std::collections::HashSet;
3use std::fmt::{Debug, Formatter};
4use std::marker::PhantomData;
5use std::sync::{Arc, Mutex};
6
7use crate::defaults::tasks::Empty;
8use crate::error::PayloadError;
9use crate::exception::BuildException;
10use crate::identifier::{InvalidId, TaskId};
11use crate::immutable::Immutable;
12use crate::lazy_evaluation::{Provider, ProviderError};
13use crate::project::buildable::{Buildable, IntoBuildable};
14use crate::project::error::{ProjectError, ProjectResult};
15use crate::project::shared::SharedProject;
16use crate::project::shared::WeakSharedProject;
17use crate::task::flags::{OptionDeclarations, OptionsDecoder};
18use crate::task::up_to_date::UpToDate;
19use crate::task::{BuildableTask, FullTask, HasTaskId, TaskOrdering};
20use crate::{BuildResult, Executable, Project};
21
22use super::ExecutableTask;
23use super::Task;
24
25pub struct ConfigureTask<T: Task> {
26    func: Box<dyn FnOnce(&mut Executable<T>, &Project) -> ProjectResult + Send>,
27}
28
29impl<T: Task> Debug for ConfigureTask<T> {
30    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31        write!(f, "<configure task type <{}>>", type_name::<T>())
32    }
33}
34
35impl<T: Task> ConfigureTask<T> {
36    pub fn configure(self, task: &mut Executable<T>, project: &Project) -> ProjectResult {
37        (self.func)(task, project)
38    }
39}
40
41pub trait ResolveTask {
42    type Executable: ExecutableTask;
43
44    fn resolve_task(self, project: &SharedProject) -> ProjectResult<Self::Executable>;
45}
46
47pub trait ResolveInnerTask: Send {
48    fn resolve_task(&mut self, project: &SharedProject) -> ProjectResult<()>;
49}
50
51assert_obj_safe!(ResolveInnerTask);
52
53#[derive(Debug)]
54pub struct LazyTask<T: Task + Send + Debug + 'static> {
55    task_type: PhantomData<T>,
56    task_id: Immutable<TaskId>,
57    configurations: Vec<ConfigureTask<T>>,
58    shared: Option<WeakSharedProject>,
59}
60
61impl<T: Task + Send + Debug + 'static> LazyTask<T> {
62    fn new(id: TaskId, shared: &WeakSharedProject) -> Self {
63        Self {
64            task_type: PhantomData,
65            task_id: Immutable::new(id),
66            configurations: vec![],
67            shared: Some(shared.clone()),
68        }
69    }
70
71    fn empty() -> Self {
72        Self {
73            task_type: PhantomData,
74            task_id: Immutable::default(),
75            configurations: vec![],
76            shared: None,
77        }
78    }
79}
80
81impl<T: Task + Send + Sync + Debug + 'static> ResolveTask for LazyTask<T> {
82    type Executable = Executable<T>;
83
84    fn resolve_task(self, project: &SharedProject) -> ProjectResult<Executable<T>> {
85        trace!("Resolving task {}", self.task_id.as_ref());
86        let task = project.with(|project| T::new(self.task_id.as_ref(), project))?;
87        let mut executable = Executable::new(
88            self.shared
89                .unwrap()
90                .upgrade()
91                .expect("could not upgrade project"),
92            task,
93            self.task_id,
94        );
95
96        project.with(|project| executable.initialize(project))?;
97        executable.configure_io()?;
98
99        for config in self.configurations {
100            project.with(|project| config.configure(&mut executable, project))?;
101        }
102
103        Ok(executable)
104    }
105}
106
107#[derive(Debug)]
108enum TaskHandleInner<T: Task + Send + Sync + Debug + 'static> {
109    Lazy(LazyTask<T>),
110    Configured(Executable<T>),
111}
112
113impl<T: Task + Send + Sync + Debug + 'static> TaskHandleInner<T> {
114    fn bare_resolve(&mut self) -> ProjectResult<()> {
115        let project = match self {
116            TaskHandleInner::Lazy(l) => l
117                .shared
118                .as_ref()
119                .ok_or(ProjectError::NoSharedProjectSet)?
120                .clone(),
121            TaskHandleInner::Configured(_) => {
122                return Ok(());
123            }
124        };
125        let project = project.upgrade()?;
126        self.resolve(&project)
127    }
128
129    fn resolve(&mut self, project: &SharedProject) -> ProjectResult<()> {
130        let configured: Executable<T> = match self {
131            TaskHandleInner::Lazy(lazy) => {
132                let lazy = std::mem::replace(lazy, LazyTask::empty());
133                lazy.resolve_task(project)?
134            }
135            TaskHandleInner::Configured { .. } => return Ok(()),
136        };
137        *self = TaskHandleInner::Configured(configured);
138        Ok(())
139    }
140
141    fn bare_configured(&mut self) -> ProjectResult<&Executable<T>> {
142        self.bare_resolve()?;
143        if let TaskHandleInner::Configured(exec) = &*self {
144            Ok(exec)
145        } else {
146            panic!("task should be configured")
147        }
148    }
149
150    fn bare_configured_mut(&mut self) -> ProjectResult<&mut Executable<T>> {
151        self.bare_resolve()?;
152        if let TaskHandleInner::Configured(exec) = &mut *self {
153            Ok(exec)
154        } else {
155            panic!("task should be configured")
156        }
157    }
158
159    fn configured(&mut self, project: &SharedProject) -> ProjectResult<&Executable<T>> {
160        self.resolve(project)?;
161        if let TaskHandleInner::Configured(exec) = &*self {
162            Ok(exec)
163        } else {
164            panic!("task should be configured")
165        }
166    }
167
168    fn configured_mut(&mut self, project: &SharedProject) -> ProjectResult<&mut Executable<T>> {
169        self.resolve(project)?;
170        if let TaskHandleInner::Configured(exec) = &mut *self {
171            Ok(exec)
172        } else {
173            panic!("task should be configured")
174        }
175    }
176}
177
178impl<T: Task + Send + Sync + Debug + 'static> ResolveInnerTask for TaskHandleInner<T> {
179    fn resolve_task(&mut self, project: &SharedProject) -> ProjectResult<()> {
180        self.resolve(project)
181    }
182}
183
184pub struct TaskHandle<T: Task + Send + Sync + Debug + 'static> {
185    id: TaskId,
186    connection: Arc<Mutex<TaskHandleInner<T>>>,
187}
188
189impl<T: Task + Send + Sync + Debug + 'static> UpToDate for TaskHandle<T> {
190    fn up_to_date(&self) -> bool {
191        let mut guard = {
192            if let Ok(guard) = self.connection.lock() {
193                guard
194            } else {
195                return false;
196            }
197        };
198        if let Ok(configured) = guard.bare_configured() {
199            configured.task_up_to_date()
200        } else {
201            false
202        }
203    }
204}
205
206impl<T: Task + Send + Sync + Debug + 'static> TaskHandle<T> {
207    /// Gets the id of the created task.
208    pub fn id(&self) -> &TaskId {
209        &self.id
210    }
211
212    pub fn configure_with<F>(&mut self, config: F) -> ProjectResult
213    where
214        F: FnOnce(&mut Executable<T>, &Project) -> ProjectResult + Send + 'static,
215    {
216        let mut guard = self.connection.lock().map_err(PayloadError::new)?;
217        match &mut *guard {
218            TaskHandleInner::Lazy(lazy) => {
219                lazy.configurations.push(ConfigureTask {
220                    func: Box::new(config),
221                });
222            }
223            TaskHandleInner::Configured(c) => {
224                let shared_project = c.project();
225                shared_project.with(|project| (config)(c, project))?;
226            }
227        }
228        Ok(())
229    }
230
231    fn configured<R, F: FnOnce(&Executable<T>) -> R>(&self, func: F) -> ProjectResult<R> {
232        Ok((func)(
233            self.connection
234                .lock()
235                .map_err(PayloadError::new)?
236                .bare_configured()?,
237        ))
238    }
239
240    pub fn provides<F, R>(&self, func: F) -> TaskProvider<T, R, F>
241    where
242        F: Fn(&Executable<T>) -> R + Send + Sync,
243        R: Clone + Send + Sync,
244    {
245        TaskProvider::new(self.clone(), func)
246    }
247}
248
249assert_impl_all!(TaskHandle<Empty>: Sync);
250
251impl<T: Task + Send + Sync + Debug + 'static> Debug for TaskHandle<T> {
252    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
253        if f.alternate() {
254            f.debug_struct("TaskHandle")
255                .field("type", &type_name::<T>())
256                .field("id", &self.id)
257                .finish()
258        } else {
259            write!(f, "{:?}", self.id)
260        }
261    }
262}
263
264impl<T: Task + Send + Sync + Debug + 'static> Buildable for TaskHandle<T> {
265    fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
266        let mut guard = self.connection.lock().map_err(PayloadError::new)?;
267        let configured = guard.configured(&project.as_shared())?;
268        configured.into_buildable().get_dependencies(project)
269    }
270}
271
272impl<T: Task + Send + Sync + Debug + 'static> HasTaskId for TaskHandle<T> {
273    fn task_id(&self) -> TaskId {
274        self.id.clone()
275    }
276}
277
278impl<T: Task + Send + Sync + Debug + 'static> BuildableTask for TaskHandle<T> {
279    fn ordering(&self) -> Vec<TaskOrdering> {
280        let mut guard = self.connection.lock().unwrap();
281        guard
282            .bare_configured()
283            .expect("could not get configured")
284            .ordering()
285    }
286}
287
288impl<T: Task + Send + Sync + Debug + 'static> Clone for TaskHandle<T> {
289    fn clone(&self) -> Self {
290        Self {
291            id: self.id.clone(),
292            connection: self.connection.clone(),
293        }
294    }
295}
296
297impl<T: Task + Send + Sync + Debug + 'static> ResolveInnerTask for TaskHandle<T> {
298    fn resolve_task(&mut self, project: &SharedProject) -> ProjectResult<()> {
299        self.connection
300            .lock()
301            .map_err(PayloadError::new)?
302            .resolve_task(project)
303    }
304}
305impl<T: Task + Send + Sync + Debug + 'static> ExecutableTask for TaskHandle<T> {
306    fn options_declarations(&self) -> Option<OptionDeclarations> {
307        let mut guard = self.connection.lock().unwrap();
308        guard.bare_configured().unwrap().options_declarations()
309    }
310
311    fn try_set_from_decoder(&mut self, decoder: &OptionsDecoder) -> ProjectResult<()> {
312        let mut guard = self
313            .connection
314            .lock()
315            .map_err(|_| ProjectError::PoisonError)?;
316        guard
317            .bare_configured_mut()
318            .unwrap()
319            .try_set_from_decoder(decoder)
320    }
321
322    fn execute(&mut self, project: &Project) -> BuildResult {
323        let mut guard = self
324            .connection
325            .lock()
326            .map_err(|_| BuildException::new("Could not get access to provider"))?;
327        guard.configured_mut(&project.as_shared())?.execute(project)
328    }
329
330    fn did_work(&self) -> bool {
331        let mut guard = self
332            .connection
333            .lock()
334            .map_err(|_| BuildException::new("Could not get access to provider"))
335            .unwrap();
336        guard.bare_configured().unwrap().did_work()
337    }
338
339    fn task_up_to_date(&self) -> bool {
340        let mut guard = self
341            .connection
342            .lock()
343            .map_err(|_| BuildException::new("Could not get access to provider"))
344            .unwrap();
345        guard.bare_configured().unwrap().task_up_to_date()
346    }
347
348    fn group(&self) -> String {
349        self.configured(|e| e.group()).unwrap()
350    }
351
352    fn description(&self) -> String {
353        self.configured(|e| e.description()).unwrap()
354    }
355}
356
357pub trait ResolveExecutable: ResolveInnerTask {
358    fn get_executable(&mut self, project: &SharedProject) -> ProjectResult<Box<dyn FullTask>>;
359}
360
361impl<T: Task + Send + Sync + Debug + 'static> ResolveExecutable for TaskHandle<T> {
362    fn get_executable(&mut self, project: &SharedProject) -> ProjectResult<Box<dyn FullTask>> {
363        self.resolve_task(project)?;
364        Ok(Box::new(self.clone()))
365    }
366}
367
368pub struct TaskProvider<T, R, F>
369where
370    T: Task + Send + Sync + Debug + 'static,
371    F: Fn(&Executable<T>) -> R + Send + Sync,
372    R: Clone + Send + Sync,
373{
374    handle: TaskHandle<T>,
375    lift: F,
376}
377
378impl<T, R, F> Clone for TaskProvider<T, R, F>
379where
380    T: Task + Send + Sync + Debug + 'static,
381    F: Fn(&Executable<T>) -> R + Send + Sync + Clone,
382    R: Clone + Send + Sync,
383{
384    fn clone(&self) -> Self {
385        Self {
386            handle: self.handle.clone(),
387            lift: self.lift.clone(),
388        }
389    }
390}
391
392impl<T, F, R> Buildable for TaskProvider<T, R, F>
393where
394    F: Fn(&Executable<T>) -> R + Send + Sync,
395    R: Clone + Send + Sync,
396    T: 'static + Debug + Send + Task + Sync,
397{
398    fn get_dependencies(&self, _: &Project) -> ProjectResult<HashSet<TaskId>> {
399        Ok(HashSet::from_iter([self.handle.id.clone()]))
400    }
401}
402
403impl<T, F, R> Debug for TaskProvider<T, R, F>
404where
405    F: Fn(&Executable<T>) -> R + Send + Sync,
406    R: Clone + Send + Sync,
407    T: 'static + Debug + Send + Task + Sync,
408{
409    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
410        f.debug_struct("TaskProvider")
411            .field("handle", &self.handle)
412            .finish_non_exhaustive()
413    }
414}
415
416impl<T, F, R> Provider<R> for TaskProvider<T, R, F>
417where
418    T: Task + Send + Sync + Debug + 'static,
419    F: Fn(&Executable<T>) -> R + Send + Sync,
420    R: Clone + Send + Sync,
421{
422    fn missing_message(&self) -> String {
423        format!("couldn't get a value from task {}", self.handle.id)
424    }
425
426    fn try_get(&self) -> Option<R> {
427        let mut guard = self.handle.connection.lock().expect("Could not get inner");
428        let configured = guard.bare_configured().expect("could not configure task");
429        Some((self.lift)(configured))
430    }
431
432    fn fallible_get(&self) -> Result<R, ProviderError> {
433        let mut guard = self
434            .handle
435            .connection
436            .lock()
437            .map_err(|e| ProviderError::new(e.to_string()))?;
438        let configured = guard
439            .bare_configured()
440            .map_err(|e| ProviderError::new(e.to_string()))?;
441        Ok((self.lift)(configured))
442    }
443}
444
445impl<T, F, R> TaskProvider<T, R, F>
446where
447    T: Task + Send + Sync + Debug + 'static,
448    F: Fn(&Executable<T>) -> R + Send + Sync,
449    R: Clone + Send + Sync,
450{
451    pub fn new(handle: TaskHandle<T>, lift: F) -> Self {
452        Self { handle, lift }
453    }
454}
455
456#[derive(Debug)]
457pub struct TaskHandleFactory {
458    project: WeakSharedProject,
459}
460
461impl TaskHandleFactory {
462    pub(crate) fn new(project: WeakSharedProject) -> Self {
463        Self { project }
464    }
465
466    /// Creates a task handle that's not configured
467    pub fn create_handle<T: Task + Send + Sync + Debug + 'static>(
468        &self,
469        id: TaskId,
470    ) -> Result<TaskHandle<T>, InvalidId> {
471        let lazy = LazyTask::<T>::new(id.clone(), &self.project);
472        let inner = TaskHandleInner::Lazy(lazy);
473        Ok(TaskHandle {
474            id,
475            connection: Arc::new(Mutex::new(inner)),
476        })
477    }
478}
479
480#[cfg(test)]
481mod tests {
482
483    #[test]
484    fn lazy_create_task() {}
485}