Merge branch 'logs-n-verbosity' into 'master'

Allow logging for non TUI and verbose tuning

See merge request redox-os/cookbook!682
This commit is contained in:
Jeremy Soller 2025-11-07 10:40:05 -07:00
commit 6c080eb8f1
6 changed files with 102 additions and 64 deletions

View File

@ -63,6 +63,7 @@ const REPO_HELP_STR: &str = r#"
cook env and their defaults:
CI= set to any value to disable TUI
COOKBOOK_LOGS= whether to capture build logs (default is !CI)
COOKBOOK_OFFLINE=false prevent internet access if possible
COOKBOOK_NONSTOP=false pkeep running even a recipe build failed
COOKBOOK_VERBOSE=true print success/error on each recipe
@ -144,7 +145,7 @@ impl CliConfig {
cookbook_dir: current_dir.join("recipes"),
repo_dir: current_dir.join("repo"),
// build dir here is hardcoded in repo_builder as well
logs_dir: if get_config().cook.tui_logs {
logs_dir: if get_config().cook.logs {
Some(current_dir.join("build/logs"))
} else {
None
@ -212,17 +213,15 @@ fn main_inner() -> anyhow::Result<()> {
for recipe in &recipe_names {
match repo_inner(&config, &command, recipe) {
Ok(_) => {
if verbose {
eprintln!(
"{}{}{} {} - successful{}{}",
style::Bold,
color::Fg(color::AnsiValue(46)),
command.to_string(),
recipe.name.as_str(),
color::Fg(color::Reset),
style::Reset,
);
}
eprintln!(
"{}{}{} {} - successful{}{}",
style::Bold,
color::Fg(color::AnsiValue(46)),
command.to_string(),
recipe.name.as_str(),
color::Fg(color::Reset),
style::Reset,
);
}
Err(e) => {
if config.cook.nonstop && verbose {
@ -264,12 +263,48 @@ fn repo_inner(
recipe: &CookRecipe,
) -> Result<(), anyhow::Error> {
Ok(match *command {
CliCommand::Fetch => {
handle_fetch(recipe, config, &None)?;
}
CliCommand::Cook => {
let source_dir = handle_fetch(recipe, config, &None)?;
handle_cook(recipe, config, source_dir, recipe.is_deps, &None)?
CliCommand::Fetch | CliCommand::Cook => {
let repo_inner_fn = move |logger: &PtyOut| -> Result<(), anyhow::Error> {
let source_dir = handle_fetch(recipe, config, logger)?;
if *command == CliCommand::Cook {
handle_cook(recipe, config, source_dir, recipe.is_deps, logger)?;
}
Ok(())
};
let Some(log_path) = &config.logs_dir else {
return repo_inner_fn(&None);
};
let (status_tx, status_rx) = mpsc::channel::<StatusUpdate>();
let (mut stdout_writer, mut stderr_writer) = setup_logger(&status_tx, &recipe.name);
let mut app = TuiApp::new(vec![recipe.clone()]);
app.dump_logs_anyway = true;
let th = thread::spawn(move || {
while let Ok(update) = status_rx.recv() {
let mut should_break = false;
if let StatusUpdate::FlushLog(_p, _q) = &update {
should_break = true;
}
app.update_status(update);
if should_break {
break;
}
}
});
let mut logger = Some((&mut stdout_writer, &mut stderr_writer));
let result = repo_inner_fn(&logger);
if let Err(err_ctx) = &result {
log_to_pty!(&logger, "\n{:?}", err_ctx)
}
// successful fetch is not that useful to log
if *command == CliCommand::Cook || result.is_err() {
flush_pty(&mut logger);
let log_path = log_path.join(format!("{}.log", recipe.name.as_str()));
status_tx
.send(StatusUpdate::FlushLog(recipe.name.clone(), log_path))
.unwrap_or_default();
}
let _ = th.join();
}
CliCommand::Unfetch => handle_clean(recipe, config, true, true)?,
CliCommand::Clean => handle_clean(recipe, config, false, true)?,
@ -598,7 +633,7 @@ enum RecipeStatus {
Failed(String),
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
enum StatusUpdate {
StartFetch(PackageName),
Fetched(CookRecipe),
@ -650,6 +685,7 @@ struct TuiApp {
cook_panel_rect: Option<Rect>,
log_panel_rect: Option<Rect>,
prompt: Option<FailurePrompt>,
dump_logs_anyway: bool,
dump_logs_on_exit: Option<(PackageName, String)>,
}
@ -681,6 +717,7 @@ impl TuiApp {
cook_panel_rect: None,
log_panel_rect: None,
prompt: None,
dump_logs_anyway: false,
dump_logs_on_exit: None,
}
}
@ -759,6 +796,9 @@ impl TuiApp {
StatusUpdate::PushLog(name, chunk) => {
let buffer = self.log_byte_buffer.entry(name.clone()).or_default();
buffer.extend_from_slice(&chunk);
if self.dump_logs_anyway {
let _ = std::io::stdout().write_all(&chunk);
}
let log_list = self.logs.entry(name.clone()).or_default();
while let Some(newline_pos) = buffer.iter().position(|&b| b == b'\n') {
let line_bytes = buffer.drain(..=newline_pos).collect::<Vec<u8>>();
@ -842,12 +882,12 @@ fn run_tui_cook(
'done: for (recipe, source_dir) in work_rx {
let name = recipe.name.clone();
let is_deps = recipe.is_deps;
cooker_status_tx
.send(StatusUpdate::StartCook(name.clone()))
.unwrap();
let (mut stdout_writer, mut stderr_writer) = setup_logger(&cooker_status_tx, &name);
let mut logger = Some((&mut stdout_writer, &mut stderr_writer));
'again: loop {
cooker_status_tx
.send(StatusUpdate::StartCook(name.clone()))
.unwrap();
let handler = handle_cook(
&recipe,
&cooker_config,
@ -937,14 +977,12 @@ fn run_tui_cook(
let fetcher_handle = thread::spawn(move || {
'done: for recipe in fetcher_recipes {
let name = recipe.name.clone();
fetcher_status_tx
.send(StatusUpdate::StartFetch(name.clone()))
.unwrap();
let (mut stdout_writer, mut stderr_writer) = setup_logger(&fetcher_status_tx, &name);
let mut logger = Some((&mut stdout_writer, &mut stderr_writer));
'again: loop {
fetcher_status_tx
.send(StatusUpdate::StartFetch(name.clone()))
.unwrap();
let handler = handle_fetch(&recipe, &fetcher_config, &logger);
if let Some(log_path) = fetcher_config.logs_dir.as_ref()
// successful fetch log usually not that helpful

View File

@ -12,12 +12,11 @@ pub struct CookConfigOpt {
/// whether to use TUI to allow parallel build
/// default value is yes if "CI" env unset and STDIN is open.
pub tui: Option<bool>,
/// whether to write logs to build/logs dir
/// only usable when tui is used
pub tui_logs: Option<bool>,
/// whether to write logs to build/logs dir, default true on TUI
pub logs: Option<bool>,
/// whether to ignore build errors
pub nonstop: Option<bool>,
/// whether to print success recipes info and warnings
/// whether to print verbose logs to certain commands
/// build failure still be printed anyway
pub verbose: Option<bool>,
}
@ -27,7 +26,7 @@ pub struct CookConfig {
pub offline: bool,
pub jobs: usize,
pub tui: bool,
pub tui_logs: bool,
pub logs: bool,
pub nonstop: bool,
pub verbose: bool,
}
@ -38,7 +37,7 @@ impl From<CookConfigOpt> for CookConfig {
offline: value.offline.unwrap(),
jobs: value.jobs.unwrap(),
tui: value.tui.unwrap(),
tui_logs: value.tui_logs.unwrap(),
logs: value.logs.unwrap(),
nonstop: value.nonstop.unwrap(),
verbose: value.verbose.unwrap(),
}
@ -80,8 +79,8 @@ pub fn init_config() {
.unwrap_or(1),
));
}
if config.cook_opt.tui_logs.is_none() {
config.cook_opt.tui_logs = config.cook_opt.tui;
if config.cook_opt.logs.is_none() {
config.cook_opt.logs = Some(extract_env("COOKBOOK_LOGS", config.cook_opt.tui.unwrap()));
}
if config.cook_opt.offline.is_none() {
config.cook_opt.offline = Some(extract_env("COOKBOOK_OFFLINE", false));

View File

@ -29,6 +29,7 @@ fn auto_deps_from_dynamic_linking(
) -> BTreeSet<PackageName> {
let mut paths = BTreeSet::new();
let mut visited = BTreeSet::new();
let verbose = crate::config::get_config().cook.verbose;
// Base directories may need to be updated for packages that place binaries in odd locations.
let mut walk = VecDeque::from([
stage_dir.join("libexec"),
@ -95,7 +96,9 @@ fn auto_deps_from_dynamic_linking(
continue;
};
if let Ok(relative_path) = path.strip_prefix(stage_dir) {
log_to_pty!(logger, "DEBUG: {} needs {}", relative_path.display(), name);
if verbose {
log_to_pty!(logger, "DEBUG: {} needs {}", relative_path.display(), name);
}
}
needed.insert(name.to_string());
}
@ -129,7 +132,9 @@ fn auto_deps_from_dynamic_linking(
continue;
};
if needed.contains(child_name) {
log_to_pty!(logger, "DEBUG: {} provides {}", dep, child_name);
if verbose {
log_to_pty!(logger, "DEBUG: {} provides {}", dep, child_name);
}
deps.insert(dep.clone());
missing.remove(child_name);
}
@ -138,8 +143,10 @@ fn auto_deps_from_dynamic_linking(
}
}
for name in missing {
log_to_pty!(logger, "WARN: {} missing", name);
if verbose {
for name in missing {
log_to_pty!(logger, "INFO: {} missing", name);
}
}
deps
@ -171,6 +178,8 @@ pub fn build(
) -> Result<(PathBuf, BTreeSet<PackageName>), String> {
let sysroot_dir = target_dir.join("sysroot");
let stage_dir = target_dir.join("stage");
let cli_verbose = crate::config::get_config().cook.verbose;
let cli_jobs = crate::config::get_config().cook.jobs;
if recipe.build.kind == BuildKind::None {
// metapackages don't need to do anything here
return Ok((stage_dir, BTreeSet::new()));
@ -207,12 +216,7 @@ pub fn build(
if sysroot_dir.is_dir() {
let sysroot_modified = modified_dir(&sysroot_dir)?;
if sysroot_modified < source_modified || sysroot_modified < deps_modified {
log_to_pty!(
logger,
"DEBUG: '{}' newer than '{}'",
source_dir.display(),
sysroot_dir.display()
);
log_to_pty!(logger, "DEBUG: updating '{}'", sysroot_dir.display());
remove_all(&sysroot_dir)?;
}
}
@ -257,12 +261,7 @@ pub fn build(
if stage_dir.is_dir() {
let stage_modified = modified_dir(&stage_dir)?;
if stage_modified < source_modified || stage_modified < deps_modified {
log_to_pty!(
logger,
"DEBUG: '{}' newer than '{}'",
source_dir.display(),
stage_dir.display()
);
log_to_pty!(logger, "DEBUG: updating '{}'", stage_dir.display());
remove_all(&stage_dir)?;
}
}
@ -328,10 +327,10 @@ pub fn build(
let cookbook_stage = stage_dir_tmp.canonicalize().unwrap();
let cookbook_source = source_dir.canonicalize().unwrap();
let cookbook_sysroot = sysroot_dir.canonicalize().unwrap();
let bash_args = if cli_verbose { "-ex" } else { "-e" };
let mut command = if is_redox() {
let mut command = Command::new("bash");
command.arg("-ex");
command.arg(bash_args);
command.env("COOKBOOK_REDOXER", "cargo");
command
} else {
@ -339,7 +338,7 @@ pub fn build(
.canonicalize()
.unwrap_or(PathBuf::from("/bin/false"));
let mut command = Command::new(&cookbook_redoxer);
command.arg("env").arg("bash").arg("-ex");
command.arg("env").arg("bash").arg(bash_args);
command.env("COOKBOOK_REDOXER", &cookbook_redoxer);
command
};
@ -351,6 +350,10 @@ pub fn build(
command.env("COOKBOOK_STAGE", &cookbook_stage);
command.env("COOKBOOK_SOURCE", &cookbook_source);
command.env("COOKBOOK_SYSROOT", &cookbook_sysroot);
command.env("COOKBOOK_MAKE_JOBS", cli_jobs.to_string());
if cli_verbose {
command.env("COOKBOOK_VERBOSE", "1");
}
if offline_mode {
command.env("COOKBOOK_OFFLINE", "1");
}

View File

@ -364,11 +364,14 @@ pub(crate) fn fetch_extract_tar(
logger: &PtyOut,
) -> Result<(), String> {
let mut command = Command::new("tar");
let verbose = crate::config::get_config().cook.verbose;
if is_redox() {
command.arg("xvf");
command.arg(if verbose { "xvf" } else { "xf" });
} else {
command.arg("--extract");
command.arg("--verbose");
if verbose {
command.arg("--verbose");
}
command.arg("--file");
}
command.arg(&source_tar);

View File

@ -44,12 +44,7 @@ pub fn package(
if package_file.is_file() {
let stage_modified = modified_dir(stage_dir)?;
if modified(&package_file)? < stage_modified {
log_to_pty!(
logger,
"DEBUG: '{}' newer than '{}'",
stage_dir.display(),
package_file.display()
);
log_to_pty!(logger, "DEBUG: updating '{}'", package_file.display());
remove_all(&package_file)?;
remove_all(&package_meta)?;
}

View File

@ -11,11 +11,11 @@ function DYNAMIC_INIT {
if [ "${TARGET}" != "x86_64-unknown-redox" ]
then
echo "WARN: ${TARGET} does not support dynamic linking." >&2
[ -z "${COOKBOOK_VERBOSE}" ] || echo "WARN: ${TARGET} does not support dynamic linking." >&2
return
fi
echo "DEBUG: Program is being compiled dynamically."
[ -z "${COOKBOOK_VERBOSE}" ] || echo "DEBUG: Program is being compiled dynamically."
COOKBOOK_CONFIGURE_FLAGS=(
--host="${GNU_TARGET}"