use std::{
env,
ffi::OsStr,
fs::{self, DirEntry, File, OpenOptions},
io::{self, BufRead, BufReader, Write},
path::{Path, PathBuf},
process::{Child, Command},
sync::Arc,
thread,
time::Instant,
};
use glob::glob;
use super::{
file_copy_to_dir,
get_versioned_hub_dirs,
is_core_module,
MODULES,
OUT_DIR,
Result,
SRC_CPP_DIR,
SRC_DIR,
};
fn read_dir(path: &Path) -> Result<impl Iterator<Item=DirEntry>> {
Ok(path.read_dir()?.filter_map(|e| e.ok()))
}
fn copy_indent(mut read: impl BufRead, mut write: impl Write, indent: &str) -> Result<()> {
let mut line = Vec::with_capacity(100);
while read.read_until(b'\n', &mut line)? != 0 {
write.write(indent.as_bytes())?;
write.write(&line)?;
line.clear();
}
Ok(())
}
fn file_move_to_dir(src_file: &Path, target_dir: &Path) -> Result<PathBuf> {
if !target_dir.exists() {
fs::create_dir_all(&target_dir)?;
}
let src_filename = src_file.file_name()
.ok_or_else(|| "Can't calculate filename")?;
let target_file = target_dir.join(src_filename);
if fs::rename(&src_file, &target_file).is_err() {
fs::copy(&src_file, &target_file)?;
fs::remove_file(src_file)?;
}
Ok(target_file)
}
pub fn gen_wrapper(opencv_header_dir: &Path, generator_build: Option<Child>) -> Result<()> {
let out_dir_as_str = OUT_DIR.to_str().unwrap();
let (rust_hub_dir, cpp_hub_dir) = get_versioned_hub_dirs();
let module_dir = rust_hub_dir.join("hub");
let manual_dir = SRC_DIR.join("manual");
let opencv_dir = opencv_header_dir.join("opencv2");
eprintln!("=== Using OpenCV headers from: {}", opencv_dir.display());
eprintln!("=== Generating code in: {}", out_dir_as_str);
eprintln!("=== Placing generated bindings into: {}", rust_hub_dir.display());
let modules = MODULES.get().expect("MODULES not initialized");
for entry in read_dir(&OUT_DIR)? {
let path = entry.path();
if path.is_file() && path.extension().and_then(OsStr::to_str).map_or(true, |ext| !ext.eq_ignore_ascii_case("dll")) {
let _ = fs::remove_file(path);
}
}
let version = if cfg!(feature = "opencv-32") {
"3.2.0"
} else if cfg!(feature = "opencv-34") {
"3.4.10"
} else if cfg!(feature = "opencv-4") {
"4.3.0"
} else {
unreachable!();
};
let clang_stdlib_include_dir = Arc::new(env::var_os("OPENCV_CLANG_STDLIB_PATH")
.map(|p| PathBuf::from(p))
);
let num_jobs = env::var("NUM_JOBS").ok()
.and_then(|jobs| jobs.parse().ok())
.unwrap_or(2);
let job_server = jobserver::Client::new(num_jobs).expect("Can't create job server");
let mut join_handles = Vec::with_capacity(modules.len());
let start;
let clang = clang::Clang::new().expect("Cannot initialize clang");
println!("=== Clang: {}", clang::get_version());
let gen = binding_generator::Generator::new(clang_stdlib_include_dir.as_deref(), &opencv_header_dir, &*SRC_CPP_DIR, clang);
eprintln!("=== Clang command line args: {:#?}", gen.build_clang_command_line_args());
if cfg!(feature = "clang-runtime") {
let status = generator_build.expect("Impossible").wait()?;
if !status.success() {
return Err("Failed to build the bindings generator".into());
}
let opencv_header_dir = Arc::new(opencv_header_dir.to_owned());
start = Instant::now();
modules.iter().for_each(|module| {
let token = job_server.acquire().expect("Can't acquire token from job server");
let join_handle = thread::spawn({
let clang_stdlib_include_dir = Arc::clone(&clang_stdlib_include_dir);
let opencv_header_dir = Arc::clone(&opencv_header_dir);
move || {
let clang_stdlib_include_dir = (*clang_stdlib_include_dir).as_ref()
.and_then(|p| p.to_str())
.unwrap_or("None");
let mut bin_generator = Command::new(OUT_DIR.join("release/binding-generator"));
bin_generator.arg(&*opencv_header_dir)
.arg(&*SRC_CPP_DIR)
.arg(&*OUT_DIR)
.arg(&module)
.arg(version)
.arg(clang_stdlib_include_dir);
eprintln!("=== Running binding generator binary: {:#?}", bin_generator);
let res = bin_generator.status().expect("Can't run bindings generator");
if !res.success() {
panic!("Failed to run the bindings generator");
}
eprintln!("=== Generated: {}", module);
drop(token); }
});
join_handles.push(join_handle);
});
} else {
let gen = Arc::new(gen);
start = Instant::now();
modules.iter().for_each(|module| {
let token = job_server.acquire().expect("Can't acquire token from job server");
let join_handle = thread::spawn({
let gen = Arc::clone(&gen);
move || {
let bindings_writer = binding_generator::writer::RustNativeBindingWriter::new(
&*SRC_CPP_DIR,
&*OUT_DIR,
&module,
version,
false,
);
gen.process_opencv_module(&module, bindings_writer);
eprintln!("=== Generated: {}", module);
drop(token); }
});
join_handles.push(join_handle);
});
}
for join_handle in join_handles {
join_handle.join().expect("Can't join thread");
}
eprintln!("=== Total binding generation time: {:?}", start.elapsed());
if !module_dir.exists() {
fs::create_dir_all(&module_dir)?;
}
for entry in read_dir(&module_dir)? {
let path = entry.path();
if path.extension().map_or(false, |e| e == "rs") {
let _ = fs::remove_file(path);
}
}
if !cpp_hub_dir.exists() {
fs::create_dir_all(&cpp_hub_dir)?;
}
for entry in read_dir(&cpp_hub_dir)? {
let path = entry.path();
if path.is_file() {
let _ = fs::remove_file(path);
}
}
for module in modules {
let module_cpp = OUT_DIR.join(format!("{}.cpp", module));
if module_cpp.is_file() {
file_copy_to_dir(&module_cpp, &cpp_hub_dir)?;
let module_types_cpp = OUT_DIR.join(format!("{}_types.hpp", module));
let mut module_types_file = OpenOptions::new().create(true).truncate(true).write(true).open(&module_types_cpp)?;
let mut type_files: Vec<PathBuf> = glob(&format!("{}/???-{}-*.type.cpp", out_dir_as_str, module))?
.collect::<Result<_, glob::GlobError>>()?;
type_files.sort_unstable();
for entry in type_files.into_iter() {
io::copy(&mut File::open(entry)?, &mut module_types_file)?;
}
file_copy_to_dir(&module_types_cpp, &cpp_hub_dir)?;
}
}
let add_manual = |file: &mut File, mod_name: &str| -> Result<bool> {
if manual_dir.join(format!("{}.rs", mod_name)).exists() {
writeln!(file, "pub use crate::manual::{}::*;", mod_name)?;
Ok(true)
} else {
Ok(false)
}
};
{
let mut hub_rs = File::create(rust_hub_dir.join("hub.rs"))?;
let mut types_rs = File::create(module_dir.join("types.rs"))?;
writeln!(&mut types_rs)?;
let mut sys_rs = File::create(module_dir.join("sys.rs"))?;
writeln!(&mut sys_rs, "use crate::{{mod_prelude_types::*, core}};")?;
writeln!(&mut sys_rs)?;
for module in modules {
let is_core_module = is_core_module(module.as_str());
let write_if_contrib = |write: &mut File| -> Result<()> {
if !is_core_module {
writeln!(write, r#"#[cfg(feature = "contrib")]"#)?;
}
Ok(())
};
write_if_contrib(&mut hub_rs)?;
writeln!(&mut hub_rs, "pub mod {};", module)?;
let module_filename = format!("{}.rs", module);
let target_file = file_move_to_dir(&OUT_DIR.join(&module_filename), &module_dir)?;
let mut f = OpenOptions::new().append(true).open(&target_file)?;
add_manual(&mut f, module)?;
let mut write_header = true;
for entry in glob(&format!("{}/???-{}-*.type.rs", out_dir_as_str, module))? {
let entry = entry?;
if entry.metadata().map(|meta| meta.len()).unwrap_or(0) > 0 {
if write_header {
write_if_contrib(&mut types_rs)?;
writeln!(&mut types_rs, "mod {}_types {{", module)?;
writeln!(&mut types_rs, "\tuse crate::{{mod_prelude::*, core, types, sys}};")?;
writeln!(&mut types_rs)?;
write_header = false;
}
copy_indent(BufReader::new(File::open(&entry)?), &mut types_rs, "\t")?;
}
}
if !write_header {
writeln!(&mut types_rs, "}}")?;
write_if_contrib(&mut types_rs)?;
writeln!(&mut types_rs, "pub use {}_types::*;", module)?;
writeln!(&mut types_rs)?;
}
let path = OUT_DIR.join(format!("{}.externs.rs", module));
write_if_contrib(&mut sys_rs)?;
writeln!(&mut sys_rs, "mod {}_sys {{", module)?;
writeln!(&mut sys_rs, "\tuse super::*;")?;
writeln!(&mut sys_rs)?;
for entry in glob(&format!("{}/{}-*.rv.rs", out_dir_as_str, module))? {
let entry: PathBuf = entry?;
copy_indent(BufReader::new(File::open(entry)?), &mut sys_rs, "\t")?;
}
copy_indent(BufReader::new(File::open(&path)?), &mut sys_rs, "\t")?;
writeln!(&mut sys_rs, "}}")?;
write_if_contrib(&mut sys_rs)?;
writeln!(&mut sys_rs, "pub use {}_sys::*;", module)?;
writeln!(&mut sys_rs)?;
}
writeln!(&mut hub_rs, "pub mod types;")?;
writeln!(&mut hub_rs, "#[doc(hidden)]")?;
writeln!(&mut hub_rs, "pub mod sys;")?;
add_manual(&mut types_rs, "types")?;
add_manual(&mut sys_rs, "sys")?;
writeln!(&mut hub_rs, "pub mod hub_prelude {{")?;
for module in modules {
if !is_core_module(module.as_str()) {
writeln!(&mut hub_rs, r#" #[cfg(feature = "contrib")]"#)?;
}
writeln!(&mut hub_rs, r#" pub use super::{}::prelude::*;"#, module)?;
}
writeln!(&mut hub_rs, "}}")?;
}
Ok(())
}