diff --git a/src/console_utils.rs b/src/console_utils.rs index ca9bb688..cbba45f0 100644 --- a/src/console_utils.rs +++ b/src/console_utils.rs @@ -1,7 +1,6 @@ //! This module contains utilities for logging and progress bar handling. use std::{ borrow::Cow, - collections::HashMap, future::Future, io, str::FromStr, @@ -56,11 +55,17 @@ where } } +#[derive(Debug)] +struct SpanInfo { + id: Id, + start_time: Instant, + header: String, + header_printed: bool, +} + #[derive(Debug, Default)] struct SharedState { - indentation_level: usize, - timestamps: HashMap, - formatted_spans: HashMap, + span_stack: Vec, warnings: Vec, } @@ -155,104 +160,104 @@ where fn on_new_span( &self, attrs: &tracing_core::span::Attributes<'_>, - id: &tracing_core::span::Id, + id: &Id, ctx: Context<'_, S>, ) { let mut state = self.state.lock().unwrap(); - state.timestamps.insert(id.clone(), Instant::now()); - let span = ctx.span(id); - if let Some(span) = span { + if let Some(span) = ctx.span(id) { let mut s = Vec::new(); let mut w = io::Cursor::new(&mut s); attrs.record(&mut CustomVisitor::new(&mut w)); let s = String::from_utf8_lossy(w.get_ref()); - if !s.is_empty() { - state - .formatted_spans - .insert(id.clone(), format!("{}{}", span.name(), s)); + let name = if s.is_empty() { + span.name().to_string() } else { - state - .formatted_spans - .insert(id.clone(), span.name().to_string()); - } - } - } + format!("{}{}", span.name(), s) + }; - fn on_enter(&self, id: &Id, _ctx: Context<'_, S>) { - let mut state = self.state.lock().unwrap(); - let ind = indent_levels(state.indentation_level); - if let Some(txt) = state.formatted_spans.get(id) { - eprintln!("{ind}\n{ind} {} {}", style("╭─").cyan(), txt); - } + let indent = indent_levels(state.span_stack.len()); + let header = format!("{indent}\n{indent} {} {}", style("╭─").cyan(), name); - state.indentation_level += 1; + state.span_stack.push(SpanInfo { + id: id.clone(), + start_time: Instant::now(), + header, + header_printed: false, + }); + } } fn on_exit(&self, id: &Id, _ctx: Context<'_, S>) { let mut state = self.state.lock().unwrap(); - let prev_ind = indent_levels(state.indentation_level); + if let Some(pos) = state.span_stack.iter().position(|info| &info.id == id) { + let elapsed = state.span_stack[pos].start_time.elapsed(); + let header_printed = state.span_stack[pos].header_printed; + state.span_stack.truncate(pos); - if state.indentation_level > 0 { - state.indentation_level -= 1; - } - - let ind = indent_levels(state.indentation_level); - - let elapsed_time = state - .timestamps - .remove(id) - .map(|t| t.elapsed()) - .unwrap_or_default(); + if !header_printed { + return; + } - let human_duration = HumanDuration(elapsed_time); + let indent = indent_levels(pos); + let indent_plus_one = indent_levels(pos + 1); - eprintln!( - "{prev_ind}\n{ind} {} (took {})", - style("╰───────────────────").cyan(), - human_duration - ); + eprintln!( + "{indent_plus_one}\n{indent} {} (took {})", + style("╰───────────────────").cyan(), + HumanDuration(elapsed) + ); + } } fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { let mut state = self.state.lock().unwrap(); - let indent_str = indent_levels(state.indentation_level); + let indent = indent_levels(state.span_stack.len()); + + // Print pending headers + for span_info in &mut state.span_stack { + if !span_info.header_printed { + eprintln!("{}", span_info.header); + span_info.header_printed = true; + } + } let mut s = Vec::new(); event.record(&mut CustomVisitor::new(&mut s)); - let s = String::from_utf8_lossy(&s); - - let (prefix, prefix_len) = - if event.metadata().level() <= &tracing_core::metadata::Level::WARN { - state.warnings.push(s.to_string()); - if event.metadata().level() == &tracing_core::metadata::Level::ERROR { - (style("× error ").red().bold(), 7) - } else { - (style("⚠ warning ").yellow().bold(), 9) - } - } else { - (style(""), 0) - }; - - let width: usize = terminal_size::terminal_size() - .map(|(w, _)| w.0) - .unwrap_or(160) as usize; + let message = String::from_utf8_lossy(&s); - let max_width = width - (state.indentation_level * 2) - 1 - prefix_len; + let (prefix, prefix_len) = if event.metadata().level() <= &Level::WARN { + state.warnings.push(message.to_string()); + if event.metadata().level() == &Level::ERROR { + (style("× error ").red().bold(), 7) + } else { + (style("⚠ warning ").yellow().bold(), 9) + } + } else { + (style(""), 0) + }; self.progress_bars.suspend(|| { - for line in s.lines() { - // split line into max_width chunks - if line.len() <= max_width { - eprintln!("{} {}{}", indent_str, prefix, line); - } else { - chunk_string_without_ansi(line, max_width) - .iter() - .for_each(|chunk| { - eprintln!("{} {}{}", indent_str, prefix, chunk); - }); + if !self.wrap_lines { + for line in message.lines() { + eprintln!("{} {}{}", indent, prefix, line); + } + } else { + let width = terminal_size::terminal_size() + .map(|(w, _)| w.0) + .unwrap_or(160) as usize; + let max_width = width - (state.span_stack.len() * 2) - 1 - prefix_len; + + for line in message.lines() { + if line.len() <= max_width { + eprintln!("{} {}{}", indent, prefix, line); + } else { + for chunk in chunk_string_without_ansi(line, max_width) { + eprintln!("{} {}{}", indent, prefix, chunk); + } + } } } }); @@ -263,6 +268,7 @@ where #[derive(Debug)] pub struct LoggingOutputHandler { state: Arc>, + wrap_lines: bool, progress_bars: MultiProgress, writer: io::Stderr, } @@ -270,6 +276,7 @@ pub struct LoggingOutputHandler { impl Clone for LoggingOutputHandler { fn clone(&self) -> Self { Self { + wrap_lines: self.wrap_lines, state: self.state.clone(), progress_bars: self.progress_bars.clone(), writer: io::stderr(), @@ -281,6 +288,7 @@ impl Default for LoggingOutputHandler { /// Creates a new output handler. fn default() -> Self { Self { + wrap_lines: true, state: Arc::new(Mutex::new(SharedState::default())), progress_bars: MultiProgress::new(), writer: io::stderr(), @@ -289,20 +297,11 @@ impl Default for LoggingOutputHandler { } impl LoggingOutputHandler { - /// Create a new logging handler with the given multi-progress. - pub fn from_multi_progress(multi_progress: MultiProgress) -> LoggingOutputHandler { - Self { - state: Arc::new(Mutex::new(SharedState::default())), - progress_bars: multi_progress, - writer: io::stderr(), - } - } - /// Return a string with the current indentation level (bars added to the /// front of the string). pub fn with_indent_levels(&self, template: &str) -> String { let state = self.state.lock().unwrap(); - let indent_str = indent_levels(state.indentation_level); + let indent_str = indent_levels(state.span_stack.len()); format!("{} {}", indent_str, template) } @@ -311,6 +310,12 @@ impl LoggingOutputHandler { &self.progress_bars } + /// Set the multi-progress instance. + pub fn with_multi_progress(mut self, multi_progress: MultiProgress) -> Self { + self.progress_bars = multi_progress; + self + } + /// Returns the style to use for a progressbar that is currently in /// progress. pub fn default_bytes_style(&self) -> indicatif::ProgressStyle { @@ -482,6 +487,7 @@ impl<'a> MakeWriter<'a> for LoggingOutputHandler { self.clone() } } + /////////////////////// // LOGGING CLI utils // /////////////////////// @@ -560,11 +566,19 @@ pub fn init_logging( log_style: &LogStyle, verbosity: &Verbosity, color: &Color, + wrap_lines: Option, #[cfg(feature = "tui")] tui_log_sender: Option< tokio::sync::mpsc::UnboundedSender, >, ) -> Result { - let log_handler = LoggingOutputHandler::default(); + let mut log_handler = LoggingOutputHandler::default(); + + // Wrap lines by default, but disable it in CI + if let Some(wrap_lines) = wrap_lines { + log_handler.wrap_lines = wrap_lines; + } else if std::env::var("CI").is_ok() { + log_handler.wrap_lines = false; + } let use_colors = match color { Color::Always => Some(true), diff --git a/src/main.rs b/src/main.rs index 84809f43..aa07881a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ async fn main() -> miette::Result<()> { &app.log_style, &app.verbose, &app.color, + app.wrap_log_lines, #[cfg(feature = "tui")] None, ) @@ -94,6 +95,7 @@ async fn main() -> miette::Result<()> { &app.log_style, &app.verbose, &app.color, + Some(true), Some(tui.event_handler.sender.clone()), ) .into_diagnostic()?; diff --git a/src/opt.rs b/src/opt.rs index a3cfe644..70be2e02 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -129,6 +129,14 @@ pub struct App { )] pub log_style: LogStyle, + #[clap( + long, + env = "RATTLER_BUILD_WRAP_LOG_LINES", + default_value = "true", + global = true + )] + pub wrap_log_lines: Option, + /// Enable or disable colored output from rattler-build. /// Also honors the `CLICOLOR` and `CLICOLOR_FORCE` environment variable. #[clap( diff --git a/src/script.rs b/src/script.rs index b0bbdcfc..56fa2b5d 100644 --- a/src/script.rs +++ b/src/script.rs @@ -374,16 +374,7 @@ impl Interpreter for CmdExeInterpreter { let build_script = format!( "{}\n{}", - CMDEXE_PREAMBLE - .replace("((script_path))", &build_env_path.to_string_lossy()) - .replace( - "((LIBRARY_INC))", - &args.env_vars.get("LIBRARY_INC").unwrap_or(&"".to_string()) - ) - .replace( - "((LIBRARY_LIB))", - &args.env_vars.get("LIBRARY_LIB").unwrap_or(&"".to_string()) - ), + CMDEXE_PREAMBLE.replace("((script_path))", &build_env_path.to_string_lossy()), args.script.script() ); tokio::fs::write( diff --git a/src/source/extract.rs b/src/source/extract.rs index b616aed5..db2e3d29 100644 --- a/src/source/extract.rs +++ b/src/source/extract.rs @@ -214,7 +214,8 @@ mod test { let mut file = File::create(&file_path).unwrap(); _ = file.write_all(HELLO_WORLD_ZIP_FILE); - let fancy_log = LoggingOutputHandler::from_multi_progress(multi_progress); + let fancy_log = LoggingOutputHandler::default().with_multi_progress(multi_progress.clone()); + let res = extract_zip(file_path, tempdir.path(), &fancy_log); assert!(term.contents().trim().starts_with( "Extracting zip [00:00:00] [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━]"