use std::io::{IsTerminal, Write};
use std::path::Path;
use std::process::Command;
use anyhow::{Result, bail};
use clap::Args;
use crate::cmd::util::pkg_pattern;
use bob::db::Database;
use bob::{BuildResult, PackageState, Stage};
#[derive(Debug, Args)]
pub struct LogArgs {
#[arg(short, long)]
list: bool,
#[arg(short, long, value_enum)]
stage: Option<Stage>,
package: String,
}
pub fn run(db: &Database, args: LogArgs) -> Result<()> {
let pattern = pkg_pattern(&args.package)?;
let results = db.get_all_build_results()?;
let matches: Vec<&BuildResult> = results
.iter()
.filter(|r| matches!(r.state, PackageState::Failed(_)))
.filter(|r| {
pattern.is_match(r.pkgname.pkgname())
|| r.pkgpath
.as_ref()
.is_some_and(|p| pattern.is_match(p.as_str()))
})
.collect();
match matches.len() {
0 => bail!("No failed packages match '{}'", args.package),
1 => {
let result = matches[0];
if args.list {
list_logs(result)
} else {
let log_file = stage_log(result, args.stage)?;
page_file(&log_file)
}
}
_ => {
let mut msg = format!("Multiple failed packages match '{}':\n", args.package);
for r in &matches {
let pkgpath = r
.pkgpath
.as_ref()
.map_or(String::new(), |p| format!(" ({})", p));
msg.push_str(&format!(" {}{}\n", r.pkgname.pkgname(), pkgpath));
}
bail!("{}", msg.trim_end());
}
}
}
fn list_logs(result: &BuildResult) -> Result<()> {
let pkgname = result.pkgname.pkgname();
let log_dir = result
.log_dir
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No log directory for {}", pkgname))?;
let mut entries: Vec<(std::time::SystemTime, std::path::PathBuf)> = Vec::new();
for entry in std::fs::read_dir(log_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|e| e == "log") {
let mtime = entry.metadata()?.modified()?;
entries.push((mtime, path));
}
}
entries.sort_by_key(|(mtime, _)| *mtime);
for (_, path) in &entries {
println!("{}", path.display());
}
Ok(())
}
fn stage_log(result: &BuildResult, stage: Option<Stage>) -> Result<std::path::PathBuf> {
let pkgname = result.pkgname.pkgname();
let log_dir = result
.log_dir
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No log directory for {}", pkgname))?;
let stage = stage
.or(result.build_stats.stage)
.ok_or_else(|| anyhow::anyhow!("No failed stage recorded for {}", pkgname))?;
let log_file = log_dir.join(format!("{}.log", stage.into_str()));
if !log_file.exists() {
bail!("Log file not found: {}", log_file.display());
}
Ok(log_file)
}
fn page_file(path: &Path) -> Result<()> {
if !std::io::stdout().is_terminal() {
let content = std::fs::read(path)?;
std::io::stdout().write_all(&content)?;
return Ok(());
}
let pager = std::env::var("PAGER").unwrap_or_else(|_| "less".to_string());
let file = std::fs::File::open(path)?;
Command::new("sh")
.args(["-c", &pager])
.stdin(file)
.status()?;
Ok(())
}