mod realizer; mod symbol_runner; mod util; pub use symbol_runner::{ DelayingSymbolRunner, DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner, }; mod cache; mod runnable; use crate::loggers::Logger; use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource}; use crate::symbols::Symbol; use crate::{DefaultBuilder, DefaultLocator}; use crate::{ImplementationBuilder, ResourceLocator}; use async_trait::async_trait; use cache::Cache; use realizer::Realizer; use runnable::Runnable; use slog::o; use std::error::Error; use std::fmt::Debug; use std::hash::Hash; use std::rc::Rc; use util::{Add, AddGeneric, AddResult, AddableResource, Recorder}; // Necessary for the recursive type #[derive(Debug)] pub struct ActualSetup(Cache, Rs, As>); #[async_trait(?Send)] impl Add for ActualSetup where Cache, Rs, As>: Add, { async fn add(&self, logger: &Rc, r: Rc, force_run: bool) -> AddResult { self.0.add(logger, r, force_run).await } } // This is for self-referential T // FIXME: Wait for specialization #[async_trait(?Send)] impl< SR: 'static + SymbolRunner, T: AddableResource, Rs: 'static + Hash + Eq + FromResource, As: 'static + FromArtifact + Clone, L: 'static + ResourceLocator>, B: 'static + ImplementationBuilder, > AddGeneric> for ActualSetup where >::Implementation: Runnable + Debug, Self: AddGeneric, T::Artifact: Clone, // These bounds cannot be replaced by // `Realizer: Add` // because the prerequisites are Option, too, and thus this would // require AddGeneric> to already be implemented { async fn add_generic( &self, logger: &Rc, r: Option, force_run: bool, ) -> AddResult> { Ok(match r { Some(r) => { let (result, did_run) = self.add(logger, Rc::new(r), force_run).await?; (Some(result), did_run) } None => (None, false), }) } } #[derive(Debug)] pub struct Setup< SR, LOG, L = DefaultLocator, B = DefaultBuilder, Rs = DefaultResources<'static, &'static str>, As = DefaultArtifacts<'static, &'static str>, >(LOG, Rc, Rc>); impl Setup { pub fn new(symbol_runner: SR, logger: LOG) -> Self { Self::new_with(symbol_runner, logger) } } impl Setup { pub fn new_with(symbol_runner: SR, logger: LOG) -> Self { let runner = Rc::new(symbol_runner); let inner = Rc::new_cyclic(|inner| { ActualSetup(Cache::new(Realizer::new(Rc::clone(&runner), inner.clone()))) }); Self(logger, runner, inner) } } impl< SR: 'static, LOG: 'static + Logger, L: 'static, B: 'static, Rs: 'static + Eq + Hash, As: 'static, > Setup { pub async fn add_force(&self, resource: R, force_run: bool) -> AddResult where Rs: FromResource, As: FromArtifact + Clone, ActualSetup: Add, { let recorder = Recorder::default(); let result = { let log = Rc::new(slog::Logger::root(recorder.clone(), o!())); self.2.add(&log, Rc::new(resource), force_run).await }; self.log_result(recorder, result.as_ref().map(|(_, did_run)| *did_run)); result } pub async fn add(&self, resource: R) -> AddResult where Rs: FromResource, As: FromArtifact + Clone, R::Artifact: Clone, ActualSetup: Add, { self.add_force(resource, false).await } pub async fn run_symbol( &self, symbol: S, force: bool, ) -> Result> where SR: SymbolRunner, { let recorder = Recorder::default(); let result = { let log = Rc::new(slog::Logger::root( recorder.clone(), o!("symbol" => format!("{symbol:?}")), )); self.1.run_symbol(&symbol, &log, force).await }; self.log_result(recorder, result.as_ref().copied()); result } fn log_result(&self, recorder: Recorder, result: Result>) { let log = match result { Ok(false) => String::new(), Ok(true) => recorder.into_string(slog::Level::Info), Err(e) => recorder.into_string(slog::Level::Trace), }; if log.is_empty() { self.0.write(3, "."); } else { self.0.writeln(3, &log); } } } #[cfg(test)] mod test { use super::symbol_runner::TestSymbolRunner; use crate::async_utils::run; use crate::loggers::{Entry, StoringLogger}; use crate::resources::{FromArtifact, FromResource, Resource}; use crate::symbols::Symbol; use crate::to_artifact::ToArtifact; use crate::{ImplementationBuilder, ResourceLocator, Setup}; use async_trait::async_trait; use regex::Regex; use std::cell::RefCell; use std::error::Error; use std::fmt::Debug; use std::rc::{Rc, Weak}; #[derive(Debug, PartialEq, Eq, Hash)] struct TestResource(&'static str, T); impl Resource for TestResource { type Artifact = (); } #[derive(Debug, Hash, PartialEq, Eq)] enum Resources { A(Rc>), B(Rc>), C(Rc>), D(Rc>), } impl FromResource> for Resources { fn from_resource(inner: &Rc>) -> (Self, Weak>) { (Self::A(Rc::clone(&inner)), Rc::downgrade(&inner)) } } impl FromResource> for Resources { fn from_resource( inner: &Rc>, ) -> (Self, Weak>) { (Self::B(Rc::clone(&inner)), Rc::downgrade(&inner)) } } impl FromResource> for Resources { fn from_resource( inner: &Rc>, ) -> (Self, Weak>) { (Self::C(Rc::clone(&inner)), Rc::downgrade(&inner)) } } impl FromResource> for Resources { fn from_resource( inner: &Rc>, ) -> ( Self, Weak>, ) { (Self::D(Rc::clone(&inner)), Rc::downgrade(&inner)) } } #[derive(Clone)] struct Artifacts; impl FromArtifact> for Artifacts { fn from_artifact(_from: ()) -> Self { Self } #[allow(clippy::unused_unit)] fn into_artifact(self) -> () { #[allow(clippy::unused_unit)] () } } struct TestResourceLocator; impl ResourceLocator> for TestResourceLocator { type Prerequisites = (); fn locate(_resource: &TestResource) -> ( as ToArtifact>::Artifact, ()) { ((), ()) } } #[derive(Debug)] struct TestSymbol { result: Result>, } impl TestSymbol { fn new(def: &str) -> Self { let first_char = def.chars().next().unwrap(); Self { result: if first_char == '!' { Err(def.into()) } else { Ok(first_char.is_uppercase()) }, } } } #[async_trait(?Send)] impl Symbol for TestSymbol { async fn target_reached(&self) -> Result> { self.result.clone().map_err(|s| s.to_string().into()) } async fn execute(&self) -> Result<(), Box> { Ok(()) } } struct TestImplementationBuilder; impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = (TestResource<(&'static str, &'static str)>, TestResource<()>); fn prerequisites( resource: &TestResource<((&'static str, &'static str), &'static str)>, ) -> Self::Prerequisites { ( TestResource("complex_resource", (resource.1).0), // FIXME: Only one of these can exist TestResource((resource.1).1, ()), ) } fn create( resource: &TestResource<((&'static str, &'static str), &'static str)>, (): &(), _inputs: ::Artifact, ) -> Self::Implementation { TestSymbol::new(resource.0) } } impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = (TestResource<()>, TestResource<()>); fn prerequisites(resource: &TestResource<(&'static str, &'static str)>) -> Self::Prerequisites { ( TestResource((resource.1).0, ()), TestResource((resource.1).1, ()), ) } fn create( resource: &TestResource<(&'static str, &'static str)>, (): &(), _inputs: ::Artifact, ) -> Self::Implementation { TestSymbol::new(resource.0) } } impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = TestResource<()>; fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites { TestResource(resource.1, ()) } fn create( resource: &TestResource<&'static str>, (): &(), _inputs: ::Artifact, ) -> Self::Implementation { TestSymbol::new(resource.0) } } impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = (); fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation { TestSymbol::new(resource.0) } } #[allow(clippy::type_complexity)] fn get_setup() -> ( Rc>, Setup< TestSymbolRunner, StoringLogger, TestResourceLocator, TestImplementationBuilder, Resources, Artifacts, >, StoringLogger, ) { let (count, runner) = TestSymbolRunner::new(); let logger = StoringLogger::new(); (count, Setup::new_with(runner, logger.clone()), logger) } #[test] fn correctly_uses_force() { run(async { let (count, setup, _) = get_setup(); setup.add(TestResource("A", "b")).await.unwrap(); assert_eq!(*count.borrow(), 2); setup.add(TestResource("A", "b")).await.unwrap(); assert_eq!(*count.borrow(), 2); let (count, setup, _) = get_setup(); setup.add(TestResource("A", "B")).await.unwrap(); assert_eq!(*count.borrow(), 0); }); } #[test] fn failing_dependencies_deadlock() { run(async { let (count, setup, _) = get_setup(); assert_eq!( setup .add(TestResource("a", (("b", "!x"), "!x"))) .await .unwrap_err() .to_string(), "!x" ); assert_eq!(*count.borrow(), 1); }); } #[test] fn run_reached_symbol() { run(async { let (count, setup, log) = get_setup(); let did_run = setup .run_symbol(TestSymbol { result: Ok(true) }, false) .await .unwrap(); drop(setup); assert!(!did_run); assert_eq!(*count.borrow(), 0); assert_eq!(log.release(), vec![Entry(3, ".".into())]); }); } #[test] fn run_not_reached_symbol() { run(async { let (count, setup, log) = get_setup(); let did_run = setup .run_symbol(TestSymbol { result: Ok(false) }, false) .await .unwrap(); drop(setup); assert!(did_run); assert_eq!(*count.borrow(), 1); let log = log.release(); assert_eq!(log.len(), 1); assert_eq!(log[0].0, 3); let re = Regex::new(r"^symbol: TestSymbol \{ result: Ok\(false\) \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap(); assert!(re.is_match(&log[0].1)); }); } use super::{ActualSetup, AddGeneric, Cache, Realizer}; #[test] fn empty_tuple_add_generic() { let setup = Rc::new_cyclic(|inner| { ActualSetup(Cache::, (), ()>::new( Realizer::new(Rc::new(()), inner.clone()), )) }); run(async { assert!(setup .add_generic( &Rc::new(slog::Logger::root(slog::Discard, slog::o!())), (), false ) .await .is_ok()); }) } }