1use std::collections::HashMap;
2use std::fmt::Debug;
3use std::fs::{File, OpenOptions};
4use std::io::{BufWriter, ErrorKind, Write};
5use std::path::{Path, PathBuf};
6use std::sync::LazyLock;
7use std::{fs, io, iter};
8
9use class::ClassExt;
10use comment::RenderComment;
11use dunce::canonicalize;
12use element::{RustElement, RustNativeGeneratedElement};
13use func::FuncExt;
14pub use string_ext::RustStringExt;
15
16use crate::comment::strip_doxygen_comment_markers;
17use crate::field::Field;
18use crate::name_pool::NamePool;
19use crate::type_ref::{Constness, CppNameStyle, FishStyle, NameStyle};
20use crate::{
21 opencv_module_from_path, settings, Class, CompiledInterpolation, Const, Element, Enum, Func, GeneratedType, GeneratorVisitor,
22 IteratorExt, StrExt, SupportedModule, Typedef,
23};
24
25mod abstract_ref_wrapper;
26mod class;
27mod comment;
28mod constant;
29pub mod element;
30mod enumeration;
31mod field;
32mod func;
33mod function;
34pub mod renderer;
35mod smart_ptr;
36mod string_ext;
37mod tuple;
38pub mod type_ref;
39mod typedef;
40mod vector;
41
42type Entries = Vec<(String, String)>;
43type UniqueEntries = HashMap<String, String>;
44
45#[derive(Clone, Debug)]
47pub struct RustNativeBindingWriter<'s> {
48 debug: bool,
49 src_cpp_dir: PathBuf,
50 module: SupportedModule,
51 opencv_version: &'s str,
52 debug_path: PathBuf,
53 out_dir: PathBuf,
54 comment: String,
55 prelude_traits: Vec<String>,
56 consts: Entries,
57 enums: Entries,
58 rust_funcs: Entries,
59 rust_typedefs: UniqueEntries,
60 rust_classes: Entries,
61 extern_funcs: Entries,
62 extern_classes: Entries,
63 cpp_funcs: Entries,
64 cpp_classes: Entries,
65}
66
67impl<'s> RustNativeBindingWriter<'s> {
68 pub fn new(
69 src_cpp_dir: &Path,
70 out_dir: impl Into<PathBuf>,
71 module: SupportedModule,
72 opencv_version: &'s str,
73 debug: bool,
74 ) -> Self {
75 let out_dir = out_dir.into();
76 let debug_path = out_dir.join(format!("{}.log", module.opencv_name()));
77 #[allow(clippy::collapsible_if)]
78 if false {
79 if debug {
80 File::create(&debug_path).expect("Can't create debug log");
81 }
82 }
83 Self {
84 debug,
85 src_cpp_dir: canonicalize(src_cpp_dir).expect("Can't canonicalize src_cpp_dir"),
86 module,
87 opencv_version,
88 debug_path,
89 out_dir,
90 comment: String::new(),
91 prelude_traits: vec![],
92 consts: vec![],
93 enums: vec![],
94 rust_funcs: vec![],
95 rust_typedefs: UniqueEntries::new(),
96 rust_classes: vec![],
97 extern_funcs: vec![],
98 extern_classes: vec![],
99 cpp_funcs: vec![],
100 cpp_classes: vec![],
101 }
102 }
103
104 fn emit_debug_log(&mut self, obj: &impl Debug) {
105 #[allow(clippy::collapsible_if)]
106 if false {
107 if self.debug {
108 let mut f = OpenOptions::new()
109 .append(true)
110 .open(&self.debug_path)
111 .expect("Can't open debug file");
112 writeln!(f, "{obj:#?}").expect("Can't write debug info");
113 }
114 }
115 }
116}
117
118impl GeneratorVisitor<'_> for RustNativeBindingWriter<'_> {
119 fn wants_file(&mut self, path: &Path) -> bool {
120 match self.module {
121 SupportedModule::Tracking if path.ends_with("video/detail/tracking.detail.hpp") => true,
123 _ => opencv_module_from_path(path) == Some(self.module),
124 }
125 }
126
127 fn visit_module_comment(&mut self, comment: String) {
128 self.comment = strip_doxygen_comment_markers(&comment);
129 }
130
131 fn visit_const(&mut self, cnst: Const) {
132 self.emit_debug_log(&cnst);
133 self.consts.push((
134 cnst.rust_name(NameStyle::decl()).into_owned(),
135 cnst.gen_rust(self.opencv_version),
136 ));
137 }
138
139 fn visit_enum(&mut self, enm: Enum) {
140 self.emit_debug_log(&enm);
141 self.enums.push((
142 enm.rust_name(NameStyle::decl()).into_owned(),
143 enm.gen_rust(self.opencv_version),
144 ));
145 }
146
147 fn visit_func(&mut self, func: Func) {
148 self.emit_debug_log(&func);
149 for func in func.with_companion_functions() {
150 let name = func.identifier();
151 self.rust_funcs.push((name.clone(), func.gen_rust(self.opencv_version)));
152 self.extern_funcs.push((name.clone(), func.gen_rust_externs()));
153 self.cpp_funcs.push((name, func.gen_cpp()));
154 }
155 }
156
157 fn visit_typedef(&mut self, typedef: Typedef) {
158 self.emit_debug_log(&typedef);
159 let opencv_version = self.opencv_version;
160 let cpp_refname = typedef.cpp_name(CppNameStyle::Reference);
161 if !self.rust_typedefs.contains_key(cpp_refname.as_ref()) {
162 self
163 .rust_typedefs
164 .insert(cpp_refname.into_owned(), typedef.gen_rust(opencv_version));
165 }
166 }
167
168 fn visit_class(&mut self, class: Class) {
169 self.emit_debug_log(&class);
170 if class.kind().is_trait() {
171 self
172 .prelude_traits
173 .push(class.rust_trait_name(NameStyle::decl(), Constness::Const).into_owned());
174 self
175 .prelude_traits
176 .push(class.rust_trait_name(NameStyle::decl(), Constness::Mut).into_owned());
177 }
178 let name = class.cpp_name(CppNameStyle::Reference).into_owned();
179 self.rust_classes.push((name.clone(), class.gen_rust(self.opencv_version)));
180 self.extern_classes.push((name.clone(), class.gen_rust_externs()));
181 self.cpp_classes.push((name, class.gen_cpp()));
182 }
183
184 fn visit_generated_type(&mut self, typ: GeneratedType) {
185 let typ = typ.as_ref();
186 let safe_id = typ.element_safe_id();
187
188 fn write_generated_type(types_dir: &Path, typ: &str, safe_id: &str, generator: impl FnOnce() -> String) {
189 let suffix = format!(".type.{typ}");
190 let mut file_name = format!("050-{safe_id}");
191 ensure_filename_length(&mut file_name, suffix.len());
192 file_name.push_str(&suffix);
193 let path = types_dir.join(file_name);
194 let file = OpenOptions::new().create_new(true).write(true).open(&path);
195 match file {
196 Ok(mut file) => {
197 let gen = generator();
198 if !gen.is_empty() {
199 file
200 .write_all(gen.as_bytes())
201 .unwrap_or_else(|e| panic!("Can't write to {typ} file: {e}"));
202 } else {
203 drop(file);
204 fs::remove_file(&path).expect("Can't remove empty file");
205 }
206 }
207 Err(e) if e.kind() == ErrorKind::AlreadyExists => { }
208 Err(e) if e.kind() == ErrorKind::PermissionDenied => { }
209 Err(e) => panic!("Error while creating file: {} for {typ} generated type: {e}", path.display()),
210 }
211 }
212
213 write_generated_type(&self.out_dir, "rs", &safe_id, || typ.gen_rust(self.opencv_version));
214 write_generated_type(&self.out_dir, "externs.rs", &safe_id, || typ.gen_rust_externs());
215 write_generated_type(&self.out_dir, "cpp", &safe_id, || typ.gen_cpp());
216 }
217
218 fn goodbye(mut self) {
219 static RUST_HDR: LazyLock<CompiledInterpolation> =
220 LazyLock::new(|| include_str!("tpl/module/rust_hdr.tpl").compile_interpolation());
221
222 static RUST_PRELUDE: LazyLock<CompiledInterpolation> =
223 LazyLock::new(|| include_str!("tpl/module/prelude.tpl.rs").compile_interpolation());
224
225 static CPP_HDR: LazyLock<CompiledInterpolation> =
226 LazyLock::new(|| include_str!("tpl/module/cpp_hdr.tpl.cpp").compile_interpolation());
227
228 let pub_use_traits = if self.prelude_traits.is_empty() {
229 "".to_string()
230 } else {
231 self.prelude_traits.sort_unstable();
232 format!("pub use super::{{{}}};", self.prelude_traits.join(", "))
233 };
234 let prelude = RUST_PRELUDE.interpolate(&HashMap::from([("pub_use_traits", pub_use_traits)]));
235 let comment = RenderComment::new(self.comment, self.opencv_version);
236 let comment = comment.render_with_comment_marker("//!");
237 let module_opencv_name = self.module.opencv_name();
238 let rust_path = self.out_dir.join(format!("{module_opencv_name}.rs"));
239 {
240 let mut rust = BufWriter::new(File::create(rust_path).expect("Can't create rust file"));
241 rust
242 .write_all(
243 RUST_HDR
244 .interpolate(&HashMap::from([
245 ("static_modules", settings::STATIC_RUST_MODULES.iter().join(", ").as_str()),
246 ("comment", comment.as_ref()),
247 ("prelude", &prelude),
248 ]))
249 .as_bytes(),
250 )
251 .expect("Can't write rust file");
252 write_lines(&mut rust, self.consts).expect("Can't write consts to rust file");
253 write_lines(&mut rust, self.enums).expect("Can't write enums to rust file");
254 write_lines(&mut rust, self.rust_typedefs.into_iter().collect()).expect("Can't write typedefs to rust file");
255 write_lines(&mut rust, self.rust_funcs).expect("Can't write funcs to rust file");
256 write_lines(&mut rust, self.rust_classes).expect("Can't write classes to rust file");
257 }
258
259 let includes = if self.src_cpp_dir.join(format!("{module_opencv_name}.hpp")).exists() {
260 format!("#include \"{module_opencv_name}.hpp\"")
261 } else {
262 format!("#include \"ocvrs_common.hpp\"\n#include <opencv2/{module_opencv_name}.hpp>")
263 };
264 {
265 let cpp_path = self.out_dir.join(format!("{module_opencv_name}.cpp"));
266 let mut cpp = BufWriter::new(File::create(cpp_path).expect("Can't create cpp file"));
267 cpp.write_all(
268 CPP_HDR
269 .interpolate(&HashMap::from([("module", module_opencv_name), ("includes", &includes)]))
270 .as_bytes(),
271 )
272 .expect("Can't write cpp file");
273 cpp.write_all(b"extern \"C\" {\n")
274 .expect("Can't write code wrapper begin to cpp file");
275 write_lines(&mut cpp, self.cpp_funcs).expect("Can't write cpp funcs to file");
276 write_lines(&mut cpp, self.cpp_classes).expect("Can't write cpp classes to file");
277 cpp.write_all(b"}\n").expect("Can't write code wrapper end to cpp file");
278 }
279
280 let externs_path = self.out_dir.join(format!("{module_opencv_name}.externs.rs"));
281 let mut externs_rs = BufWriter::new(File::create(externs_path).expect("Can't create rust exports file"));
282 write_lines(&mut externs_rs, self.extern_funcs).expect("Can't write extern funcs to file");
283 write_lines(&mut externs_rs, self.extern_classes).expect("Can't write extern classes to file");
284 }
285}
286
287fn write_lines<T: AsRef<[u8]>>(mut out: impl Write, mut v: Vec<(String, T)>) -> io::Result<()> {
288 v.sort_unstable_by(|(name_left, _), (name_right, _)| name_left.cmp(name_right));
289 for (_, code) in v {
290 out.write_all(code.as_ref())?;
291 }
292 Ok(())
293}
294
295fn ensure_filename_length(file_name: &mut String, reserve: usize) {
296 const MAX_FILENAME_LEN: usize = 255;
297
298 let max_length = MAX_FILENAME_LEN - reserve;
299
300 if file_name.len() > max_length {
301 *file_name = file_name[..max_length].to_string();
302 }
303}
304
305fn rust_disambiguate_names<'tu, 'ge>(
306 args: impl IntoIterator<Item = Field<'tu, 'ge>>,
307) -> impl Iterator<Item = (String, Field<'tu, 'ge>)>
308where
309 'tu: 'ge,
310{
311 let args = args.into_iter();
312 let size_hint = args.size_hint();
313 NamePool::with_capacity(size_hint.1.unwrap_or(size_hint.0)).into_disambiguator(args, |f| f.rust_leafname(FishStyle::No))
314}
315
316fn rust_disambiguate_names_ref<'f, 'tu, 'ge>(
317 args: impl IntoIterator<Item = &'f Field<'tu, 'ge>>,
318) -> impl Iterator<Item = (String, &'f Field<'tu, 'ge>)>
319where
320 'tu: 'ge,
321 'tu: 'f,
322 'ge: 'f,
323{
324 let args = args.into_iter();
325 let size_hint = args.size_hint();
326 NamePool::with_capacity(size_hint.1.unwrap_or(size_hint.0)).into_disambiguator(args, |f| f.rust_leafname(FishStyle::No))
327}
328
329pub fn disambiguate_single_name(name: &str) -> impl Iterator<Item = String> + '_ {
330 let mut i = 0;
331 iter::from_fn(move || {
332 let out = format!("{}{}", name, disambiguate_num(i));
333 i += 1;
334 Some(out)
335 })
336}
337
338fn disambiguate_num(counter: usize) -> String {
339 match counter {
340 0 => "".to_string(),
341 n => format!("_{n}"),
342 }
343}