use std::{collections::HashMap, ffi::OsStr, fs::File, path::Path, result}; use scoped_tls_hkt::scoped_thread_local; use serde::{Deserialize, Deserializer}; use walkdir::WalkDir; use common::{error::*, types::*}; use crate::task::{RecipeMeta, TaskDef, TaskMeta}; scoped_thread_local!(static CURRENT_RECIPE: str); fn current_recipe() -> String { CURRENT_RECIPE.with(|current| current.to_string()) } pub fn deserialize_task_id<'de, D>(deserializer: D) -> result::Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct RecipeTaskID { recipe: Option, task: String, } let RecipeTaskID { recipe, task } = RecipeTaskID::deserialize(deserializer)?; Ok(TaskID { recipe: recipe.unwrap_or_else(current_recipe), task, }) } #[derive(Debug, Deserialize)] struct Recipe { #[serde(default)] pub meta: RecipeMeta, pub tasks: HashMap, } fn read_recipe(path: &Path) -> Result { let f = File::open(path).context("IO error")?; let recipe: Recipe = serde_yaml::from_reader(f) .map_err(Error::new) .context("YAML error")?; Ok(recipe) } const RECIPE_NAME: &str = "build"; const RECIPE_PREFIX: &str = "build."; fn recipe_name(path: &Path) -> Option<&str> { if path.extension() != Some("yml".as_ref()) { return None; } let stem = path.file_stem()?.to_str()?; if stem == RECIPE_NAME { return Some(""); } stem.strip_prefix(RECIPE_PREFIX) } pub fn read_recipes>(path: P) -> Result>> { let mut tasks = HashMap::>::new(); for entry in WalkDir::new(path) .sort_by_file_name() .into_iter() .filter_map(|e| e.ok()) { let path = entry.path(); if !path.is_file() { continue; } let Some(recipename) = recipe_name(path) else { continue; }; let Some(basename) = path .parent() .and_then(Path::file_name) .and_then(OsStr::to_str) else { continue; }; let recipename_full = if recipename.is_empty() { basename.to_string() } else { format!("{basename}/{recipename}") }; let recipe = CURRENT_RECIPE.set(&recipename_full, || read_recipe(path))?; for (label, mut task) in recipe.tasks { let task_id = TaskID { recipe: recipename_full.clone(), task: label, }; task.meta = TaskMeta { basename: basename.to_string(), recipename: recipename.to_string(), recipe: recipename_full.clone(), version: recipe.meta.version.clone(), }; tasks.entry(task_id).or_default().push(task); } } Ok(tasks) }