Skip to main content

opencv_binding_generator/
generator.rs

1use std::borrow::Cow;
2use std::env;
3use std::fs::File;
4use std::io::BufReader;
5use std::mem::ManuallyDrop;
6use std::ops::ControlFlow;
7use std::path::{Path, PathBuf};
8
9use clang::diagnostic::{Diagnostic, Severity};
10use clang::{Clang, Entity, EntityKind, Index};
11use dunce::canonicalize;
12use semver::Version;
13use shlex::Shlex;
14
15use crate::name_pool::NamePool;
16use crate::type_ref::{CppNameStyle, FishStyle, TypeRef, TypeRefKind};
17use crate::typedef::NewTypedefResult;
18use crate::writer::rust_native::element::RustElement;
19use crate::{
20	AbstractRefWrapper, Class, ClassKindOverride, Const, Element, EntityExt, EntityWalkerExt, EntityWalkerVisitor, Enum, Func,
21	GeneratorEnv, SmartPtr, SupportedModule, Tuple, Typedef, Vector, get_definition_text, line_reader, settings,
22};
23
24#[derive(Debug)]
25pub enum GeneratedType<'tu, 'ge> {
26	AbstractRefWrapper(AbstractRefWrapper<'tu, 'ge>),
27	Vector(Vector<'tu, 'ge>),
28	SmartPtr(SmartPtr<'tu, 'ge>),
29	Tuple(Tuple<'tu, 'ge>),
30}
31
32impl<'tu, 'ge> TryFrom<TypeRef<'tu, 'ge>> for GeneratedType<'tu, 'ge> {
33	type Error = ();
34
35	fn try_from(value: TypeRef<'tu, 'ge>) -> Result<Self, Self::Error> {
36		match value.kind().into_owned() {
37			TypeRefKind::StdVector(vec) => Ok(Self::Vector(vec)),
38			TypeRefKind::StdTuple(tuple) => Ok(Self::Tuple(tuple)),
39			TypeRefKind::SmartPtr(ptr) => Ok(Self::SmartPtr(ptr)),
40			_ => Err(()),
41		}
42	}
43}
44
45/// Visitor of the different supported OpenCV entities, used in conjunction with [Generator]
46#[expect(unused)]
47pub trait GeneratorVisitor<'tu>: Sized {
48	/// Check whether the visitor is interested in entities from the specified file
49	fn wants_file(&mut self, path: &Path) -> bool {
50		true
51	}
52
53	/// Top-level module comment
54	fn visit_module_comment(&mut self, comment: String) {}
55
56	/// Top-level constant
57	fn visit_const(&mut self, cnst: Const<'tu>) {}
58
59	/// Top-level enum
60	fn visit_enum(&mut self, enm: Enum<'tu, '_>) {}
61
62	/// Top-level function
63	fn visit_func(&mut self, func: Func<'tu, '_>) {}
64
65	/// Top-level type alias
66	fn visit_typedef(&mut self, typedef: Typedef<'tu, '_>) {}
67
68	/// Top-level class or an internal class of another class
69	fn visit_class(&mut self, class: Class<'tu, '_>) {}
70
71	/// Dependent generated type like `std::vector<Mat>` or `std::tuple<1, 2, 3>`
72	fn visit_generated_type(&mut self, typ: GeneratedType<'tu, '_>) {}
73
74	/// Called at the end of the visitation
75	fn goodbye(self) {}
76}
77
78/// Bridge between [EntityWalkerVisitor] and [GeneratorVisitor]
79///
80/// It takes [Entity]s supplied by the entity walker, extracts their export data (whether the entity should appear in bindings at
81/// all or is internal) and calls the corresponding method in [GeneratorVisitor] based on their type. This is the 2nd pass of the
82/// binding generation.
83pub struct OpenCvWalker<'tu, 'r, V> {
84	module: SupportedModule,
85	opencv_module_header_dir: &'r Path,
86	visitor: V,
87	func_names: NamePool,
88	gen_env: GeneratorEnv<'tu>,
89}
90
91impl<'tu, V: GeneratorVisitor<'tu>> EntityWalkerVisitor<'tu> for OpenCvWalker<'tu, '_, V> {
92	fn wants_file(&mut self, path: &Path) -> bool {
93		self.visitor.wants_file(path) || path.ends_with("ocvrs_common.hpp")
94	}
95
96	fn visit_entity(&mut self, entity: Entity<'tu>) -> ControlFlow<()> {
97		match entity.get_kind() {
98			EntityKind::MacroDefinition => Self::process_const(&mut self.visitor, entity),
99			EntityKind::MacroExpansion => {
100				if let Some(name) = entity.get_name() {
101					const BOXED: [&str; 6] = [
102						"CV_EXPORTS",
103						"CV_EXPORTS_W",
104						"CV_WRAP",
105						"GAPI_EXPORTS",
106						"GAPI_EXPORTS_W",
107						"GAPI_WRAP",
108					];
109					const SIMPLE: [&str; 4] = [
110						"CV_EXPORTS_W_SIMPLE",
111						"CV_EXPORTS_W_PARAMS",
112						"CV_EXPORTS_W_MAP",
113						"GAPI_EXPORTS_W_SIMPLE",
114					];
115					const RENAME: [&str; 2] = ["CV_EXPORTS_AS", "CV_WRAP_AS"];
116					if BOXED.contains(&name.as_str()) {
117						self.gen_env.make_export_config(entity);
118					} else if SIMPLE.contains(&name.as_str()) {
119						self.gen_env.make_export_config(entity).class_kind_override = ClassKindOverride::Simple;
120					} else if let Some(rename_macro) = RENAME.iter().find(|&r| r == &name) {
121						if let Some(new_name) = get_definition_text(entity)
122							.strip_prefix(rename_macro)
123							.and_then(|s| s.strip_prefix('('))
124							.and_then(|d| d.strip_suffix(')'))
125						{
126							self.gen_env.make_export_config(entity);
127							self.gen_env.make_rename_config(entity).rename = new_name.trim().into();
128						}
129					} else if name == "CV_NORETURN" {
130						self.gen_env.make_export_config(entity).no_return = true;
131					} else if name == "CV_NOEXCEPT" {
132						self.gen_env.make_export_config(entity).no_except = true;
133					} else if name == "CV_DEPRECATED" || name == "CV_DEPRECATED_EXTERNAL" {
134						self.gen_env.make_export_config(entity).deprecated = true;
135					} else if name == "CV_NODISCARD_STD" || name == "CV_NODISCARD" {
136						self.gen_env.make_export_config(entity).no_discard = true;
137					} else if name == "OCVRS_ONLY_DEPENDENT_TYPES" {
138						self.gen_env.make_export_config(entity).only_generated_types = true;
139					}
140				}
141			}
142			EntityKind::ClassDecl
143			| EntityKind::ClassTemplate
144			| EntityKind::ClassTemplatePartialSpecialization
145			| EntityKind::StructDecl => Self::process_class(&mut self.visitor, &mut self.gen_env, entity),
146			EntityKind::EnumDecl => Self::process_enum(&mut self.visitor, &self.gen_env, entity),
147			EntityKind::FunctionDecl => Self::process_func(&mut self.visitor, &mut self.func_names, &self.gen_env, entity),
148			EntityKind::TypedefDecl | EntityKind::TypeAliasDecl => Self::process_typedef(&mut self.visitor, &self.gen_env, entity),
149			EntityKind::VarDecl => {
150				if !entity.is_mutable() {
151					Self::process_const(&mut self.visitor, entity);
152				} else {
153					unreachable!("Unsupported VarDecl: {:#?}", entity)
154				}
155			}
156			_ => unreachable!("Unsupported entity: {:#?}", entity),
157		}
158		ControlFlow::Continue(())
159	}
160
161	fn goodbye(mut self) {
162		// Some module level comments like "bioinspired" are not attached to anything and libclang
163		// doesn't seem to offer a way to extract them, do it the hard way then.
164		// That's actually the case for all modules starting with OpenCV 4.8.0 so this is now a single
165		// method of extracting comments
166		let mut comment = String::with_capacity(2048);
167		let mut found_module_comment = false;
168		let module_path = self
169			.opencv_module_header_dir
170			.join(format!("{}.hpp", self.module.opencv_name()));
171		if let Ok(module_file) = File::open(module_path).map(BufReader::new) {
172			let mut defgroup_found = false;
173			line_reader(module_file, |line| {
174				if !found_module_comment && line.trim_start().starts_with("/**") {
175					found_module_comment = true;
176					defgroup_found = false;
177				}
178				if found_module_comment {
179					if comment.contains("@defgroup") {
180						defgroup_found = true;
181					}
182					comment.push_str(line);
183					if line.trim_end().ends_with("*/") {
184						if defgroup_found {
185							return ControlFlow::Break(());
186						} else {
187							comment.clear();
188							found_module_comment = false;
189						}
190					}
191				}
192				ControlFlow::Continue(())
193			});
194		}
195		if found_module_comment {
196			self.visitor.visit_module_comment(comment);
197		}
198		for inject_func_fact in &self.gen_env.settings.func_inject {
199			let inject_func: Func = inject_func_fact();
200			if !inject_func.kind().as_class_method().is_some() {
201				self.visitor.visit_func(inject_func);
202			}
203		}
204		for generated in self.gen_env.settings.generator_module_tweaks.generate_types {
205			if let Ok(generated) = GeneratedType::try_from(generated()) {
206				self.visitor.visit_generated_type(generated);
207			}
208		}
209		self.visitor.goodbye();
210	}
211}
212
213impl<'tu, 'r, V: GeneratorVisitor<'tu>> OpenCvWalker<'tu, 'r, V> {
214	pub fn new(module: SupportedModule, opencv_module_header_dir: &'r Path, visitor: V, gen_env: GeneratorEnv<'tu>) -> Self {
215		Self {
216			module,
217			opencv_module_header_dir,
218			visitor,
219			func_names: NamePool::with_capacity(512),
220			gen_env,
221		}
222	}
223
224	fn process_const(visitor: &mut V, const_decl: Entity<'tu>) {
225		let cnst = Const::new(const_decl);
226		if cnst.exclude_kind().is_included() {
227			visitor.visit_const(cnst);
228		}
229	}
230
231	fn process_class(visitor: &mut V, gen_env: &mut GeneratorEnv<'tu>, class_decl: Entity<'tu>) {
232		if gen_env.get_export_config(class_decl).is_some() {
233			let cls = Class::new(class_decl, gen_env);
234			if cls.exclude_kind().is_included() {
235				cls.generated_types().into_iter().for_each(|dep| {
236					visitor.visit_generated_type(dep);
237				});
238				let _ = class_decl.walk_enums_while(|enm| {
239					Self::process_enum(visitor, gen_env, enm);
240					ControlFlow::Continue(())
241				});
242				let _ = class_decl.walk_classes_while(|sub_cls| {
243					if !gen_env.get_export_config(sub_cls).is_some() {
244						gen_env.make_export_config(sub_cls).class_kind_override = if Class::new(sub_cls, gen_env).can_be_simple() {
245							ClassKindOverride::Simple
246						} else {
247							ClassKindOverride::Boxed
248						};
249					}
250					Self::process_class(visitor, gen_env, sub_cls);
251					ControlFlow::Continue(())
252				});
253				let _ = class_decl.walk_typedefs_while(|tdef| {
254					Self::process_typedef(visitor, gen_env, tdef);
255					ControlFlow::Continue(())
256				});
257				let cls = Class::new(class_decl, gen_env);
258				if let Some(enm) = cls.as_enum() {
259					visitor.visit_enum(enm);
260				} else {
261					visitor.visit_class(cls);
262				}
263			}
264		}
265	}
266
267	fn process_enum(visitor: &mut V, gen_env: &GeneratorEnv<'tu>, enum_decl: Entity<'tu>) {
268		let enm = Enum::new(enum_decl, gen_env);
269		if enm.exclude_kind().is_included() {
270			for cnst in enm.consts() {
271				if cnst.exclude_kind().is_included() {
272					visitor.visit_const(cnst);
273				}
274			}
275			if !enm.is_anonymous() {
276				visitor.visit_enum(enm);
277			}
278		}
279	}
280
281	fn process_func(visitor: &mut V, func_names: &mut NamePool, gen_env: &GeneratorEnv<'tu>, func_decl: Entity<'tu>) {
282		if let Some(e) = gen_env.get_export_config(func_decl) {
283			let mut func = Func::new(func_decl, gen_env);
284			if let Some(func_fact) = gen_env.settings.func_replace.get(&mut func.matcher()) {
285				func = func_fact(&func)
286			}
287			if func.exclude_kind().is_included() {
288				let mut processor = |mut func: Func<'tu, '_>| {
289					func.generated_types().into_iter().for_each(|dep| {
290						visitor.visit_generated_type(dep);
291					});
292					if !e.only_generated_types {
293						let mut name = func.rust_leafname(FishStyle::No).into_owned().into();
294						let mut rust_custom_leafname = None;
295						if func_names.make_unique_name(&mut name).is_changed() {
296							rust_custom_leafname = Some(name.into());
297						}
298						func.set_rust_custom_leafname(rust_custom_leafname);
299						visitor.visit_func(func);
300					}
301				};
302				if let Some(specs) = gen_env.settings.func_specialize.get(&mut func.matcher()) {
303					for spec in specs {
304						processor(func.clone().specialize(spec));
305					}
306				} else {
307					processor(func);
308				}
309			}
310		}
311	}
312
313	fn process_typedef(visitor: &mut V, gen_env: &GeneratorEnv<'tu>, typedef_decl: Entity<'tu>) {
314		let typedef = Typedef::try_new(typedef_decl, gen_env);
315		if typedef.exclude_kind().is_included() {
316			match typedef {
317				NewTypedefResult::Typedef(typedef) => {
318					let export = gen_env.get_export_config(typedef_decl).is_some() || {
319						let underlying_type = typedef.underlying_type_ref();
320						underlying_type.kind().is_function()
321							|| !underlying_type.exclude_kind().is_ignored()
322							|| underlying_type
323								.template_kind()
324								.as_template_specialization()
325								.is_some_and(|templ| {
326									settings::IMPLEMENTED_GENERICS.contains(templ.cpp_name(CppNameStyle::Reference).as_ref())
327								})
328					};
329
330					if export {
331						typedef
332							.generated_types()
333							.into_iter()
334							.for_each(|dep| visitor.visit_generated_type(dep));
335						visitor.visit_typedef(typedef)
336					}
337				}
338				NewTypedefResult::Class(_) | NewTypedefResult::Enum(_) => {
339					// don't generate those because libclang will also emit normal classes and enums too
340				}
341			}
342		}
343	}
344}
345
346/// Main workhorse for generating OpenCV bindings for a specific module
347///
348/// Full binding generation for a module is happening in the following major phases:
349/// 1. Headers are parsed with `libclang`
350/// 2. [crate::generator_env::GeneratorEnvPopulator] collects the data necessary in the binding generation (1st pass)
351/// 3. Binding entities are extracted using the data from step 2 (2nd pass)
352/// 4. Specific source files are generated by [crate::writer::RustNativeBindingWriter] (at the moment)
353#[derive(Debug)]
354pub struct Generator {
355	clang_include_dirs: Vec<PathBuf>,
356	opencv_include_dir: PathBuf,
357	opencv_module_header_dir: PathBuf,
358	src_cpp_dir: PathBuf,
359	clang: ManuallyDrop<Clang>,
360}
361
362impl Drop for Generator {
363	fn drop(&mut self) {
364		const BAD_VERSIONS: [&str; 3] = [" 19.", " 20.", " 21."];
365		// `clang` has an issue on Windows when running with the `runtime` feature and clang-19+:
366		// https://github.com/KyleMayes/clang-rs/issues/63
367		// So we avoid dropping clang in that case as a workaround.
368		// `clang::get_version()` is a string like "Apple clang version 15.0.0 (clang-1500.1.0.2.5)"
369		if cfg!(windows)
370			&& cfg!(feature = "clang-runtime")
371			&& BAD_VERSIONS
372				.iter()
373				.any(|bad_version| clang::get_version().contains(bad_version))
374		{
375			eprintln!("=== Windows + clang-runtime + clang version is known to be problematic, skipping drop of Generator");
376			return;
377		}
378		unsafe {
379			ManuallyDrop::drop(&mut self.clang);
380		}
381	}
382}
383
384impl Generator {
385	pub fn new(opencv_include_dir: &Path, additional_include_dirs: &[&Path], src_cpp_dir: &Path) -> Self {
386		let clang_bin = clang_sys::support::Clang::find(None, &[]).expect("Can't find clang binary");
387		let mut clang_include_dirs = clang_bin.cpp_search_paths.unwrap_or_default();
388		for additional_dir in additional_include_dirs {
389			match canonicalize(additional_dir) {
390				Ok(dir) => clang_include_dirs.push(dir),
391				Err(err) => {
392					eprintln!(
393						"=== Cannot canonicalize one of the additional_include_dirs: {}, reason: {}",
394						additional_dir.display(),
395						err
396					);
397				}
398			};
399		}
400		let mut opencv_module_header_dir = opencv_include_dir.join("opencv2.framework/Headers");
401		if !opencv_module_header_dir.exists() {
402			opencv_module_header_dir = opencv_include_dir.join("opencv2");
403		}
404		Self {
405			clang_include_dirs,
406			opencv_include_dir: canonicalize(opencv_include_dir).expect("Can't canonicalize opencv_include_dir"),
407			opencv_module_header_dir: canonicalize(opencv_module_header_dir).expect("Can't canonicalize opencv_module_header_dir"),
408			src_cpp_dir: canonicalize(src_cpp_dir).expect("Can't canonicalize src_cpp_dir"),
409			clang: ManuallyDrop::new(Clang::new().expect("Can't initialize clang")),
410		}
411	}
412
413	fn handle_diags(diags: &[Diagnostic], panic_on_error: bool) {
414		if !diags.is_empty() {
415			let mut has_error = false;
416			eprintln!("=== WARNING: {} diagnostic messages", diags.len());
417			for diag in diags {
418				if !has_error && matches!(diag.get_severity(), Severity::Error | Severity::Fatal) {
419					has_error = true;
420				}
421				eprintln!("===    {diag}");
422			}
423			if has_error && panic_on_error {
424				panic!("=== Errors during header parsing");
425			}
426		}
427	}
428
429	pub fn is_clang_loaded(&self) -> bool {
430		#[cfg(feature = "clang-runtime")]
431		{
432			clang_sys::is_loaded()
433		}
434		#[cfg(not(feature = "clang-runtime"))]
435		{
436			true
437		}
438	}
439
440	pub fn clang_version(&self) -> String {
441		clang::get_version()
442	}
443
444	pub fn build_clang_command_line_args(&self) -> Vec<Cow<'static, str>> {
445		let mut args = self
446			.clang_include_dirs
447			.iter()
448			.map(|d| format!("-isystem{}", d.to_str().expect("Incorrect system include path")).into())
449			.chain([&self.opencv_include_dir, &self.src_cpp_dir].iter().flat_map(|d| {
450				let include_path = d.to_str().expect("Incorrect include path");
451				[format!("-I{include_path}").into(), format!("-F{include_path}").into()]
452			}))
453			.collect::<Vec<_>>();
454		args.push("-DOCVRS_PARSING_HEADERS".into());
455		args.push("-includeocvrs_common.hpp".into());
456		args.push("-std=c++17".into());
457		// allow us to use some custom clang args
458		let clang_arg = env::var_os("OPENCV_CLANG_ARGS");
459		if let Some(clang_arg) = clang_arg.as_ref().and_then(|s| s.to_str()) {
460			args.extend(Shlex::new(clang_arg).map(Cow::Owned));
461		}
462		args
463	}
464
465	/// Runs the clang header parsing, check for the compilation errors and hands off to `entity_processor`
466	pub fn pre_process(&self, module: SupportedModule, panic_on_error: bool, entity_processor: impl FnOnce(Entity)) {
467		let module = module.opencv_name();
468		let index = Index::new(&self.clang, true, false);
469		let mut module_file = self.src_cpp_dir.join(format!("{module}.hpp"));
470		if !module_file.exists() {
471			module_file = self.opencv_module_header_dir.join(format!("{module}.hpp"));
472		}
473		let root_tu = index
474			.parser(module_file)
475			.arguments(&self.build_clang_command_line_args())
476			.detailed_preprocessing_record(true)
477			.skip_function_bodies(true)
478			.parse()
479			.unwrap_or_else(|e| panic!("Cannot parse module: {module}, error: {e}"));
480		Self::handle_diags(&root_tu.get_diagnostics(), panic_on_error);
481		entity_processor(root_tu.get_entity());
482	}
483
484	/// Runs the full binding generation process using the supplied `visitor`
485	pub fn generate(
486		&self,
487		module: SupportedModule,
488		opencv_version: &Version,
489		panic_on_error: bool,
490		visitor: impl for<'tu> GeneratorVisitor<'tu>,
491	) {
492		self.pre_process(module, panic_on_error, |root_entity| {
493			let gen_env = GeneratorEnv::global(module, root_entity, opencv_version);
494			let opencv_walker = OpenCvWalker::new(module, &self.opencv_module_header_dir, visitor, gen_env);
495			root_entity.walk_opencv_entities(opencv_walker);
496		});
497	}
498}