use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::fmt; use std::fs::metadata; use std::mem; use std::rc::Rc; use std::time::SystemTime; use eyre::{Result, WrapErr}; use super::{CommandLine, MacroSet, 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, pub macros: MacroSet, } impl Target { pub fn extend(&mut self, other: Self) { assert_eq!(&self.name, &other.name); match (self.commands.is_empty(), other.commands.is_empty()) { (false, false) => { // both targets have commands, so replace this entirely *self = other; } (true, false) => { // this target doesn't have commands, but the other one does, // so it's the real one let mut other = other; mem::swap(self, &mut other); self.extend(other); } (false, true) | (true, true) => { // this target might have commands, but the other one doesn't, // so append non-command stuff self.prerequisites.extend(other.prerequisites); self.stem = self.stem.take().or(other.stem); let already_updated = self.already_updated.get() || other.already_updated.get(); self.already_updated.set(already_updated); self.macros.extend(other.macros); } } } 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, }) } pub 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!( "{:} exists: {}, newer than dependencies: {}", self.name, exists, newer_than_all_dependencies ); exists && newer_than_all_dependencies } pub fn update(&self, file: &Makefile) -> Result<()> { log::info!("{}: {:?}", &self.name, &self.prerequisites); 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(()) } } // for targets that won't change out from under us (don't need refcelling) #[derive(Clone, Default)] pub struct StaticTargetSet { data: HashMap, } impl StaticTargetSet { pub fn get(&self, name: &str) -> Option<&Target> { self.data.get(name) } pub fn put(&mut self, target: Target) { if target.name == ".SUFFIXES" && target.prerequisites.is_empty() { self.data.remove(&target.name); } if let Some(existing_target) = self.data.get_mut(&target.name) { existing_target.extend(target); } else { self.data.insert(target.name.clone(), target); } } } impl From for HashMap { fn from(value: StaticTargetSet) -> Self { value.data } } // for targets that might become updated and so need refcelling #[derive(Clone, Default)] pub struct DynamicTargetSet { data: RefCell>>>, } impl DynamicTargetSet { pub fn get(&self, name: &str) -> Option>> { self.data.borrow().get(name).map(Rc::clone) } pub fn put(&self, target: Target) { if let Some(existing_target) = self.data.borrow().get(&target.name) { existing_target.borrow_mut().extend(target); return; } self.data .borrow_mut() .insert(target.name.clone(), Rc::new(RefCell::new(target))); } pub fn has(&self, name: &str) -> bool { self.data.borrow().contains_key(name) } } impl fmt::Display for DynamicTargetSet { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for target in self.data.borrow().values() { writeln!(f, "{}", target.borrow())?; } Ok(()) } }