11use std:: collections:: { HashMap , HashSet } ;
22use std:: env;
33use std:: ffi:: OsStr ;
4- use std:: fs:: File ;
5- use std:: io:: { BufRead , BufReader } ;
64use std:: path:: { Path , PathBuf } ;
75use std:: time:: Instant ;
86
@@ -169,61 +167,128 @@ fn files_with_extension<'e>(dir: &Path, extension: impl AsRef<OsStr> + 'e) -> Re
169167 } )
170168}
171169
172- fn get_module_header_dir ( header_dir : & Path ) -> Option < PathBuf > {
173- let mut out = header_dir. join ( "opencv2.framework/Headers" ) ;
174- if out. exists ( ) {
175- return Some ( out) ;
176- }
177- out = header_dir. join ( "opencv2" ) ;
178- if out. exists ( ) {
179- return Some ( out) ;
170+ mod header {
171+ use std:: fs:: File ;
172+ use std:: io:: { BufRead , BufReader } ;
173+ use std:: path:: { Path , PathBuf } ;
174+ use std:: process:: Command ;
175+
176+ use semver:: Version ;
177+
178+ pub fn get_module_header_dir ( header_dir : & Path ) -> Option < PathBuf > {
179+ let mut out = header_dir. join ( "opencv2.framework/Headers" ) ;
180+ if out. exists ( ) {
181+ return Some ( out) ;
182+ }
183+ out = header_dir. join ( "opencv2" ) ;
184+ if out. exists ( ) {
185+ return Some ( out) ;
186+ }
187+ None
180188 }
181- None
182- }
183189
184- fn get_version_header ( header_dir : & Path ) -> Option < PathBuf > {
185- get_module_header_dir ( header_dir)
186- . map ( |dir| dir. join ( "core/version.hpp" ) )
187- . filter ( |hdr| hdr. is_file ( ) )
188- }
190+ /// Something like `/usr/include/x86_64-linux-gnu/opencv4/opencv2/` on newer Debian-derived distros
191+ pub fn get_multiarch_module_header_dir ( ) -> Option < PathBuf > {
192+ let try_multiarch = Command :: new ( "dpkg-architecture" )
193+ . args ( [ "--query" , "DEB_TARGET_MULTIARCH" ] )
194+ . output ( )
195+ . ok ( )
196+ . or_else ( || Command :: new ( "dpkg-architecture" ) . output ( ) . ok ( ) )
197+ . and_then ( |output| String :: from_utf8 ( output. stdout ) . ok ( ) )
198+ . map_or_else (
199+ || {
200+ eprintln ! ( "=== Failed to get DEB_TARGET_MULTIARCH, trying common multiarch paths" ) ;
201+ vec ! [
202+ "x86_64-linux-gnu" . to_string( ) ,
203+ "aarch64-linux-gnu" . to_string( ) ,
204+ "arm-linux-gnueabihf" . to_string( ) ,
205+ ]
206+ } ,
207+ |multiarch| vec ! [ multiarch] ,
208+ ) ;
189209
190- fn get_version_from_headers ( header_dir : & Path ) -> Option < Version > {
191- let version_hpp = get_version_header ( header_dir) ?;
192- let mut major = None ;
193- let mut minor = None ;
194- let mut revision = None ;
195- let mut line = String :: with_capacity ( 256 ) ;
196- let mut reader = BufReader :: new ( File :: open ( version_hpp) . ok ( ) ?) ;
197- while let Ok ( bytes_read) = reader. read_line ( & mut line) {
198- if bytes_read == 0 {
199- break ;
210+ for multiarch in try_multiarch {
211+ let header = format ! ( "/usr/include/{multiarch}/opencv4/opencv2" ) ;
212+ if Path :: new ( & header) . exists ( ) {
213+ return Some ( PathBuf :: from ( header) ) ;
214+ }
200215 }
201- if let Some ( line) = line. strip_prefix ( "#define CV_VERSION_" ) {
202- let mut parts = line. split_whitespace ( ) ;
203- if let ( Some ( ver_spec) , Some ( version) ) = ( parts. next ( ) , parts. next ( ) ) {
204- match ver_spec {
205- "MAJOR" => {
206- major = Some ( version. parse ( ) . ok ( ) ?) ;
207- }
208- "MINOR" => {
209- minor = Some ( version. parse ( ) . ok ( ) ?) ;
210- }
211- "REVISION" => {
212- revision = Some ( version. parse ( ) . ok ( ) ?) ;
216+ None
217+ }
218+
219+ pub fn get_version_header ( header_dir : & Path ) -> Option < PathBuf > {
220+ get_module_header_dir ( header_dir)
221+ . map ( |dir| dir. join ( "core/version.hpp" ) )
222+ . filter ( |hdr| hdr. is_file ( ) )
223+ }
224+
225+ pub fn get_config_header ( header_dir : & Path ) -> Option < PathBuf > {
226+ get_module_header_dir ( header_dir)
227+ . map ( |dir| dir. join ( "cvconfig.h" ) )
228+ . filter ( |hdr| hdr. is_file ( ) )
229+ . or_else ( || {
230+ eprintln ! ( "=== Failed to find cvconfig.h in the regular header dir, trying multiarch" ) ;
231+ get_multiarch_module_header_dir ( )
232+ . map ( |dir| dir. join ( "cvconfig.h" ) )
233+ . filter ( |hdr| hdr. is_file ( ) )
234+ } )
235+ }
236+
237+ pub fn find_version ( header_dir : & Path ) -> Option < Version > {
238+ let version_hpp = get_version_header ( header_dir) ?;
239+ let mut major = None ;
240+ let mut minor = None ;
241+ let mut revision = None ;
242+ let mut line = String :: with_capacity ( 256 ) ;
243+ let mut reader = BufReader :: new ( File :: open ( version_hpp) . ok ( ) ?) ;
244+ while let Ok ( bytes_read) = reader. read_line ( & mut line) {
245+ if bytes_read == 0 {
246+ break ;
247+ }
248+ if let Some ( line) = line. strip_prefix ( "#define CV_VERSION_" ) {
249+ let mut parts = line. split_whitespace ( ) ;
250+ if let ( Some ( ver_spec) , Some ( version) ) = ( parts. next ( ) , parts. next ( ) ) {
251+ match ver_spec {
252+ "MAJOR" => {
253+ major = Some ( version. parse ( ) . ok ( ) ?) ;
254+ }
255+ "MINOR" => {
256+ minor = Some ( version. parse ( ) . ok ( ) ?) ;
257+ }
258+ "REVISION" => {
259+ revision = Some ( version. parse ( ) . ok ( ) ?) ;
260+ }
261+ _ => { }
213262 }
214- _ => { }
263+ }
264+ if major. is_some ( ) && minor. is_some ( ) && revision. is_some ( ) {
265+ break ;
215266 }
216267 }
217- if major. is_some ( ) && minor. is_some ( ) && revision. is_some ( ) {
268+ line. clear ( ) ;
269+ }
270+ if let ( Some ( major) , Some ( minor) , Some ( revision) ) = ( major, minor, revision) {
271+ Some ( Version :: new ( major, minor, revision) )
272+ } else {
273+ None
274+ }
275+ }
276+
277+ pub fn find_enabled_features ( header_dir : & Path ) -> Option < Vec < String > > {
278+ let config_h = get_config_header ( header_dir) ?;
279+ let mut out = Vec :: with_capacity ( 64 ) ;
280+ let mut line = String :: with_capacity ( 256 ) ;
281+ let mut reader = BufReader :: new ( File :: open ( config_h) . ok ( ) ?) ;
282+ while let Ok ( bytes_read) = reader. read_line ( & mut line) {
283+ if bytes_read == 0 {
218284 break ;
219285 }
286+ if let Some ( feature) = line. strip_prefix ( "#define HAVE_" ) {
287+ out. push ( feature. trim ( ) . to_lowercase ( ) ) ;
288+ }
289+ line. clear ( ) ;
220290 }
221- line. clear ( ) ;
222- }
223- if let ( Some ( major) , Some ( minor) , Some ( revision) ) = ( major, minor, revision) {
224- Some ( Version :: new ( major, minor, revision) )
225- } else {
226- None
291+ Some ( out)
227292 }
228293}
229294
@@ -264,13 +329,18 @@ fn make_modules_and_alises(
264329 Ok ( ( modules, aliases) )
265330}
266331
267- fn emit_inherent_features ( opencv_version : & Version ) {
332+ fn emit_inherent_features ( opencv : & Library ) {
268333 if VersionReq :: parse ( ">=4.10" )
269334 . expect ( "Static version requirement" )
270- . matches ( opencv_version )
335+ . matches ( & opencv . version )
271336 {
272337 println ! ( "cargo::rustc-cfg=ocvrs_has_inherent_feature_hfloat" ) ;
273338 }
339+ for feature in & opencv. enabled_features {
340+ if feature == "opencl" {
341+ println ! ( "cargo::rustc-cfg=ocvrs_has_inherent_feature_opencl" ) ;
342+ }
343+ }
274344}
275345
276346fn make_compiler ( opencv : & Library , ffi_export_suffix : & str ) -> cc:: Build {
@@ -374,9 +444,7 @@ fn main() -> Result<()> {
374444 for module in SUPPORTED_MODULES {
375445 println ! ( "cargo::rustc-check-cfg=cfg(ocvrs_has_module_{module})" ) ;
376446 }
377- // MSRV: switch to #[expect] when MSRV is 1.81
378- #[ allow( clippy:: single_element_loop) ]
379- for inherent_feature in [ "hfloat" ] {
447+ for inherent_feature in [ "hfloat" , "opencl" ] {
380448 println ! ( "cargo::rustc-check-cfg=cfg(ocvrs_has_inherent_feature_{inherent_feature})" ) ;
381449 }
382450
@@ -419,10 +487,10 @@ fn main() -> Result<()> {
419487 let opencv_header_dir = opencv
420488 . include_paths
421489 . iter ( )
422- . find ( |p| get_version_header ( p) . is_some ( ) )
423- . expect ( "Discovered OpenCV include paths is empty or contains non-existent paths " ) ;
490+ . find ( |p| header :: get_version_header ( p ) . is_some ( ) && header :: get_config_header ( p) . is_some ( ) )
491+ . expect ( "Discovered OpenCV include paths do not contain valid OpenCV headers " ) ;
424492
425- if let Some ( header_version) = get_version_from_headers ( opencv_header_dir) {
493+ if let Some ( header_version) = header :: find_version ( opencv_header_dir) {
426494 if header_version != opencv. version {
427495 panic ! (
428496 "OpenCV version from the headers: {header_version} (at {}) must match version of the OpenCV library: {} (include paths: {:?})" ,
@@ -442,7 +510,7 @@ fn main() -> Result<()> {
442510 )
443511 }
444512
445- let opencv_module_header_dir = get_module_header_dir ( opencv_header_dir) . expect ( "Can't find OpenCV module header dir" ) ;
513+ let opencv_module_header_dir = header :: get_module_header_dir ( opencv_header_dir) . expect ( "Can't find OpenCV module header dir" ) ;
446514 eprintln ! (
447515 "=== Detected OpenCV module header dir at: {}" ,
448516 opencv_module_header_dir. display( )
@@ -452,7 +520,7 @@ fn main() -> Result<()> {
452520 println ! ( "cargo::rustc-cfg=ocvrs_has_module_{module}" ) ;
453521 }
454522
455- emit_inherent_features ( & opencv. version ) ;
523+ emit_inherent_features ( & opencv) ;
456524
457525 setup_rerun ( ) ?;
458526
0 commit comments