use crate::error::Result;
use crate::project::Project;
use std::path::PathBuf;
pub struct CompiledObject {
obj_path: PathBuf,
compile_command: String,
}
pub struct CompileOptions {
compiler: String,
includes: Vec<PathBuf>,
cflags: Vec<String>,
}
pub fn build_project(project: &Project) -> Result<()> {
let sources = project.source_files()?;
std::fs::create_dir_all(&project.build_dir)?;
let compiler = compiler_binary(&project.config.build.compiler)?;
println!("Using compiler: {}", compiler);
let mut opts = CompileOptions {
compiler: compiler.to_string(),
includes: vec![project.root.join("include")],
cflags: project.config.build.cflags.clone(),
};
for (name, output) in &project.resolved_deps {
let prefix = project.dep_prefix(name);
opts.includes.extend(output.include_dirs.clone());
}
let mut object_files: Vec<PathBuf> = Vec::new();
for src in sources {
let file_stem = src.file_stem().unwrap().to_str().unwrap();
let obj_path = project.build_dir.join(format!("{}.o", file_stem));
let mut cmd = std::process::Command::new(&opts.compiler);
cmd.arg("-c").arg(&src).arg("-o").arg(&obj_path);
cmd.args(&opts.includes.iter().map(|p| format!("-I{}", p.display()))
.collect::<Vec<_>>());
cmd.args(&opts.cflags);
let output = cmd.output()?;
if !output.status.success() {
return Err(crate::error::BuildError::Compile(
src.display().to_string(),
String::from_utf8_lossy(&output.stderr).to_string(),
));
}
object_files.push(obj_path);
}
let binary_path = project.build_dir.join("bin").join(&project.config.project.name);
std::fs::create_dir_all(project.build_dir.join("bin"))?;
let mut link_cmd = std::process::Command::new(&opts.compiler);
link_cmd.args(&object_files);
link_cmd.arg("-o").arg(&binary_path);
let output = link_cmd.output()?;
if !output.status.success() {
return Err(crate::error::BuildError::Link(
String::from_utf8_lossy(&output.stderr).to_string(),
));
}
Ok(())
}
pub fn run_project(project: &Project) -> Result<()> {
build_project(project)?;
let binary_path = project.build_dir.join("bin").join(&project.config.project.name);
println!("Running: {}", binary_path.display());
let status = std::process::Command::new(&binary_path).status()?;
if !status.success() {
return Err(crate::error::BuildError::CommandFailed {
cmd: binary_path.display().to_string(),
code: status.code(),
});
}
Ok(())
}
fn compiler_binary(kind: &crate::config::CompilerKind) -> Result<&'static str> {
use crate::config::CompilerKind;
match kind {
CompilerKind::Gcc => {
if compiler_exists("gcc") {
Ok("gcc")
} else {
Err(crate::error::BuildError::CompilerNotFound("gcc".to_string()))
}
}
CompilerKind::Tcc => {
if compiler_exists("tcc") {
Ok("tcc")
} else {
Err(crate::error::BuildError::CompilerNotFound("tcc".to_string()))
}
}
CompilerKind::Clang => {
if compiler_exists("clang") {
Ok("clang")
} else {
Err(crate::error::BuildError::CompilerNotFound("clang".to_string()))
}
}
CompilerKind::Auto => {
for candidate in ["clang", "tcc", "cc", "gcc"] {
if compiler_exists(candidate) {
return Ok(candidate);
}
}
Err(crate::error::BuildError::CompilerNotFound(
"clang, tcc, cc, gcc".to_string(),
))
}
}
}
fn compiler_exists(name: &str) -> bool {
std::process::Command::new(name)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|status| status.success())
.unwrap_or(false)
}