use std::cell::Cell; use std::fmt; use std::fs::metadata; use std::time::SystemTime; use eyre::{Result, WrapErr}; use crate::makefile::command_line::CommandLine; use super::Makefile; #[derive(PartialEq, Eq, Clone, Debug)] pub struct Target { pub name: String, pub prerequisites: Vec, pub commands: Vec, pub stem: Option, pub already_updated: Cell, } impl Target { fn modified_time(&self) -> Option { metadata(&self.name) .and_then(|metadata| metadata.modified()) .ok() } pub fn newer_than(&self, other: &Self) -> Option { let self_updated = self.already_updated.get(); let other_updated = other.already_updated.get(); Some(match (self.modified_time(), other.modified_time()) { (Some(self_mtime), Some(other_mtime)) => self_mtime >= other_mtime, // per POSIX: "If the target does not exist after the target has been // successfully made up-to-date, the target shall be treated as being // newer than any target for which it is a prerequisite." (None, _) if self_updated && other.prerequisites.contains(&self.name) => true, (_, None) if other_updated && self.prerequisites.contains(&other.name) => false, _ => return None, }) } fn is_up_to_date(&self, file: &Makefile) -> bool { if self.already_updated.get() { return true; } #[cfg(feature = "full")] if file.special_target_has_prereq(".PHONY", &self.name) { return false; } let exists = metadata(&self.name).is_ok(); let newer_than_all_dependencies = self.prerequisites.iter().all(|t| { file.get_target(t) .ok() .and_then(|t| self.newer_than(&t.borrow())) .unwrap_or(false) }); log::trace!( "{:>50} exists: {}, newer than dependencies: {}", self.name, exists, newer_than_all_dependencies ); exists && newer_than_all_dependencies } pub fn update(&self, file: &Makefile) -> Result<()> { for prereq in &self.prerequisites { file.update_target(prereq) .wrap_err_with(|| format!("as a dependency for target {}", self.name))?; } if !self.is_up_to_date(file) { log::debug!("rebuilding {}...", self.name); if self.commands.is_empty() { log::warn!("no commands found to rebuild {}", self.name); } self.execute_commands(file) .wrap_err_with(|| format!("while updating target {}", self.name))?; } self.already_updated.set(true); Ok(()) } fn execute_commands(&self, file: &Makefile) -> Result<()> { for command in &self.commands { log::trace!(" executing {}", command); command.execute(file, self)?; } Ok(()) } } impl fmt::Display for Target { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let prereqs = self.prerequisites.join(" "); writeln!(f, "{}: {}", &self.name, prereqs)?; for command in &self.commands { writeln!(f, "\t{}", command)?; } Ok(()) } }