Skip to content

Commit 96309ae

Browse files
committed
Include additional core headers conditionally
1 parent c43f377 commit 96309ae

File tree

7 files changed

+204
-85
lines changed

7 files changed

+204
-85
lines changed

build.rs

Lines changed: 134 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,137 @@ 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/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+
.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 = format!("/usr/include/{multiarch}/opencv4/opencv2");
221+
if Path::new(&header).is_dir() {
222+
return Some(PathBuf::from(header));
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+
.or_else(|| {
239+
eprintln!("=== Failed to find cvconfig.h in the regular header dir, trying multiarch");
240+
get_multiarch_module_header_dir()
241+
.map(|dir| dir.join("cvconfig.h"))
242+
.filter(|hdr| hdr.is_file())
243+
})
244+
}
245+
246+
pub fn find_version(header_dir: &Path) -> Option<Version> {
247+
let version_hpp = get_version_header(header_dir)?;
248+
let mut major = None;
249+
let mut minor = None;
250+
let mut revision = None;
251+
let mut line = String::with_capacity(256);
252+
let mut reader = BufReader::new(File::open(version_hpp).ok()?);
253+
while let Ok(bytes_read) = reader.read_line(&mut line) {
254+
if bytes_read == 0 {
255+
break;
256+
}
257+
if let Some(line) = line.strip_prefix("#define CV_VERSION_") {
258+
let mut parts = line.split_whitespace();
259+
if let (Some(ver_spec), Some(version)) = (parts.next(), parts.next()) {
260+
match ver_spec {
261+
"MAJOR" => {
262+
major = Some(version.parse().ok()?);
263+
}
264+
"MINOR" => {
265+
minor = Some(version.parse().ok()?);
266+
}
267+
"REVISION" => {
268+
revision = Some(version.parse().ok()?);
269+
}
270+
_ => {}
213271
}
214-
_ => {}
272+
}
273+
if major.is_some() && minor.is_some() && revision.is_some() {
274+
break;
215275
}
216276
}
217-
if major.is_some() && minor.is_some() && revision.is_some() {
277+
line.clear();
278+
}
279+
if let (Some(major), Some(minor), Some(revision)) = (major, minor, revision) {
280+
Some(Version::new(major, minor, revision))
281+
} else {
282+
None
283+
}
284+
}
285+
286+
pub fn find_enabled_features(header_dir: &Path) -> Option<Vec<String>> {
287+
let config_h = get_config_header(header_dir)?;
288+
let mut out = Vec::with_capacity(64);
289+
let mut line = String::with_capacity(256);
290+
let mut reader = BufReader::new(File::open(config_h).ok()?);
291+
while let Ok(bytes_read) = reader.read_line(&mut line) {
292+
if bytes_read == 0 {
218293
break;
219294
}
295+
if let Some(feature) = line.strip_prefix("#define HAVE_") {
296+
out.push(feature.trim().to_lowercase());
297+
}
298+
line.clear();
220299
}
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
300+
Some(out)
227301
}
228302
}
229303

@@ -264,13 +338,18 @@ fn make_modules_and_alises(
264338
Ok((modules, aliases))
265339
}
266340

267-
fn emit_inherent_features(opencv_version: &Version) {
341+
fn emit_inherent_features(opencv: &Library) {
268342
if VersionReq::parse(">=4.10")
269343
.expect("Static version requirement")
270-
.matches(opencv_version)
344+
.matches(&opencv.version)
271345
{
272346
println!("cargo::rustc-cfg=ocvrs_has_inherent_feature_hfloat");
273347
}
348+
for feature in &opencv.enabled_features {
349+
if feature == "opencl" {
350+
println!("cargo::rustc-cfg=ocvrs_has_inherent_feature_opencl");
351+
}
352+
}
274353
}
275354

276355
fn make_compiler(opencv: &Library, ffi_export_suffix: &str) -> cc::Build {
@@ -374,9 +453,7 @@ fn main() -> Result<()> {
374453
for module in SUPPORTED_MODULES {
375454
println!("cargo::rustc-check-cfg=cfg(ocvrs_has_module_{module})");
376455
}
377-
// MSRV: switch to #[expect] when MSRV is 1.81
378-
#[allow(clippy::single_element_loop)]
379-
for inherent_feature in ["hfloat"] {
456+
for inherent_feature in ["hfloat", "opencl"] {
380457
println!("cargo::rustc-check-cfg=cfg(ocvrs_has_inherent_feature_{inherent_feature})");
381458
}
382459

@@ -419,10 +496,10 @@ fn main() -> Result<()> {
419496
let opencv_header_dir = opencv
420497
.include_paths
421498
.iter()
422-
.find(|p| get_version_header(p).is_some())
423-
.expect("Discovered OpenCV include paths is empty or contains non-existent paths");
499+
.find(|p| header::get_version_header(p).is_some() && header::get_config_header(p).is_some())
500+
.expect("Discovered OpenCV include paths do not contain valid OpenCV headers");
424501

425-
if let Some(header_version) = get_version_from_headers(opencv_header_dir) {
502+
if let Some(header_version) = header::find_version(opencv_header_dir) {
426503
if header_version != opencv.version {
427504
panic!(
428505
"OpenCV version from the headers: {header_version} (at {}) must match version of the OpenCV library: {} (include paths: {:?})",
@@ -442,7 +519,7 @@ fn main() -> Result<()> {
442519
)
443520
}
444521

445-
let opencv_module_header_dir = get_module_header_dir(opencv_header_dir).expect("Can't find OpenCV module header dir");
522+
let opencv_module_header_dir = header::get_module_header_dir(opencv_header_dir).expect("Can't find OpenCV module header dir");
446523
eprintln!(
447524
"=== Detected OpenCV module header dir at: {}",
448525
opencv_module_header_dir.display()
@@ -452,7 +529,7 @@ fn main() -> Result<()> {
452529
println!("cargo::rustc-cfg=ocvrs_has_module_{module}");
453530
}
454531

455-
emit_inherent_features(&opencv.version);
532+
emit_inherent_features(&opencv);
456533

457534
setup_rerun()?;
458535

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)