use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::sync::LazyLock; use pkg::{Package, PackageError, PackageName}; // This file contains code that caches recipe paths. // TODO: This file is previously resides in `pkg` crate, // and can actually be merged with other logic in this cookbook. static RECIPE_PATHS: LazyLock> = LazyLock::new(|| { let mut recipe_paths = HashMap::new(); let mut walker = ignore::WalkBuilder::new("recipes"); walker.follow_links(true); for entry_res in walker.build() { let Ok(entry) = entry_res else { continue; }; if entry.file_name() == OsStr::new("recipe.toml") { let recipe_file = entry.path(); let Some(recipe_dir) = recipe_file.parent() else { continue; }; let Some(recipe_name) = recipe_dir .file_name() .and_then(|x| x.to_str()?.try_into().ok()) else { continue; }; if let Some(other_dir) = recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()) { eprintln!( "recipe {:?} has two or more entries: {:?} replaced by {:?}", recipe_dir.file_name(), other_dir, recipe_dir, ); } } } recipe_paths }); pub fn find(recipe: &str) -> Option<&'static Path> { RECIPE_PATHS.get(recipe).map(PathBuf::as_path) } pub fn list(prefix: impl AsRef) -> BTreeSet { let prefix = prefix.as_ref(); RECIPE_PATHS .values() .map(|path| prefix.join(path)) .collect() } pub fn new(name: &PackageName) -> Result { let dir = find(name.name()).ok_or_else(|| PackageError::PackageNotFound(name.clone()))?; from_path(dir, name.suffix()) } pub fn from_path(dir: &Path, feature: Option<&str>) -> Result { let target = redoxer::target(); let stage_name = match feature { Some(f) => Cow::Owned(format!("stage.{f}.toml")), None => Cow::Borrowed("stage.toml"), }; let file = dir.join("target").join(target).join(stage_name.as_ref()); if !file.is_file() { return Err(PackageError::FileMissing(file)); } let toml = std::fs::read_to_string(&file) .map_err(|err| PackageError::FileError(err.raw_os_error(), file.clone()))?; toml::from_str(&toml).map_err(|err| PackageError::Parse(err, Some(file))) } pub fn new_recursive( names: &[PackageName], nonstop: bool, recursion: usize, ) -> Result, PackageError> { if names.len() == 0 { return Ok(vec![]); } let (list, map) = new_recursive_nonstop(names, recursion); if nonstop && list.len() > 0 { Ok(list) } else if !nonstop && map.len() == list.len() { Ok(list) } else { let (_, res) = map.into_iter().find(|(_, v)| v.is_err()).unwrap(); Err(res.err().unwrap()) } } /// List ordered success packages and map of failed packages. /// A package can be both success and failed if dependencies aren't satistied. pub fn new_recursive_nonstop( names: &[PackageName], recursion: usize, ) -> ( Vec, BTreeMap>, ) { let mut packages = Vec::new(); let mut packages_map = BTreeMap::new(); for name in names { if packages_map.contains_key(name) { continue; } let package = if recursion == 0 { Err(PackageError::Recursion(Default::default())) } else { new(name) }; match package { Ok(package) => { let mut has_invalid_dependency = false; let (dependencies, dependencies_map) = new_recursive_nonstop(&package.depends, recursion - 1); for dependency in dependencies { if !packages_map.contains_key(&dependency.name) { packages_map.insert(dependency.name.clone(), Ok(())); packages.push(dependency); } } for (dep_name, result) in dependencies_map { if let Err(mut e) = result { if !packages_map.contains_key(&dep_name) { e.append_recursion(name); packages_map.insert(dep_name, Err(e)); } has_invalid_dependency = true; } } // TODO: this check is redundant if !packages_map.contains_key(name) { packages_map.insert( name.clone(), if has_invalid_dependency { Err(PackageError::DependencyInvalid(name.clone())) } else { Ok(()) }, ); packages.push(package); } } Err(e) => { packages_map.insert(name.clone(), Err(e)); } } } (packages, packages_map) }