From 2b76224e0b0ab424b44176ca69a783bf1b3039e2 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Tue, 18 Aug 2026 15:35:36 +0200 Subject: [PATCH 1/3] Deal with optional inter-module dependencies. --- .../opencv2/opencv_modules.hpp | 4 ++++ .../src/bin/binding-generator.rs | 16 ++++++++++---- binding-generator/src/bin/settings-cleanup.rs | 7 ++++--- binding-generator/src/generator.rs | 13 +++++++++++- build/binding-generator.rs | 21 +++++++++++++++---- build/generator.rs | 10 ++++++++- 6 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 binding-generator/override-headers/opencv2/opencv_modules.hpp diff --git a/binding-generator/override-headers/opencv2/opencv_modules.hpp b/binding-generator/override-headers/opencv2/opencv_modules.hpp new file mode 100644 index 00000000..ee52b952 --- /dev/null +++ b/binding-generator/override-headers/opencv2/opencv_modules.hpp @@ -0,0 +1,4 @@ +// Empty override for the `opencv_modules.hpp` file included with opencv. +// +// This original file contains a number of `#define HAVE_OPENCV_` directives. +// However, we manually define those based on the modules enabled by cargo features. diff --git a/binding-generator/src/bin/binding-generator.rs b/binding-generator/src/bin/binding-generator.rs index d3a54ba3..6af5b41f 100644 --- a/binding-generator/src/bin/binding-generator.rs +++ b/binding-generator/src/bin/binding-generator.rs @@ -22,9 +22,17 @@ fn main() { opencv_header_dir = args.next(); } let opencv_header_dir = PathBuf::from(opencv_header_dir.expect("1st argument must be OpenCV header dir")); - let src_cpp_dir = PathBuf::from(args.next().expect("2nd argument must be dir with custom cpp")); - let out_dir = PathBuf::from(args.next().expect("3rd argument must be output dir")); - let module = args.next().expect("4th argument must be module name"); + let enabled_modules = args + .next() + .expect("2nd argument must be enabled OpenCV modules") + .into_string() + .expect("2nd argument (enabled modules) contains invalid UTF-8") + .split(',') + .map(|x| SupportedModule::try_from_opencv_name(x).expect("invalid enabled OpenCV module: {x}")) + .collect(); + let src_cpp_dir = PathBuf::from(args.next().expect("3rd argument must be dir with custom cpp")); + let out_dir = PathBuf::from(args.next().expect("4th argument must be output dir")); + let module = args.next().expect("5th argument must be module name"); let module = module .to_str() .and_then(SupportedModule::try_from_opencv_name) @@ -43,7 +51,7 @@ fn main() { .map(Path::new) .collect::>(); let bindings_writer = RustNativeBindingWriter::new(&src_cpp_dir, &out_dir, module, version.clone()); - Generator::new(&opencv_header_dir, &additional_include_dirs, &src_cpp_dir).generate( + Generator::new(&opencv_header_dir, enabled_modules, &additional_include_dirs, &src_cpp_dir).generate( module, &version, !opencv_binding_generator::debug::enabled(), diff --git a/binding-generator/src/bin/settings-cleanup.rs b/binding-generator/src/bin/settings-cleanup.rs index 38463e55..064c5cf4 100644 --- a/binding-generator/src/bin/settings-cleanup.rs +++ b/binding-generator/src/bin/settings-cleanup.rs @@ -81,7 +81,7 @@ fn main() { let version = opencv_header_dir .opencv_find_version() .expect("Can't find version in header dir"); - let modules = opencv_header_dir + let modules: Vec<_> = opencv_header_dir .join("opencv2") .read_dir() .expect("Can't read dir") @@ -92,8 +92,9 @@ fn main() { p.file_name() .and_then(|f| f.to_str()) .and_then(SupportedModule::try_from_opencv_name) - }); - let gener = Generator::new(&opencv_header_dir, &[], &src_cpp_dir); + }) + .collect(); + let gener = Generator::new(&opencv_header_dir, modules.clone(), &[], &src_cpp_dir); for module in modules { println!(" {}", module.opencv_name()); gener.pre_process(module, false, { diff --git a/binding-generator/src/generator.rs b/binding-generator/src/generator.rs index 048a1e34..1f8da64a 100644 --- a/binding-generator/src/generator.rs +++ b/binding-generator/src/generator.rs @@ -355,6 +355,7 @@ pub struct Generator { clang_include_dirs: Vec, opencv_include_dir: PathBuf, opencv_module_header_dir: PathBuf, + enabled_modules: Vec, src_cpp_dir: PathBuf, clang: ManuallyDrop, } @@ -382,7 +383,12 @@ impl Drop for Generator { } impl Generator { - pub fn new(opencv_include_dir: &Path, additional_include_dirs: &[&Path], src_cpp_dir: &Path) -> Self { + pub fn new( + opencv_include_dir: &Path, + enabled_modules: Vec, + additional_include_dirs: &[&Path], + src_cpp_dir: &Path, + ) -> Self { let clang_bin = clang_sys::support::Clang::find(None, &[]).expect("Can't find clang binary"); let mut clang_include_dirs = clang_bin.cpp_search_paths.unwrap_or_default(); for additional_dir in additional_include_dirs { @@ -405,6 +411,7 @@ impl Generator { clang_include_dirs, opencv_include_dir: canonicalize(opencv_include_dir).expect("Can't canonicalize opencv_include_dir"), opencv_module_header_dir: canonicalize(opencv_module_header_dir).expect("Can't canonicalize opencv_module_header_dir"), + enabled_modules, src_cpp_dir: canonicalize(src_cpp_dir).expect("Can't canonicalize src_cpp_dir"), clang: ManuallyDrop::new(Clang::new().expect("Can't initialize clang")), } @@ -446,6 +453,7 @@ impl Generator { .clang_include_dirs .iter() .map(|d| format!("-isystem{}", d.to_str().expect("Incorrect system include path")).into()) + .chain([format!("-I{}/override-headers", env!("CARGO_MANIFEST_DIR")).into()]) .chain([&self.opencv_include_dir, &self.src_cpp_dir].iter().flat_map(|d| { let include_path = d.to_str().expect("Incorrect include path"); [format!("-I{include_path}").into(), format!("-F{include_path}").into()] @@ -454,6 +462,9 @@ impl Generator { args.push("-DOCVRS_PARSING_HEADERS".into()); args.push("-includeocvrs_common.hpp".into()); args.push("-std=c++17".into()); + for module in &self.enabled_modules { + args.push(format!("-DHAVE_OPENCV_{}", module.opencv_name().to_uppercase()).into()); + } // allow us to use some custom clang args let clang_arg = env::var_os("OPENCV_CLANG_ARGS"); if let Some(clang_arg) = clang_arg.as_ref().and_then(|s| s.to_str()) { diff --git a/build/binding-generator.rs b/build/binding-generator.rs index c5f16125..798edbb5 100644 --- a/build/binding-generator.rs +++ b/build/binding-generator.rs @@ -24,9 +24,17 @@ pub fn handle_running_binding_generator(mut args: Peekable) -> Result<()> { let opencv_header_dir = PathBuf::from(args.next().ok_or("1st argument must be OpenCV header dir")?); - let src_cpp_dir = PathBuf::from(args.next().ok_or("2nd argument must be dir with custom cpp")?); - let out_dir = PathBuf::from(args.next().ok_or("3rd argument must be output dir")?); - let module = args.next().ok_or("4th argument must be module name")?; + let enabled_modules = args + .next() + .ok_or("2nd argument must be enabled OpenCV modules")? + .into_string() + .expect("2nd argument (enabled modules) contains invalid UTF-8") + .split(',') + .map(|x| SupportedModule::try_from_opencv_name(x).expect("invalid enabled OpenCV module: {x}")) + .collect(); + let src_cpp_dir = PathBuf::from(args.next().ok_or("3rd argument must be dir with custom cpp")?); + let out_dir = PathBuf::from(args.next().ok_or("4th argument must be output dir")?); + let module = args.next().ok_or("5th argument must be module name")?; let module = module .to_str() .and_then(SupportedModule::try_from_opencv_name) @@ -45,6 +53,11 @@ pub fn run(mut args: impl Iterator) -> Result<()> { .map(Path::new) .collect::>(); let bindings_writer = RustNativeBindingWriter::new(&src_cpp_dir, &out_dir, module, version.clone()); - Generator::new(&opencv_header_dir, &additional_include_dirs, &src_cpp_dir).generate(module, &version, true, bindings_writer); + Generator::new(&opencv_header_dir, enabled_modules, &additional_include_dirs, &src_cpp_dir).generate( + module, + &version, + true, + bindings_writer, + ); Ok(()) } diff --git a/build/generator.rs b/build/generator.rs index f8638bc7..ff9e2b56 100644 --- a/build/generator.rs +++ b/build/generator.rs @@ -167,7 +167,12 @@ impl<'r> BindingGenerator<'r> { .map(|path| path.as_path()) .collect::>(); - let gener = Generator::new(opencv_header_dir, &additional_include_dirs, &SRC_CPP_DIR); + let gener = Generator::new( + opencv_header_dir, + self.gen_modules.modules.clone(), + &additional_include_dirs, + &SRC_CPP_DIR, + ); if !gener.is_clang_loaded() { eprintln!( "=== ERROR: Unable to load libclang library, check item #8 in https://github.com/twistedfall/opencv-rust/blob/master/TROUBLESHOOTING.md" @@ -184,6 +189,7 @@ impl<'r> BindingGenerator<'r> { .into_iter() .map(|p| p.to_str().expect("Can't convert additional include dir to UTF-8 string")) .join(","); + let enabled_modules = self.gen_modules.modules.iter().map(|x| x.opencv_name()).join(","); let job_server = Jobserver::build()?; let start = Instant::now(); eprintln!("=== Generating {} modules", self.gen_modules.modules.len()); @@ -199,11 +205,13 @@ impl<'r> BindingGenerator<'r> { .name(format!("gen-{module_opencv_name}")) .spawn_scoped(scope, { let additional_include_dirs = additional_include_dirs.as_str(); + let enabled_modules = enabled_modules.as_str(); move || { let module_start = Instant::now(); let mut bin_generator = Command::new(self.build_script_path); bin_generator .arg(opencv_header_dir) + .arg(enabled_modules) .arg(&*SRC_CPP_DIR) .arg(&*OUT_DIR) .arg(module_opencv_name) From 409488e12b935a84f6844b613569daff467a8ae2 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Tue, 18 Aug 2026 15:35:51 +0200 Subject: [PATCH 2/3] Make `objdetect` depend on `features` module. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 402e4331..8591d27d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -195,7 +195,7 @@ intensity_transform = [] line_descriptor = [] mcc = ["dnn"] ml = [] -objdetect = [] +objdetect = ["features"] optflow = ["video"] ovis = [] phase_unwrapping = [] From ca4dba36bb3c96411768a9db0e743520934489de Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Tue, 18 Aug 2026 15:56:39 +0200 Subject: [PATCH 3/3] Fix formatting. --- build/library.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build/library.rs b/build/library.rs index 171d5cec..f724bc66 100644 --- a/build/library.rs +++ b/build/library.rs @@ -33,9 +33,10 @@ impl PackageName { } pub fn pkg_config() -> Vec> { - Self::env() - .or_else(Self::env_pkg_config) - .map_or_else(|| vec!["opencv5".into(), "opencv4".into(), "opencv".into()], |env_name| vec![env_name.into()]) + Self::env().or_else(Self::env_pkg_config).map_or_else( + || vec!["opencv5".into(), "opencv4".into(), "opencv".into()], + |env_name| vec![env_name.into()], + ) } pub fn cmake() -> Cow<'static, str> {