use std::cell::RefCell;
use std::rc::Rc;
type RecipeFn = dyn Fn(Target);
type PruneFn = dyn Fn(Target) -> bool;
struct TargetInner {
name: String,
dependencies: Vec<Target>,
is_file: bool,
prune_fn: Option<Box<PruneFn>>,
recipe_fn: Option<Box<RecipeFn>>,
}
#[derive(Clone)]
pub struct Target {
inner: Rc<RefCell<TargetInner>>,
}
fn modify_time(file: &str) -> Option<std::time::SystemTime> {
std::path::Path::new(file).metadata().ok()?.modified().ok()
}
impl Target {
pub fn name(&self) -> String {
self.inner.borrow().name.clone()
}
pub fn deps(&self) -> Vec<Target> {
self.inner.borrow().dependencies.clone()
}
pub fn new_phony(name: &str) -> Self {
Target {
inner: Rc::new(RefCell::new(TargetInner {
name: name.to_string(),
prune_fn: None,
is_file: false,
dependencies: Vec::new(),
recipe_fn: None,
})),
}
}
pub fn new_file(name: &str) -> Self {
Target {
inner: Rc::new(RefCell::new(TargetInner {
name: name.to_string(),
prune_fn: None,
is_file: true,
dependencies: Vec::new(),
recipe_fn: None,
})),
}
}
pub fn depends_on_target(self, target: Target) -> Self {
self.inner.borrow_mut().dependencies.push(target);
self
}
pub fn depends_on_file(self, file: &str) -> Self {
let file_target = Target::new_file(file);
self.depends_on_target(file_target)
}
pub fn prune<F>(self, reached: F) -> Self
where
F: Fn(Target) -> bool + 'static,
{
self.inner.borrow_mut().prune_fn = Some(Box::new(reached) as Box<PruneFn>);
self
}
pub fn recipe<F>(self, recipe: F) -> Self
where
F: Fn(Target) + 'static,
{
self.inner.borrow_mut().recipe_fn = Some(Box::new(recipe) as Box<RecipeFn>);
self
}
pub fn make(&self) {
if let Some(ref reached) = self.inner.borrow().prune_fn
&& reached(self.clone())
{
return;
}
for dep in self.deps() {
dep.make();
}
if self.inner.borrow().is_file
&& let Some(modify) = modify_time(&self.name())
&& self.deps().iter().all(|dep| {
dep.inner.borrow().is_file && {
let dep_modify = modify_time(&dep.name());
dep_modify.is_some() && dep_modify.unwrap() < modify
}
})
{
return;
}
if let Some(ref recipe) = self.inner.borrow().recipe_fn {
recipe(self.clone());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
fs::File,
io::{Read, Write},
};
#[test]
fn test_make_files() {
let dir = std::env::args().nth(0).unwrap();
let dir = std::path::Path::new(dir.as_str()).parent().unwrap();
let src1 = dir.join("src1").to_str().unwrap().to_owned();
let src2 = dir.join("src2").to_str().unwrap().to_owned();
let src3 = dir.join("src3").to_str().unwrap().to_owned();
let dst = dir.join("dst").to_str().unwrap().to_owned();
let mut fopt = File::options();
fopt.write(true).create(true);
let mut s1 = fopt.open(&src1).unwrap();
s1.write(b"src1").unwrap();
drop(s1);
let mut s2 = fopt.open(&src2).unwrap();
s2.write(b"src2").unwrap();
drop(s2);
let fopt1 = fopt.clone();
let fopt2 = fopt.clone();
let t3 = Target::new_file(&src3)
.depends_on_file(&src2)
.recipe(move |t| {
let mut s3 = fopt1.open(t.name()).unwrap();
let src2 = t.deps().remove(0);
let mut s2 = File::open(src2.name()).unwrap();
let mut buf = Vec::new();
s2.read_to_end(&mut buf).unwrap();
let buf = buf.repeat(3);
s3.write_all(&buf).unwrap();
});
let d = Target::new_file(&dst)
.depends_on_file(&src1)
.depends_on_target(t3)
.recipe(move |t| {
let mut d = fopt2.open(t.name()).unwrap();
let mut buf = Vec::new();
for dep in t.deps() {
let mut s = File::open(dep.name()).unwrap();
let mut buf1 = Vec::new();
s.read_to_end(&mut buf1).unwrap();
buf.append(&mut buf1);
}
d.write_all(&buf).unwrap();
});
d.make();
let mut buf = Vec::new();
let mut f = File::open(&dst).unwrap();
f.read_to_end(&mut buf).unwrap();
assert_eq!(buf, b"src1src2src2src2");
}
}