opentitanlib/util/printer.rs
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use std::io::Read;
7use std::sync::{Arc, Mutex};
8
9// If the logger is configured to use the module's path, the log message will always
10// look like `[<timestamp> INFO ...util::printer] <message>` which is inconvenient
11// because we loose the source of the message.
12// However, if the logger is configured to print the target instead of the module path
13// then it will look like `[<timestamp> INFO <target>] <message>`. Since the default
14// target is the module's path, this allows for a non-breaking change by configuring
15// the logger to always print the target instead of the module.
16
17// Accumulates output from a process's `stdout`.
18pub fn accumulate(stdout: impl Read, target: &str, accumulator: Arc<Mutex<String>>) {
19 if let Err(e) = worker(stdout, target, accumulator) {
20 log::error!("accumulate error: {:?}", e);
21 }
22}
23
24fn worker(mut stdout: impl Read, target: &str, accumulator: Arc<Mutex<String>>) -> Result<()> {
25 let mut s = String::default();
26 loop {
27 read(&mut stdout, &mut s)?;
28 let mut lines = s.split('\n').collect::<Vec<&str>>();
29 let next = if !s.ends_with('\n') {
30 // If we didn't read a complete line at the end, save it for the
31 // next read.
32 lines.pop()
33 } else {
34 None
35 };
36 for line in lines {
37 // see comment at the top of this module
38 log::info!(target: target, "{}", line.trim_end_matches('\r'));
39 }
40 accumulator.lock().unwrap().push_str(&s);
41 s = next.unwrap_or("").to_string();
42 }
43}
44
45fn read(stdout: &mut impl Read, s: &mut String) -> Result<()> {
46 let mut buf = [0u8; 256];
47 let n = stdout.read(&mut buf)?;
48 s.push_str(&String::from_utf8_lossy(&buf[..n]));
49 Ok(())
50}