use crate::error::{BuildError, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::Path;
#[derive(Deserialize, Serialize)]
pub struct ManifestConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project: Option<ProjectSection>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build: Option<BuildSection>,
#[serde(default)]
pub dependencies: BTreeMap<String, DependencySpec>,
#[serde(default)]
pub workspace: Option<WorkspacesConfig>,
#[serde(default, skip_serializing_if = "ProfilesSection::is_empty")]
pub profile: ProfilesSection,
#[serde(default, rename = "bin", skip_serializing_if = "Vec::is_empty")]
pub extra_bins: Vec<BinTarget>,
#[serde(default, skip_serializing_if = "PathsConfig::is_empty")]
pub paths: PathsConfig,
}
#[derive(Deserialize, Serialize)]
pub struct ProjectSection {
pub name: String,
pub version: String,
#[serde(rename = "type")]
pub project_type: ProjectType,
#[serde(default)]
pub language: Language,
#[serde(default)]
pub c_standard: CStandard,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cpp_standard: Option<CppStandard>,
pub authors: Vec<String>,
pub authors_email: Vec<String>,
pub description: Option<String>,
pub license: Option<String>,
#[serde(default)]
pub output_name: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq, clap::ValueEnum)]
pub enum ProjectType {
#[default]
#[serde(rename = "bin")]
#[value(name = "bin")]
Binary,
#[serde(rename = "static")]
#[value(name = "static")]
StaticLibrary,
#[serde(rename = "dynamic")]
#[value(name = "dynamic")]
SharedLibrary,
}
#[derive(Debug, Clone, clap::ValueEnum, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Language {
#[default]
C,
Cpp,
}
#[derive(Debug, Clone, clap::ValueEnum, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CStandard {
C89,
C90,
C99,
C11,
#[default]
C17,
C23,
Gnu99,
Gnu11,
Gnu17,
}
#[derive(Debug, Clone, clap::ValueEnum, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CppStandard {
Cpp98,
Cpp03,
Cpp11,
Cpp14,
Cpp17,
#[default]
Cpp20,
Cpp23,
Cpp26,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct WorkspacesConfig {
pub members: Vec<String>,
}
#[derive(Deserialize, Serialize)]
pub struct BuildSection {
pub compiler: CompilerKind,
pub cflags: Vec<String>,
pub libs: Vec<String>,
#[serde(default)]
pub linker_flags: Vec<String>,
}
#[derive(Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum DependencySpec {
Version(String),
Detailed {
git: Option<String>,
path: Option<String>,
tag: Option<String>,
#[serde(default)]
build_system: BuildSystemKind,
#[serde(default)]
build_commands: Vec<String>,
#[serde(default)]
extra_includes: Vec<String>,
#[serde(default)]
libs: Vec<String>,
},
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
pub struct ProfilesSection {
pub debug: Option<ProfileSection>,
pub release: Option<ProfileSection>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
pub struct ProfileSection {
pub opt_level: Option<OptLevel>,
pub warnings: Option<WarningLevel>,
pub debug_symbols: Option<bool>,
pub lto: Option<bool>,
pub strip: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResolvedProfile {
pub opt_level: OptLevel,
pub warnings: WarningLevel,
pub debug_symbols: bool,
pub lto: bool,
pub strip: bool,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum OptLevel {
#[default]
None, Speed, Size, Max, }
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq)]
pub enum WarningLevel {
None,
#[default]
Standard,
Strict,
}
#[derive(Deserialize, Serialize)]
pub struct BinTarget {
pub name: String,
pub path: String,
}
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq, Clone)]
#[serde(rename_all = "lowercase")]
pub enum BuildSystemKind {
#[default]
Auto,
Cmake,
Meson,
Make,
Custom,
}
#[derive(Deserialize, Serialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum CompilerKind {
#[default]
Auto,
Gcc,
Tcc,
Clang,
}
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct PathsConfig {
#[serde(default)]
pub src_dir: Option<String>,
#[serde(default)]
pub include: Option<String>,
#[serde(flatten)]
pub custom: BTreeMap<String, String>,
}
impl ManifestConfig {
pub fn load(project_dir: &Path) -> Result<Self> {
let manifest_path = project_dir.join("Smidr.toml");
if !manifest_path.exists() {
return Err(BuildError::ManifestNotFound(manifest_path));
}
let text = std::fs::read_to_string(&manifest_path)?;
let config: ManifestConfig = toml::from_str(&text)?;
Ok(config)
}
pub fn to_toml_string(&self) -> Result<String> {
Ok(toml::to_string_pretty(self)?)
}
pub fn get_release_profile(&self) -> ResolvedProfile {
let user_profile = self.profile.release.as_ref();
ResolvedProfile {
opt_level: user_profile
.and_then(|p| p.opt_level)
.unwrap_or(OptLevel::Max),
warnings: user_profile
.and_then(|p| p.warnings)
.unwrap_or(WarningLevel::Strict),
debug_symbols: user_profile
.and_then(|p| p.debug_symbols)
.unwrap_or(false),
lto: user_profile.and_then(|p| p.lto).unwrap_or(true),
strip: user_profile.and_then(|p| p.strip).unwrap_or(false),
}
}
pub fn get_debug_profile(&self) -> ResolvedProfile {
let user_profile = self.profile.debug.as_ref();
ResolvedProfile {
opt_level: user_profile
.and_then(|p| p.opt_level)
.unwrap_or(OptLevel::None),
warnings: user_profile
.and_then(|p| p.warnings)
.unwrap_or(WarningLevel::Standard),
debug_symbols: user_profile
.and_then(|p| p.debug_symbols)
.unwrap_or(true),
lto: user_profile.and_then(|p| p.lto).unwrap_or(false),
strip: user_profile.and_then(|p| p.strip).unwrap_or(false),
}
}
}
impl ProjectSection {
pub fn output_name(&self) -> &str {
self.output_name.as_deref().unwrap_or(&self.name)
}
}
impl ProfilesSection {
pub fn is_empty(&self) -> bool {
self.debug.is_none() && self.release.is_none()
}
}
impl PathsConfig {
pub fn is_empty(&self) -> bool {
self.src_dir.is_none() && self.include.is_none() && self.custom.is_empty()
}
}
impl std::fmt::Display for CStandard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
CStandard::C89 => "c89",
CStandard::C90 => "c90",
CStandard::C99 => "c99",
CStandard::C11 => "c11",
CStandard::C17 => "c17",
CStandard::C23 => "c23",
CStandard::Gnu99 => "gnu99",
CStandard::Gnu11 => "gnu11",
CStandard::Gnu17 => "gnu17",
};
f.write_str(s)
}
}
impl std::fmt::Display for CppStandard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
CppStandard::Cpp98 => "c++98",
CppStandard::Cpp03 => "c++03",
CppStandard::Cpp11 => "c++11",
CppStandard::Cpp14 => "c++14",
CppStandard::Cpp17 => "c++17",
CppStandard::Cpp20 => "c++20",
CppStandard::Cpp23 => "c++23",
CppStandard::Cpp26 => "c++26",
};
f.write_str(s)
}
}