Merge branch 'build-cache-hint' into 'master'

Implement hints to cached build

See merge request redox-os/redox!2013
This commit is contained in:
Jeremy Soller 2026-03-18 10:56:33 -06:00
commit 290cabe8fc
3 changed files with 103 additions and 44 deletions

View File

@ -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<bool, anyhow::Error> {
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<bool, anyhow::Error> {
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<bool> {
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<bool> {
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<PathBuf> = 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<u8>),
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()));
@ -1146,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();
}
@ -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
@ -1414,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',
_ => '?',
};

View File

@ -167,6 +167,30 @@ fn auto_deps_from_static_package_deps(
Ok(pkgs.into_iter().collect())
}
pub struct BuildResult {
pub stage_dirs: Vec<PathBuf>,
pub auto_deps: BTreeSet<PackageName>,
pub cached: bool,
}
impl BuildResult {
pub fn new(stage_dirs: Vec<PathBuf>, auto_deps: BTreeSet<PackageName>) -> Self {
BuildResult {
stage_dirs,
auto_deps,
cached: false,
}
}
pub fn cached(stage_dirs: Vec<PathBuf>, auto_deps: BTreeSet<PackageName>) -> 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<PathBuf>, BTreeSet<PackageName>), String> {
) -> Result<BuildResult, String> {
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<PathBuf>, BTreeSet<PackageName>), String> {
) -> Result<BuildResult, String> {
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)]

View File

@ -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<PathBuf>,
auto_deps: &BTreeSet<PackageName>,
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() {