From 01090ac792255dc45077ad84288120450e63daef Mon Sep 17 00:00:00 2001 From: wires Date: Sat, 22 Mar 2025 14:22:43 -0400 Subject: [PATCH] automatically find C compiler and let user set one --- src/cc.rs | 86 +++++++++++++++++++++++++++++++++++++++++-------- src/main.rs | 10 ++++-- src/settings.rs | 3 ++ 3 files changed, 83 insertions(+), 16 deletions(-) diff --git a/src/cc.rs b/src/cc.rs index bc35a3e..066f2a8 100644 --- a/src/cc.rs +++ b/src/cc.rs @@ -1,6 +1,40 @@ -use std::{io, path::Path, process::Command}; +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 }, +} + +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>, } @@ -8,30 +42,41 @@ pub struct Build<'a> { impl<'a> Build<'a> { pub fn new() -> Self { Self { + compiler: None, dir: None, inputs: Vec::new(), } } - pub fn compile(self, output_path: &Path) -> io::Result<()> { - let mut cmd = Command::new("cc"); + 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); - cmd.arg("-fpic").arg("-shared").arg("-std=c11"); - - if let Some(path) = self.dir { - cmd.current_dir(path); + return match self.try_compile(cmd, output_path) { + Err(e) => Err(Error::BadCompiler { + cmd: compiler.to_owned(), + src: e, + }), + Ok(output) => to_res(output), + }; } - cmd.args(&self.inputs); - cmd.arg("-o").arg(output_path); + for cc in DEFAULT_COMPILERS { + let cmd = Command::new(cc); - dbg!(&cmd); + if let Ok(output) = self.try_compile(cmd, output_path) { + return to_res(output); + } + } - let mut child = cmd.spawn()?; + Err(Error::NoCompiler) + } - let _ = child.wait(); - - Ok(()) + 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 { @@ -45,4 +90,17 @@ impl<'a> Build<'a> { } self } + + fn try_compile(&self, mut cmd: Command, output_path: &Path) -> io::Result { + 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() + } } diff --git a/src/main.rs b/src/main.rs index f2e4438..eeef899 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,10 @@ struct Cli { fn main() -> anyhow::Result<()> { let Cli { language } = Cli::parse(); - let Settings { parser_dir } = get_settings()?; + let Settings { + parser_dir, + compiler, + } = get_settings()?; let src_dir = tempfile::tempdir()?; @@ -37,7 +40,10 @@ fn main() -> anyhow::Result<()> { let output_path = parser_dir.join(language).with_extension(DLL_EXTENSION); let mut build = cc::Build::new(); - build.dir(&repo_path).input_files(files); + build + .compiler(compiler.as_deref()) + .dir(&repo_path) + .input_files(files); build.compile(&output_path)?; // close explicitly to prevent premature drop diff --git a/src/settings.rs b/src/settings.rs index 293e668..acc8834 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -13,6 +13,9 @@ lazy_static! { pub struct Settings { #[serde(default = "default_parser_dir")] pub parser_dir: PathBuf, + + #[serde(alias = "cc")] + pub compiler: Option, } fn default_parser_dir() -> PathBuf {