use crate::command::info;
use crate::preset;
use crate::runner;
use log::warn;
pub enum TargetStatus {
Available,
Unavailable(String),
}
pub fn expand_binary_dir(raw: &str, preset: &str) -> std::path::PathBuf {
let source_dir = std::env::current_dir()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
let expanded = raw
.replace("${sourceDir}", &source_dir)
.replace("${presetName}", preset)
.replace(
"${sourceDirName}",
std::path::Path::new(&source_dir)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.as_ref(),
);
std::path::PathBuf::from(expanded)
}
pub fn cache_value(bdir: &std::path::Path, variable: &str) -> anyhow::Result<Option<String>> {
let cache = bdir.join("CMakeCache.txt");
if !cache.exists() {
return Ok(None);
}
let content = std::fs::read_to_string(&cache)?;
let value = content
.lines()
.find(|l| l.starts_with(&format!("{}:", variable)))
.and_then(|l| l.split_once('=').map(|x| x.1.to_string()));
Ok(value)
}
pub fn cache_bool(bdir: &std::path::Path, variable: &str) -> anyhow::Result<Option<bool>> {
let value = cache_value(bdir, variable)?;
Ok(value.map(|v| {
let v = v.to_uppercase();
matches!(v.as_str(), "ON" | "YES" | "TRUE" | "Y")
|| v.parse::<f64>().map_or(false, |n| n != 0.0)
}))
}
pub fn ensure_libra_feature_enabled(
ctx: &crate::runner::Context,
preset: &str,
variable: &str,
) -> anyhow::Result<()> {
if ctx.dry_run {
return Ok(());
}
let bdir = binary_dir(preset).ok_or_else(|| {
anyhow::anyhow!(
"Build directory does not exist for preset '{}'.\n\
Run 'libra build --preset {}' first.",
preset,
preset
)
})?;
match cache_bool(&bdir, variable)? {
Some(false) => anyhow::bail!(
"Preset '{}' does not have {variable}=ON.\n\
Add {variable}=ON to your preset or use a preset that enables it.",
preset
),
_ => Ok(()),
}
}
pub fn generator(preset: &str) -> anyhow::Result<String> {
let bdir = binary_dir(preset).ok_or_else(|| {
anyhow::anyhow!(
"Build directory does not exist for preset '{}'.\n\
Run 'clibra build' first to configure the project.",
preset
)
})?;
let content = std::fs::read_to_string(bdir.join("CMakeCache.txt"))?;
let generator = content
.lines()
.find(|l| l.starts_with("CMAKE_GENERATOR:"))
.and_then(|l| l.split_once('=').map(|x| x.1))
.ok_or_else(|| anyhow::anyhow!("CMAKE_GENERATOR not found in CMakeCache.txt"))?
.to_string();
Ok(generator)
}
pub fn binary_dir(preset: &str) -> Option<std::path::PathBuf> {
let path = {
let from_user =
preset::read_configure_preset_field("CMakeUserPresets.json", preset, "binaryDir")
.unwrap_or(None);
let from_project =
preset::read_configure_preset_field("CMakePresets.json", preset, "binaryDir")
.unwrap_or(None);
from_user
.or(from_project)
.unwrap_or_else(|| "./build".to_string())
};
let bdir = expand_binary_dir(&path, preset);
if bdir.exists() {
return bdir.canonicalize().ok();
}
None
}
pub fn target_status(target: &str, preset: &str) -> anyhow::Result<TargetStatus> {
let bdir = binary_dir(preset).ok_or_else(|| {
anyhow::anyhow!(
"Build directory does not exist for preset '{}'.\n\
Run 'libra build --preset {}' first.",
preset,
preset
)
})?;
let text = std::fs::read_to_string(bdir.join("libra_targets.json"))?;
let data: info::HelpTargets = serde_json::from_str(&text)?;
for t in data.targets {
if t.name == target {
return Ok(TargetStatus::Available);
}
}
Ok(TargetStatus::Unavailable("unknown".to_string()))
}
pub fn reconf(
ctx: &runner::Context,
preset: &str,
fresh: bool,
defines: &[String],
) -> anyhow::Result<()> {
let mut cmd = base_conf(preset);
cmd.args(defines.iter().map(|d| format!("-D{}", d)));
if fresh {
cmd.arg("--fresh");
}
ctx.run(&mut cmd)?;
Ok(())
}
pub fn base_build(preset: &str) -> std::process::Command {
let mut cmd = std::process::Command::new("cmake");
cmd.args(["--build", "--preset", preset]);
cmd
}
pub fn base_conf(preset: &str) -> std::process::Command {
let mut cmd = std::process::Command::new("cmake");
cmd.args(["--preset", preset]);
cmd
}
pub fn base_test(preset: &str) -> std::process::Command {
let mut cmd = std::process::Command::new("ctest");
cmd.args(["--preset", preset]);
cmd
}
pub fn base_workflow(preset: &str) -> std::process::Command {
let mut cmd = std::process::Command::new("cmake");
cmd.args(["--workflow", "--preset", preset]);
cmd
}
pub fn with_keep_going(
mut cmd: std::process::Command,
preset: &str,
) -> anyhow::Result<std::process::Command> {
let generator = generator(preset).unwrap_or_else(|e| {
warn!("Failed to detect CMake generator: {e}, defaulting to Unix Makefiles");
"Unix Makefiles".to_string()
});
if generator == "Ninja" {
cmd.args(["--", "-k0"]);
} else if generator == "Unix Makefiles" {
cmd.args(["--", "--keep-going"]);
} else {
anyhow::bail!("--keep-going only supported with {{Ninja, Unix Makefiles}} generators");
}
Ok(cmd)
}