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<String>,
pub commands: Vec<CommandLine>,
pub stem: Option<String>,
pub already_updated: Cell<bool>,
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) => {
*self = other;
}
(true, false) => {
let mut other = other;
mem::swap(self, &mut other);
self.extend(other);
}
(false, true) | (true, true) => {
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<SystemTime> {
metadata(&self.name)
.and_then(|metadata| metadata.modified())
.ok()
}
pub fn newer_than(&self, other: &Self) -> Option<bool> {
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,
(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(())
}
}
#[derive(Clone, Default)]
pub struct StaticTargetSet {
data: HashMap<String, Target>,
}
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);
}
}
pub fn has(&self, name: &str) -> bool {
self.data.contains_key(name)
}
}
impl From<StaticTargetSet> for HashMap<String, Target> {
fn from(value: StaticTargetSet) -> Self {
value.data
}
}
#[derive(Clone, Default)]
pub struct DynamicTargetSet {
data: RefCell<HashMap<String, Rc<RefCell<Target>>>>,
}
impl DynamicTargetSet {
pub fn get(&self, name: &str) -> Option<Rc<RefCell<Target>>> {
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(())
}
}