use std::{ collections::{HashMap, HashSet}, fs, io, path::{Path, PathBuf}, }; use crate::{runner, types::*, util::cjson}; fn tmp_filename(hash: &InputHash) -> PathBuf { Path::new("build/state/tmp/task").join(format!("{}.json", hash)) } fn task_filename(hash: &InputHash) -> PathBuf { Path::new("build/state/task").join(format!("{}.json", hash)) } fn save_output(output: &TaskOutput) -> io::Result<()> { let filename = tmp_filename(&output.input_hash); cjson::to_file(&filename, output)?; fs::rename(filename, task_filename(&output.input_hash)) } const TASK_ENV: &[(&str, &str)] = &[ ("PATH", "/usr/sbin:/usr/bin:/sbin:/bin"), ("HOME", "/home/build"), ("WORKDIR", "/home/build"), ("DESTDIR", "/home/build/dest"), ("PREFIX", "/opt/sysroot"), ]; #[derive(Debug)] pub struct Executor<'a> { tasks: &'a TaskMap, tasks_blocked: HashSet, tasks_runnable: Vec, tasks_done: HashMap, rdeps: HashMap>, env: HashMap, } impl<'a> Executor<'a> { pub fn new(tasks: &'a TaskMap, taskset: HashSet) -> io::Result { let env: HashMap = TASK_ENV .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); let mut exc = Executor { tasks, tasks_blocked: HashSet::new(), tasks_runnable: Vec::new(), tasks_done: HashMap::new(), rdeps: HashMap::new(), env, }; for task in taskset { let task_def = tasks.get(&task).expect("Invalid TaskID"); if task_def.depends.is_empty() { exc.tasks_runnable.push(task); } else { for dep in &task_def.depends { let rdep = exc.rdeps.entry(dep.clone()).or_default(); rdep.push(task.clone()); } exc.tasks_blocked.insert(task); } } let mut dir = fs::DirBuilder::new(); dir.recursive(true); dir.create("build/state/task")?; dir.create("build/state/tmp/task")?; Ok(exc) } fn deps_satisfied(&self, task: &TaskID) -> bool { let task_def = self.tasks.get(task).expect("Invalid TaskID"); task_def .depends .iter() .all(|dep| self.tasks_done.contains_key(dep)) } fn task_deps(&self, task: &TaskDef) -> HashMap { task.depends .iter() .map(|dep| { ( dep.clone(), self.tasks_done .get(dep) .expect("Invalid dependency") .output_hash, ) }) .collect() } fn run_one(&mut self, runner: &impl runner::Runner) -> runner::Result<()> { let task_id = self.tasks_runnable.pop().expect("No runnable tasks left"); let task_def = self.tasks.get(&task_id).expect("Invalid TaskID"); let task_deps = self.task_deps(&task_def); let task = Task { id: task_id, def: task_def.clone(), depends: task_deps, env: self.env.clone(), }; let input_hash = task.input_hash(); let output_hash = runner.run(&task)?; let output = TaskOutput { id: task.id.clone(), depends: task.depends, input_hash, output_hash, env: task.env, }; save_output(&output)?; let rdeps = self.rdeps.get(&task.id); self.tasks_done.insert(task.id, output); for rdep in rdeps.unwrap_or(&Vec::new()) { if !self.tasks_blocked.contains(rdep) { continue; } if self.deps_satisfied(rdep) { self.tasks_blocked.remove(rdep); self.tasks_runnable.push(rdep.clone()); } } Ok(()) } pub fn run(&mut self, runner: &impl runner::Runner) -> runner::Result<()> { while !self.tasks_runnable.is_empty() { self.run_one(runner)?; } assert!(self.tasks_blocked.is_empty(), "No runnable tasks left"); println!("{}", serde_json::to_string_pretty(&self.tasks_done)?); Ok(()) } }