use anyhow;
use clap;
use colored::Colorize;
use log::{debug, trace, warn};
use std::fmt::Write;
use crate::cmake;
use crate::preset;
use crate::runner;
#[derive(clap::Parser, Debug)]
pub struct InfoArgs {
#[arg(short = 'a', long, default_value_t = true)]
pub all: bool,
#[arg(short = 't', long)]
pub targets: bool,
#[arg(long)]
pub build: bool,
}
fn emit_target_group(
out: &mut String,
label: &str,
items: &[(&str, &str, String)],
targets: &[&str],
) {
let indent = " "; let filtered: Vec<_> = items
.iter()
.filter(|(t, _, _)| targets.contains(t))
.collect();
let width = filtered.iter().map(|(t, _, _)| t.len()).max().unwrap_or(20);
let _ = writeln!(out, " {}", label.bold());
if filtered.is_empty() {
let _ = writeln!(out, "{} (None)", indent);
return;
}
for item in filtered {
let _ = writeln!(
out,
"{}{:.<width$} {} {}",
indent,
item.0,
if item.1 == "YES" {
item.1.green().bold().to_string()
} else {
item.1.dimmed().to_string()
},
if item.1 == "YES" {
String::new()
} else {
format!("({})", item.2.yellow())
}
);
}
}
pub fn run_paged(output: &str) -> anyhow::Result<()> {
use std::io::Write;
use std::process::{Command, Stdio};
let mut child = Command::new("less")
.arg("-r") .arg("-F") .arg("-X") .stdin(Stdio::piped())
.spawn()?;
if let Some(stdin) = child.stdin.take() {
let mut stdin = stdin;
stdin.write_all(output.as_bytes())?;
}
child.wait()?;
Ok(())
}
fn emit_libra_targets(out: &mut String, ctx: &runner::Context, preset: &str) -> anyhow::Result<()> {
let tests_targets = [
"all-tests",
"integration-tests",
"unit-tests",
"regression-tests",
"build-and-test",
];
let coverage_targets = [
"lcov-preinfo",
"lcov-report",
"gcovr-check",
"gcovr-report",
"llvm-summary",
"llvm-show",
"llvm-report-coverage",
"llvm-export-lcov",
];
let docs_targets = [
"apidoc",
"sphinxdoc",
"apidoc-check-doxygen",
"apidoc-check-clang",
];
let analysis_targets = [
"analyze",
"analyze-clang-tidy",
"analyze-clang-check",
"analyze-cppcheck",
"analyze-cmake-format",
"format",
"format-clang-format",
"format-cmake-format",
"fix",
"fix-clang-tidy",
"fix-clang-check",
];
let (_, stderr) =
ctx.run_capture(cmake::base_build(preset).args(["--target", "help-targets"]))?;
let mut items = vec![];
debug!("Parsing help-targets output: {:?}", stderr);
for line in stderr.lines().skip(3) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
trace!(
"Found target={},status={},reason={}",
parts[0],
parts[1],
parts[2..].join(" ")
);
items.push((parts[0], parts[1], parts[2..].join(" ")));
}
if parts.len() == 2 {
trace!(
"Found target={},status={},reason=enabled",
parts[0],
parts[1]
);
items.push((parts[0], parts[1], String::new()));
}
}
let _ = writeln!(out, "{}", "\nAvailable LIBRA targets\n".bold().underline());
emit_target_group(out, "Tests", &items, &tests_targets);
emit_target_group(out, "Docs", &items, &docs_targets);
emit_target_group(out, "Coverage", &items, &coverage_targets);
emit_target_group(out, "Analysis", &items, &analysis_targets);
Ok(())
}
fn emit_build_configuration(
out: &mut String,
preset: &str,
items: &Vec<(String, String)>,
width: usize,
) -> anyhow::Result<()> {
let bdir = cmake::binary_dir(preset);
if bdir.is_none() {
warn!("Build directory does not exist--no build configuration nifo can be emitted");
return Ok(());
}
let _ = writeln!(out, "{}", "\nBuild configuration\n".bold().underline());
let generator = cmake::generator(&preset)?;
let _ = writeln!(out, " Build dir: {}", bdir.unwrap().to_string_lossy());
let _ = writeln!(out, " Generator: {}", generator);
for (k, v) in items {
let _ = writeln!(out, " {:<width$} = {}", k, v);
}
Ok(())
}
fn emit_libra_vars(out: &mut String, items: &Vec<(String, String)>, width: usize) {
let _ = writeln!(out, "{}", "\nLIBRA feature flags\n".bold().underline());
if items.is_empty() {
let _ = writeln!(out, " (None)");
return;
}
for (k, v) in items {
let value_str = match v.as_str() {
"YES" => v.bold().green().to_string(),
"ON" => v.bold().green().to_string(),
_ => v.to_string(),
};
let _ = writeln!(out, " {:<width$} = {}", k, value_str);
}
}
fn parse_cmake_cache(
ctx: &runner::Context,
preset: &str,
) -> anyhow::Result<(Vec<(String, String)>, Vec<(String, String)>)> {
if ctx.dry_run {
debug!("dry-run: skipping cache read");
return Ok((Vec::new(), Vec::new()));
}
let bdir = cmake::binary_dir(&preset).ok_or_else(|| {
anyhow::anyhow!("Build directory does not exist — run 'clibra build' first")
})?;
debug!("Reading CMake cache");
let cache_path = bdir.join("CMakeCache.txt");
let content = std::fs::read_to_string(&cache_path)
.map_err(|_| anyhow::anyhow!("CMakeCache.txt not found — run 'clibra build' first"))?;
let mut libra_items = vec![];
let mut cmake_items = vec![];
const CMAKE_INTERESTING: &[&str] = &[
"CMAKE_BUILD_TYPE",
"CMAKE_CXX_COMPILER",
"CMAKE_C_COMPILER",
"CMAKE_CXX_FLAGS",
"CMAKE_C_FLAGS",
"CMAKE_INSTALL_PREFIX",
"CMAKE_EXPORT_COMPILE_COMMANDS",
"CMAKE_PROJECT_NAME",
"CMAKE_GENERATOR",
];
for line in content.lines() {
if line.starts_with('#') || line.starts_with("//") || line.is_empty() {
continue;
}
if let Some((key_type, value)) = line.split_once('=') {
let key = key_type.split(':').next().unwrap_or(key_type);
if key.starts_with("CMAKE_") && CMAKE_INTERESTING.contains(&key) {
trace!("Found CMake variable {}={}", key, value);
cmake_items.push((key.to_string(), value.to_string()));
} else if key.starts_with("LIBRA_") {
trace!("Found LIBRA variable {}={}", key, value);
libra_items.push((key.to_string(), value.to_string()));
}
}
}
return Ok((cmake_items, libra_items));
}
pub fn run(ctx: &runner::Context, mut args: InfoArgs) -> anyhow::Result<()> {
preset::ensure_project_root(ctx)?;
let use_color = colored::control::SHOULD_COLORIZE.should_colorize();
colored::control::set_override(use_color);
let preset = preset::resolve(ctx, None)?;
if args.targets || args.build {
args.all = false;
}
let (cmake_items, libra_items) = parse_cmake_cache(ctx, &preset)?;
let width = libra_items
.iter()
.chain(cmake_items.iter())
.map(|(k, _)| k.len())
.max()
.unwrap_or(0);
let mut out = String::new();
if args.all || args.build {
emit_build_configuration(&mut out, &preset, &cmake_items, width)?;
emit_libra_vars(&mut out, &libra_items, width);
}
if args.all || args.targets {
emit_libra_targets(&mut out, ctx, &preset)?;
}
run_paged(&out)?;
Ok(())
}