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 = ::Err; fn from_str(s: &str) -> Result { 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 { 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, /// 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, }, } 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, } 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::>()) }; 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); } }