cmake-preset 0.1.0

A Rust library for building C++ code in Rust projects using CMake Presets, providing type-safe builder patterns and automatic Cargo integration.
Documentation
//! A tool library for building C++ code using CMake Presets in Rust projects
//!
//! This library provides a simple way to call CMake Presets from Rust's build.rs script.

use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::process::Command;

/// Type status: Set the project directory
pub struct StatusSetProjectDir;

/// Type status: Set the preset name
pub struct StatusSetConfigPreset;

/// Type status: Set the library name
pub struct StatusSetLibraryName;

/// Type status: Configuration
pub struct StatusConfig;

/// Type status: Construction
pub struct StatusBuild;

/// Builder for CMake presets
#[derive(Debug)]
pub struct CMakePresetBuilder<S> {
    output_dir: PathBuf,
    project_dir: PathBuf,
    config_preset: String,
    library_name: String,
    _phantom: PhantomData<S>,
}

impl CMakePresetBuilder<StatusSetProjectDir> {
    /// Create a new CMakePresetBuilder
    pub fn new() -> CMakePresetBuilder<StatusSetProjectDir> {
        CMakePresetBuilder {
            output_dir: std::env::var("OUT_DIR")
                .unwrap_or_else(|_| ".".to_string())
                .into(),
            project_dir: PathBuf::new(),
            config_preset: "".to_string(),
            library_name: "".to_string(),
            _phantom: PhantomData,
        }
    }

    /// Set the project directory
    /// The project directory is the directory where the CMakeLists.txt file is located
    /// # Arguments
    /// * `project_dir` - The project directory
    pub fn set_project_dir<P: AsRef<Path>>(
        self,
        project_dir: P,
    ) -> CMakePresetBuilder<StatusSetConfigPreset> {
        if !project_dir.as_ref().exists() {
            panic!(
                "Project directory:{:?} does not exist",
                project_dir.as_ref()
            );
        } else if !project_dir.as_ref().is_dir() {
            panic!(
                "Project directory:{:?} is not a directory",
                project_dir.as_ref()
            );
        }

        println!(
            "cargo:rerun-if-changed={}/",
            project_dir.as_ref().to_str().unwrap()
        );
        CMakePresetBuilder {
            output_dir: self.output_dir,
            project_dir: project_dir.as_ref().to_path_buf(),
            config_preset: self.config_preset,
            library_name: self.library_name,
            _phantom: PhantomData,
        }
    }
}

impl CMakePresetBuilder<StatusSetConfigPreset> {
    /// Set the preset to use for configuring the project.
    /// # Arguments
    /// * `preset_name` - The name of the preset to use for configuring the project.
    pub fn set_config_preset(
        self,
        preset_name: impl AsRef<str>,
    ) -> CMakePresetBuilder<StatusSetLibraryName> {
        CMakePresetBuilder {
            output_dir: self.output_dir,
            project_dir: self.project_dir,
            config_preset: preset_name.as_ref().into(),
            library_name: self.library_name,
            _phantom: PhantomData,
        }
    }
}

impl CMakePresetBuilder<StatusSetLibraryName> {
    /// Set the name of the generated static library
    /// # Arguments
    /// * `library_name` - The name of the generated static library
    pub fn set_library_name(
        self,
        library_name: impl AsRef<str>,
    ) -> CMakePresetBuilder<StatusConfig> {
        println!(
            "cargo:rustc-link-search=native={}",
            self.output_dir.to_str().unwrap()
        );
        println!("cargo:rustc-link-lib=static={}", library_name.as_ref());
        CMakePresetBuilder {
            output_dir: self.output_dir,
            project_dir: self.project_dir,
            config_preset: self.config_preset,
            library_name: library_name.as_ref().to_string(),
            _phantom: PhantomData,
        }
    }
}

impl CMakePresetBuilder<StatusConfig> {
    /// Run cmake --preset <preset_name>
    pub fn config(self) -> CMakePresetBuilder<StatusBuild> {
        let output_dir = std::env::var("OUT_DIR").unwrap_or_else(|_| ".".to_string());

        if !self.project_dir.exists() {
            panic!("Project directory does not exist: {:?}", self.project_dir);
        }

        let output = Command::new("cmake")
            .arg("--preset")
            .arg(&self.config_preset)
            .arg("-B")
            .arg(&output_dir)
            .current_dir(&self.project_dir)
            .output()
            .unwrap();

        if !output.status.success() {
            panic!(
                "CMake configure failed: {}",
                String::from_utf8_lossy(&output.stderr)
            );
        }

        CMakePresetBuilder {
            output_dir: self.output_dir,
            project_dir: self.project_dir,
            config_preset: self.config_preset,
            library_name: self.library_name,
            _phantom: PhantomData,
        }
    }
}

impl CMakePresetBuilder<StatusBuild> {
    /// Run cmake --build
    pub fn build(self) {
        let output = Command::new("cmake")
            .arg("--build")
            .arg(&self.output_dir)
            .arg("--parallel")
            .current_dir(&self.project_dir)
            .output()
            .unwrap();

        if !output.status.success() {
            panic!(
                "CMake build failed: {}",
                String::from_utf8_lossy(&output.stdout)
            );
        }
    }
}