From fea64e54988dda605aebdbfebd634c5fef56b4b0 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Tue, 17 Mar 2026 13:29:11 +0700 Subject: [PATCH 1/2] Implement hints to cached build --- src/bin/repo.rs | 83 +++++++++++++++++++++++++++++++----------- src/cook/cook_build.rs | 38 +++++++++++++++---- src/cook/package.rs | 17 ++------- 3 files changed, 97 insertions(+), 41 deletions(-) diff --git a/src/bin/repo.rs b/src/bin/repo.rs index 1c5817a72..b7965dd5d 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -227,9 +227,13 @@ fn main_inner() -> anyhow::Result<()> { let verbose = config.cook.verbose; for recipe in &recipes { match repo_inner(&config, &command, recipe) { - Ok(_) => { + Ok(cached) => { if !command.is_informational() { - print_success(&command, Some(&recipe.name)); + if cached { + print_cached(&command, Some(&recipe.name)); + } else { + print_success(&command, Some(&recipe.name)); + } } } Err(e) => { @@ -298,20 +302,44 @@ fn print_success(command: &CliCommand, recipe: Option<&PackageName>) { } } +fn print_cached(command: &CliCommand, recipe: Option<&PackageName>) { + if let Some(recipe) = recipe { + eprintln!( + "{}{}{} {} - cached{}{}", + style::Bold, + color::Fg(color::AnsiValue(45)), + command.to_string(), + recipe.as_str(), + color::Fg(color::Reset), + style::Reset, + ); + } else { + eprintln!( + "{}{}{} - cached{}{}", + style::Bold, + color::Fg(color::AnsiValue(45)), + command.to_string(), + color::Fg(color::Reset), + style::Reset, + ); + } +} + fn repo_inner( config: &CliConfig, command: &CliCommand, recipe: &CookRecipe, -) -> Result<(), anyhow::Error> { +) -> Result { Ok(match *command { CliCommand::Fetch | CliCommand::Cook => { - let repo_inner_fn = move |logger: &PtyOut| -> Result<(), anyhow::Error> { + let repo_inner_fn = move |logger: &PtyOut| -> Result { let is_cook = *command == CliCommand::Cook; + let mut cached = false; let source_dir = handle_fetch(recipe, config, is_cook, logger)?; if is_cook { - handle_cook(recipe, config, source_dir, logger)?; + cached = handle_cook(recipe, config, source_dir, logger)?; } - Ok(()) + Ok(cached) }; let Some(log_path) = &config.logs_dir else { return repo_inner_fn(&None); @@ -359,7 +387,7 @@ fn repo_inner( .send(StatusUpdate::CookThreadFinished) .unwrap_or_default(); let _ = th.join(); - result?; + result? } CliCommand::Unfetch | CliCommand::Clean | CliCommand::CleanTarget => { handle_clean(recipe, config, command)? @@ -367,7 +395,10 @@ fn repo_inner( CliCommand::Push => unreachable!(), CliCommand::PushTree => unreachable!(), CliCommand::CookTree => unreachable!(), - CliCommand::Find => println!("{}", recipe.dir.display()), + CliCommand::Find => { + println!("{}", recipe.dir.display()); + false + } }) } @@ -690,10 +721,10 @@ fn handle_cook( config: &CliConfig, source_dir: PathBuf, logger: &PtyOut, -) -> anyhow::Result<()> { +) -> anyhow::Result { let recipe_dir = &recipe.dir; let target_dir = create_target_dir(recipe_dir, recipe.target).map_err(|e| anyhow!(e))?; - let (stage_dirs, auto_deps) = build( + let build_result = build( recipe_dir, &source_dir, &target_dir, @@ -703,12 +734,11 @@ fn handle_cook( ) .map_err(|err| anyhow!("failed to build: {:?}", err))?; - package(&recipe, &stage_dirs, &auto_deps, &config.cook, logger) + package(&recipe, &build_result, &config.cook, logger) .map_err(|err| anyhow!("failed to package: {:?}", err))?; if config.cook.clean_target || config.cook.write_filetree { - let stage_dirs = get_stage_dirs(&recipe.recipe.optional_packages, &target_dir); - for stage_dir in stage_dirs { + for stage_dir in &build_result.stage_dirs { if stage_dir.is_dir() { if config.cook.write_filetree { let mut stage_files_buf = String::new(); @@ -723,7 +753,7 @@ fn handle_cook( } } } - Ok(()) + Ok(build_result.cached) } /// delete stage artifacts upon nonstop failure to let repo_builder know @@ -741,19 +771,22 @@ fn handle_clean( recipe: &CookRecipe, _config: &CliConfig, command: &CliCommand, -) -> anyhow::Result<()> { +) -> anyhow::Result { let mut dir = recipe.dir.join("target"); + let mut cached = true; if matches!(*command, CliCommand::CleanTarget) { dir = dir.join(redoxer::target()) } if dir.exists() { fs::remove_dir_all(&dir).context(format!("failed to delete {}", dir.display()))?; + cached = false; } let dir = recipe.dir.join("source"); if dir.exists() && matches!(*command, CliCommand::Unfetch) { fs::remove_dir_all(&dir).context(format!("failed to delete {}", dir.display()))?; + cached = false; } - Ok(()) + Ok(cached) } static PUSH_SYSROOT_DIR: OnceLock = OnceLock::new(); @@ -918,6 +951,7 @@ enum RecipeStatus { Fetching, Fetched, Cooking, + Cached, Done, Failed(String), } @@ -928,7 +962,7 @@ enum StatusUpdate { Fetched(CookRecipe), FailFetch(CookRecipe, String), StartCook(PackageName), - Cooked(CookRecipe), + Cooked(CookRecipe, bool), FailCook(CookRecipe, String), PushLog(PackageName, Vec), FlushLog(PackageName, PathBuf), @@ -1104,12 +1138,19 @@ impl TuiApp { let _ = self.write_log(&name, &path); return; } - StatusUpdate::Cooked(recipe) => { + StatusUpdate::Cooked(recipe, cached) => { if self.active_cook.as_ref() == Some(&recipe.name) { self.active_cook = None; } self.auto_scroll = true; - (recipe.name.clone(), RecipeStatus::Done) + ( + recipe.name.clone(), + if cached { + RecipeStatus::Cached + } else { + RecipeStatus::Done + }, + ) } StatusUpdate::FailCook(recipe, err) => { self.prompt = Some(FailurePrompt::new(recipe.clone(), err.clone())); @@ -1189,9 +1230,9 @@ fn run_tui_cook( .unwrap_or_default(); } match handler { - Ok(()) => { + Ok(cached) => { cooker_status_tx - .send(StatusUpdate::Cooked(recipe)) + .send(StatusUpdate::Cooked(recipe, cached)) .unwrap_or_default(); if cooker_config.cook.nonstop && cooker_prompting.load(Ordering::SeqCst) == 4 diff --git a/src/cook/cook_build.rs b/src/cook/cook_build.rs index 5424cb392..2718a1532 100644 --- a/src/cook/cook_build.rs +++ b/src/cook/cook_build.rs @@ -167,6 +167,30 @@ fn auto_deps_from_static_package_deps( Ok(pkgs.into_iter().collect()) } +pub struct BuildResult { + pub stage_dirs: Vec, + pub auto_deps: BTreeSet, + pub cached: bool, +} + +impl BuildResult { + pub fn new(stage_dirs: Vec, auto_deps: BTreeSet) -> Self { + BuildResult { + stage_dirs, + auto_deps, + cached: false, + } + } + + pub fn cached(stage_dirs: Vec, auto_deps: BTreeSet) -> Self { + BuildResult { + stage_dirs, + auto_deps, + cached: true, + } + } +} + pub fn build( recipe_dir: &Path, source_dir: &Path, @@ -174,7 +198,7 @@ pub fn build( cook_recipe: &CookRecipe, cook_config: &CookConfig, logger: &PtyOut, -) -> Result<(Vec, BTreeSet), String> { +) -> Result { let recipe = &cook_recipe.recipe; let name = &cook_recipe.name; let check_source = !cook_recipe.is_deps; @@ -189,7 +213,7 @@ pub fn build( let cli_jobs = cook_config.jobs; if recipe.build.kind == BuildKind::None { // metapackages don't need to do anything here - return Ok((stage_dirs, BTreeSet::new())); + return Ok(BuildResult::new(stage_dirs, BTreeSet::new())); } let mut dep_pkgars = BTreeSet::new(); @@ -234,7 +258,7 @@ pub fn build( log_to_pty!(logger, "DEBUG: using cached build, not checking source"); } let auto_deps = make_auto_deps!()?; - return Ok((stage_dirs, auto_deps)); + return Ok(BuildResult::cached(stage_dirs, auto_deps)); } } @@ -276,7 +300,7 @@ pub fn build( if cook_config.clean_target { // stop early otherwise we'll end up rebuilding let auto_deps = make_auto_deps!()?; - return Ok((stage_dirs, auto_deps)); + return Ok(BuildResult::cached(stage_dirs, auto_deps)); } } @@ -493,7 +517,7 @@ pub fn build( } let auto_deps = make_auto_deps!()?; - Ok((stage_dirs, auto_deps)) + Ok(BuildResult::new(stage_dirs, auto_deps)) } pub fn remove_stage_dir(stage_dir: &PathBuf) -> Result<(), String> { @@ -661,7 +685,7 @@ pub fn build_remote( recipe: &Recipe, target_dir: &Path, cook_config: &CookConfig, -) -> Result<(Vec, BTreeSet), String> { +) -> Result { let source_toml = target_dir.join("source.toml"); let source_pubkey = "build/remotes/pub_key_static.redox-os.org.toml"; @@ -714,7 +738,7 @@ pub fn build_remote( serialize_and_write(&auto_deps_path, &wrapper)?; wrapper.packages }; - Ok((stage_dirs, auto_deps)) + Ok(BuildResult::new(stage_dirs, auto_deps)) } #[cfg(test)] diff --git a/src/cook/package.rs b/src/cook/package.rs index 2f0331339..63607d1bc 100644 --- a/src/cook/package.rs +++ b/src/cook/package.rs @@ -10,20 +10,20 @@ use pkgar_core::HeaderFlags; use crate::{ blake3::hash_to_hex, config::CookConfig, - cook::{fetch, fs::*, pty::PtyOut}, + cook::{cook_build::BuildResult, fetch, fs::*, pty::PtyOut}, log_to_pty, recipe::{BuildKind, CookRecipe, OptionalPackageRecipe}, }; pub fn package( recipe: &CookRecipe, - stage_dirs: &Vec, - auto_deps: &BTreeSet, + build_result: &BuildResult, cook_config: &CookConfig, logger: &PtyOut, ) -> Result<(), String> { let name = &recipe.name; let target_dir = &recipe.target_dir(); + let auto_deps = &build_result.auto_deps; if recipe.recipe.build.kind == BuildKind::None { // metapackages don't have stage dir and optional packages package_toml( @@ -51,21 +51,12 @@ pub fn package( .map_err(|err| format!("failed to save pkgar secret key: {:?}", err))?; } - let Ok(stage_modified) = modified_all(stage_dirs, modified_dir) else { - // stage dirs doesn't exist, assume safe only when clean_target = true - if !crate::config::get_config().cook.clean_target { - return Err("Stage directory is not present at packaging step".into()); - } else { - return Ok(()); - } - }; - let packages = recipe.recipe.get_packages_list(); for package in packages { let (stage_dir, package_file, package_meta) = package_stage_paths(package, target_dir); // Rebuild package if stage is newer - if package_file.is_file() && modified(&package_file)? < stage_modified { + if package_file.is_file() && !build_result.cached { log_to_pty!(logger, "DEBUG: updating '{}'", package_file.display()); remove_all(&package_file)?; if package_meta.is_file() { From 7c62fb9f8420d3db4c87320e1de59073e43784e2 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Tue, 17 Mar 2026 16:18:18 +0700 Subject: [PATCH 2/2] Fix cached recipes TUI indicator --- src/bin/repo.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bin/repo.rs b/src/bin/repo.rs index b7965dd5d..e0afa1f91 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -1187,7 +1187,7 @@ impl TuiApp { self.done = self .recipes .iter() - .filter(|(_, s)| *s == RecipeStatus::Done) + .filter(|(_, s)| *s == RecipeStatus::Done || *s == RecipeStatus::Cached) .map(|(r, _)| r.name.clone()) .collect(); } @@ -1455,20 +1455,23 @@ fn run_tui_cook( *s == RecipeStatus::Fetched || *s == RecipeStatus::Cooking || *s == RecipeStatus::Done + || *s == RecipeStatus::Cached || matches!(s, RecipeStatus::Failed(_)) }) .map(|(r, s)| { let style = match s { - RecipeStatus::Fetched => Style::default().fg(Color::Cyan), + RecipeStatus::Fetched => Style::default(), RecipeStatus::Cooking => Style::default().fg(Color::Yellow), RecipeStatus::Done => Style::default().fg(Color::Green), + RecipeStatus::Cached => Style::default().fg(Color::Cyan), RecipeStatus::Failed(_) => Style::default().fg(Color::Red), _ => Style::default(), }; let icon = match s { RecipeStatus::Fetched => ' ', RecipeStatus::Cooking => spin, - RecipeStatus::Done => ' ', + RecipeStatus::Done => '+', + RecipeStatus::Cached => ' ', RecipeStatus::Failed(_) => 'X', _ => '?', };