Add support for using on-the-fly generated definitions
MODIFY
binding-generator/src/bin/settings-cleanup.rs
binding-generator/src/bin/settings-cleanup.rs
@@ -138,7 +138,8 @@ fn main() {
let gen = Generator::new(None, &opencv_header_dir, &src_cpp_dir, clang);
for module in modules {
println!(" {}", module);
- gen.process_module(&module, false, |root_entity| {
+ gen.process_module(&module, false, |root_tu| {
+ let root_entity = root_tu.get_entity();
let gen_env = GeneratorEnv::new(root_entity, &module);
let walker = EntityWalker::new(root_entity);
walker.walk_opencv_entities(FunctionFinder {

MODIFY
binding-generator/src/generator.rs
binding-generator/src/generator.rs
use std::{
borrow::Cow,
- fmt,
+ collections::{HashMap, HashSet},
+ fmt::{self, Write},
fs::File,
io::BufReader,
path::{Path, PathBuf},
@@ -8,18 +9,24 @@ use std::{
use clang::{
Clang,
+ diagnostic::Diagnostic,
diagnostic::Severity,
Entity,
EntityKind,
+ EntityVisitResult,
Index,
TranslationUnit,
Type,
+ Unsaved,
};
use dunce::canonicalize;
+use maplit::hashmap;
+use once_cell::sync::Lazy;
use crate::{
AbstractRefWrapper,
Class,
+ CompiledInterpolation,
Const,
Element,
EntityExt,
@@ -31,9 +38,11 @@ use crate::{
GeneratorEnv,
get_definition_text,
line_reader,
+ opencv_module_from_path,
return_type_wrapper::ReturnTypeWrapper,
settings,
smart_ptr::SmartPtr,
+ StrExt,
type_ref::Kind as TypeRefKind,
Typedef,
vector::Vector,
@@ -59,6 +68,114 @@ pub trait GeneratorVisitor<'tu> {
fn visit_typedef(&mut self, typedef: Typedef) {}
fn visit_class(&mut self, class: Class) {}
fn visit_dependent_type(&mut self, typ: Self::D) {}
+
+ fn visit_ephemeral_header(&mut self, contents: &str) {}
+}
+
+struct EphemeralGenerator<'m> {
+ module: &'m str,
+ used_in_smart_ptr: HashSet<String>,
+ descendants: HashMap<String, HashSet<String>>,
+}
+
+impl<'m> EphemeralGenerator<'m> {
+ pub fn new(module: &'m str) -> Self {
+ Self {
+ module,
+ used_in_smart_ptr: HashSet::with_capacity(32),
+ descendants: HashMap::with_capacity(16),
+ }
+ }
+
+ fn add_used_in_smart_ptr(&mut self, func: Entity) {
+ for arg in func.get_arguments().into_iter().flatten() {
+ if let Some(arg_type) = arg.get_type() {
+ if arg_type.get_declaration().map_or(false, |ent| ent.cpp_fullname().starts_with("cv::Ptr")) {
+ let inner_type_ent = arg_type.get_template_argument_types().into_iter()
+ .flatten()
+ .flatten()
+ .next()
+ .and_then(|t| t.get_declaration());
+ if let Some(inner_type_ent) = inner_type_ent {
+ self.used_in_smart_ptr.insert(inner_type_ent.cpp_fullname().into_owned());
+ }
+ }
+ }
+ }
+ }
+
+ pub fn make_generated_header(&self) -> String {
+ static TPL: Lazy<CompiledInterpolation> = Lazy::new(
+ || include_str!("../tpl/ephemeral/ephemeral.tpl.hpp").compile_interpolation()
+ );
+
+ let mut includes = String::with_capacity(128);
+ let mut resolve_types = String::with_capacity(1024);
+ let mut generate_types: Vec<Cow<_>> = Vec::with_capacity(32);
+
+ let mut resolve_types_idx = 0usize;
+ let global_tweaks = settings::GENERATOR_MODULE_TWEAKS.get("*");
+ let module_tweaks = settings::GENERATOR_MODULE_TWEAKS.get(self.module);
+ for tweak in global_tweaks.iter().chain(module_tweaks.iter()) {
+ for &include in &tweak.includes {
+ writeln!(&mut includes, "#include <opencv2/{}>", include).expect("Can't fail");
+ }
+ for &res_type in &tweak.resolve_types {
+ writeln!(&mut resolve_types, "typedef {} ephem{};", res_type, resolve_types_idx).expect("Can't fail");
+ resolve_types_idx += 1;
+ }
+ for &gen_type in &tweak.generate_types {
+ generate_types.push(gen_type.into());
+ }
+ }
+ for used_cppfull in &self.used_in_smart_ptr {
+ for desc_cppfull in self.descendants.get(used_cppfull).into_iter().flatten() {
+ if !self.used_in_smart_ptr.contains(desc_cppfull) {
+ generate_types.push(format!("cv::Ptr<{}>", desc_cppfull).into());
+ }
+ }
+ }
+
+ TPL.interpolate(&hashmap! {
+ "includes" => includes,
+ "resolve_types" => resolve_types,
+ "generate_types" => generate_types.join(",\n"),
+ })
+ }
+}
+
+impl EntityWalkerVisitor<'_> for &mut EphemeralGenerator<'_> {
+ fn wants_file(&mut self, path: &Path) -> bool {
+ opencv_module_from_path(path).map_or(false, |m| m == self.module)
+ }
+
+ fn visit_entity(&mut self, entity: Entity<'_>) -> bool {
+ match entity.get_kind() {
+ EntityKind::ClassDecl | EntityKind::StructDecl => {
+ entity.visit_children(|c, _| {
+ match c.get_kind() {
+ EntityKind::BaseSpecifier => {
+ let c_decl = c.get_definition().expect("Can't get base class definition");
+ self.descendants.entry(c_decl.cpp_fullname().into_owned())
+ .or_insert_with(|| HashSet::with_capacity(4))
+ .insert(entity.cpp_fullname().into_owned());
+ }
+ EntityKind::Constructor | EntityKind::Method | EntityKind::FunctionTemplate
+ | EntityKind::ConversionFunction => {
+ self.add_used_in_smart_ptr(c)
+ }
+ _ => {}
+ }
+ EntityVisitResult::Continue
+ });
+ }
+ EntityKind::FunctionDecl => {
+ self.add_used_in_smart_ptr(entity);
+ }
+ _ => {}
+ }
+ true
+ }
}
#[derive(Debug)]
@@ -349,6 +466,26 @@ impl Generator {
}
}
+ fn make_ephemeral_header(&self, contents: &str) -> Unsaved {
+ Unsaved::new(self.src_cpp_dir.join("ocvrs_ephemeral.hpp"), contents)
+ }
+
+ fn handle_diags(diags: &[Diagnostic], panic_on_error: bool) {
+ if !diags.is_empty() {
+ let mut has_error = false;
+ eprintln!("=== WARNING: {} diagnostic messages", diags.len());
+ for diag in diags {
+ if !has_error && matches!(diag.get_severity(), Severity::Error | Severity::Fatal) {
+ has_error = true;
+ }
+ eprintln!("=== {}", diag);
+ }
+ if has_error && panic_on_error {
+ panic!("=== Errors during header parsing");
+ }
+ }
+ }
+
pub fn build_clang_command_line_args(&self) -> Vec<Cow<'static, str>> {
let mut args = self.clang_include_dirs.iter()
.map(|d| format!("-isystem{}", d.to_str().expect("Incorrect system include path")).into())
@@ -357,42 +494,39 @@ impl Generator {
)
.collect::<Vec<_>>();
args.push("-DOCVRS_PARSING_HEADERS".into());
- args.push("-includeocvrs_resolve_types.hpp".into());
+ args.push("-includeocvrs_ephemeral.hpp".into());
// need to have c++14 here because VS headers contain features that require it
args.push("-std=c++14".into());
args
}
- pub fn process_module(&self, module: &str, panic_on_error: bool, entity_processor: impl FnOnce(Entity)) {
+ pub fn process_module(&self, module: &str, panic_on_error: bool, entity_processor: impl FnOnce(TranslationUnit)) {
let index = Index::new(&self.clang, true, false);
let mut module_file = self.src_cpp_dir.join(format!("{}.hpp", module));
if !module_file.exists() {
module_file = self.opencv_include_dir.join(format!("opencv2/{}.hpp", module));
}
let root_tu: TranslationUnit = index.parser(module_file)
+ .unsaved(&[self.make_ephemeral_header("")])
.arguments(&self.build_clang_command_line_args())
.detailed_preprocessing_record(true)
.skip_function_bodies(true)
.parse().expect("Cannot parse");
- let diags = root_tu.get_diagnostics();
- if !diags.is_empty() {
- let mut has_error = false;
- eprintln!("=== WARNING: {} diagnostic messages", diags.len());
- for diag in diags {
- if !has_error && matches!(diag.get_severity(), Severity::Error | Severity::Fatal) {
- has_error = true;
- }
- eprintln!("=== {}", diag);
- }
- if has_error && panic_on_error {
- panic!("=== Errors during header parsing");
- }
- }
- entity_processor(root_tu.get_entity());
+ Self::handle_diags(&root_tu.get_diagnostics(), panic_on_error);
+ entity_processor(root_tu);
}
- pub fn process_opencv_module(&self, module: &str, visitor: impl for<'gtu> GeneratorVisitor<'gtu>) {
- self.process_module(module, true, |root_entity| {
+ pub fn process_opencv_module(&self, module: &str, mut visitor: impl for<'gtu> GeneratorVisitor<'gtu>) {
+ self.process_module(module, true, |mut root_tu| {
+ let mut root_entity = root_tu.get_entity();
+ let walker = EntityWalker::new(root_entity);
+ let mut ephem_gen = EphemeralGenerator::new(module);
+ walker.walk_opencv_entities(&mut ephem_gen);
+ let hdr = ephem_gen.make_generated_header();
+ visitor.visit_ephemeral_header(&hdr);
+ root_tu = root_tu.reparse(&[self.make_ephemeral_header(&hdr)]).expect("Can't reparse file");
+ Self::handle_diags(&root_tu.get_diagnostics(), true);
+ root_entity = root_tu.get_entity();
let gen_env = GeneratorEnv::new(root_entity, module);
let opencv_walker = OpenCVWalker::new(
&self.opencv_include_dir,
@@ -405,6 +539,10 @@ impl Generator {
}
}
+pub fn is_ephemeral_header(path: &Path) -> bool {
+ path.ends_with("ocvrs_ephemeral.hpp")
+}
+
#[allow(unused)]
pub fn dbg_clang_type(type_ref: Type) {
struct TypeWrapper<'tu>(Type<'tu>);

MODIFY
binding-generator/src/generator_env.rs
binding-generator/src/generator_env.rs
@@ -16,6 +16,7 @@ use crate::{
Element,
EntityWalker,
EntityWalkerVisitor,
+ is_ephemeral_header,
is_opencv_path,
memo_map,
MemoizeMap,
@@ -72,7 +73,7 @@ impl<'tu> DBPopulator<'_, 'tu> {
impl<'tu> EntityWalkerVisitor<'tu> for DBPopulator<'_, 'tu> {
fn wants_file(&mut self, path: &Path) -> bool {
is_opencv_path(path)
- || path.ends_with("ocvrs_resolve_types.hpp")
+ || is_ephemeral_header(path)
|| opencv_module_from_path(path).map_or(false, |m| m == self.gen_env.module)
}
@@ -146,8 +147,12 @@ impl<'tu> GeneratorEnv<'tu> {
fn key(entity: Entity) -> ExportIdx {
let (loc, offset) = if entity.get_kind() == EntityKind::MacroExpansion {
// sometimes CV_EXPORT macros are located on a separate line so for those we compensate the offset
- let l = entity.get_range().expect("Can't get export macro range").get_end().get_spelling_location();
- let mut f = File::open(l.file.expect("Can't get export macro file").get_path()).expect("Can't open export macro file");
+ let l = entity.get_range().expect("Can't get exported macro range").get_end().get_spelling_location();
+ let path = l.file.expect("Can't get exported macro file").get_path();
+ if is_ephemeral_header(&path) {
+ (l, 0)
+ } else {
+ let mut f = File::open(path).expect("Can't open export macro file");
f.seek(SeekFrom::Start(l.offset as u64)).expect("Can't seek export macro file");
let mut buf = [0; 8];
let read = f.read(&mut buf).expect("Can't read file");
@@ -158,6 +163,7 @@ impl<'tu> GeneratorEnv<'tu> {
panic!("Line offset more than 1 is not supported, modify fuzzy_key in get_export_config() to support higher values");
}
(l, line_offset as u32)
+ }
} else {
let loc = if let Some(range) = entity.get_range() {
range.get_start().get_spelling_location()

MODIFY
binding-generator/src/lib.rs
binding-generator/src/lib.rs
@@ -42,7 +42,7 @@ pub use func::{Func, FunctionTypeHint};
use function::Function;
#[allow(unused)]
use generator::{dbg_clang_entity, dbg_clang_type};
-pub use generator::{DependentType, Generator, GeneratorVisitor};
+pub use generator::{DependentType, Generator, GeneratorVisitor, is_ephemeral_header};
pub use generator_env::{ExportConfig, GeneratorEnv};
pub use iterator_ext::IteratorExt;
#[allow(unused)]

MODIFY
binding-generator/src/settings.rs
binding-generator/src/settings.rs
@@ -622,6 +622,7 @@ pub static ELEMENT_EXPORT: Lazy<HashMap<&str, ExportConfig>> = Lazy::new(|| hash
"cv::PlaneWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::AffineWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::CylindricalWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
+ "cv::CylindricalWarperGpu" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::SphericalWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::FisheyeWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::StereographicWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
@@ -629,13 +630,16 @@ pub static ELEMENT_EXPORT: Lazy<HashMap<&str, ExportConfig>> = Lazy::new(|| hash
"cv::CompressedRectilinearPortraitWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::PaniniWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::PaniniPortraitWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
+ "cv::PlaneWarperGpu" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::MercatorWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
+ "cv::SphericalWarperGpu" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::TransverseMercatorWarper" => ExportConfig::default(), // 3.2 3.4 stitching warpers
"cv::TermCriteria" => ExportConfig::simple(),
"cv::optflow::GPCTrainingParams" => ExportConfig::simple(),
"cv::optflow::GPCMatchingParams" => ExportConfig::simple(),
"cv::cudacodec::FormatInfo" => ExportConfig::simple(),
"cv::kinfu::Intr" => ExportConfig::simple(),
+ "cv::videostab::MaskFrameSource" => ExportConfig::default(),
// override boxed
"cv::DetectionBasedTracker::ExtObject" => ExportConfig::default(),
@@ -941,3 +945,106 @@ pub static PREVENT_VECTOR_TYPEDEF_GENERATION: Lazy<HashSet<&str>> = Lazy::new(||
"cv::ppf_match_3d::Pose3DPtr",
"cv::dnn::Net::LayerId",
});
+
+#[derive(Default)]
+pub struct ModuleTweak {
+ pub includes: Vec<&'static str>,
+ pub resolve_types: Vec<&'static str>,
+ pub generate_types: Vec<&'static str>,
+}
+
+pub static GENERATOR_MODULE_TWEAKS: Lazy<HashMap<&str, ModuleTweak>> = Lazy::new(|| hashmap! {
+ "*" => ModuleTweak {
+ resolve_types: vec![
+ // void is used as return type for property setters
+ "void",
+ // base types
+ "bool",
+ "int",
+ "unsigned int",
+ "double",
+ // return of String
+ "const char*",
+ "void*",
+ // handling vector of strings
+ "std::vector<cv::String>",
+ "std::vector<std::string>",
+ // for return of vector<DataType> types
+ "cv::_InputArray",
+ "cv::_OutputArray",
+ "cv::_InputOutputArray",
+ ],
+ ..Default::default()
+ },
+ "aruco" => ModuleTweak {
+ generate_types: vec![
+ "std::vector<cv::Vec3f>",
+ "std::vector<cv::Vec3d>",
+ ],
+ ..Default::default()
+ },
+ "calib3d" => ModuleTweak {
+ generate_types: vec![
+ "std::vector<cv::Point3i>",
+ "std::vector<std::vector<cv::Point3i>>",
+ "std::vector<cv::Point3d>",
+ "std::vector<std::vector<cv::Point3d>>",
+ ],
+ ..Default::default()
+ },
+ "dnn" => ModuleTweak {
+ includes: vec![
+ "dnn/dict.hpp",
+ ],
+ resolve_types: vec![
+ // for specializing cv::dnn::Dict::set
+ "cv::dnn::DictValue",
+ // for specializing cv::dnn::DictValue::get
+ "cv::String",
+ "int64_t",
+ ],
+ ..Default::default()
+ },
+ "face" => ModuleTweak {
+ includes: vec![
+ "face.hpp",
+ ],
+ ..Default::default()
+ },
+ "imgproc" => ModuleTweak {
+ generate_types: vec![
+ "std::vector<cv::Vec4i>",
+ ],
+ ..Default::default()
+ },
+ "shape" => ModuleTweak {
+ includes: vec![
+ "shape.hpp",
+ ],
+ ..Default::default()
+ },
+ "stitching" => ModuleTweak {
+ includes: vec![
+ "stitching.hpp",
+ ],
+ ..Default::default()
+ },
+ "tracking" => ModuleTweak {
+ includes: vec![
+ "tracking.hpp",
+ ],
+ ..Default::default()
+ },
+ "videostab" => ModuleTweak {
+ includes: vec![
+ "videostab.hpp",
+ ],
+ ..Default::default()
+ },
+ "ximgproc" => ModuleTweak {
+ includes: vec![
+ "ximgproc.hpp",
+ ],
+ ..Default::default()
+ },
+});

MODIFY
binding-generator/src/writer/rust_native/mod.rs
binding-generator/src/writer/rust_native/mod.rs
@@ -20,6 +20,7 @@ use crate::{
Enum,
Func,
GeneratorVisitor,
+ is_ephemeral_header,
IteratorExt,
opencv_module_from_path,
settings,
@@ -121,7 +122,7 @@ impl<'tu> GeneratorVisitor<'tu> for RustNativeBindingWriter<'_> {
type D = DepType<'tu>;
fn wants_file(&mut self, path: &Path) -> bool {
- opencv_module_from_path(path).map_or(false, |m| m == self.module)
+ is_ephemeral_header(path) || opencv_module_from_path(path).map_or(false, |m| m == self.module)
}
fn visit_module_comment(&mut self, comment: String) {
@@ -212,6 +213,13 @@ impl<'tu> GeneratorVisitor<'tu> for RustNativeBindingWriter<'_> {
},
}
}
+
+ fn visit_ephemeral_header(&mut self, contents: &str) {
+ if false {
+ let mut file = File::create(self.types_dir.join(format!("ocvrs_ephemeral_{}.hpp", self.module))).expect("Can't create debug ephemeral file");
+ file.write_all(contents.as_bytes()).expect("Can't write debug ephemeral file");
+ }
+ }
}
fn join<T: AsRef<str>>(v: &mut Vec<(String, T)>) -> String {
@@ -258,7 +266,7 @@ impl Drop for RustNativeBindingWriter<'_> {
let mut cpp = join(&mut self.cpp_funcs);
cpp += &join(&mut self.cpp_classes);
let includes = if self.src_cpp_dir.join(format!("{}.hpp", self.module)).exists() {
- format!("#include \"{module}.hpp\"\n", module=self.module)
+ format!("#include \"{module}.hpp\"", module=self.module)
} else {
format!("#include \"ocvrs_common.hpp\"\n#include <opencv2/{module}.hpp>", module=self.module)
};

ADD
binding-generator/tpl/ephemeral/ephemeral.tpl.hpp
+#include "ocvrs_common.hpp"
+{{includes}}
+
+/* Dummy namespace that contains types that will be used in the bindings generator to resolve clang::Type from
+ * string. Due to limitations of libclang it's not possible to resolve arbitrary strings. That's why we pre-add
+ * typedefs that will be analyzed in the initial parsing step and added to the global cache that can resolve
+ * e.g. "void" to a specific clang::Type.
+ *
+ * Only typedef's are analyzed in this namespace and each entry should alias distinct type, the name of the
+ * typedef is ignored by the parser.
+ */
+namespace ocvrs_resolve_types {
+ {{resolve_types}}
+}
+
+/* This is used to generate types otherwise not referenced in the OpenCV headers. It's useful for example to
+ * generate Ptr<Child> when OpenCV function only accepts Ptr<Parent>.
+ */
+namespace cv {
+ OCVRS_ONLY_DEPENDENT_TYPES void dependent_types_dummy(
+ {{generate_types}}
+ ) {}
+}

ADD
src_cpp/alphamat.hpp
+// alphamat.hpp doesn't include core.hpp
+#include "ocvrs_common.hpp"
+#include <opencv2/alphamat.hpp>

MODIFY
src_cpp/aruco.hpp
src_cpp/aruco.hpp
#include "ocvrs_common.hpp"
#include <opencv2/aruco.hpp>
#include <opencv2/aruco/charuco.hpp>
-
-namespace cv {
- // needed for estimatePoseSingleMarkers()
- OCVRS_ONLY_DEPENDENT_TYPES void dummy(
- std::vector<cv::Vec3f>,
- std::vector<cv::Vec3d>
- ) {}
-}

DELETE
src_cpp/calib3d.hpp
-#include "ocvrs_common.hpp"
-#include <opencv2/calib3d.hpp>
-
-namespace cv {
- // needed for calibrateCamera()
- OCVRS_ONLY_DEPENDENT_TYPES void dummy(
- std::vector<cv::Point3i>,
- std::vector<std::vector<cv::Point3i>>,
- std::vector<cv::Point3d>,
- std::vector<std::vector<cv::Point3d>>
- ) {}
-}

MODIFY
src_cpp/dnn.hpp
src_cpp/dnn.hpp
#endif
#include <opencv2/dnn/all_layers.hpp>
#include <opencv2/dnn/shape_utils.hpp>
-
-// see ocvrs_resolve_types.hpp
-namespace ocvrs_resolve_types {
- // for specializing cv::dnn::Dict::set
- typedef cv::dnn::DictValue dnn1;
-
- // for specializing cv::dnn::DictValue::get
- typedef cv::String dnn2;
- typedef int64_t dnn3;
-}

ADD
src_cpp/hdf.hpp
+#include "ocvrs_common.hpp"
+#include <opencv2/hdf.hpp>

DELETE
src_cpp/imgproc.hpp
-#include "ocvrs_common.hpp"
-#include <opencv2/imgproc.hpp>
-
-namespace cv {
- // needed for findContours()
- OCVRS_ONLY_DEPENDENT_TYPES void dummy(
- std::vector<cv::Vec4i>
- ) {}
-}

DELETE
src_cpp/ocvrs_resolve_types.hpp
-#include "ocvrs_common.hpp"
-#include <vector>
-
-/* Dummy namespace that contains types that will be used in the bindings generator to resolve clang::Type from
- string. Due to limitations of libclang it's not possible to resolve arbitrary strings. That's why we pre-add
- typedefs that will be analyzed in the initial parsing step and added to the global cache that can resolve
- e.g. "void" to a specific clang::Type.
-
- Only typedef's are analyzed in this namespace and each entry should alias distinct type, the name of the
- typedef is ignored by the parser.
-*/
-namespace ocvrs_resolve_types {
- typedef void core1; // void is used as return type for property setters
-
- // base types
- typedef bool core2;
- typedef int core3;
- typedef unsigned int core4;
- typedef double core5;
- typedef const char* core6;
- // return of String
- typedef void* core7;
-
- // handling vector of strings
- typedef std::vector<cv::String> core8;
- typedef std::vector<std::string> core9;
-
- // for return of vector<DataType> types
- typedef cv::_InputArray ioa1;
- typedef cv::_OutputArray ioa2;
- typedef cv::_InputOutputArray ioa3;
-}

Add support for using on-the-fly generated definitions