repo: Export as CSV

This commit is contained in:
Wildan M 2026-07-29 09:29:16 +07:00
parent d589ee31fb
commit 997597e517
No known key found for this signature in database
GPG Key ID: 01AC53185C679C79
5 changed files with 432 additions and 202 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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=<repo_dir> the "repo" folder, default to $PWD/repo
--with-package-deps include package deps (always implied in push command)
--all apply to all recipes in <cookbook_dir>
--all-binaries apply to all recipes in <cookbook_dir> that is configured as "binary"
--all-compiled apply to all compiled recipes in <cookbook_dir>
--all-binaries apply to all compiled recipes in <cookbook_dir> that is configured as "binary"
--category=<category> apply to all recipes in <cookbook_dir>/<category>
--filesystem=<filesystem> override recipes config using installer file
--repo-binary override recipes config to use repo_binary
--sysroot=<sysroot_dir> used in "push", the "root" dir, default to $PWD/sysroot
--no-metadata used in "push", do not write pkgar_head or etc dir
--display=<format> used in "*-list", either "name", "path", "csv", "tree"
--set-rule=<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<PathBuf>,
filesystem: Option<redox_installer::Config>,
set_rule: Option<String>,
display: DisplayOptions,
unset: bool,
no_metadata: bool,
with_rollback: bool,
with_package_deps: bool,
all: bool,
binaries_only: bool,
all: Option<AllOption>,
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<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
"--sysroot" => 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<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
"--no-metadata" => 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<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
config.set_rule = split.next().map(|s| s.to_string());
cmd
} else {
if command == "find" {
config.display = DisplayOptions::Path;
}
str::parse(&command)?
};
if command.is_informational() {
@ -528,13 +548,13 @@ fn parse_args(args: Vec<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
let mut preloaded_recipes: BTreeMap<PackageName, CookRecipe> = 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<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
for path in all_recipes_path {
// TODO: Allow selecting recipes from category as host?
let recipe = CookRecipe::from_path(&path, !command.is_cleaning(), false)?;
let recipe = match CookRecipe::from_path(&path, !command.is_cleaning(), false) {
Ok(recipe) => 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<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
CookRecipe::get_package_deps_recursive(&binary_recipe_names, true)?;
}
let mut recipes = if config.binaries_only {
// This codepath is only true if combined with `config.all`,
// otherwise it will be confusing for users because we don't warn
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
@ -883,7 +906,7 @@ fn handle_clean(recipe: &CookRecipe, config: &CliConfig, command: &CliCommand) -
{
remove_all(&tar)?;
cached = false;
} else if config.all {
} else if config.all.is_some() {
remove_all(&tar)?;
cached = false;
}
@ -902,31 +925,25 @@ fn handle_push(recipes: &Vec<CookRecipe>, 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<PackageName> = 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<bool> {
let handle_push_inner = move |item: TreeItem| -> Result<bool> {
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<CookRecipe>, 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<CookRecipe>, 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<CookRecipe>, config: &CliConfig) -> Result<()> {
Ok(())
}
fn handle_tree(recipes: &Vec<CookRecipe>, is_build_tree: bool, _config: &CliConfig) -> Result<()> {
fn handle_tree(recipes: &Vec<CookRecipe>, 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<PackageName> = 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<PackageName> = 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(())

View File

@ -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<PackageName>,
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<PackageName>,
/// list of dependents, if requested
pub dependents: HashMap<PackageName, HashSet<PackageName>>,
/// 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<PackageName>>,
}
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<Self, Self::Err> {
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<Self, Self::Err> {
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<TreeData> {
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<PackageName, HashSet<PackageName>> {
let mut dependents_map: HashMap<PackageName, HashSet<PackageName>> = 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<PackageName, HashSet<PackageName>>>,
prefix: &str,
is_last: bool,
is_build_tree: bool,
visited: &mut HashSet<PackageName>,
total_size: &mut u64,
total_count: &mut u64,
op: fn(&PackageName, &str, bool, &WalkTreeEntry) -> Result<bool>,
tree_opt: TreeOptions,
data: &mut TreeData,
op: fn(TreeItem) -> Result<bool>,
) -> 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<bool> {
let size_str = match entry {
WalkTreeEntry::Built(_path_buf, size) => format!("[{}]", format_size(*size)),
fn display_tree_fn(item: TreeItem) -> Result<bool> {
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<bool> {
if item.entry.is_valid() {
println!("{}", item.recipe.name.as_str());
}
Ok(false)
}
fn display_path_fn(item: TreeItem) -> Result<bool> {
if item.entry.is_valid() {
println!("{}", item.recipe.dir.display());
}
Ok(false)
}
fn display_csv_fn(item: TreeItem) -> Result<bool> {
// 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)
}

View File

@ -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<Self, PackageError> {
let dir = staged_pkg::find(name.name())
.ok_or_else(|| PackageError::PackageNotFound(name.clone()))?;