diff --git a/Cargo.lock b/Cargo.lock index 82933fda5..2d3558c91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -895,7 +895,7 @@ dependencies = [ [[package]] name = "redox_installer" version = "0.2.42" -source = "git+https://gitlab.redox-os.org/redox-os/installer.git#1c2534e44c68ace0a21db2a1bc9503945a38c809" +source = "git+https://gitlab.redox-os.org/redox-os/installer.git#d7409a2cc6ebd44eab9f0c57551bb0b2798d21e1" dependencies = [ "anyhow", "arg_parser", diff --git a/mk/repo.mk b/mk/repo.mk index 410547abd..5785b7e86 100644 --- a/mk/repo.mk +++ b/mk/repo.mk @@ -18,7 +18,7 @@ repo-tree: $(FSTOOLS_TAG) $(CONTAINER_TAG) ifeq ($(PODMAN_BUILD),1) $(PODMAN_RUN) make $@ else - @$(REPO_BIN) cook-tree $(COOKBOOK_OPTS) --with-package-deps + @$(REPO_BIN) cook-list $(COOKBOOK_OPTS) --with-package-deps endif # List all recipes in a push-tree fashion specified by the filesystem config @@ -26,7 +26,7 @@ image-tree: $(FSTOOLS_TAG) $(CONTAINER_TAG) ifeq ($(PODMAN_BUILD),1) $(PODMAN_RUN) make $@ else - @$(REPO_BIN) push-tree $(COOKBOOK_OPTS) + @$(REPO_BIN) push-list $(COOKBOOK_OPTS) endif # Clean specific target to all recipes, similar to repo_clean but more specific @@ -115,7 +115,7 @@ rt.%: $(FSTOOLS_TAG) FORCE ifeq ($(PODMAN_BUILD),1) $(PODMAN_RUN) make $@ else - $(REPO_BIN) cook-tree $(foreach f,$(subst $(comma), ,$*),$(f)) $(COOKBOOK_OPTS) + $(REPO_BIN) cook-list $(foreach f,$(subst $(comma), ,$*),$(f)) $(COOKBOOK_OPTS) endif MOUNTED_TAG=$(MOUNT_DIR)~ @@ -147,7 +147,7 @@ pt.%: $(FSTOOLS_TAG) FORCE ifeq ($(PODMAN_BUILD),1) $(PODMAN_RUN) make $@ else - $(REPO_BIN) push-tree $(foreach f,$(subst $(comma), ,$*),$(f)) $(COOKBOOK_OPTS) + $(REPO_BIN) push-list $(foreach f,$(subst $(comma), ,$*),$(f)) $(COOKBOOK_OPTS) endif # Push all recipes specified by the filesystem config diff --git a/src/bin/repo/main.rs b/src/bin/repo/main.rs index dd5833df2..b1d3789cb 100644 --- a/src/bin/repo/main.rs +++ b/src/bin/repo/main.rs @@ -7,7 +7,7 @@ use cookbook::cook::fs::{ }; use cookbook::cook::package::{package, package_handle_push}; use cookbook::cook::pty::{PtyOut, flush_pty, write_to_pty}; -use cookbook::cook::tree::{self, WalkTreeEntry}; +use cookbook::cook::tree::{self, DisplayOptions, TreeData, TreeItem, TreeOptions, WalkTreeEntry}; use cookbook::cook::tui::join_logs; use cookbook::cook::{fetch_repo, ident}; use cookbook::recipe::{ @@ -41,9 +41,9 @@ const REPO_HELP_STR: &str = r#" clean delete recipe artifacts clean-target delete recipe artifacts for one target push extract package into sysroot - find find path of recipe packages - cook-tree show tree of recipe build - push-tree show tree of recipe packages + repo-list show list of recipes + cook-list show list of recipes to build + push-list show list of recipes to package capture-rev write lock to git recipes change-rule override rule to recipes change-rule-local override rule to specific recipes @@ -53,12 +53,14 @@ const REPO_HELP_STR: &str = r#" --repo= the "repo" folder, default to $PWD/repo --with-package-deps include package deps (always implied in push command) --all apply to all recipes in - --all-binaries apply to all recipes in that is configured as "binary" + --all-compiled apply to all compiled recipes in + --all-binaries apply to all compiled recipes in that is configured as "binary" --category= apply to all recipes in / --filesystem= override recipes config using installer file --repo-binary override recipes config to use repo_binary --sysroot= used in "push", the "root" dir, default to $PWD/sysroot --no-metadata used in "push", do not write pkgar_head or etc dir + --display= used in "*-list", either "name", "path", "csv", "tree" --set-rule= used in "change-rule", set wanted config rule --rollback used in "capture-rev", allow git to rollback --unset used in "capture-rev" and "change-rule", unset locks @@ -87,12 +89,12 @@ struct CliConfig { category: Option, filesystem: Option, set_rule: Option, + display: DisplayOptions, unset: bool, no_metadata: bool, with_rollback: bool, with_package_deps: bool, - all: bool, - binaries_only: bool, + all: Option, cook: CookConfig, } @@ -100,37 +102,62 @@ struct CliConfig { enum CliCommand { Fetch, Cook, - CookTree, Unfetch, Clean, CleanTarget, Push, - PushTree, - Find, + CookList, + PushList, + RepoList, CaptureRev, ChangeRule, ChangeRuleLocal, } +#[derive(Clone)] +enum AllOption { + All, + AllCompiled, + AllBinaries, +} + impl CliCommand { pub fn is_informational(&self) -> bool { - *self == CliCommand::PushTree || *self == CliCommand::CookTree || *self == CliCommand::Find + *self == CliCommand::PushList + || *self == CliCommand::CookList + || *self == CliCommand::RepoList + } + pub fn is_tree(&self) -> bool { + self.is_informational() + } + pub fn is_change_rule(&self) -> bool { + *self == CliCommand::ChangeRuleLocal + || *self == CliCommand::CaptureRev + || *self == CliCommand::ChangeRule } pub fn is_building(&self) -> bool { *self == CliCommand::Fetch || *self == CliCommand::Cook - || *self == CliCommand::CookTree + || *self == CliCommand::CookList || *self == CliCommand::CaptureRev || *self == CliCommand::ChangeRule } pub fn is_pushing(&self) -> bool { - *self == CliCommand::Push || *self == CliCommand::PushTree + *self == CliCommand::Push || *self == CliCommand::PushList } pub fn is_cleaning(&self) -> bool { *self == CliCommand::Clean || *self == CliCommand::CleanTarget || *self == CliCommand::Unfetch } + pub fn to_tree(&self) -> TreeOptions { + match self { + CliCommand::CookList => TreeOptions::Cook, + CliCommand::PushList => TreeOptions::Push, + CliCommand::RepoList => TreeOptions::Repo, + _ => unreachable!(), + } + } } impl FromStr for CliCommand { @@ -146,9 +173,11 @@ impl FromStr for CliCommand { "clean" => Ok(CliCommand::Clean), "clean-target" => Ok(CliCommand::CleanTarget), "push" => Ok(CliCommand::Push), - "push-tree" => Ok(CliCommand::PushTree), - "cook-tree" => Ok(CliCommand::CookTree), - "find" => Ok(CliCommand::Find), + "repo-tree" => Ok(CliCommand::RepoList), + "push-tree" => Ok(CliCommand::PushList), + "cook-tree" => Ok(CliCommand::CookList), + // alias for scripts + "find" => Ok(CliCommand::RepoList), "capture-rev" => Ok(CliCommand::CaptureRev), "change-rule" => Ok(CliCommand::ChangeRule), "change-rule-local" => Ok(CliCommand::ChangeRuleLocal), @@ -166,9 +195,9 @@ impl ToString for CliCommand { CliCommand::Clean => "clean".to_string(), CliCommand::CleanTarget => "clean-target".to_string(), CliCommand::Push => "push".to_string(), - CliCommand::PushTree => "push-tree".to_string(), - CliCommand::CookTree => "cook-tree".to_string(), - CliCommand::Find => "find".to_string(), + CliCommand::PushList => "push-tree".to_string(), + CliCommand::CookList => "cook-tree".to_string(), + CliCommand::RepoList => "repo-tree".to_string(), CliCommand::CaptureRev => "capture-rev".to_string(), CliCommand::ChangeRule => "change-rule".to_string(), CliCommand::ChangeRuleLocal => "change-rule-local".to_string(), @@ -190,11 +219,11 @@ impl CliConfig { None }, category: None, + display: DisplayOptions::Tree, sysroot_dir: current_dir.join("sysroot"), with_package_deps: false, cook: get_config().cook.clone(), - all: false, - binaries_only: false, + all: None, unset: false, no_metadata: false, filesystem: None, @@ -255,21 +284,15 @@ fn main_inner() -> Result<()> { } return publish_packages(&recipes, &config.repo_dir); } - if command == CliCommand::PushTree { - return handle_tree(&recipes, false, &config); + if command.is_tree() { + return handle_tree(&recipes, command.to_tree(), &config); } - if command == CliCommand::CookTree { - return handle_tree(&recipes, true, &config); + if command.is_change_rule() { + return handle_change_rule(&recipes, &config, &command); } if command == CliCommand::Push { return handle_push(&recipes, &config); } - if matches!( - command, - CliCommand::ChangeRule | CliCommand::ChangeRuleLocal | CliCommand::CaptureRev - ) { - return handle_change_rule(&recipes, &config, &command); - } let verbose = config.cook.verbose; for recipe in &recipes { @@ -414,10 +437,6 @@ fn repo_inner(config: &CliConfig, command: &CliCommand, recipe: &CookRecipe) -> CliCommand::Unfetch | CliCommand::Clean | CliCommand::CleanTarget => { handle_clean(recipe, config, command)? } - CliCommand::Find => { - println!("{}", recipe.dir.display()); - false - } _ => unreachable!(), }) } @@ -456,6 +475,7 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec config.sysroot_dir = PathBuf::from(value), "--category" => config.category = Some(PathBuf::from(value)), "--set-rule" => config.set_rule = Some(value.into()), + "--display" => config.display = DisplayOptions::from_str(value)?, "--filesystem" => { config.filesystem = Some({ let r = redox_installer::Config::from_file(&PathBuf::from(value)); @@ -476,12 +496,9 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec config.no_metadata = true, "--rollback" => config.with_rollback = true, "--unset" => config.unset = true, - "--all" => config.all = true, - "--all-binaries" => { - // TODO: an option to just set binaries_only? - config.all = true; - config.binaries_only = true; - } + "--all" => config.all = Some(AllOption::All), + "--all-compiled" => config.all = Some(AllOption::AllCompiled), + "--all-binaries" => config.all = Some(AllOption::AllBinaries), _ => bail_options_err!("Error: Unknown flag: {}", arg), } } @@ -518,6 +535,9 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec) -> Result<(CliConfig, CliCommand, Vec = BTreeMap::new(); if recipe_names.is_empty() { - if config.all || config.category.is_some() { - let all_recipes_path = match command.is_cleaning() || config.category.is_some() { - true => { - // some wip recipes have broken toml + if config.all.is_some() || config.category.is_some() { + let all_recipes_path = match (&config.all, config.category.is_some()) { + (Some(AllOption::All), _) | (None, true) => { + // everything in recipes staged_pkg::list() } - false => { + _ => { // get the list from repo/TARGET/repo.toml staged_pkg::list_repo(&config.repo_dir)? } @@ -557,7 +577,11 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec recipe, + Err(_) if matches!(config.all, Some(AllOption::All)) => continue, + Err(e) => return Err(e.into()), + }; let recipe_name = recipe.name.clone(); preloaded_recipes.insert(recipe_name.clone(), recipe); recipe_names.push(recipe_name); @@ -665,9 +689,8 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec, config: &CliConfig) -> Result<()> { } let recipe_map: HashMap<&PackageName, &CookRecipe> = recipes.iter().map(|r| (&r.name, r)).collect(); - let mut total_size: u64 = 0; - let mut total_count: u64 = 0; - let mut visited: HashSet = HashSet::new(); - let num_recipes = recipes.len(); PUSH_CONFIG .set(config.clone()) .unwrap_or_else(|_| panic!("PUSH_CONFIG is initialized")); - let handle_push_inner = move |package_name: &PackageName, - _prefix: &str, - _is_last: bool, - entry: &WalkTreeEntry| - -> Result { + let handle_push_inner = move |item: TreeItem| -> Result { + let package_name = &item.recipe.name; if package_name.is_host() { return Ok(true); // TODO: skip altogether from recipes list } - let r = match entry { - WalkTreeEntry::Built(archive_path, _) => { + let r = match item.entry { + WalkTreeEntry::Built(_) => { let config = PUSH_CONFIG.get().unwrap(); let install_path = &config.sysroot_dir; + let archive_path = item.recipe.stage_paths().1; let mut state = if !config.no_metadata { Some(PackageState::from_sysroot(&install_path).map_err(Error::from)?) } else { None }; - let r = package_handle_push(state.as_mut(), archive_path, &install_path); + let r = package_handle_push(state.as_mut(), &archive_path, &install_path); if matches!(r, Ok(false)) && state.is_some() { state .unwrap() @@ -963,16 +980,17 @@ fn handle_push(recipes: &Vec, config: &CliConfig) -> Result<()> { } } }; - for (i, recipe) in recipes.iter().enumerate() { + + let mut data = TreeData::new(); + for recipe in recipes.iter() { tree::walk_tree_entry( &recipe.name, &recipe_map, + None, "", - i == num_recipes - 1, - false, - &mut visited, - &mut total_size, - &mut total_count, + false, // don't care + TreeOptions::Push, + &mut data, handle_push_inner, )?; } @@ -981,9 +999,9 @@ fn handle_push(recipes: &Vec, config: &CliConfig) -> Result<()> { println!(""); println!( "Pushed {} of {} {}", - tree::format_size(total_size), - total_count, - if total_count == 1 { + tree::format_size(data.total_size), + data.total_count, + if data.total_count == 1 { "package" } else { "packages" @@ -994,51 +1012,37 @@ fn handle_push(recipes: &Vec, config: &CliConfig) -> Result<()> { Ok(()) } -fn handle_tree(recipes: &Vec, is_build_tree: bool, _config: &CliConfig) -> Result<()> { +fn handle_tree(recipes: &Vec, cmd: TreeOptions, config: &CliConfig) -> Result<()> { let recipe_map: HashMap<&PackageName, &CookRecipe> = recipes.iter().map(|r| (&r.name, r)).collect(); - let mut total_size: u64 = 0; - let mut total_count: u64 = 0; - let mut visited: HashSet = HashSet::new(); - let roots: Vec<&CookRecipe> = recipes.iter().filter(|r| !r.is_deps).collect(); - let num_roots = roots.len(); - for (i, root) in roots.iter().enumerate() { - tree::display_tree_entry( - &root.name, - &recipe_map, - "", - i == num_roots - 1, - is_build_tree, - &mut visited, - &mut total_size, - &mut total_count, - )?; - } + let roots: Vec = recipes.iter().map(|s| s.name.clone()).collect(); + let data = tree::display_tree_entry(&roots[..], &recipe_map, cmd, config.display)?; - println!(""); - if is_build_tree { - println!( - "Build summary: {} need build, {} may rebuild, with total of {} {}", - total_size, - roots.len(), - visited.len(), - if visited.len() == 1 { - "recipe" - } else { - "recipes" - }, - ); - } else { - println!( - "Estimated image size: {} of {} {}", - tree::format_size(total_size), - visited.len(), - if visited.len() == 1 { - "package" - } else { - "packages" - }, - ); + if matches!(config.display, DisplayOptions::Tree) { + println!(""); + match cmd { + TreeOptions::Repo => {} + TreeOptions::Cook => println!( + "Build summary: {} need build with total of {} {}", + data.total_notbuilt, + data.visited.len(), + if data.visited.len() == 1 { + "recipe" + } else { + "recipes" + }, + ), + TreeOptions::Push => println!( + "Estimated image size: {} of {} {}", + tree::format_size(data.total_size), + data.visited.len(), + if data.visited.len() == 1 { + "package" + } else { + "packages" + }, + ), + } } Ok(()) diff --git a/src/cook/tree.rs b/src/cook/tree.rs index 5190f2a1f..0fefc6f9f 100644 --- a/src/cook/tree.rs +++ b/src/cook/tree.rs @@ -1,126 +1,302 @@ use pkg::{Package, PackageName}; use std::{ collections::{HashMap, HashSet}, + fmt::Write, path::PathBuf, + str::FromStr, }; -use crate::Result; -use crate::cook::fs; use crate::recipe::CookRecipe; +use crate::{Error, Result}; -pub enum WalkTreeEntry<'a> { - Built(&'a PathBuf, u64), +#[derive(Clone, Copy, Debug)] +pub enum WalkTreeEntry { + Built(u64), NotBuilt, Deduped, Missing, } -pub fn display_tree_entry( - package_name: &PackageName, - recipe_map: &HashMap<&PackageName, &CookRecipe>, - prefix: &str, - is_last: bool, - is_build_tree: bool, - visited: &mut HashSet, - total_size: &mut u64, - total_count: &mut u64, -) -> Result<()> { - walk_tree_entry( - package_name, - recipe_map, - prefix, - is_last, - is_build_tree, - visited, - total_size, - total_count, - display_pkg_fn, - ) +impl WalkTreeEntry { + pub fn is_valid(&self) -> bool { + return matches!(self, WalkTreeEntry::Built(_) | WalkTreeEntry::NotBuilt); + } } +pub struct TreeData { + /// list of deduped packages + pub visited: HashSet, + /// list of dependents, if requested + pub dependents: HashMap>, + /// total package size, only valid if tree_opt is push + pub total_size: u64, + /// count of packages that not exist in recipe_map + pub total_missing: u64, + /// count of packages that don't have package.toml + pub total_notbuilt: u64, + /// total packages + pub total_count: u64, +} + +pub struct TreeItem<'a> { + /// the recipe data + pub recipe: &'a CookRecipe, + /// prefix for tree + pub prefix: &'a str, + /// last item on tree? + pub is_last: bool, + /// recipe status + pub entry: WalkTreeEntry, + /// dependencies, if not deduped + pub dependencies: Option<&'a HashSet<&'a PackageName>>, + /// dependents, if supplied + pub dependents: Option<&'a HashSet>, +} + +impl TreeData { + pub fn new() -> Self { + Self { + visited: HashSet::new(), + dependents: HashMap::new(), + total_size: 0, + total_count: 0, + total_missing: 0, + total_notbuilt: 0, + } + } +} + +#[derive(Clone, Copy)] +pub enum DisplayOptions { + Name, + Path, + Csv, + Tree, +} + +impl Default for DisplayOptions { + fn default() -> Self { + Self::Tree + } +} + +impl FromStr for DisplayOptions { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "name" => Ok(DisplayOptions::Name), + "path" => Ok(DisplayOptions::Path), + "csv" => Ok(DisplayOptions::Csv), + "tree" => Ok(DisplayOptions::Tree), + _ => Err(Error::Options(format!("unknown display: {s}"))), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum TreeOptions { + Repo, + Cook, + Push, +} + +impl FromStr for TreeOptions { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "repo" => Ok(TreeOptions::Repo), + "cook" => Ok(TreeOptions::Cook), + "push" => Ok(TreeOptions::Push), + _ => Err(Error::Options(format!("unknown tree: {s}"))), + } + } +} + +pub fn display_tree_entry( + package_roots: &[PackageName], + recipe_map: &HashMap<&PackageName, &CookRecipe>, + tree_opt: TreeOptions, + display_opt: DisplayOptions, +) -> Result { + let mut data = TreeData::new(); + let num_roots = package_roots.len() - 1; + let dependents = if matches!(display_opt, DisplayOptions::Csv) { + Some(build_dependents(recipe_map, tree_opt)) + } else { + None + }; + for (i, name) in package_roots.iter().enumerate() { + walk_tree_entry( + name, + recipe_map, + dependents.as_ref(), + "", + i == num_roots, + tree_opt, + &mut data, + match display_opt { + DisplayOptions::Name => display_name_fn, + DisplayOptions::Path => display_path_fn, + DisplayOptions::Csv => display_csv_fn, + DisplayOptions::Tree => display_tree_fn, + }, + )?; + } + Ok(data) +} + +pub fn build_dependents( + recipe_map: &HashMap<&PackageName, &CookRecipe>, + tree_opt: TreeOptions, +) -> HashMap> { + let mut dependents_map: HashMap> = HashMap::new(); + for cook_recipe in recipe_map.values() { + let mut all_deps_set: HashSet<&PackageName> = HashSet::new(); + let pkg_meta: Package; + match tree_opt { + TreeOptions::Repo => { + // no dependency recursion + } + TreeOptions::Cook => { + // recursion from recipe.toml + all_deps_set.extend(cook_recipe.recipe.build.dependencies.iter()); + all_deps_set.extend(cook_recipe.recipe.build.dev_dependencies.iter()); + all_deps_set.extend(cook_recipe.recipe.package.dependencies.iter()); + } + TreeOptions::Push => { + // recursion from package.toml + if let Ok(pkg) = Package::from_file(&cook_recipe.stage_paths().2) { + pkg_meta = pkg; + all_deps_set.extend(pkg_meta.depends.iter()); + } + } + } + for dep in all_deps_set { + dependents_map + .entry(dep.clone()) + .or_default() + .insert(cook_recipe.name.clone()); + } + } + dependents_map +} + +/// Does tree recursion to `op` call. +/// Tree recursion is different than recursion in [`CookRecipe`], +/// this version does call `op` from root then to their deps. +/// dependents can be precalculated with [`build_dependents`]. pub fn walk_tree_entry( package_name: &PackageName, recipe_map: &HashMap<&PackageName, &CookRecipe>, + dependents_map: Option<&HashMap>>, prefix: &str, is_last: bool, - is_build_tree: bool, - visited: &mut HashSet, - total_size: &mut u64, - total_count: &mut u64, - op: fn(&PackageName, &str, bool, &WalkTreeEntry) -> Result, + tree_opt: TreeOptions, + data: &mut TreeData, + op: fn(TreeItem) -> Result, ) -> Result<()> { let cook_recipe = match recipe_map.get(package_name) { Some(r) => r, None => { // Data not provided, will not be processed by the build system - op(package_name, prefix, is_last, &WalkTreeEntry::Missing)?; + op(TreeItem { + recipe: &CookRecipe::dummy(package_name), + prefix, + is_last, + entry: WalkTreeEntry::Missing, + dependencies: None, + dependents: None, + })?; + data.total_missing += 1; return Ok(()); } }; let (_, pkg_path, pkg_toml) = cook_recipe.stage_paths(); - let deduped = visited.contains(package_name); - let entry = if deduped { - WalkTreeEntry::Deduped + let deduped = data.visited.contains(package_name); + let pkg_meta: Package; + + let (entry, dependencies) = if deduped { + (WalkTreeEntry::Deduped, None) } else { - match (std::fs::metadata(&pkg_path), pkg_toml.is_file()) { - (Ok(meta), _) => WalkTreeEntry::Built(&pkg_path, meta.len()), - (Err(_), true) => WalkTreeEntry::Built(&pkg_path, 0), + let entry = match (std::fs::metadata(&pkg_path), pkg_toml.is_file()) { + (Ok(meta), _) => WalkTreeEntry::Built(meta.len()), + (Err(_), true) => WalkTreeEntry::Built(0), (Err(_), false) => WalkTreeEntry::NotBuilt, + }; + let mut all_deps_set: HashSet<&PackageName> = HashSet::new(); + match tree_opt { + TreeOptions::Repo => { + // no dependency recursion + } + TreeOptions::Cook => { + // recursion from recipe.toml + all_deps_set.extend(cook_recipe.recipe.build.dependencies.iter()); + all_deps_set.extend(cook_recipe.recipe.build.dev_dependencies.iter()); + all_deps_set.extend(cook_recipe.recipe.package.dependencies.iter()); + } + TreeOptions::Push => { + // recursion from package.toml + if let Ok(pkg) = Package::from_file(&pkg_toml) { + pkg_meta = pkg; + all_deps_set.extend(pkg_meta.depends.iter()); + } + } } + (entry, Some(all_deps_set)) }; - let cached = op(package_name, prefix, is_last, &entry)?; + let dependents = dependents_map.and_then(|s| s.get(&cook_recipe.name)); + + let cached = op(TreeItem { + recipe: cook_recipe, + prefix, + is_last, + entry, + dependencies: dependencies.as_ref(), + dependents, + })?; if deduped || cached { return Ok(()); } - visited.insert(package_name.clone()); + data.visited.insert(package_name.clone()); if !cached { - if is_build_tree { - if matches!(entry, WalkTreeEntry::NotBuilt) { - *total_size += 1; - } - } else { - if let WalkTreeEntry::Built(_p, pkg_size) = &entry { - *total_size += pkg_size; + if matches!(tree_opt, TreeOptions::Push) { + if let WalkTreeEntry::Built(pkg_size) = &entry { + data.total_size += pkg_size; } } - *total_count += 1; - } - let pkg_meta: Package; - - let mut all_deps_set: HashSet<&PackageName> = HashSet::new(); - if is_build_tree { - all_deps_set.extend(cook_recipe.recipe.build.dependencies.iter()); - all_deps_set.extend(cook_recipe.recipe.package.dependencies.iter()); - } else { - if let Ok(pkg_toml_str) = fs::read_to_string(&pkg_toml) { - // more accurate with auto deps - pkg_meta = Package::from_toml(&pkg_toml_str)?; - all_deps_set.extend(pkg_meta.depends.iter()); + if matches!(entry, WalkTreeEntry::NotBuilt) { + data.total_notbuilt += 1; } + data.total_count += 1; } - if all_deps_set.is_empty() { + let dependencies = dependencies.unwrap(); + if dependencies.is_empty() { return Ok(()); } - let sorted_deps: Vec<&PackageName> = all_deps_set.into_iter().collect(); - let deps_count = sorted_deps.len(); + let deps_count = dependencies.len(); let child_prefix = if is_last { " " } else { "│ " }; - for (i, dep_name) in sorted_deps.iter().enumerate() { + for (i, dep_name) in dependencies.iter().enumerate() { + data.dependents + .entry((*dep_name).clone()) + .or_insert_with(|| HashSet::new()) + .insert(package_name.clone()); walk_tree_entry( dep_name, recipe_map, + dependents_map, &format!("{}{}", prefix, child_prefix), i == deps_count - 1, - is_build_tree, - visited, - total_size, - total_count, + tree_opt, + data, op, )?; } @@ -128,24 +304,68 @@ pub fn walk_tree_entry( Ok(()) } -pub fn display_pkg_fn( - package_name: &PackageName, - prefix: &str, - is_last: bool, - entry: &WalkTreeEntry, -) -> Result { - let size_str = match entry { - WalkTreeEntry::Built(_path_buf, size) => format!("[{}]", format_size(*size)), +fn display_tree_fn(item: TreeItem) -> Result { + let size_str = match item.entry { + WalkTreeEntry::Built(size) => format!("[{}]", format_size(size)), WalkTreeEntry::NotBuilt => "(not built)".to_string(), WalkTreeEntry::Deduped => "".to_string(), WalkTreeEntry::Missing => "(omitted)".to_string(), }; + let is_last = item.is_last; let line_prefix = if is_last { "└── " } else { "├── " }; - println!("{}{}{} {}", prefix, line_prefix, package_name, size_str); + println!( + "{}{}{} {}", + item.prefix, line_prefix, item.recipe.name, size_str + ); // TODO: check dirty build by checking source ident Ok(false) } +fn display_name_fn(item: TreeItem) -> Result { + if item.entry.is_valid() { + println!("{}", item.recipe.name.as_str()); + } + Ok(false) +} + +fn display_path_fn(item: TreeItem) -> Result { + if item.entry.is_valid() { + println!("{}", item.recipe.dir.display()); + } + Ok(false) +} + +fn display_csv_fn(item: TreeItem) -> Result { + // name, path, status, dependencies, dependents + if item.entry.is_valid() { + let mut deps = String::new(); + let mut depz = String::new(); + for dep in item.dependencies.unwrap() { + if !deps.is_empty() { + let _ = deps.write_char(';'); + } + let _ = deps.write_str(dep.as_str()); + } + if let Some(dependents) = item.dependents { + for dep in dependents { + if !depz.is_empty() { + let _ = depz.write_char(';'); + } + let _ = depz.write_str(dep.as_str()); + } + } + println!( + "{},{},{:?},{},{}", + item.recipe.name.as_str(), + item.recipe.dir.display(), + item.entry, + deps, + depz, + ); + } + Ok(false) +} + pub fn walk_file_tree( dir: &PathBuf, prefix: &str, @@ -204,11 +424,5 @@ pub fn walk_file_tree( } pub fn format_size(bytes: u64) -> String { - if bytes == 0 { - return "0 B".to_string(); - } - const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"]; - let i = (bytes as f64).log(1024.0).floor() as usize; - let size = bytes as f64 / 1024.0_f64.powi(i as i32); - format!("{:.2} {}", size, UNITS[i]) + redox_installer::format_bytes(bytes) } diff --git a/src/recipe.rs b/src/recipe.rs index 8dd3fd9cc..6def996a5 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -276,6 +276,18 @@ impl CookRecipe { }) } + pub fn dummy(name: &PackageName) -> Self { + Self { + dir: PathBuf::new(), + is_deps: false, + name: name.clone(), + recipe: Recipe::default(), + target: redoxer::target(), + pinned: false, + rule: "".into(), + } + } + pub fn from_name(name: PackageName) -> Result { let dir = staged_pkg::find(name.name()) .ok_or_else(|| PackageError::PackageNotFound(name.clone()))?;