chrysopoeia/src/cc.rs

106 lines
2.8 KiB
Rust

use std::{
io,
path::Path,
process::{Command, ExitStatus, Output},
};
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// no compiler specified, all defaults failed
#[error("couldn't find a C compiler")]
NoCompiler,
/// user specified a compiler and it didn't work
#[error("couldn't execute `{cmd}`: {src}")]
BadCompiler { cmd: String, src: io::Error },
/// user specified a compiler and it's nonsense
#[error("compiler can't be empty")]
EmptyCompiler,
/// compiler ran but failed for some reason
#[error("compiler failed with {status}\n\n{}", String::from_utf8_lossy(.stderr))]
ExecFailed { status: ExitStatus, stderr: Vec<u8> },
}
fn to_res(Output { status, stderr, .. }: Output) -> Result<(), Error> {
status
.success()
.then_some(())
.ok_or(Error::ExecFailed { status, stderr })
}
// there's probably a platform dependent better order for searching these. too bad !
const DEFAULT_COMPILERS: [&str; 3] = ["cc", "gcc", "clang"];
pub struct Build<'a> {
compiler: Option<&'a str>,
dir: Option<&'a Path>,
inputs: Vec<&'a str>,
}
impl<'a> Build<'a> {
pub fn new() -> Self {
Self {
compiler: None,
dir: None,
inputs: Vec::new(),
}
}
pub fn compile(self, output_path: &Path) -> Result<(), Error> {
if let Some(compiler) = self.compiler {
let mut parts = compiler.split_whitespace();
let mut cmd = Command::new(parts.next().ok_or(Error::EmptyCompiler)?);
cmd.args(parts);
return match self.try_compile(cmd, output_path) {
Err(e) => Err(Error::BadCompiler {
cmd: compiler.to_owned(),
src: e,
}),
Ok(output) => to_res(output),
};
}
for cc in DEFAULT_COMPILERS {
let cmd = Command::new(cc);
if let Ok(output) = self.try_compile(cmd, output_path) {
return to_res(output);
}
}
Err(Error::NoCompiler)
}
pub fn compiler(&mut self, compiler: Option<&'a str>) -> &mut Self {
self.compiler = compiler;
self
}
pub fn dir(&mut self, path: &'a Path) -> &mut Self {
self.dir = Some(path);
self
}
pub fn input_files(&mut self, files: &[&'a str]) -> &mut Self {
for file in files {
self.inputs.push(*file);
}
self
}
fn try_compile(&self, mut cmd: Command, output_path: &Path) -> io::Result<Output> {
cmd.arg("-fpic").arg("-shared").arg("-std=c11");
if let Some(path) = self.dir {
cmd.current_dir(path);
}
cmd.args(&self.inputs);
cmd.arg("-o").arg(output_path);
cmd.output()
}
}