Skip to content

Commit 20f47f5

Browse files
committed
Include additional core headers conditionally
1 parent c43f377 commit 20f47f5

File tree

7 files changed

+195
-85
lines changed

7 files changed

+195
-85
lines changed

build.rs

Lines changed: 125 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use std::collections::{HashMap, HashSet};
22
use std::env;
33
use std::ffi::OsStr;
4-
use std::fs::File;
5-
use std::io::{BufRead, BufReader};
64
use std::path::{Path, PathBuf};
75
use 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

276346
fn 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

build/binding-generator.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
55
use opencv_binding_generator::writer::RustNativeBindingWriter;
66
use opencv_binding_generator::Generator;
77

8-
use super::{get_version_from_headers, GenerateFullBindings, Result};
8+
use super::{header, GenerateFullBindings, Result};
99

1010
/// Because clang can't be used from multiple threads we run the binding generator helper for each
1111
/// module as a separate process. Building an additional helper binary from the build script is problematic,
@@ -27,7 +27,7 @@ pub fn run(mut args: impl Iterator<Item = OsString>) -> Result<()> {
2727
let out_dir = PathBuf::from(args.next().ok_or("3rd argument must be output dir")?);
2828
let module = args.next().ok_or("4th argument must be module name")?;
2929
let module = module.to_str().ok_or("Not a valid module name")?;
30-
let version = get_version_from_headers(&opencv_header_dir)
30+
let version = header::find_version(&opencv_header_dir)
3131
.ok_or("Can't find the version in the headers")?
3232
.to_string();
3333
let arg_additional_include_dirs = args.next();

build/library.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use dunce::canonicalize;
88
use semver::Version;
99

1010
use super::cmake_probe::{CmakeProbe, LinkLib, LinkSearch};
11-
use super::{get_version_from_headers, Result, MANIFEST_DIR, OUT_DIR, TARGET_VENDOR_APPLE};
11+
use super::{header, Result, MANIFEST_DIR, OUT_DIR, TARGET_VENDOR_APPLE};
1212

1313
struct PackageName;
1414

@@ -149,12 +149,19 @@ impl Linkage {
149149
pub struct Library {
150150
pub include_paths: Vec<PathBuf>,
151151
pub version: Version,
152+
pub enabled_features: Vec<String>,
152153
pub cargo_metadata: Vec<String>,
153154
}
154155

155156
impl Library {
156157
fn version_from_include_paths(include_paths: impl IntoIterator<Item = impl AsRef<Path>>) -> Option<Version> {
157-
include_paths.into_iter().find_map(|x| get_version_from_headers(x.as_ref()))
158+
include_paths.into_iter().find_map(|x| header::find_version(x.as_ref()))
159+
}
160+
161+
fn enabled_features_from_include_paths(include_paths: impl IntoIterator<Item = impl AsRef<Path>>) -> Option<Vec<String>> {
162+
include_paths
163+
.into_iter()
164+
.find_map(|x| header::find_enabled_features(x.as_ref()))
158165
}
159166

160167
fn process_env_var_list<'a, T: From<&'a str>>(env_list: Option<EnvList<'a>>, sys_list: Vec<T>) -> Vec<T> {
@@ -231,14 +238,18 @@ impl Library {
231238
let mut cargo_metadata = Vec::with_capacity(64);
232239
let include_paths: Vec<_> = include_paths.iter().map(PathBuf::from).collect();
233240

234-
let version = Self::version_from_include_paths(&include_paths).ok_or("Could not OpenCV version from include_paths")?;
241+
let version =
242+
Self::version_from_include_paths(&include_paths).ok_or("Could not get OpenCV version from include_paths")?;
243+
let enabled_features =
244+
Self::enabled_features_from_include_paths(&include_paths).ok_or("Could not get OpenCV config from include_paths")?;
235245

236246
cargo_metadata.extend(Self::process_link_paths(Some(link_paths), vec![]));
237247
cargo_metadata.extend(Self::process_link_libs(Some(link_libs), vec![]));
238248

239249
Ok(Self {
240250
include_paths,
241251
version,
252+
enabled_features,
242253
cargo_metadata,
243254
})
244255
} else {
@@ -306,10 +317,13 @@ impl Library {
306317
}
307318

308319
let include_paths = Self::process_env_var_list(include_paths, opencv.include_paths);
320+
let enabled_features =
321+
Self::enabled_features_from_include_paths(&include_paths).ok_or("Could not get OpenCV config from include_paths")?;
309322

310323
Ok(Self {
311324
include_paths,
312325
version: Version::parse(&opencv.version)?,
326+
enabled_features,
313327
cargo_metadata,
314328
})
315329
}
@@ -358,9 +372,14 @@ impl Library {
358372
cargo_metadata.extend(Self::process_link_paths(link_paths, probe_result.link_paths));
359373
cargo_metadata.extend(Self::process_link_libs(link_libs, probe_result.link_libs));
360374

375+
let include_paths = Self::process_env_var_list(include_paths, probe_result.include_paths);
376+
let enabled_features =
377+
Self::enabled_features_from_include_paths(&include_paths).ok_or("Could not get OpenCV config from include_paths")?;
378+
361379
Ok(Self {
362-
include_paths: Self::process_env_var_list(include_paths, probe_result.include_paths),
380+
include_paths,
363381
version: probe_result.version.unwrap_or_else(|| Version::new(0, 0, 0)),
382+
enabled_features,
364383
cargo_metadata,
365384
})
366385
}
@@ -414,9 +433,13 @@ impl Library {
414433
}
415434
cargo_metadata.extend(Self::process_link_libs(link_libs, vec![]));
416435

436+
let enabled_features =
437+
Self::enabled_features_from_include_paths(&include_paths).ok_or("Could not get OpenCV config from include_paths")?;
438+
417439
Ok(Self {
418440
include_paths,
419441
version: version.unwrap_or_else(|| Version::new(0, 0, 0)),
442+
enabled_features,
420443
cargo_metadata,
421444
})
422445
}

0 commit comments

Comments
 (0)