#![deny(missing_docs)]
#[cfg(not(test))]
use bindgen::Builder;
#[cfg(test)]
use tests::busshi::bindgen_builder::Builder;
use bindgen::CargoCallbacks;
#[cfg(not(test))]
use cc::Build;
#[cfg(test)]
use tests::busshi::cc_build::Build;
#[cfg(not(test))]
use glob::glob_with;
#[cfg(test)]
use tests::busshi::glob::glob_with;
use glob::MatchOptions;
use std::boxed::Box;
use std::cell::RefCell;
use std::convert::AsRef;
use std::env;
use std::ffi::OsStr;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::iter::Iterator;
use std::path::Path;
#[cfg(not(test))]
use std::path::PathBuf;
#[cfg(test)]
use tests::busshi::std_path_path_buf::PathBuf;
use std::path::PathBuf as StdPathBuf;
use std::process::{ExitCode, Termination};
use super::error::Error as MldError;
use super::hooks::bindgen::reflect as reflect_bindgen;
use super::hooks::cc::reflect as reflect_cc;
use super::hooks::glob::reflect as reflect_glob;
#[cfg(doc)]
use system_deps::Config as SystemDepsConfig;
#[cfg(test)]
pub mod tests;
pub static ENV_KEY_OUT_DIR: &str = "OUT_DIR";
pub static SOURCE_EXTS: [&str; 5] =
[
"c",
"cc",
"cpp",
"cxx",
"s",
];
pub static HEADER_EXTS: [&str; 4] =
[
"h",
"hh",
"hpp",
"hxx",
];
pub static RUST_FFI_BINDING_EXT: &str = "in";
pub struct Config<'a>
{
out_dir: PathBuf,
input_files: Vec<&'a str>,
lib_name: Option<&'a str>,
cc_exts: Vec<String>,
bindgen_exts: Vec<String>,
binding_ext: &'a str,
cc_build_hook: RefCell<Box<dyn FnOnce(&mut Build) -> &mut Build + 'a>>,
bindgen_builder_hook: RefCell<Box<dyn FnMut(Builder) -> Builder + 'a>>,
glob_matchoptions_hook: RefCell<Box<dyn FnOnce(MatchOptions) -> MatchOptions + 'a>>,
}
impl<'a> Default for Config<'a>
{
fn default() -> Config<'a>
{
Config {
out_dir: PathBuf::from(
env::var(&ENV_KEY_OUT_DIR).unwrap_or(".".to_string())),
input_files: Vec::new(),
lib_name: None,
cc_exts: SOURCE_EXTS
.iter().map(|&x| {String::from(x)}).collect(),
bindgen_exts: HEADER_EXTS
.iter().map(|&x| {String::from(x)}).collect(),
binding_ext: RUST_FFI_BINDING_EXT,
cc_build_hook: RefCell::new(Box::new(reflect_cc)),
bindgen_builder_hook: RefCell::new(Box::new(reflect_bindgen)),
glob_matchoptions_hook: RefCell::new(Box::new(reflect_glob)),
}
}
}
impl<'a> Config<'a>
{
pub fn out_dir(mut self, out_dir: &Path) -> Self
{
self.out_dir.clear();
self.out_dir.push(out_dir);
self
}
pub fn input_file(mut self, filename: &'a str) -> Self
{
self.input_files.clear();
self.add_input_file(filename)
}
pub fn input_files<IT>(mut self, filename_iter: IT) -> Self
where IT: Iterator<Item = &'a str>
{
self.input_files.clear();
self.add_input_files(filename_iter)
}
pub fn add_input_file(mut self, filename: &'a str) -> Self
{
self.input_files.push(filename);
self
}
pub fn add_input_files<IT>(mut self, filename_iter: IT) -> Self
where IT: Iterator<Item = &'a str>
{
for filename in filename_iter {
self.input_files.push(filename);
}
self
}
pub fn lib_name(mut self, lib_name: &'a str) -> Self
{
self.lib_name = Some(lib_name);
self
}
pub fn binding_ext(mut self, binding_ext: &'a str) -> Self
{
self.binding_ext = binding_ext;
self
}
pub fn add_source_ext(mut self, ext: &str) -> Self
{
if self.cc_exts.iter().find(|&x| {x == ext}).is_none() {
self.cc_exts.push(ext.to_string());
}
self
}
pub fn delete_source_ext(mut self, ext: &str) -> Self
{
if let Some(i) = self.cc_exts.iter().position(|x| {x == ext}) {
self.cc_exts.remove(i);
}
self
}
pub fn add_header_ext(mut self, ext: &str) -> Self
{
if self.bindgen_exts.iter().find(|&x| {x == ext}).is_none() {
self.bindgen_exts.push(ext.to_string());
}
self
}
pub fn delete_header_ext(mut self, ext: &str) -> Self
{
if let Some(i) = self.bindgen_exts.iter().position(|x| {x == ext}) {
self.bindgen_exts.remove(i);
}
self
}
pub fn cc_build_hook<CcBuildHook>(
mut self,
cc_build_hook: CcBuildHook)
-> Self
where CcBuildHook: FnOnce(&mut Build) -> &mut Build + 'a
{
self.cc_build_hook = RefCell::new(Box::new(cc_build_hook));
self
}
pub fn add_cc_build_hook<CcBuildHook>(
mut self,
cc_build_hook: CcBuildHook)
-> Self
where CcBuildHook: FnOnce(&mut Build) -> &mut Build + 'a
{
let cc_build_hook_fn = (self.cc_build_hook)
.replace(Box::new(reflect_cc));
self.cc_build_hook = RefCell::new(Box::new(move |build: &mut Build| {
cc_build_hook(cc_build_hook_fn(build))
}));
self
}
pub fn bindgen_builder_hook<BindgenBuildHook>(
mut self,
bindgen_builder_hook: BindgenBuildHook)
-> Self
where BindgenBuildHook: FnMut(Builder) -> Builder + 'a
{
self.bindgen_builder_hook = RefCell::new(Box::new(bindgen_builder_hook));
self
}
pub fn add_bindgen_builder_hook<BindgenBuildHook>(
mut self,
bindgen_builder_hook: BindgenBuildHook)
-> Self
where BindgenBuildHook: FnMut(Builder) -> Builder + 'a
{
let mut bindgen_builder_hook_ = bindgen_builder_hook;
let mut bindgen_builder_hook_fn = self.bindgen_builder_hook
.replace(Box::new(reflect_bindgen));
self.bindgen_builder_hook = RefCell::new(Box::new(move |builder: Builder| {
bindgen_builder_hook_(bindgen_builder_hook_fn(builder))
}));
self
}
pub fn glob_matchoptions_hook<GlobMatchOptionsHook>(
mut self,
glob_matchoptions_hook: GlobMatchOptionsHook)
-> Self
where GlobMatchOptionsHook: FnOnce(MatchOptions) -> MatchOptions + 'a
{
self.glob_matchoptions_hook = RefCell::new(Box::new(glob_matchoptions_hook));
self
}
pub fn add_glob_matchoptions_hook<GlobMatchOptionsHook>(
mut self,
glob_matchoptions_hook: GlobMatchOptionsHook)
-> Self
where GlobMatchOptionsHook: FnOnce(MatchOptions) -> MatchOptions + 'a
{
let glob_matchoptions_hook_fn = (self.glob_matchoptions_hook)
.replace(Box::new(reflect_glob));
self.glob_matchoptions_hook = RefCell::new(Box::new(move |match_options: MatchOptions| {
glob_matchoptions_hook(glob_matchoptions_hook_fn(match_options))
}));
self
}
pub fn build(self)
-> Result<BuildResults, MldError>
{
let mut results = BuildResults::new();
let mut built_something = false;
if !self.out_dir.is_dir() {
return Err(
MldError::from(
format!("output directory {} MUST be created before calling Config::build",
self.out_dir.to_str().expect("output directory path string MUST be valid"))));
}
results.out_dir = StdPathBuf::new();
results.out_dir.push(self.out_dir.clone());
let mut build = Build::default();
let cc_build_hook_fn = (self.cc_build_hook)
.replace(Box::new(reflect_cc));
build.out_dir::<&Path>(self.out_dir.as_ref());
cc_build_hook_fn(&mut build);
let glob_matchoptions = MatchOptions::new();
let glob_matchoptions_hook_fn = (self.glob_matchoptions_hook)
.replace(Box::new(reflect_glob));
let glob_matchoptions = glob_matchoptions_hook_fn(glob_matchoptions);
for src_fn_glob in &self.input_files {
for src_fn_pathbuf in glob_with(src_fn_glob, glob_matchoptions)?
.filter_map(Result::ok) {
let src_filename = src_fn_pathbuf
.to_str()
.expect("globbed path MUST make a valid string");
let ext = self.find_filetype(src_fn_pathbuf.extension());
match ext {
FileType::Source | FileType::Header => {
println!("cargo:rerun-if-changed={src_filename}");
},
FileType::Unsupported(_) => {
eprintln!("Ignoring non-source file {src_filename}.");
},
}
match ext {
FileType::Source => {
build.file(src_fn_pathbuf.as_path());
results.source_files.push(src_fn_pathbuf);
},
FileType::Header => {
let mut binding_pathbuf = self.out_dir
.clone()
.join(src_fn_pathbuf.file_name()
.expect("source file PathBuf MUST make a valid string"));
binding_pathbuf.set_extension(self.binding_ext);
let builder = Builder::default()
.header(src_filename)
.parse_callbacks(Box::new(CargoCallbacks));
let builder = (self.
bindgen_builder_hook
.borrow_mut())
(builder);
let bindings = builder.generate()?;
bindings.write_to_file(&binding_pathbuf)?;
built_something = true;
results.header_bindings.push(
HeaderBinding::from((src_fn_pathbuf, binding_pathbuf)));
},
FileType::Unsupported(_) => {},
}
}
}
if !results.source_files.is_empty() {
let lib_name = self.lib_name.ok_or_else(
|| MldError::from(
"library name MUST be configured when at least one cc source is configured"))?;
build.try_compile(lib_name)?;
built_something = true;
results.lib_name = Some(String::from(lib_name));
}
if !built_something {
return Err(MldError::from("no source files configured"));
}
Ok(results)
}
fn find_filetype(&self, ext: Option<&OsStr>) -> FileType
{
match ext {
Some(ext_os) => {
match ext_os.to_str() {
Some(ext_str) => {
if self.cc_exts.iter().find(|x| {*x == ext_str}).is_some() {
FileType::Source
} else if self.bindgen_exts.iter().find(|x| {*x == ext_str}).is_some() {
FileType::Header
} else {
FileType::Unsupported(String::from(ext_str))
}
},
None => FileType::Unsupported("".to_string())
}
},
None => {
FileType::Unsupported("".to_string())
}
}
}
}
#[derive(Debug)]
pub struct HeaderBinding
{
pub input_header_file: StdPathBuf,
pub rust_binding_file: StdPathBuf,
}
impl From<(StdPathBuf, StdPathBuf)> for HeaderBinding
{
fn from(paths: (StdPathBuf, StdPathBuf)) -> HeaderBinding
{
HeaderBinding {
input_header_file: paths.0,
rust_binding_file: paths.1,
}
}
}
impl Display for HeaderBinding
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError>
{
write!(f,
"({} -> {})",
self.input_header_file.display(),
self.rust_binding_file.display())
}
}
#[derive(Debug)]
pub struct BuildResults
{
pub out_dir: StdPathBuf,
pub lib_name: Option<String>,
pub source_files: Vec<StdPathBuf>,
pub header_bindings: Vec<HeaderBinding>,
}
impl BuildResults
{
fn new() -> BuildResults
{
BuildResults {
out_dir: StdPathBuf::from("."),
lib_name: None,
source_files: Vec::new(),
header_bindings: Vec::new(),
}
}
}
impl Display for BuildResults
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError>
{
write!(f,
"(out_dir: {}, lib_name: {}, source_files: {}, header_bindings: {})",
self.out_dir.display(),
(self.lib_name.as_ref()).unwrap_or(&("None".to_string())),
str_iter_to_string(self.source_files.iter().map(|path_buf| {path_buf.display()})),
str_iter_to_string(self.header_bindings.iter()))
}
}
impl Termination for BuildResults
{
fn report(self) -> ExitCode
{
ExitCode::SUCCESS
}
}
fn str_iter_to_string<T, IT>(iter: IT) -> String
where T: ToString, IT: Iterator<Item = T>
{
let mut string = String::new();
string += "[";
let mut first = true;
for i in iter {
string += &format!("{}", i.to_string());
if first {
string += ", ";
first = false;
}
}
string += "]";
string
}
#[derive(Debug, PartialEq)]
enum FileType
{
Source,
Header,
Unsupported(String),
}
impl Display for FileType
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError>
{
match self {
FileType::Source => write!(f, "FileType: Source"),
FileType::Header => write!(f, "FileType: Header"),
FileType::Unsupported(ext) => write!(f, "FileType: Unsupported({})", ext),
}
}
}