use crate::config::{BuildSection, CStandard, Language, ManifestConfig, ProjectSection, ProjectType};
use crate::error::{BuildError, Result};
use crate::toolchain::BuildOutput;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
const GITIGNORE_TEMPLATE: &str = include_str!("../templates/.gitignore");
const MAIN_C_TEMPLATE: &str = include_str!("../templates/main.c");
const LIB_C_TEMPLATE: &str = include_str!("../templates/lib.c");
const LIB_H_TEMPLATE: &str = include_str!("../templates/lib.h");
const MAIN_CPP_TEMPLATE: &str = include_str!("../templates/main.cpp");
const LIB_CPP_TEMPLATE: &str = include_str!("../templates/lib.cpp");
const LIB_HPP_TEMPLATE: &str = include_str!("../templates/lib.hpp");
pub struct Project {
pub root: PathBuf,
pub config: ManifestConfig,
pub src_dir: PathBuf,
pub build_dir: PathBuf,
pub install_dir: PathBuf,
pub resolved_deps: Vec<(String, BuildOutput)>,
}
impl Project {
pub fn load(project_dir: &Path) -> Result<Self> {
let config = ManifestConfig::load(project_dir)?;
Ok(Self {
root: project_dir.to_path_buf(),
src_dir: project_dir.join("src"),
build_dir: project_dir.join("target"),
install_dir: project_dir.join("target/deps"),
config,
resolved_deps: Vec::new(),
})
}
pub fn init(
name: &str,
project_type: ProjectType,
c_standard: Option<CStandard>,
) -> Result<()> {
if name.is_empty()
|| name.contains('/')
|| name.contains('\\')
|| name == "."
|| name == ".."
{
return Err(BuildError::InvalidProjectName(name.to_string()));
}
let root = PathBuf::from(name);
if root.exists() {
return Err(BuildError::ProjectAlreadyExists(root));
}
std::fs::create_dir_all(root.join("src"))?;
std::fs::create_dir_all(root.join("include"))?;
match project_type {
ProjectType::Binary => {
std::fs::write(root.join("src/main.c"), MAIN_C_TEMPLATE)?;
}
ProjectType::StaticLibrary | ProjectType::SharedLibrary => {
std::fs::write(root.join("src/lib.c"), LIB_C_TEMPLATE)?;
std::fs::write(root.join("include/lib.h"), LIB_H_TEMPLATE)?;
}
}
let config = ManifestConfig {
project: ProjectSection {
name: name.to_string(),
version: "0.1.0".to_string(),
authors: Vec::new(),
authors_email: Vec::new(),
description: None,
license: None,
project_type,
language: Language::C,
c_standard: c_standard.unwrap_or_default(),
cpp_standard: None,
output_name: None,
},
build: BuildSection {
compiler: Default::default(),
cflags: Vec::new(),
libs: Vec::new(),
},
dependencies: BTreeMap::new(),
workspace: None,
profile: Default::default(),
extra_bins: Vec::new(),
};
std::fs::write(root.join("Smidr.toml"), config.to_toml_string()?)?;
std::fs::write(root.join(".gitignore"), GITIGNORE_TEMPLATE)?;
println!("Created project: {}", name);
Ok(())
}
fn collect_files(&self, dirs: &[&Path], extensions: &[&str]) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
for dir in dirs {
if !dir.exists() {
continue;
}
Self::collect_files_recursive(dir, extensions, &mut files)?;
}
Ok(files)
}
fn collect_files_recursive(dir: &Path, extensions: &[&str], out: &mut Vec<PathBuf>) -> Result<()> {
for entry in std::fs::read_dir(dir).map_err(BuildError::Io)? {
let entry = entry.map_err(BuildError::Io)?;
let path = entry.path();
if path.is_dir() {
Self::collect_files_recursive(&path, extensions, out)?;
} else if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
if extensions.contains(&ext) {
out.push(path);
}
}
}
Ok(())
}
pub fn source_files(&self) -> Result<Vec<PathBuf>> {
let sources = self.collect_files(&[&self.src_dir], &["c", "cpp", "cc", "cxx"])?;
if sources.is_empty() {
return Err(BuildError::NoSourceFiles);
}
Ok(sources)
}
pub fn header_files(&self) -> Result<Vec<PathBuf>> {
let headers = self.collect_files(&[&self.root.join("include")], &["h", "hpp", "hh"])?;
if headers.is_empty() {
return Err(BuildError::NoHeaderFiles);
}
Ok(headers)
}
pub fn formattable_files(&self) -> Result<Vec<PathBuf>> {
let include_dir = self.root.join("include");
self.collect_files(
&[&self.src_dir, &include_dir],
&["c", "h", "cpp", "hpp", "cc", "hxx", "cxx", "hh"],
)
}
pub fn dep_prefix(&self, dep_name: &str) -> PathBuf {
self.install_dir.join(dep_name)
}
}