opencv_binding_generator/writer/rust_native/
mod.rs

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::{fs, io, iter};
7
8use class::ClassExt;
9use comment::RenderComment;
10use dunce::canonicalize;
11use element::{RustElement, RustNativeGeneratedElement};
12use func::FuncExt;
13use once_cell::sync::Lazy;
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/// Writer of the source files used when building OpenCV to run on a native platform (as opposed to for example WASM)
46#[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		opencv_module_from_path(path) == Some(self.module)
121	}
122
123	fn visit_module_comment(&mut self, comment: String) {
124		self.comment = strip_doxygen_comment_markers(&comment);
125	}
126
127	fn visit_const(&mut self, cnst: Const) {
128		self.emit_debug_log(&cnst);
129		self.consts.push((
130			cnst.rust_name(NameStyle::decl()).into_owned(),
131			cnst.gen_rust(self.opencv_version),
132		));
133	}
134
135	fn visit_enum(&mut self, enm: Enum) {
136		self.emit_debug_log(&enm);
137		self.enums.push((
138			enm.rust_name(NameStyle::decl()).into_owned(),
139			enm.gen_rust(self.opencv_version),
140		));
141	}
142
143	fn visit_func(&mut self, func: Func) {
144		self.emit_debug_log(&func);
145		for func in func.with_companion_functions() {
146			let name = func.identifier();
147			self.rust_funcs.push((name.clone(), func.gen_rust(self.opencv_version)));
148			self.extern_funcs.push((name.clone(), func.gen_rust_externs()));
149			self.cpp_funcs.push((name, func.gen_cpp()));
150		}
151	}
152
153	fn visit_typedef(&mut self, typedef: Typedef) {
154		self.emit_debug_log(&typedef);
155		let opencv_version = self.opencv_version;
156		let cpp_refname = typedef.cpp_name(CppNameStyle::Reference);
157		if !self.rust_typedefs.contains_key(cpp_refname.as_ref()) {
158			self
159				.rust_typedefs
160				.insert(cpp_refname.into_owned(), typedef.gen_rust(opencv_version));
161		}
162	}
163
164	fn visit_class(&mut self, class: Class) {
165		self.emit_debug_log(&class);
166		if class.kind().is_trait() {
167			self
168				.prelude_traits
169				.push(class.rust_trait_name(NameStyle::decl(), Constness::Const).into_owned());
170			self
171				.prelude_traits
172				.push(class.rust_trait_name(NameStyle::decl(), Constness::Mut).into_owned());
173		}
174		let name = class.cpp_name(CppNameStyle::Reference).into_owned();
175		self.rust_classes.push((name.clone(), class.gen_rust(self.opencv_version)));
176		self.extern_classes.push((name.clone(), class.gen_rust_externs()));
177		self.cpp_classes.push((name, class.gen_cpp()));
178	}
179
180	fn visit_generated_type(&mut self, typ: GeneratedType) {
181		let typ = typ.as_ref();
182		let safe_id = typ.element_safe_id();
183
184		fn write_generated_type(types_dir: &Path, typ: &str, safe_id: &str, generator: impl FnOnce() -> String) {
185			let suffix = format!(".type.{typ}");
186			let mut file_name = format!("050-{safe_id}");
187			ensure_filename_length(&mut file_name, suffix.len());
188			file_name.push_str(&suffix);
189			let path = types_dir.join(file_name);
190			let file = OpenOptions::new().create_new(true).write(true).open(&path);
191			match file {
192				Ok(mut file) => {
193					let gen = generator();
194					if !gen.is_empty() {
195						file
196							.write_all(gen.as_bytes())
197							.unwrap_or_else(|e| panic!("Can't write to {typ} file: {e}"));
198					} else {
199						drop(file);
200						fs::remove_file(&path).expect("Can't remove empty file");
201					}
202				}
203				Err(e) if e.kind() == ErrorKind::AlreadyExists => { /* expected, we need to exclusively create file */ }
204				Err(e) if e.kind() == ErrorKind::PermissionDenied => { /* happens sporadically on Windows */ }
205				Err(e) => panic!("Error while creating file: {} for {typ} generated type: {e}", path.display()),
206			}
207		}
208
209		write_generated_type(&self.out_dir, "rs", &safe_id, || typ.gen_rust(self.opencv_version));
210		write_generated_type(&self.out_dir, "externs.rs", &safe_id, || typ.gen_rust_externs());
211		write_generated_type(&self.out_dir, "cpp", &safe_id, || typ.gen_cpp());
212	}
213
214	fn goodbye(mut self) {
215		static RUST_HDR: Lazy<CompiledInterpolation> =
216			Lazy::new(|| include_str!("tpl/module/rust_hdr.tpl").compile_interpolation());
217
218		static RUST_PRELUDE: Lazy<CompiledInterpolation> =
219			Lazy::new(|| include_str!("tpl/module/prelude.tpl.rs").compile_interpolation());
220
221		static CPP_HDR: Lazy<CompiledInterpolation> =
222			Lazy::new(|| include_str!("tpl/module/cpp_hdr.tpl.cpp").compile_interpolation());
223
224		let pub_use_traits = if self.prelude_traits.is_empty() {
225			"".to_string()
226		} else {
227			self.prelude_traits.sort_unstable();
228			format!("pub use super::{{{}}};", self.prelude_traits.join(", "))
229		};
230		let prelude = RUST_PRELUDE.interpolate(&HashMap::from([("pub_use_traits", pub_use_traits)]));
231		let comment = RenderComment::new(&self.comment, self.opencv_version);
232		let comment = comment.render_with_comment_marker("//!");
233		let module_opencv_name = self.module.opencv_name();
234		let rust_path = self.out_dir.join(format!("{module_opencv_name}.rs"));
235		{
236			let mut rust = BufWriter::new(File::create(rust_path).expect("Can't create rust file"));
237			rust
238				.write_all(
239					RUST_HDR
240						.interpolate(&HashMap::from([
241							("static_modules", settings::STATIC_RUST_MODULES.iter().join(", ").as_str()),
242							("comment", comment.as_ref()),
243							("prelude", &prelude),
244						]))
245						.as_bytes(),
246				)
247				.expect("Can't write rust file");
248			write_lines(&mut rust, self.consts).expect("Can't write consts to rust file");
249			write_lines(&mut rust, self.enums).expect("Can't write enums to rust file");
250			write_lines(&mut rust, self.rust_typedefs.into_iter().collect()).expect("Can't write typedefs to rust file");
251			write_lines(&mut rust, self.rust_funcs).expect("Can't write funcs to rust file");
252			write_lines(&mut rust, self.rust_classes).expect("Can't write classes to rust file");
253		}
254
255		let includes = if self.src_cpp_dir.join(format!("{module_opencv_name}.hpp")).exists() {
256			format!("#include \"{module_opencv_name}.hpp\"")
257		} else {
258			format!("#include \"ocvrs_common.hpp\"\n#include <opencv2/{module_opencv_name}.hpp>")
259		};
260		{
261			let cpp_path = self.out_dir.join(format!("{module_opencv_name}.cpp"));
262			let mut cpp = BufWriter::new(File::create(cpp_path).expect("Can't create cpp file"));
263			cpp.write_all(
264				CPP_HDR
265					.interpolate(&HashMap::from([("module", module_opencv_name), ("includes", &includes)]))
266					.as_bytes(),
267			)
268			.expect("Can't write cpp file");
269			cpp.write_all(b"extern \"C\" {\n")
270				.expect("Can't write code wrapper begin to cpp file");
271			write_lines(&mut cpp, self.cpp_funcs).expect("Can't write cpp funcs to file");
272			write_lines(&mut cpp, self.cpp_classes).expect("Can't write cpp classes to file");
273			cpp.write_all(b"}\n").expect("Can't write code wrapper end to cpp file");
274		}
275
276		let externs_path = self.out_dir.join(format!("{module_opencv_name}.externs.rs"));
277		let mut externs_rs = BufWriter::new(File::create(externs_path).expect("Can't create rust exports file"));
278		write_lines(&mut externs_rs, self.extern_funcs).expect("Can't write extern funcs to file");
279		write_lines(&mut externs_rs, self.extern_classes).expect("Can't write extern classes to file");
280	}
281}
282
283fn write_lines<T: AsRef<[u8]>>(mut out: impl Write, mut v: Vec<(String, T)>) -> io::Result<()> {
284	v.sort_unstable_by(|(name_left, _), (name_right, _)| name_left.cmp(name_right));
285	for (_, code) in v {
286		out.write_all(code.as_ref())?;
287	}
288	Ok(())
289}
290
291fn ensure_filename_length(file_name: &mut String, reserve: usize) {
292	const MAX_FILENAME_LEN: usize = 255;
293
294	let max_length = MAX_FILENAME_LEN - reserve;
295
296	if file_name.len() > max_length {
297		*file_name = file_name[..max_length].to_string();
298	}
299}
300
301fn rust_disambiguate_names<'tu, 'ge>(
302	args: impl IntoIterator<Item = Field<'tu, 'ge>>,
303) -> impl Iterator<Item = (String, Field<'tu, 'ge>)>
304where
305	'tu: 'ge,
306{
307	let args = args.into_iter();
308	let size_hint = args.size_hint();
309	NamePool::with_capacity(size_hint.1.unwrap_or(size_hint.0)).into_disambiguator(args, |f| f.rust_leafname(FishStyle::No))
310}
311
312fn rust_disambiguate_names_ref<'f, 'tu, 'ge>(
313	args: impl IntoIterator<Item = &'f Field<'tu, 'ge>>,
314) -> impl Iterator<Item = (String, &'f Field<'tu, 'ge>)>
315where
316	'tu: 'ge,
317	'tu: 'f,
318	'ge: 'f,
319{
320	let args = args.into_iter();
321	let size_hint = args.size_hint();
322	NamePool::with_capacity(size_hint.1.unwrap_or(size_hint.0)).into_disambiguator(args, |f| f.rust_leafname(FishStyle::No))
323}
324
325pub fn disambiguate_single_name(name: &str) -> impl Iterator<Item = String> + '_ {
326	let mut i = 0;
327	iter::from_fn(move || {
328		let out = format!("{}{}", name, disambiguate_num(i));
329		i += 1;
330		Some(out)
331	})
332}
333
334fn disambiguate_num(counter: usize) -> String {
335	match counter {
336		0 => "".to_string(),
337		n => format!("_{n}"),
338	}
339}