automatically find C compiler and let user set one

This commit is contained in:
wires 2025-03-22 14:22:43 -04:00
parent 0cd8af9c81
commit 01090ac792
Signed by: wires
SSH key fingerprint: SHA256:9GtP+M3O2IivPDlw1UY872UPUuJH2gI0yG6ExBxaaiM
3 changed files with 83 additions and 16 deletions

View file

@ -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<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> { pub struct Build<'a> {
compiler: Option<&'a str>,
dir: Option<&'a Path>, dir: Option<&'a Path>,
inputs: Vec<&'a str>, inputs: Vec<&'a str>,
} }
@ -8,30 +42,41 @@ pub struct Build<'a> {
impl<'a> Build<'a> { impl<'a> Build<'a> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
compiler: None,
dir: None, dir: None,
inputs: Vec::new(), inputs: Vec::new(),
} }
} }
pub fn compile(self, output_path: &Path) -> io::Result<()> { pub fn compile(self, output_path: &Path) -> Result<(), Error> {
let mut cmd = Command::new("cc"); 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"); return match self.try_compile(cmd, output_path) {
Err(e) => Err(Error::BadCompiler {
if let Some(path) = self.dir { cmd: compiler.to_owned(),
cmd.current_dir(path); src: e,
}),
Ok(output) => to_res(output),
};
} }
cmd.args(&self.inputs); for cc in DEFAULT_COMPILERS {
cmd.arg("-o").arg(output_path); 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(); pub fn compiler(&mut self, compiler: Option<&'a str>) -> &mut Self {
self.compiler = compiler;
Ok(()) self
} }
pub fn dir(&mut self, path: &'a Path) -> &mut Self { pub fn dir(&mut self, path: &'a Path) -> &mut Self {
@ -45,4 +90,17 @@ impl<'a> Build<'a> {
} }
self 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()
}
} }

View file

@ -19,7 +19,10 @@ struct Cli {
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let Cli { language } = Cli::parse(); let Cli { language } = Cli::parse();
let Settings { parser_dir } = get_settings()?; let Settings {
parser_dir,
compiler,
} = get_settings()?;
let src_dir = tempfile::tempdir()?; 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 output_path = parser_dir.join(language).with_extension(DLL_EXTENSION);
let mut build = cc::Build::new(); 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)?; build.compile(&output_path)?;
// close explicitly to prevent premature drop // close explicitly to prevent premature drop

View file

@ -13,6 +13,9 @@ lazy_static! {
pub struct Settings { pub struct Settings {
#[serde(default = "default_parser_dir")] #[serde(default = "default_parser_dir")]
pub parser_dir: PathBuf, pub parser_dir: PathBuf,
#[serde(alias = "cc")]
pub compiler: Option<String>,
} }
fn default_parser_dir() -> PathBuf { fn default_parser_dir() -> PathBuf {