|
1 | | -use std::ffi::OsStr; |
2 | | -use std::fs::{File, OpenOptions}; |
3 | | -use std::io::{BufRead, BufReader, BufWriter, Write}; |
4 | 1 | use std::ops::Deref; |
5 | 2 | use std::path::{Path, PathBuf}; |
6 | 3 | use std::process::Command; |
7 | 4 | use std::time::Instant; |
8 | | -use std::{env, fs, io, thread}; |
| 5 | +use std::{env, fs, thread}; |
9 | 6 |
|
| 7 | +use collector::Collector; |
10 | 8 | use opencv_binding_generator::{Generator, IteratorExt}; |
11 | 9 |
|
12 | 10 | use super::docs::transfer_bindings_to_docs; |
13 | | -use super::{files_with_extension, files_with_predicate, Library, Result, MODULES, OUT_DIR, SRC_CPP_DIR, SRC_DIR}; |
| 11 | +use super::{files_with_predicate, Library, Result, MODULES, OUT_DIR, SRC_CPP_DIR, SRC_DIR}; |
| 12 | + |
| 13 | +#[path = "generator/collector.rs"] |
| 14 | +mod collector; |
14 | 15 |
|
15 | 16 | pub struct BindingGenerator { |
16 | 17 | build_script_path: PathBuf, |
@@ -47,7 +48,8 @@ impl BindingGenerator { |
47 | 48 |
|
48 | 49 | self.run(modules, opencv_header_dir, opencv)?; |
49 | 50 |
|
50 | | - collect_generated_bindings(modules, &target_module_dir, &manual_dir)?; |
| 51 | + let collector = Collector::new(modules, &target_module_dir, &manual_dir, &OUT_DIR); |
| 52 | + collector.collect_bindings()?; |
51 | 53 |
|
52 | 54 | if let Some(target_docs_dir) = target_docs_dir { |
53 | 55 | if !target_docs_dir.exists() { |
@@ -82,7 +84,7 @@ impl BindingGenerator { |
82 | 84 | .into_iter() |
83 | 85 | .map(|p| p.to_str().expect("Can't convert additional include dir to UTF-8 string")) |
84 | 86 | .join(","); |
85 | | - let job_server = build_job_server()?; |
| 87 | + let job_server = Jobserver::build()?; |
86 | 88 | let start = Instant::now(); |
87 | 89 | eprintln!("=== Generating {} modules", modules.len()); |
88 | 90 | thread::scope(|scope| { |
@@ -123,233 +125,48 @@ impl BindingGenerator { |
123 | 125 | } |
124 | 126 | } |
125 | 127 |
|
126 | | -fn is_type_file(path: &Path, module: &str) -> bool { |
127 | | - path.file_stem().and_then(OsStr::to_str).map_or(false, |stem| { |
128 | | - let mut stem_chars = stem.chars(); |
129 | | - (&mut stem_chars).take(3).all(|c| c.is_ascii_digit()) && // first 3 chars are digits |
130 | | - matches!(stem_chars.next(), Some('-')) && // dash |
131 | | - module.chars().zip(&mut stem_chars).all(|(m, s)| m == s) && // module name |
132 | | - matches!(stem_chars.next(), Some('-')) && // dash |
133 | | - stem.ends_with(".type") // ends with ".type" |
134 | | - }) |
135 | | -} |
136 | | - |
137 | | -fn is_type_externs_file(path: &Path, module: &str) -> bool { |
138 | | - path.file_stem().and_then(OsStr::to_str).map_or(false, |stem| { |
139 | | - let mut stem_chars = stem.chars(); |
140 | | - (&mut stem_chars).take(3).all(|c| c.is_ascii_digit()) && // first 3 chars are digits |
141 | | - matches!(stem_chars.next(), Some('-')) && // dash |
142 | | - module.chars().zip(&mut stem_chars).all(|(m, s)| m == s) && // module name |
143 | | - matches!(stem_chars.next(), Some('-')) && // dash |
144 | | - stem.ends_with(".type.externs") // ends with ".type" |
145 | | - }) |
146 | | -} |
147 | | - |
148 | | -fn copy_indent(mut read: impl BufRead, mut write: impl Write, indent: &str) -> Result<()> { |
149 | | - let mut line = Vec::with_capacity(100); |
150 | | - while read.read_until(b'\n', &mut line)? != 0 { |
151 | | - write.write_all(indent.as_bytes())?; |
152 | | - write.write_all(&line)?; |
153 | | - line.clear(); |
154 | | - } |
155 | | - Ok(()) |
| 128 | +pub struct Jobserver { |
| 129 | + client: jobserver::Client, |
| 130 | + reacquire_token_on_drop: bool, |
156 | 131 | } |
157 | 132 |
|
158 | | -fn collect_generated_bindings(modules: &[String], target_module_dir: &Path, manual_dir: &Path) -> Result<()> { |
159 | | - if !target_module_dir.exists() { |
160 | | - fs::create_dir(target_module_dir)?; |
161 | | - } |
162 | | - for path in files_with_extension(target_module_dir, "rs")? { |
163 | | - let _ = fs::remove_file(path); |
164 | | - } |
165 | | - |
166 | | - fn write_has_module(mut write: impl Write, module: &str) -> Result<()> { |
167 | | - Ok(writeln!(write, "#[cfg(ocvrs_has_module_{module})]")?) |
168 | | - } |
169 | | - |
170 | | - fn write_module_include(write: &mut BufWriter<File>, module: &str) -> Result<()> { |
171 | | - // Use include instead of #[path] attribute because rust-analyzer doesn't handle #[path] inside other include! too well: |
172 | | - // https://github.com/twistedfall/opencv-rust/issues/418 |
173 | | - // https://github.com/rust-lang/rust-analyzer/issues/11682 |
174 | | - Ok(writeln!( |
175 | | - write, |
176 | | - r#"include!(concat!(env!("OUT_DIR"), "/opencv/{module}.rs"));"# |
177 | | - )?) |
178 | | - } |
179 | | - |
180 | | - let add_manual = |file: &mut BufWriter<File>, module: &str| -> Result<bool> { |
181 | | - if manual_dir.join(format!("{module}.rs")).exists() { |
182 | | - writeln!(file, "pub use crate::manual::{module}::*;")?; |
183 | | - Ok(true) |
184 | | - } else { |
185 | | - Ok(false) |
186 | | - } |
187 | | - }; |
188 | | - |
189 | | - let start = Instant::now(); |
190 | | - let mut hub_rs = BufWriter::new(File::create(target_module_dir.join("hub.rs"))?); |
191 | | - |
192 | | - let mut types_rs = BufWriter::new(File::create(target_module_dir.join("types.rs"))?); |
193 | | - writeln!(types_rs)?; |
194 | | - |
195 | | - let mut sys_rs = BufWriter::new(File::create(target_module_dir.join("sys.rs"))?); |
196 | | - writeln!(sys_rs, "use crate::{{mod_prelude_sys::*, core}};")?; |
197 | | - writeln!(sys_rs)?; |
198 | | - |
199 | | - for module in modules { |
200 | | - // merge multiple *-type.cpp files into a single module_types.hpp |
201 | | - let module_cpp = OUT_DIR.join(format!("{module}.cpp")); |
202 | | - if module_cpp.is_file() { |
203 | | - let module_types_cpp = OUT_DIR.join(format!("{module}_types.hpp")); |
204 | | - let mut module_types_file = BufWriter::new( |
205 | | - OpenOptions::new() |
206 | | - .create(true) |
207 | | - .truncate(true) |
208 | | - .write(true) |
209 | | - .open(module_types_cpp)?, |
210 | | - ); |
211 | | - let mut type_files = files_with_extension(&OUT_DIR, "cpp")? |
212 | | - .filter(|f| is_type_file(f, module)) |
213 | | - .collect::<Vec<_>>(); |
214 | | - type_files.sort_unstable(); |
215 | | - for entry in type_files { |
216 | | - io::copy(&mut BufReader::new(File::open(&entry)?), &mut module_types_file)?; |
217 | | - let _ = fs::remove_file(entry); |
218 | | - } |
219 | | - } |
220 | | - |
221 | | - // add module entry to hub.rs and move the module file into opencv/ |
222 | | - write_has_module(&mut hub_rs, module)?; |
223 | | - write_module_include(&mut hub_rs, module)?; |
224 | | - let module_filename = format!("{module}.rs"); |
225 | | - let module_src_file = OUT_DIR.join(&module_filename); |
226 | | - let mut module_rs = BufWriter::new(File::create(target_module_dir.join(&module_filename))?); |
227 | | - // Need to wrap modules inside `mod { }` because they have top-level comments (//!) and those don't play well when |
228 | | - // module file is include!d (as opposed to connecting the module with `mod` from the parent module). |
229 | | - // The same doesn't apply to `sys` and `types` below because they don't contain top-level comments. |
230 | | - writeln!(module_rs, "pub mod {module} {{")?; |
231 | | - copy_indent(BufReader::new(File::open(&module_src_file)?), &mut module_rs, "\t")?; |
232 | | - add_manual(&mut module_rs, module)?; |
233 | | - writeln!(module_rs, "}}")?; |
234 | | - let _ = fs::remove_file(module_src_file); |
235 | | - |
236 | | - // merge multiple *-.type.rs files into a single types.rs |
237 | | - let mut header_written = false; |
238 | | - let mut type_files = files_with_extension(&OUT_DIR, "rs")? |
239 | | - .filter(|f| is_type_file(f, module)) |
240 | | - .collect::<Vec<_>>(); |
241 | | - type_files.sort_unstable(); |
242 | | - for entry in type_files { |
243 | | - if entry.metadata().map(|meta| meta.len()).unwrap_or(0) > 0 { |
244 | | - if !header_written { |
245 | | - write_has_module(&mut types_rs, module)?; |
246 | | - writeln!(types_rs, "mod {module}_types {{")?; |
247 | | - writeln!(types_rs, "\tuse crate::{{mod_prelude::*, core, types, sys}};")?; |
248 | | - writeln!(types_rs)?; |
249 | | - header_written = true; |
| 133 | +impl Jobserver { |
| 134 | + pub fn build() -> Result<Self> { |
| 135 | + unsafe { jobserver::Client::from_env() } |
| 136 | + .and_then(|client| { |
| 137 | + let own_token_released = client.release_raw().is_ok(); |
| 138 | + let available_jobs = client.available().unwrap_or(0); |
| 139 | + if available_jobs > 0 { |
| 140 | + eprintln!("=== Using environment job server with the the amount of available jobs: {available_jobs}"); |
| 141 | + Some(Jobserver { |
| 142 | + client, |
| 143 | + reacquire_token_on_drop: own_token_released, |
| 144 | + }) |
| 145 | + } else { |
| 146 | + if own_token_released { |
| 147 | + client.acquire_raw().expect("Can't reacquire build script thread token"); |
| 148 | + } |
| 149 | + eprintln!( |
| 150 | + "=== Available jobs from the environment created jobserver is: {available_jobs} or there is an error reading that value" |
| 151 | + ); |
| 152 | + None |
250 | 153 | } |
251 | | - copy_indent(BufReader::new(File::open(&entry)?), &mut types_rs, "\t")?; |
252 | | - } |
253 | | - let _ = fs::remove_file(entry); |
254 | | - } |
255 | | - if header_written { |
256 | | - writeln!(types_rs, "}}")?; |
257 | | - write_has_module(&mut types_rs, module)?; |
258 | | - writeln!(types_rs, "pub use {module}_types::*;")?; |
259 | | - writeln!(types_rs)?; |
260 | | - } |
261 | | - |
262 | | - // merge module-specific *.externs.rs and generated type-specific *.type.externs.rs into a single sys.rs |
263 | | - let externs_rs = OUT_DIR.join(format!("{module}.externs.rs")); |
264 | | - write_has_module(&mut sys_rs, module)?; |
265 | | - writeln!(sys_rs, "mod {module}_sys {{")?; |
266 | | - writeln!(sys_rs, "\tuse super::*;")?; |
267 | | - writeln!(sys_rs)?; |
268 | | - writeln!(sys_rs, "\textern \"C\" {{")?; |
269 | | - copy_indent(BufReader::new(File::open(&externs_rs)?), &mut sys_rs, "\t\t")?; |
270 | | - let _ = fs::remove_file(externs_rs); |
271 | | - let mut type_extern_files = files_with_extension(&OUT_DIR, "rs")? |
272 | | - .filter(|f| is_type_externs_file(f, module)) |
273 | | - .collect::<Vec<_>>(); |
274 | | - type_extern_files.sort_unstable(); |
275 | | - for entry in type_extern_files { |
276 | | - if entry.metadata().map(|meta| meta.len()).unwrap_or(0) > 0 { |
277 | | - copy_indent(BufReader::new(File::open(&entry)?), &mut sys_rs, "\t\t")?; |
278 | | - } |
279 | | - let _ = fs::remove_file(entry); |
280 | | - } |
281 | | - writeln!(sys_rs, "\t}}")?; |
282 | | - writeln!(sys_rs, "}}")?; |
283 | | - write_has_module(&mut sys_rs, module)?; |
284 | | - writeln!(sys_rs, "pub use {module}_sys::*;")?; |
285 | | - writeln!(sys_rs)?; |
286 | | - } |
287 | | - writeln!(hub_rs, "pub mod types {{")?; |
288 | | - write!(hub_rs, "\t")?; |
289 | | - write_module_include(&mut hub_rs, "types")?; |
290 | | - writeln!(hub_rs, "}}")?; |
291 | | - writeln!(hub_rs, "#[doc(hidden)]")?; |
292 | | - writeln!(hub_rs, "pub mod sys {{")?; |
293 | | - write!(hub_rs, "\t")?; |
294 | | - write_module_include(&mut hub_rs, "sys")?; |
295 | | - writeln!(hub_rs, "}}")?; |
296 | | - |
297 | | - add_manual(&mut types_rs, "types")?; |
298 | | - |
299 | | - add_manual(&mut sys_rs, "sys")?; |
300 | | - |
301 | | - // write hub_prelude that imports all module-specific preludes |
302 | | - writeln!(hub_rs, "pub mod hub_prelude {{")?; |
303 | | - for module in modules { |
304 | | - write!(hub_rs, "\t")?; |
305 | | - write_has_module(&mut hub_rs, module)?; |
306 | | - writeln!(hub_rs, "\tpub use super::{module}::prelude::*;")?; |
307 | | - } |
308 | | - writeln!(hub_rs, "}}")?; |
309 | | - eprintln!("=== Total binding collection time: {:?}", start.elapsed()); |
310 | | - Ok(()) |
311 | | -} |
312 | | - |
313 | | -fn build_job_server() -> Result<Jobserver> { |
314 | | - unsafe { jobserver::Client::from_env() } |
315 | | - .and_then(|client| { |
316 | | - let own_token_released = client.release_raw().is_ok(); |
317 | | - let available_jobs = client.available().unwrap_or(0); |
318 | | - if available_jobs > 0 { |
319 | | - eprintln!("=== Using environment job server with the the amount of available jobs: {available_jobs}"); |
320 | | - Some(Jobserver { |
| 154 | + }) |
| 155 | + .or_else(|| { |
| 156 | + let num_jobs = env::var("NUM_JOBS") |
| 157 | + .ok() |
| 158 | + .and_then(|jobs| jobs.parse().ok()) |
| 159 | + .or_else(|| thread::available_parallelism().map(|p| p.get()).ok()) |
| 160 | + .unwrap_or(2) |
| 161 | + .max(1); |
| 162 | + eprintln!("=== Creating a new job server with num_jobs: {num_jobs}"); |
| 163 | + jobserver::Client::new(num_jobs).ok().map(|client| Jobserver { |
321 | 164 | client, |
322 | | - reacquire_token_on_drop: own_token_released, |
| 165 | + reacquire_token_on_drop: false, |
323 | 166 | }) |
324 | | - } else { |
325 | | - if own_token_released { |
326 | | - client.acquire_raw().expect("Can't reacquire build script thread token"); |
327 | | - } |
328 | | - eprintln!( |
329 | | - "=== Available jobs from the environment created jobserver is: {available_jobs} or there is an error reading that value" |
330 | | - ); |
331 | | - None |
332 | | - } |
333 | | - }) |
334 | | - .or_else(|| { |
335 | | - let num_jobs = env::var("NUM_JOBS") |
336 | | - .ok() |
337 | | - .and_then(|jobs| jobs.parse().ok()) |
338 | | - .or_else(|| thread::available_parallelism().map(|p| p.get()).ok()) |
339 | | - .unwrap_or(2) |
340 | | - .max(1); |
341 | | - eprintln!("=== Creating a new job server with num_jobs: {num_jobs}"); |
342 | | - jobserver::Client::new(num_jobs).ok().map(|client| Jobserver { |
343 | | - client, |
344 | | - reacquire_token_on_drop: false, |
345 | 167 | }) |
346 | | - }) |
347 | | - .ok_or_else(|| "Can't create job server".into()) |
348 | | -} |
349 | | - |
350 | | -pub struct Jobserver { |
351 | | - client: jobserver::Client, |
352 | | - reacquire_token_on_drop: bool, |
| 168 | + .ok_or_else(|| "Can't create job server".into()) |
| 169 | + } |
353 | 170 | } |
354 | 171 |
|
355 | 172 | impl Drop for Jobserver { |
|
0 commit comments