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,131 @@ 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. is_dir ( ) {
181+ return Some ( out) ;
182+ }
183+ out = header_dir. join ( "opencv2" ) ;
184+ if out. is_dir ( ) {
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/` on newer Debian-derived distros
191+ pub fn get_multiarch_header_dir ( ) -> Option < PathBuf > {
192+ let try_multiarch = Command :: new ( "dpkg-architecture" )
193+ . args ( [ "--query" , "DEB_TARGET_MULTIARCH" ] )
194+ . output ( )
195+ . inspect_err ( |e| eprintln ! ( "=== Failed to get DEB_TARGET_MULTIARCH: {e}" ) )
196+ . ok ( )
197+ . or_else ( || {
198+ Command :: new ( "cc" )
199+ . arg ( "-print-multiarch" )
200+ . output ( )
201+ . inspect_err ( |e| eprintln ! ( "=== Failed to get -print-multiarch: {e}" ) )
202+ . ok ( )
203+ } )
204+ . and_then ( |output| String :: from_utf8 ( output. stdout ) . ok ( ) )
205+ . map_or_else (
206+ || {
207+ eprintln ! ( "=== Failed to get DEB_TARGET_MULTIARCH, trying common multiarch paths" ) ;
208+ vec ! [
209+ "x86_64-linux-gnu" . to_string( ) ,
210+ "aarch64-linux-gnu" . to_string( ) ,
211+ "arm-linux-gnueabihf" . to_string( ) ,
212+ ]
213+ } ,
214+ |multiarch| vec ! [ multiarch. trim( ) . to_string( ) ] ,
215+ ) ;
189216
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 ;
217+ eprintln ! ( "=== Trying multiarch paths: {try_multiarch:?}" ) ;
218+
219+ for multiarch in try_multiarch {
220+ let header_dir = PathBuf :: from ( format ! ( "/usr/include/{multiarch}/opencv4" ) ) ;
221+ if header_dir. is_dir ( ) {
222+ return Some ( header_dir) ;
223+ }
200224 }
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 ( ) ?) ;
225+ None
226+ }
227+
228+ pub fn get_version_header ( header_dir : & Path ) -> Option < PathBuf > {
229+ get_module_header_dir ( header_dir)
230+ . map ( |dir| dir. join ( "core/version.hpp" ) )
231+ . filter ( |hdr| hdr. is_file ( ) )
232+ }
233+
234+ pub fn get_config_header ( header_dir : & Path ) -> Option < PathBuf > {
235+ get_module_header_dir ( header_dir)
236+ . map ( |dir| dir. join ( "cvconfig.h" ) )
237+ . filter ( |hdr| hdr. is_file ( ) )
238+ }
239+
240+ pub fn find_version ( header_dir : & Path ) -> Option < Version > {
241+ let version_hpp = get_version_header ( header_dir) ?;
242+ let mut major = None ;
243+ let mut minor = None ;
244+ let mut revision = None ;
245+ let mut line = String :: with_capacity ( 256 ) ;
246+ let mut reader = BufReader :: new ( File :: open ( version_hpp) . ok ( ) ?) ;
247+ while let Ok ( bytes_read) = reader. read_line ( & mut line) {
248+ if bytes_read == 0 {
249+ break ;
250+ }
251+ if let Some ( line) = line. strip_prefix ( "#define CV_VERSION_" ) {
252+ let mut parts = line. split_whitespace ( ) ;
253+ if let ( Some ( ver_spec) , Some ( version) ) = ( parts. next ( ) , parts. next ( ) ) {
254+ match ver_spec {
255+ "MAJOR" => {
256+ major = Some ( version. parse ( ) . ok ( ) ?) ;
257+ }
258+ "MINOR" => {
259+ minor = Some ( version. parse ( ) . ok ( ) ?) ;
260+ }
261+ "REVISION" => {
262+ revision = Some ( version. parse ( ) . ok ( ) ?) ;
263+ }
264+ _ => { }
213265 }
214- _ => { }
266+ }
267+ if major. is_some ( ) && minor. is_some ( ) && revision. is_some ( ) {
268+ break ;
215269 }
216270 }
217- if major. is_some ( ) && minor. is_some ( ) && revision. is_some ( ) {
271+ line. clear ( ) ;
272+ }
273+ if let ( Some ( major) , Some ( minor) , Some ( revision) ) = ( major, minor, revision) {
274+ Some ( Version :: new ( major, minor, revision) )
275+ } else {
276+ None
277+ }
278+ }
279+
280+ pub fn find_enabled_features ( header_dir : & Path ) -> Option < Vec < String > > {
281+ let config_h = get_config_header ( header_dir) ?;
282+ let mut out = Vec :: with_capacity ( 64 ) ;
283+ let mut line = String :: with_capacity ( 256 ) ;
284+ let mut reader = BufReader :: new ( File :: open ( config_h) . ok ( ) ?) ;
285+ while let Ok ( bytes_read) = reader. read_line ( & mut line) {
286+ if bytes_read == 0 {
218287 break ;
219288 }
289+ if let Some ( feature) = line. strip_prefix ( "#define HAVE_" ) {
290+ out. push ( feature. trim ( ) . to_lowercase ( ) ) ;
291+ }
292+ line. clear ( ) ;
220293 }
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
294+ Some ( out)
227295 }
228296}
229297
@@ -264,13 +332,18 @@ fn make_modules_and_alises(
264332 Ok ( ( modules, aliases) )
265333}
266334
267- fn emit_inherent_features ( opencv_version : & Version ) {
335+ fn emit_inherent_features ( opencv : & Library ) {
268336 if VersionReq :: parse ( ">=4.10" )
269337 . expect ( "Static version requirement" )
270- . matches ( opencv_version )
338+ . matches ( & opencv . version )
271339 {
272340 println ! ( "cargo::rustc-cfg=ocvrs_has_inherent_feature_hfloat" ) ;
273341 }
342+ for feature in & opencv. enabled_features {
343+ if feature == "opencl" {
344+ println ! ( "cargo::rustc-cfg=ocvrs_has_inherent_feature_opencl" ) ;
345+ }
346+ }
274347}
275348
276349fn make_compiler ( opencv : & Library , ffi_export_suffix : & str ) -> cc:: Build {
@@ -374,9 +447,7 @@ fn main() -> Result<()> {
374447 for module in SUPPORTED_MODULES {
375448 println ! ( "cargo::rustc-check-cfg=cfg(ocvrs_has_module_{module})" ) ;
376449 }
377- // MSRV: switch to #[expect] when MSRV is 1.81
378- #[ allow( clippy:: single_element_loop) ]
379- for inherent_feature in [ "hfloat" ] {
450+ for inherent_feature in [ "hfloat" , "opencl" ] {
380451 println ! ( "cargo::rustc-check-cfg=cfg(ocvrs_has_inherent_feature_{inherent_feature})" ) ;
381452 }
382453
@@ -419,10 +490,10 @@ fn main() -> Result<()> {
419490 let opencv_header_dir = opencv
420491 . include_paths
421492 . iter ( )
422- . find ( |p| get_version_header ( p) . is_some ( ) )
423- . expect ( "Discovered OpenCV include paths is empty or contains non-existent paths " ) ;
493+ . find ( |p| header :: get_version_header ( p ) . is_some ( ) && header :: get_config_header ( p) . is_some ( ) )
494+ . expect ( "Discovered OpenCV include paths do not contain valid OpenCV headers " ) ;
424495
425- if let Some ( header_version) = get_version_from_headers ( opencv_header_dir) {
496+ if let Some ( header_version) = header :: find_version ( opencv_header_dir) {
426497 if header_version != opencv. version {
427498 panic ! (
428499 "OpenCV version from the headers: {header_version} (at {}) must match version of the OpenCV library: {} (include paths: {:?})" ,
@@ -442,7 +513,7 @@ fn main() -> Result<()> {
442513 )
443514 }
444515
445- let opencv_module_header_dir = get_module_header_dir ( opencv_header_dir) . expect ( "Can't find OpenCV module header dir" ) ;
516+ let opencv_module_header_dir = header :: get_module_header_dir ( opencv_header_dir) . expect ( "Can't find OpenCV module header dir" ) ;
446517 eprintln ! (
447518 "=== Detected OpenCV module header dir at: {}" ,
448519 opencv_module_header_dir. display( )
@@ -452,7 +523,7 @@ fn main() -> Result<()> {
452523 println ! ( "cargo::rustc-cfg=ocvrs_has_module_{module}" ) ;
453524 }
454525
455- emit_inherent_features ( & opencv. version ) ;
526+ emit_inherent_features ( & opencv) ;
456527
457528 setup_rerun ( ) ?;
458529
0 commit comments