diff --git a/src/build.rs b/src/build.rs index 859fce7..606bc2a 100644 --- a/src/build.rs +++ b/src/build.rs @@ -25,12 +25,13 @@ use { futures::{stream::FuturesUnordered, StreamExt, TryStreamExt}, std::{ collections::HashMap, - fs, mem, + path::Path, sync::Arc, time::{Duration, SystemTime}, }, tokio::{ + fs, process::Command, sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock, Semaphore}, }, @@ -43,9 +44,6 @@ use { // when a single rule has multiple targets that each want to be // built simultaneously -// TODO: `fs::metadata` isn't async-aware, so it might slow us down a lot -// when every target is up to date, so see alternatives - /// Event #[derive(Clone, Debug)] pub enum Event { @@ -106,7 +104,7 @@ impl Builder { /// Returns all targets built. /// - /// If any target is still being build, ignores it. + /// Waits for targets being built pub async fn targets(&self) -> HashMap, Result> { self.targets .iter() @@ -214,7 +212,7 @@ impl Builder { // Else, if no rule can make it, we'll assume it's an existing file // and return it's modification date as the build time. None => { - let metadata = fs::metadata(file).map_err(AppError::missing_file(file))?; + let metadata = fs::metadata(file).await.map_err(AppError::missing_file(file))?; let build_time = self::file_modified_time(metadata); tracing::trace!(?target, ?build_time, "Found target file"); let res = BuildResult { @@ -261,41 +259,45 @@ impl Builder { let normal_deps = rule .deps .iter() - .map(|dep| match *dep { + .map(async move |dep| match *dep { DepItem::File { ref file, is_static } => Ok(Dep::File { file, is_static, is_dep_file: false, is_output: false, - exists: fs::try_exists(file).map_err(AppError::check_file_exists(file))?, + exists: fs_try_exists(file).await.map_err(AppError::check_file_exists(file))?, }), DepItem::DepsFile { ref file, is_static } => Ok(Dep::File { file, is_static, is_dep_file: true, is_output: false, - exists: fs::try_exists(file).map_err(AppError::check_file_exists(file))?, + exists: fs_try_exists(file).await.map_err(AppError::check_file_exists(file))?, }), DepItem::Rule { ref name, ref pats } => Ok(Dep::Rule { name, pats }), }) - .collect::, AppError>>()?; + .collect::>() + .try_collect::>() + .await?; // And all output dependencies let out_deps = rule .output .iter() - .map(|out| match out { + .map(async move |out| match out { OutItem::File { .. } => Ok(None), OutItem::DepsFile { file } => Ok(Some(Dep::File { file, is_static: false, is_dep_file: true, is_output: true, - exists: fs::try_exists(file).map_err(AppError::check_file_exists(file))?, + exists: fs_try_exists(file).await.map_err(AppError::check_file_exists(file))?, })), }) - .filter_map(|res| res.transpose()) - .collect::, AppError>>()?; + .collect::>() + .filter_map(async move |res| res.transpose()) + .try_collect::>() + .await?; // Then build all dependencies, as well as any dependency files let deps = util::chain!(normal_deps, out_deps) @@ -397,7 +399,7 @@ impl Builder { let deps = util::chain!(dep_res.zip(dep_guard), dep_deps.into_iter()).collect::>(); tracing::trace!(?target, ?rule.name, ?dep, ?deps, "Built target rule dependency dependencies"); - Ok::<_, AppError>(deps) + Ok(deps) } }) .collect::>() @@ -416,7 +418,7 @@ impl Builder { // Afterwards check the last time we've built the rule and compare it with // the dependency build times. - let rule_last_build_time = self::rule_last_build_time(&rule); + let rule_last_build_time = self::rule_last_build_time(&rule).await; let needs_rebuilt = match (deps_last_build_time, &rule_last_build_time) { // If any files were missing, or we had no outputs, build (_, Err(_) | Ok(None)) => true, @@ -440,7 +442,7 @@ impl Builder { // Then get the build time // Note: If we don't have any outputs, just use the current time as the build time - let cur_build_time = self::rule_last_build_time(&rule)?.unwrap_or_else(SystemTime::now); + let cur_build_time = self::rule_last_build_time(&rule).await?.unwrap_or_else(SystemTime::now); let res = BuildResult { build_time: cur_build_time, built: needs_rebuilt, @@ -461,7 +463,7 @@ impl Builder { rules: &Rules, ) -> Result, AppError> { tracing::trace!(target=?parent_target, ?rule.name, ?dep_file, "Building dependencies of target rule dependency-file"); - let (output, deps) = self::parse_deps_file(dep_file)?; + let (output, deps) = self::parse_deps_file(dep_file).await?; match rule.output.is_empty() { // If there were no outputs, make sure it matches the rule name @@ -508,7 +510,7 @@ impl Builder { }) .await; - Ok::<_, AppError>((res, dep_guard)) + Ok((res, dep_guard)) } }) .collect::>() @@ -590,7 +592,7 @@ impl BuildLock { /// Retrieves the result of this state. /// - /// Waits for any builders too finish + /// Waits for any builders to finish pub async fn res(&self) -> Option> { self.state .read() @@ -670,9 +672,9 @@ impl BuildResult { /// Parses a dependencies file // TODO: Support multiple dependencies in each file -fn parse_deps_file(file: &str) -> Result<(String, Vec), AppError> { +async fn parse_deps_file(file: &str) -> Result<(String, Vec), AppError> { // Read it - let contents = fs::read_to_string(file).map_err(AppError::read_file(file))?; + let contents = fs::read_to_string(file).await.map_err(AppError::read_file(file))?; // Parse it let (output, deps) = contents.split_once(':').ok_or_else(|| AppError::DepFileMissingColon { @@ -688,26 +690,32 @@ fn parse_deps_file(file: &str) -> Result<(String, Vec), AppError> { /// /// Returns `Err` if any files didn't exist, /// Returns `Ok(None)` if rule has no outputs -fn rule_last_build_time(rule: &Rule) -> Result, AppError> { +async fn rule_last_build_time(rule: &Rule) -> Result, AppError> { // Note: We get the time of the oldest file in order to ensure all // files are at-least that old - rule.output + let built_time = rule + .output .iter() - .map(|item| { + .map(async move |item| { let file = match item { OutItem::File { file } => file, OutItem::DepsFile { file } => file, }; fs::metadata(file) + .await .map(self::file_modified_time) .map_err(AppError::read_file_metadata(file)) }) - .reduce(|lhs, rhs| Ok(lhs?.min(rhs?))) - .transpose() + .collect::>() + .try_collect::>() + .await? + .into_iter() + .min(); + Ok(built_time) } /// Returns the file modified time -fn file_modified_time(metadata: fs::Metadata) -> SystemTime { +fn file_modified_time(metadata: std::fs::Metadata) -> SystemTime { let file_time = FileTime::from_last_modification_time(&metadata); let unix_offset = Duration::new(file_time.unix_seconds() as u64, file_time.nanoseconds()); @@ -742,3 +750,12 @@ pub fn find_rule_for_file(file: &str, rules: &Rules) -> Result) -> Result { + match fs::metadata(path).await { + Ok(_) => Ok(true), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(err) => Err(err), + } +} diff --git a/src/error.rs b/src/error.rs index 2763b63..ee7fe54 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,7 @@ pub enum AppError { /// Other // TODO: Removes usages of this, it's for quick prototyping #[error(transparent)] - Other(#[from] anyhow::Error), + Other(anyhow::Error), /// Get current directory #[error("Unable to get current directory")] diff --git a/src/watcher.rs b/src/watcher.rs index 85a62dd..ae43a42 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -56,7 +56,8 @@ impl Watcher { tracing::warn!("Error while watching: {:?}", anyhow::Error::from(err)) }, }) - .context("Unable to create file watcher")?; + .context("Unable to create file watcher") + .map_err(AppError::Other)?; // Then create the task to register all dependencies // TODO: Not do this? @@ -121,7 +122,8 @@ impl Watcher { self.watcher .watcher() .watch(path, notify::RecursiveMode::NonRecursive) - .with_context(|| format!("Unable to watch path {path:?}"))?; + .with_context(|| format!("Unable to watch path {path:?}")) + .map_err(AppError::Other)?; } let rev_deps = &self.rev_deps;