logviewer/src/main.rs
2026-03-31 17:36:18 +02:00

171 lines
4.3 KiB
Rust

use std::{
env::temp_dir,
ffi::OsString,
fmt::{Debug, Display},
fs::{self, File},
mem,
path::PathBuf,
process::{Command, exit},
str::FromStr,
};
mod tui;
use clap::{Parser, Subcommand, ValueEnum, builder::PossibleValue};
use jiff::Zoned;
use ratatui_themes::ThemeName;
#[repr(transparent)]
#[derive(Clone)]
pub struct Theme(ThemeName);
impl Debug for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.slug())
}
}
impl FromStr for Theme {
type Err = <ThemeName as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
impl ValueEnum for Theme {
fn value_variants<'a>() -> &'a [Self] {
// Safety: repr transparent
unsafe { mem::transmute(ThemeName::all()) }
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
Some(PossibleValue::new(self.to_string()))
}
}
macro_rules! types_crates {
() => {
"rustc_hir_typeck,rustc_infer,rustc_next_trait_solver,rustc_middle,rustc_traits,rustc_trait_selection,rustc_type_ir,rustc_ty_utils"
}
}
#[derive(Subcommand, Debug)]
enum Preset {
/// Explore logs
Show {
/// Path where the compiler source code lives, for links in the TUI to work.
#[arg(long = "compiler-root")]
compiler_root: Option<PathBuf>,
/// Path where the compiler source code lives, for links in the TUI to work.
#[arg(default_value_t = Theme(ThemeName::Dracula))]
#[arg(long = "theme")]
theme: Theme,
},
#[command(about = concat!("Get all the typesystem related logs: ", types_crates!()))]
Types,
/// Get all logs
All,
/// Specific, comma-separated crates to gather logs from
Crates {
#[arg(short, long)]
crates: Vec<String>,
},
}
fn default_tempdir() -> PathBuf {
let home = std::env::var("HOME").unwrap();
let t_rs_tempdirs = PathBuf::from(home).join("tempdirs");
let tempdir = if t_rs_tempdirs.exists() {
t_rs_tempdirs
} else {
temp_dir()
};
tempdir.join("rustc-logviz")
}
#[derive(Parser, Debug)]
#[command(version, about, long_about)]
struct Args {
#[command(subcommand)]
preset: Preset,
#[arg(default_value_os_t = default_tempdir())]
#[arg(global = true)]
#[arg(long = "logs-dir")]
logs_dir: PathBuf,
#[arg(trailing_var_arg = true)]
#[arg(allow_hyphen_values = true)]
#[arg(global = true)]
rest: Vec<OsString>,
}
fn main() {
let Args {
preset,
logs_dir,
rest,
} = Args::parse();
let rustc_log = match preset {
Preset::Show {
compiler_root,
theme: Theme(theme),
} => {
tui::run(logs_dir, compiler_root, theme);
exit(0);
}
Preset::Types => types_crates!().to_string(),
Preset::All => "debug".to_string(),
Preset::Crates { crates } => crates.join(",").to_string(),
};
let (first, rest) = {
let mut rest = rest.into_iter();
let Some(first) = rest.next() else {
eprintln!("no command given, exiting");
exit(0);
};
(first, rest.collect::<Vec<_>>())
};
if let Err(e) = fs::create_dir_all(&logs_dir) {
eprintln!("failed to create logs dir at {}: {e:?}", logs_dir.display());
exit(1)
}
let now = Zoned::now().strftime("%b %e %H:%M:%S");
let log_file_path = logs_dir.join(format!("{now}.log"));
let _log_file = match File::create(&log_file_path) {
Ok(i) => i,
Err(e) => {
eprintln!(
"failed to create logfile at {}: {e:?}",
log_file_path.display()
);
exit(1)
}
};
eprintln!("outputting json logs to {}", log_file_path.display());
if let Err(e) = Command::new(first)
.args(rest)
.env("RUSTC_LOG", rustc_log)
.env("RUSTC_LOG_FORMAT_JSON", "1")
.env("RUSTC_LOG_OUTPUT_TARGET", log_file_path)
.status()
{
eprintln!("failed to spawn command: {e:?}, exiting");
exit(1);
}
}