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#[expect(unused)]
47pub trait GeneratorVisitor<'tu>: Sized {
48 fn wants_file(&mut self, path: &Path) -> bool {
50 true
51 }
52
53 fn visit_module_comment(&mut self, comment: String) {}
55
56 fn visit_const(&mut self, cnst: Const<'tu>) {}
58
59 fn visit_enum(&mut self, enm: Enum<'tu, '_>) {}
61
62 fn visit_func(&mut self, func: Func<'tu, '_>) {}
64
65 fn visit_typedef(&mut self, typedef: Typedef<'tu, '_>) {}
67
68 fn visit_class(&mut self, class: Class<'tu, '_>) {}
70
71 fn visit_generated_type(&mut self, typ: GeneratedType<'tu, '_>) {}
73
74 fn goodbye(self) {}
76}
77
78pub 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 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 }
341 }
342 }
343 }
344}
345
346#[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 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 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 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 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}