use std::collections::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use crate::{ context::*, paths, resolve, runner, task::*, types::*, util::{cjson, error::*, fs}, }; // TODO: Generate dynamically from paths module const TASK_ENV: &[(&str, &str)] = &[ ("PATH", "/usr/sbin:/usr/bin:/sbin:/bin"), ("HOME", "/build"), ("DESTDIR", "/build/dest"), ("DLDIR", "/build/downloads"), ("SYSROOT", "/build/sysroot"), ("WORKDIR", "/build/work"), ("BUILD_PREFIX", "/opt/toolchain"), ("PREFIX", "/usr"), ]; #[derive(Clone, Debug, Serialize)] struct TaskInput<'ctx> { pub id: &'ctx TaskRef<'ctx>, pub inherit: Option<&'ctx InputHash>, pub depends: &'ctx HashMap, pub output: &'ctx HashMap, pub run: &'ctx str, pub env: &'ctx HashMap, } impl<'ctx> TaskInput<'ctx> { fn input_hash(&self) -> InputHash { InputHash(StringHash( cjson::digest::(self).unwrap().into(), )) } } #[derive(Clone, Debug, Deserialize, Serialize)] struct TaskMeta { pub id: TaskID, pub inherit: Vec, pub depends: HashMap, pub input_hash: InputHash, pub output: HashMap, } impl TaskMeta { fn load(input_hash: &InputHash) -> Result { let filename = paths::task_meta_filename(input_hash); let file = fs::open(&filename)?; serde_json::from_reader(file) .with_context(|| format!("Failed to read task metadata from {}", filename)) } fn save(&self) -> Result<()> { let tmp_filename = paths::task_meta_tmp_filename(&self.input_hash); let filename = paths::task_meta_filename(&self.input_hash); cjson::to_file(&tmp_filename, self) .with_context(|| format!("Failed to write task metadata to {}", tmp_filename))?; fs::rename(tmp_filename, filename)?; Ok(()) } } #[derive(Debug)] pub struct Executor<'ctx> { ctx: &'ctx Context, tasks_blocked: HashSet>, tasks_runnable: Vec>, tasks_done: HashMap, TaskMeta>, rdeps: HashMap, Vec>>, env: HashMap, } impl<'ctx> Executor<'ctx> { pub fn new(ctx: &'ctx Context, taskset: HashSet>) -> Result { let env: HashMap = TASK_ENV .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); let mut exc = Executor { ctx, tasks_blocked: HashSet::new(), tasks_runnable: Vec::new(), tasks_done: HashMap::new(), rdeps: HashMap::new(), env, }; for task in taskset { let mut has_depends = false; for dep in ctx.get_dependent_tasks(&task) { let rdep = exc.rdeps.entry(dep.clone()).or_default(); rdep.push(task.clone()); has_depends = true; } if has_depends { exc.tasks_blocked.insert(task); } else { exc.tasks_runnable.push(task); } } fs::mkdir(paths::TASK_STATE_DIR)?; Ok(exc) } // Treats both "depends" and "inherit" as dependencies fn deps_satisfied(&self, task_ref: &TaskRef) -> bool { self.ctx .get_dependent_tasks(task_ref) .into_iter() .all(|dep| self.tasks_done.contains_key(&dep)) } fn task_deps(&self, task: &TaskRef) -> HashMap { let task_def = &self.ctx[task.id]; task_def .fetch .iter() .map(|Fetch { name, sha256 }| Dependency::Fetch { name: name.clone(), sha256: *sha256, }) .chain( resolve::runtime_depends(self.ctx, self.ctx.get_build_depends(task)) .expect("invalid runtime depends of build_depends") .iter() .filter_map(|dep| self.tasks_done[&dep.task].output.get(dep.output)) .map(|&output| Dependency::BuildTask { output }), ) .chain( resolve::runtime_depends(self.ctx, self.ctx.get_target_depends(task)) .expect("invalid runtime depends of target_depends") .iter() .filter_map(|dep| self.tasks_done[&dep.task].output.get(dep.output)) .map(|&output| Dependency::TargetTask { output }), ) .map(|dep| (dep.dependency_hash(), dep)) .collect() } fn run_one(&self, task_ref: &TaskRef, runner: &impl runner::Runner) -> Result { let task_def = &self.ctx[&task_ref.id]; let task_deps = self.task_deps(&task_ref); let task_output = task_def .output .iter() .map(|(name, Output { path, .. })| { ( name.clone(), path.as_ref().map(String::as_str).unwrap_or(".").to_string(), ) }) .collect(); let env = self.env.clone(); let (inherit_chain, inherit_hash) = if let Some(inherit_dep) = &task_def.inherit { let inherit_meta = &self.tasks_done[&self.ctx.inherit_ref(inherit_dep)]; let mut inherit_chain = inherit_meta.inherit.clone(); inherit_chain.push(inherit_meta.input_hash); (inherit_chain, Some(&inherit_meta.input_hash)) } else { (Vec::new(), None) }; let input_hash = TaskInput { id: &task_ref, inherit: inherit_hash, depends: &task_deps, output: &task_output, run: &task_def.action.run, env: &env, } .input_hash(); // Use cached result if let Ok(meta) = TaskMeta::load(&input_hash) { return Ok(meta); } // Remove metadata first to ensure task invalidation fs::ensure_removed(&paths::task_meta_filename(&input_hash))?; let state_dir = paths::task_state_dir(&input_hash); fs::ensure_removed(&state_dir)?; fs::mkdir(&state_dir)?; let task = runner::Task { id: task_ref.id.clone(), run: task_def.action.run.clone(), input_hash, inherit: inherit_chain.clone(), depends: task_deps.clone(), output: task_output, env, }; let output = runner.run(&task)?; let meta = TaskMeta { id: task_ref.id.clone(), inherit: inherit_chain, depends: task_deps, input_hash, output, }; meta.save()?; Ok(meta) } fn update_runnable(&mut self, task_ref: &TaskRef) { let rdeps = self.rdeps.get(task_ref); 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()); } } } pub fn run(&mut self, runner: &impl runner::Runner) -> Result<()> { while let Some(task_ref) = self.tasks_runnable.pop() { let meta = self.run_one(&task_ref, runner)?; self.tasks_done.insert(task_ref.clone(), meta); self.update_runnable(&task_ref); } assert!(self.tasks_blocked.is_empty(), "No runnable tasks left"); println!("Summary"); for task in self.tasks_done.values() { println!(""); println!("{} ( {} )", task.id, task.input_hash); for (output, hash) in &task.output { println!("-> {} ( {} )", output, hash); } } Ok(()) } }