use std::path::{Path, PathBuf};
mod clang;
pub mod emit;
pub mod ir;
pub mod parse;
#[derive(Debug, Clone, Default)]
pub struct Builder {
parse: parse::Options,
emit: emit::Options,
probe: bool,
}
#[derive(Debug, Clone)]
pub struct Generated {
pub code: String,
pub warnings: Vec<String>,
}
impl Generated {
pub fn write_to(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::write(path, &self.code)
}
}
impl Builder {
pub fn new() -> Self {
let mut b = Self::default();
b.emit.struct_name = "Bindings".into();
b.emit.no_module_attributes = true;
b.probe = true;
b
}
pub fn probe(mut self, probe: bool) -> Self {
self.probe = probe;
self
}
pub fn header(mut self, path: impl Into<PathBuf>) -> Self {
self.parse.headers.push(path.into());
self
}
pub fn clang_arg(mut self, arg: impl Into<String>) -> Self {
self.parse.clang_args.push(arg.into());
self
}
pub fn header_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.parse.header_dirs.push(dir.into());
self
}
pub fn allow(mut self, glob: impl Into<String>) -> Self {
self.parse.allow.push(glob.into());
self
}
pub fn block(mut self, glob: impl Into<String>) -> Self {
self.parse.block.push(glob.into());
self
}
pub fn libclang(mut self, path: impl Into<PathBuf>) -> Self {
self.parse.libclang = Some(path.into());
self
}
pub fn target(mut self, triple: impl Into<String>) -> Self {
self.parse.target = Some(triple.into());
self
}
pub fn cpp(mut self, cpp: bool) -> Self {
self.parse.cpp = cpp;
self
}
pub fn keep_going(mut self, keep_going: bool) -> Self {
self.parse.keep_going = keep_going;
self
}
pub fn struct_name(mut self, name: impl Into<String>) -> Self {
self.emit.struct_name = name.into();
self
}
pub fn library_name(mut self, name: impl Into<String>) -> Self {
self.emit.library_names.push(name.into());
self
}
pub fn optional(mut self, glob: impl Into<String>) -> Self {
self.emit.optional.push(glob.into());
self
}
pub fn owned<I, S>(
mut self,
type_name: impl Into<String>,
free: impl Into<String>,
constructors: I,
) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.emit.owned.push(emit::Owned {
type_name: type_name.into(),
free: free.into(),
constructors: constructors.into_iter().map(Into::into).collect(),
});
self
}
pub fn safe(mut self, glob: impl Into<String>) -> Self {
self.emit.safe.push(glob.into());
self
}
pub fn safe_handle(mut self, glob: impl Into<String>) -> Self {
self.emit.safe_handle.push(glob.into());
self
}
pub fn wrap(mut self, spec: &str) -> Self {
match emit::Wrap::parse(spec) {
Ok(w) => self.emit.wraps.push(w),
Err(e) => panic!("invalid wrap spec: {e}"),
}
self
}
pub fn module_attributes(mut self, keep: bool) -> Self {
self.emit.no_module_attributes = !keep;
self
}
pub fn generate(&self) -> Result<Generated, parse::Error> {
let mut parsed = parse::parse(&self.parse)?;
let mut emit = self.emit.clone();
if self.probe {
let (_, missing, warnings) = probe(&parsed.header, &emit.library_names);
parsed.warnings.extend(warnings);
emit.optional.extend(missing);
}
emit.comment.push(format!(
"Generator: dlsym {} ({})",
env!("CARGO_PKG_VERSION"),
parsed.clang_version
));
emit.comment.push(format!(
"Target: {} (struct layouts are checked at compile time)",
parsed.target.as_deref().unwrap_or("host")
));
Ok(Generated {
code: emit::emit(&parsed.header, &emit),
warnings: parsed.warnings,
})
}
}
pub fn probe(header: &ir::Header, names: &[String]) -> (Option<String>, Vec<String>, Vec<String>) {
let mut missing = Vec::new();
let mut warnings = Vec::new();
let Some((name, lib)) = names.iter().find_map(|n| {
unsafe { crate::Library::open(n) }
.ok()
.map(|l| (n.clone(), l.leak()))
}) else {
return (None, missing, warnings);
};
for item in &header.items {
let (rust, symbol) = match item {
ir::Item::Function(f) => (&f.name, f.symbol()),
ir::Item::Var(v) => (&v.name, v.symbol()),
_ => continue,
};
let found = unsafe { lib.get::<*const u8>(symbol) };
if let Err(crate::Error::SymbolNotFound { .. }) = found {
warnings.push(format!(
"`{rust}` is not exported by {name}; marked #[optional]"
));
missing.push(rust.clone());
}
}
(Some(name), missing, warnings)
}