mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-11-04 19:56:36 +08:00
repo: Export as CSV
This commit is contained in:
parent
d589ee31fb
commit
997597e517
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(())
|
||||
|
||||
380
src/cook/tree.rs
380
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<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)
|
||||
}
|
||||
|
||||
@ -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()))?;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user