106 lines
2.8 KiB
Rust
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()
|
|
}
|
|
}
|