diff --git a/src/bin/repo/main.rs b/src/bin/repo/main.rs index 2b34847de..8d9a07a48 100644 --- a/src/bin/repo/main.rs +++ b/src/bin/repo/main.rs @@ -11,7 +11,7 @@ use cookbook::cook::tree::{self, DisplayOptions, TreeData, TreeItem, TreeOptions use cookbook::cook::tui::join_logs; use cookbook::cook::{fetch_repo, ident}; use cookbook::recipe::{ - CookRecipe, SourceRecipe, recipes_flatten_package_names, recipes_mark_as_deps, + CookRecipe, RecipeRule, SourceRecipe, recipes_flatten_package_names, recipes_mark_as_deps, validate_filesystem_rules, }; use cookbook::{Error, Result, staged_pkg}; @@ -625,15 +625,22 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec = Vec::new(); let mut binary_names: Vec = Vec::new(); - let mut special_rules: HashMap = HashMap::new(); - let default_rule = if repo_binary { "binary" } else { "source" }; + let mut special_rules: HashMap = HashMap::new(); + let default_rule = RecipeRule::from_repo_binary(repo_binary); for (recipe_name, recipe_lock) in lock.iter().filter(|(_, v)| v.fsrule.is_some()) { let Ok(recipe_name) = PackageName::new(recipe_name) else { continue; }; - let rule = recipe_lock.fsrule.as_ref().unwrap(); - special_rules.insert(recipe_name.clone(), rule.to_string()); + let rule = recipe_lock.fsrule.as_deref().unwrap(); + let Ok(rule) = RecipeRule::try_from(rule) else { + bail_other_err!( + "Invalid pkg config {} = {:?}\nExpecting either 'source', 'local', 'binary' or 'ignore'", + recipe_name.as_str(), + rule + ); + }; + special_rules.insert(recipe_name.clone(), rule); // lock rules does not recurse as it's done already in the file } if let Some(conf) = config.filesystem.as_ref() { @@ -643,79 +650,96 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec = source_names.into_iter().collect(); let binary_names: HashSet = binary_names.into_iter().collect(); + let recipe_names_with_pkg = if command.is_building() || command.is_pushing() { + // include all package deps (TODO: ignored recipes should be exempted) + CookRecipe::get_package_deps_recursive(&recipe_names, true)? + } else { + recipe_names.clone() + }; + // These are list that derived from recipe_names - let mut source_recipe_names: Vec = Vec::new(); - let mut binary_recipe_names: Vec = Vec::new(); - let mut ignore_recipe_names: Vec = Vec::new(); - for recipe_name in recipe_names.iter() { - if special_rules - .get(recipe_name) - .is_some_and(|s| s == "ignore") - { - ignore_recipe_names.push(recipe_name.clone()); - } else if source_names.contains(recipe_name) { - source_recipe_names.push(recipe_name.clone()); - } else if binary_names.contains(recipe_name) { - binary_recipe_names.push(recipe_name.clone()); + let mut recipe_name_rules = BTreeMap::new(); + for recipe_name in recipe_names_with_pkg { + let new_rule = if let Some(&rule) = special_rules.get(&recipe_name) { + rule + } else if source_names.contains(&recipe_name) { + RecipeRule::Source + } else if binary_names.contains(&recipe_name) { + RecipeRule::Binary } else if repo_binary { - binary_recipe_names.push(recipe_name.clone()); + RecipeRule::Binary } else { - source_recipe_names.push(recipe_name.clone()); - } + RecipeRule::Source + }; + recipe_name_rules.insert(recipe_name, new_rule); } - if command.is_building() || command.is_pushing() { - source_recipe_names = - CookRecipe::get_package_deps_recursive(&source_recipe_names, true)?; - binary_recipe_names = - CookRecipe::get_package_deps_recursive(&binary_recipe_names, true)?; + // apply rules on root recipe names + apply_special_rules_by_names( + &special_rules, + default_rule, + &source_names, + &binary_names, + &mut recipe_name_rules, + &config, + ); + + macro_rules! filter_recipes { + ($rules:expr, $pat:pat) => { + $rules + .iter() + .filter_map(|(k, v)| { + if matches!(v, $pat) { + Some(k.clone()) + } else { + None + } + }) + .collect::>() + }; } + // split map into lists of separate recipes names to apply different build deps logic + let source_recipe_names = + filter_recipes!(recipe_name_rules, RecipeRule::Source | RecipeRule::Local); + let binary_recipe_names = filter_recipes!(recipe_name_rules, RecipeRule::Binary); + let ignore_recipe_names = filter_recipes!(recipe_name_rules, RecipeRule::Ignore); + let mut recipes = if matches!(config.all, Some(AllOption::AllBinaries)) { // Removes all source-compiled recipes Vec::new() } else if command.is_building() || command.is_pushing() { // Pushing do not need dev deps, so does binary recipes at building let include_dev = command.is_building(); - if include_dev && default_rule == "source" { - // try to lookup if this recipe eventually changed to "binary" so that dev recipes - // don't really get included. TODO: move this logic down after special rules applied. - let mut i = 0; - let (_, repository) = fetch_repo::get_binary_repo(); - while i < source_recipe_names.len() { - let name = &source_recipe_names[i]; - if (binary_names.contains(name) - || special_rules - .get(name) - .is_some_and(|s| s.as_str() == "binary")) - && repository.packages.contains_key(name.as_str()) - { - binary_recipe_names.push(source_recipe_names.remove(i)); - continue; - } - i += 1; - } - } CookRecipe::get_build_deps_recursive(&source_recipe_names, include_dev)? } else { CookRecipe::from_list(source_recipe_names.clone())? @@ -733,36 +757,15 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec { - // both lists: flip logic - if repo_binary { "source" } else { "binary" } - } - (true, false) => "source", - (false, true) => "binary", - (false, false) => default_rule, - }; - if recipe.name.is_host() && rule == "binary" { - // host recipe binaries is currently not supported - continue; - } - - recipe.apply_filesystem_config(rule)?; - } + // reapply rules again for build/package deps + apply_special_rules( + &special_rules, + default_rule, + &source_names, + &binary_names, + &mut recipes, + &config, + )?; validate_filesystem_rules(&recipes)?; @@ -789,11 +792,62 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec, + default_rule: RecipeRule, + source_names: &HashSet, + binary_names: &HashSet, + recipes: &mut Vec, + config: &CliConfig, +) -> Result<()> { + for recipe in recipes.iter_mut() { + if !recipe.rule.is_empty() { + continue; + } + if let Some(&special_rule) = special_rules.get(recipe.canon_recipe_name().without_prefix()) + { + if recipe.name.is_host() && special_rule == RecipeRule::Binary { + // host recipe binaries is currently not supported + continue; + } + recipe.apply_filesystem_config(special_rule.into())?; + continue; + } + let rule = match ( + source_names.contains(&recipe.name), + binary_names.contains(&recipe.name), + ) { + (true, true) => { + // both lists: flip logic + default_rule.inverted_default() + } + (true, false) => RecipeRule::Source, + (false, true) => RecipeRule::Binary, + (false, false) => default_rule, + }; + if recipe.name.is_host() && rule == RecipeRule::Binary { + // host recipe binaries is currently not supported + continue; + } + + recipe.apply_filesystem_config(rule.into())?; + } + + // check if binary repositories are valid + if recipes.iter().any(|r| r.rule == "binary") { let (_, repository) = fetch_repo::get_binary_repo(); for recipe in recipes.iter_mut() { if recipe.rule == "binary" && !repository.packages.contains_key(recipe.name.as_str()) { - if config.cook.verbose && !(config.cook.tui && command == CliCommand::Cook) { + if config.cook.verbose { // TODO: this should be printed at fetch log, not here println!( "DEBUG: Recipe {:?} has no binary package", @@ -806,12 +860,56 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec, + default_rule: RecipeRule, + source_names: &HashSet, + binary_names: &HashSet, + recipes: &mut BTreeMap, + config: &CliConfig, +) { + for (recipe, rule) in recipes.iter_mut() { + if let Some(&special_rule) = special_rules.get(recipe.without_prefix()) { + if recipe.is_host() && special_rule == RecipeRule::Binary { + // host recipe binaries is currently not supported + continue; + } + *rule = special_rule; + continue; + } + let new_rule = match (source_names.contains(recipe), binary_names.contains(recipe)) { + (true, true) => { + // both lists: flip logic + default_rule.inverted_default() + } + (true, false) => RecipeRule::Source, + (false, true) => RecipeRule::Binary, + (false, false) => default_rule, + }; + if recipe.is_host() && new_rule == RecipeRule::Binary { + // host recipe binaries is currently not supported + continue; + } + + *rule = new_rule; } - Ok((config, command, recipes)) + // check if binary rules are valid + if recipes.values().any(|&r| r == RecipeRule::Binary) { + let (_, repository) = fetch_repo::get_binary_repo(); + for (name, rule) in recipes.iter_mut() { + if *rule == RecipeRule::Binary && !repository.packages.contains_key(name.as_str()) { + if config.cook.verbose { + // TODO: this should be printed at fetch log, not here + println!("DEBUG: Recipe {:?} has no binary package", name.as_str()); + } + *rule = RecipeRule::Source; + } + } + } } fn handle_fetch( @@ -1141,4 +1239,10 @@ macro_rules! bail_options_err { }; } +macro_rules! bail_other_err { + ($($arg:tt)*) => { + return Err(cookbook::Error::Other(format!($($arg)*))) + }; +} use bail_options_err; +use bail_other_err; diff --git a/src/recipe.rs b/src/recipe.rs index a66e45249..d93eaac9a 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -215,6 +215,56 @@ impl BuildRecipe { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum RecipeRule { + Source, + Binary, + Local, + Ignore, +} + +impl RecipeRule { + pub fn inverted_default(self) -> Self { + if self == RecipeRule::Binary { + RecipeRule::Source + } else { + RecipeRule::Binary + } + } + pub fn from_repo_binary(repo_binary: bool) -> Self { + if repo_binary { + RecipeRule::Binary + } else { + RecipeRule::Source + } + } +} + +impl<'a> TryFrom<&'a str> for RecipeRule { + type Error = &'a str; + + fn try_from(value: &'a str) -> Result { + Ok(match value { + "source" => Self::Source, + "binary" => Self::Binary, + "local" => Self::Local, + "ignore" => Self::Ignore, + v => return Err(v), + }) + } +} + +impl From for &'static str { + fn from(value: RecipeRule) -> Self { + match value { + RecipeRule::Source => "source", + RecipeRule::Binary => "binary", + RecipeRule::Local => "local", + RecipeRule::Ignore => "ignore", + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct CookRecipe { pub name: PackageName, @@ -223,6 +273,7 @@ pub struct CookRecipe { pub target: &'static str, /// If false, it's listed on install config pub is_deps: bool, + // TODO: change to Option pub rule: String, /// whether if this recipe is pinned from cookbook.lock pub pinned: bool, @@ -642,7 +693,7 @@ pub fn validate_filesystem_rules(recipes: &[CookRecipe]) -> crate::Result<()> { let ignored: BTreeSet = recipes .iter() .filter(|r| r.rule == "ignore") - .map(|r| r.canon_recipe_name().without_prefix().to_string()) + .map(|r| r.name.without_prefix().to_string()) .collect(); if ignored.is_empty() { return Ok(()); @@ -651,9 +702,10 @@ pub fn validate_filesystem_rules(recipes: &[CookRecipe]) -> crate::Result<()> { if recipe.rule == "ignore" { continue; } - if recipe.recipe.build.kind == BuildKind::None - || recipe.recipe.build.kind == BuildKind::Remote - { + if matches!( + recipe.recipe.build.kind, + BuildKind::None | BuildKind::Remote + ) { // Not built from source, so build dependencies are irrelevant. continue; }