use std::{fs, path, path::Path};
use pyo3::{prelude::PyAnyMethods, Bound, PyAny};
use crate::{
builders::builder_trait::BuilderImpl, config, file_manager::PATH_SEP, log,
shell::Shell,
};
#[derive(Debug, Clone)]
pub enum CMakeBuildType {
Debug,
Release,
RelWithDebInfo,
MinSizeRel,
}
#[derive(Debug, Clone)]
pub struct CMake {
pub build_type: CMakeBuildType,
pub jobs: Option<usize>,
pub prefix_args: Option<Vec<String>>,
pub configure_flags: Option<Vec<String>>,
pub cmake_root: Option<String>,
}
impl CMake {
fn configure<
P0: AsRef<Path> + std::fmt::Debug,
P1: AsRef<Path> + std::fmt::Debug,
>(
&self,
source_path: &P0,
output_path: &P1,
dependencies: &[String],
) -> Result<(), String> {
let source_path =
path::absolute(source_path).map_err(|err| err.to_string())?;
let output_path =
path::absolute(output_path).map_err(|err| err.to_string())?;
fs::create_dir_all(&output_path).map_err(|e| e.to_string())?;
let mut shell = Shell::default();
shell.set_current_dir(&output_path.to_str().unwrap());
for dep in dependencies {
log::info(&format!("Loading module: {dep}"));
shell.add_command(&format!("module load {dep}"));
}
let mut cmake_cmd = String::new();
if let Some(args) = &self.prefix_args {
for arg in args {
cmake_cmd.push_str(&format!("{arg} "));
}
}
cmake_cmd.push_str(&format!("cmake {source_path:?}"));
if let Some(flags) = &self.configure_flags {
for flag in flags {
cmake_cmd.push_str(&format!(" {flag}"));
}
}
cmake_cmd
.push_str(&format!(" -DCMAKE_BUILD_TYPE={:?}", self.build_type));
shell.add_command(&cmake_cmd);
let (result, stdout, stderr) = shell.exec();
if result.is_err() {
return Err("Failed to run CMake command".to_string());
}
let result = result.unwrap();
if !result.success() {
return Err(format!(
"Failed to execute command. Output:\n{}\n{}",
stdout.join("\n"),
stderr.join("\n")
));
}
Ok(())
}
fn compile<P: AsRef<Path> + std::fmt::Debug>(
&self,
path: &P,
dependencies: &[String],
) -> Result<(), String> {
let config = config::read().unwrap();
let mut shell = Shell::default();
shell.set_current_dir(&path.as_ref().to_str().unwrap());
for dep in dependencies {
shell.add_command(&format!("module load {dep}"));
}
shell.add_command(&format!(
"cmake --build . --config {:?} --parallel {}",
self.build_type,
if let Some(jobs) = &self.jobs {
jobs
} else {
&config.num_threads
}
));
let (result, stdout, stderr) = shell.exec();
let result = result.map_err(|_| "Failed to run CMake command")?;
if !result.success() {
return Err(format!(
"Failed to execute command. Output:\n{}\n{}",
stdout.join("\n"),
stderr.join("\n")
));
}
Ok(())
}
}
impl BuilderImpl for CMake {
fn from_py(object: &Bound<PyAny>) -> Result<Self, String> {
let build_type = match object
.getattr("build_type")
.map_err(|_| {
"Failed to read attribute 'build_type' of Builder object"
})?
.extract::<String>()
.map_err(|_| {
"Failed to convert attribute 'build_type' to Rust String"
})?
.to_lowercase()
.as_str()
{
"debug" => CMakeBuildType::Debug,
"release" => CMakeBuildType::Release,
"relwithdebinfo" => CMakeBuildType::RelWithDebInfo,
"minsizerel" => CMakeBuildType::MinSizeRel,
other => log::error(&format!("Unknown CMake build type {other}")),
};
let jobs: Option<usize> = object
.getattr("jobs")
.map_err(|_| "Failed to read attribute 'jobs' of Builder object")?
.extract()
.map_err(|_| {
"Failed to convert attribute 'jobs' to Rust Option<usize>"
})?;
let prefix_args: Option<Vec<String>> = object
.getattr("prefix_args")
.map_err(|_| {
"Failed to read attribute 'prefix_args' of Builder object"
})?
.extract()
.map_err(|_| {
"Failed to convert attribute 'prefix_args' to Rust Vec<String>"
})?;
let configure_flags: Option<Vec<String>> = object
.getattr("configure_flags")
.map_err(|_| "Failed to read attribute 'configure_flags' of Builder object")?
.extract()
.map_err(|_| "Failed to convert attribute 'configure_flags' to Rust Vec<String>")?;
let cmake_root: Option<String> = object
.getattr("cmake_root")
.map_err(|_| {
"Failed to read attribute 'cmake_root' of Builder object"
})?
.extract()
.map_err(|_| {
"Failed to convert attribute 'cmake_root' to Rust String"
})?;
Ok(Self { build_type, jobs, prefix_args, configure_flags, cmake_root })
}
fn build<
P0: AsRef<Path> + std::fmt::Debug,
P1: AsRef<Path> + std::fmt::Debug,
P2: AsRef<Path>,
>(
&self,
source_path: &P0,
build_path: &P1,
_: &P2,
dependencies: &[String],
) -> Result<(), String> {
let cmake_source_path = if let Some(root) = &self.cmake_root {
source_path.as_ref().to_str().unwrap().to_owned()
+ PATH_SEP.to_string().as_ref()
+ root
} else {
source_path.as_ref().to_str().unwrap().to_owned()
};
self.configure(&cmake_source_path, build_path, dependencies)?;
self.compile(build_path, dependencies)?;
Ok(())
}
fn install<P0: AsRef<Path>, P1: AsRef<Path>, P2: AsRef<Path>>(
&self,
_: &P0, build_path: &P1,
install_path: &P2,
dependencies: &[String],
) -> Result<(), String> {
let build_path =
path::absolute(build_path).map_err(|err| err.to_string())?;
let install_path =
path::absolute(install_path).map_err(|err| err.to_string())?;
fs::create_dir_all(&install_path).map_err(|e| e.to_string())?;
if !build_path.exists() {
return Err(format!(
"Build directory {build_path:?} does not exist"
));
}
let mut shell = Shell::default();
shell.set_current_dir(&build_path.to_str().unwrap());
for dep in dependencies {
shell.add_command(&format!("module load {dep}"));
}
shell.add_command(&format!(
"cmake --install . --prefix {install_path:?}"
));
let (result, stdout, stderr) = shell.exec();
let result = result.map_err(|_| "Failed to run CMake command")?;
if !result.success() {
return Err(format!(
"Failed to execute command. Output:\n{}\n{}",
stdout.join("\n"),
stderr.join("\n")
));
}
Ok(())
}
}