diff --git a/src/bin/repo.rs b/src/bin/repo.rs index 69eecb2a..0bd8628f 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -72,6 +72,7 @@ struct CliConfig { cookbook_dir: PathBuf, repo_dir: PathBuf, sysroot_dir: PathBuf, + logs_dir: Option, category: Option, filesystem: Option, with_package_deps: bool, @@ -140,6 +141,12 @@ impl CliConfig { //FIXME: This config is unused as redox-pkg harcoded this to $PWD/recipes 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 { + Some(current_dir.join("build/logs")) + } else { + None + }, category: None, sysroot_dir: if cfg!(target_os = "redox") { PathBuf::from("/") @@ -342,6 +349,9 @@ fn parse_args(args: Vec) -> anyhow::Result<(CliConfig, CliCommand, Vec), + FlushLog(PackageName, PathBuf), FetchThreadFinished, CookThreadFinished, } @@ -676,6 +687,52 @@ impl TuiApp { } } + pub fn get_active_name(&self) -> Option { + if self.log_view_job == JobType::Cook { + self.active_cook.clone() + } else { + self.active_fetch.clone() + } + } + + pub fn get_active_log( + &self, + ) -> ( + Option, + Option<&Vec>, + Option>, + ) { + let active_name = self.get_active_name(); + let (log_text, log_line) = if let Some(active_name) = active_name.as_ref() { + self.get_recipe_log(active_name) + } else { + (None, None) + }; + + (active_name, log_text, log_line) + } + + pub fn get_recipe_log( + &self, + recipe_name: &PackageName, + ) -> (Option<&Vec>, Option>) { + let log_text = self.logs.get(recipe_name); + let log_line = if let Some(b) = self.log_byte_buffer.get(recipe_name) { + Some(String::from_utf8_lossy(b)) + } else { + None + }; + (log_text, log_line) + } + + pub fn write_log(&self, recipe_name: &PackageName, log_path: &PathBuf) -> anyhow::Result<()> { + let (Some(logs), line) = self.get_recipe_log(recipe_name) else { + return Ok(()); + }; + fs::write(log_path, join_logs(logs, line))?; + return Ok(()); + } + // Update the state based on a message from a worker thread fn update_status(&mut self, update: StatusUpdate) { let (name, new_status) = match update { @@ -711,6 +768,12 @@ impl TuiApp { } return; } + StatusUpdate::FlushLog(name, path) => { + // TODO: This blocks the TUI for a moment, maybe open separate thread? + // FIXME: handle error here? + let _ = self.write_log(&name, &path); + return; + } StatusUpdate::Cooked(recipe) => { if self.active_cook.as_ref() == Some(&recipe.name) { self.active_cook = None; @@ -784,13 +847,20 @@ fn run_tui_cook( let (mut stdout_writer, mut stderr_writer) = setup_logger(&cooker_status_tx, &name); let logger = Some((&mut stdout_writer, &mut stderr_writer)); 'again: loop { - match handle_cook( + let handler = handle_cook( &recipe, &cooker_config, source_dir.clone(), is_deps, &logger, - ) { + ); + if let Some(log_path) = cooker_config.logs_dir.as_ref() { + let log_path = log_path.join(name.as_str()); + cooker_status_tx + .send(StatusUpdate::FlushLog(name.clone(), log_path)) + .unwrap_or_default(); + } + match handler { Ok(()) => { cooker_status_tx .send(StatusUpdate::Cooked(recipe)) @@ -870,7 +940,14 @@ fn run_tui_cook( let logger = Some((&mut stdout_writer, &mut stderr_writer)); 'again: loop { - match handle_fetch(&recipe, &fetcher_config, &logger) { + let handler = handle_fetch(&recipe, &fetcher_config, &logger); + if let Some(log_path) = fetcher_config.logs_dir.as_ref() { + let log_path = log_path.join(name.as_str()); + fetcher_status_tx + .send(StatusUpdate::FlushLog(name.clone(), log_path)) + .unwrap_or_default(); + } + match handler { Ok(source_dir) => { fetcher_status_tx .send(StatusUpdate::Fetched(recipe.clone())) @@ -1041,7 +1118,7 @@ fn run_tui_cook( ); f.render_stateful_widget(cook_list, cook_chunk, &mut app.cook_list_state); - let (active_name, log_text, log_line) = get_active_log(&app); + let (active_name, log_text, log_line) = app.get_active_log(); let log_title = if let Some(active_name) = active_name { format!( " {} Log: {} ", @@ -1152,16 +1229,11 @@ fn run_tui_cook( if let Some((app, res)) = handle_prompt_input(&event, &mut app) { prompting.swap(res as u32, Ordering::SeqCst); if res == PromptOption::Exit { - let (name, log, line) = get_active_log(&app); + let (name, log, line) = app.get_active_log(); if let Some(name) = name && let Some(log) = log { - let mut logs = log.join("\n"); - if let Some(line) = line { - logs.push_str("\n"); - logs.push_str(handle_cr(&line)); - } - app.dump_logs_on_exit = Some((name.to_owned(), logs)); + app.dump_logs_on_exit = Some((name.to_owned(), join_logs(log, line))); } running.store(false, Ordering::SeqCst); } @@ -1194,38 +1266,20 @@ fn run_tui_cook( Ok(app.dump_logs_on_exit) } +fn join_logs(log: &Vec, line: Option>) -> String { + let mut logs = log.join("\n"); + if let Some(line) = line { + logs.push_str("\n"); + logs.push_str(handle_cr(&line)); + } + logs +} + fn handle_cr<'a>(buffer: &'a Cow<'_, str>) -> &'a str { let st = buffer.trim_end(); st.rsplit('\r').next().unwrap_or(&st) } -fn get_active_log( - app: &TuiApp, -) -> ( - Option, - Option<&Vec>, - Option>, -) { - let active_name = if app.log_view_job == JobType::Cook { - app.active_cook.clone() - } else { - app.active_fetch.clone() - }; - let log_text = if let Some(active_name) = &active_name { - app.logs.get(active_name) - } else { - None - }; - let log_line = if let Some(active_name) = &active_name - && let Some(b) = app.log_byte_buffer.get(active_name) - { - Some(String::from_utf8_lossy(b)) - } else { - None - }; - (active_name, log_text, log_line) -} - fn handle_main_event(app: &mut TuiApp, event: &Event) { match event { Event::Key(key) => match key { diff --git a/src/config.rs b/src/config.rs index 460fa43d..f014092e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,9 @@ 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, + /// whether to write logs to build/logs dir + /// only usable when tui is used + pub tui_logs: Option, /// whether to ignore build errors pub nonstop: Option, /// whether to print success recipes info and warnings @@ -24,6 +27,7 @@ pub struct CookConfig { pub offline: bool, pub jobs: usize, pub tui: bool, + pub tui_logs: bool, pub nonstop: bool, pub verbose: bool, } @@ -34,6 +38,7 @@ impl From for CookConfig { offline: value.offline.unwrap(), jobs: value.jobs.unwrap(), tui: value.tui.unwrap(), + tui_logs: value.tui_logs.unwrap(), nonstop: value.nonstop.unwrap(), verbose: value.verbose.unwrap(), } @@ -75,6 +80,9 @@ 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.offline.is_none() { config.cook_opt.offline = Some(extract_env("COOKBOOK_OFFLINE", false)); } diff --git a/src/cook/cook_build.rs b/src/cook/cook_build.rs index 4268f08d..75f69f87 100644 --- a/src/cook/cook_build.rs +++ b/src/cook/cook_build.rs @@ -19,25 +19,10 @@ use std::{ time::SystemTime, }; -use crate::is_redox; +use crate::{is_redox, log_to_pty}; use crate::REMOTE_PKG_SOURCE; -macro_rules! log_warn { - ($logger:expr, $($arg:tt)+) => { - use std::io::Write; - - if $logger.is_some() { - let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write( - format!($($arg)+) - .as_bytes(), - ); - } else { - eprintln!($($arg)+); - } - }; -} - fn auto_deps_from_dynamic_linking( stage_dir: &Path, dep_pkgars: &BTreeSet<(PackageName, PathBuf)>, @@ -61,7 +46,7 @@ fn auto_deps_from_dynamic_linking( }; if visited.contains(&dir) { #[cfg(debug_assertions)] - log_warn!( + log_to_pty!( logger, "DEBUG: auto_deps => Skipping `{dir:?}` (already visited)" ); @@ -111,7 +96,7 @@ fn auto_deps_from_dynamic_linking( continue; }; if let Ok(relative_path) = path.strip_prefix(stage_dir) { - log_warn!(logger, "DEBUG: {} needs {}", relative_path.display(), name); + log_to_pty!(logger, "DEBUG: {} needs {}", relative_path.display(), name); } needed.insert(name.to_string()); } @@ -145,7 +130,7 @@ fn auto_deps_from_dynamic_linking( continue; }; if needed.contains(child_name) { - log_warn!(logger, "DEBUG: {} provides {}", dep, child_name); + log_to_pty!(logger, "DEBUG: {} provides {}", dep, child_name); deps.insert(dep.clone()); missing.remove(child_name); } @@ -155,7 +140,7 @@ fn auto_deps_from_dynamic_linking( } for name in missing { - log_warn!(logger, "WARN: {} missing", name); + log_to_pty!(logger, "WARN: {} missing", name); } deps @@ -225,7 +210,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_warn!( + log_to_pty!( logger, "DEBUG: '{}' newer than '{}'", source_dir.display(), @@ -275,7 +260,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_warn!( + log_to_pty!( logger, "DEBUG: '{}' newer than '{}'", source_dir.display(), diff --git a/src/cook/fetch.rs b/src/cook/fetch.rs index 7f569fb5..b15a3554 100644 --- a/src/cook/fetch.rs +++ b/src/cook/fetch.rs @@ -3,6 +3,7 @@ use crate::cook::fs::*; use crate::cook::pty::PtyOut; use crate::cook::script::*; use crate::is_redox; +use crate::log_to_pty; use crate::recipe::BuildKind; use crate::recipe::Recipe; use crate::{blake3, recipe::SourceRecipe}; @@ -10,21 +11,6 @@ use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; -macro_rules! log_warn { - ($logger:expr, $($arg:tt)+) => { - use std::io::Write; - - if $logger.is_some() { - let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write( - format!($($arg)+) - .as_bytes(), - ); - } else { - eprintln!($($arg)+); - } - }; -} - pub(crate) fn get_blake3(path: &PathBuf, show_progress: bool) -> Result { if show_progress { blake3::blake3_progress(&path) @@ -120,7 +106,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result { if !source_dir.is_dir() || modified_dir(Path::new(&path))? > modified_dir(&source_dir)? { - log_warn!( + log_to_pty!( logger, "[DEBUG]: {} is newer than {}", path, @@ -266,7 +252,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result Result Result Result { if !source_dir.is_dir() { - log_warn!( + log_to_pty!( logger, "WARNING: Recipe without source section expected source dir at '{}'", source_dir.display(), diff --git a/src/cook/pty.rs b/src/cook/pty.rs index 96bae8c8..35f641bd 100644 --- a/src/cook/pty.rs +++ b/src/cook/pty.rs @@ -17,10 +17,9 @@ pub use std::os::unix::io::RawFd; #[macro_export] macro_rules! log_to_pty { ($logger:expr, $($arg:tt)+) => { - use std::io::Write; - if $logger.is_some() { - let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write( + use std::io::Write; + let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write( format!($($arg)+) .as_bytes(), );