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