Skip to content

Commit a0a8eed

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

File tree

13 files changed

+275
-198
lines changed

13 files changed

+275
-198
lines changed

.github/workflows/opencv-rust.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,19 @@ jobs:
6868
- macos-14
6969
vcpkg-version:
7070
- 2025.01.13 # https://github.com/microsoft/vcpkg/releases
71+
vcpkg-features-cache-key:
72+
- full
73+
vcpkg-features:
74+
- contrib,nonfree,ade,opencl
75+
include:
76+
- os-image: windows-2022
77+
vcpkg-version: 2025.01.13
78+
vcpkg-features-cache-key: min
79+
vcpkg-features: contrib
7180
runs-on: ${{ matrix.os-image }}
7281
env:
7382
VCPKG_VERSION: ${{ matrix.vcpkg-version }}
83+
VCPKG_FEATURES: ${{ matrix.vcpkg-features }}
7484
SCCACHE_GHA_ENABLED: "true"
7585
RUSTC_WRAPPER: "sccache"
7686
steps:
@@ -80,7 +90,7 @@ jobs:
8090
- uses: actions/cache@v4
8191
with:
8292
path: ~/build
83-
key: vcpkg-${{ matrix.vcpkg-version }}-${{ matrix.os-image }}
93+
key: vcpkg-${{ matrix.vcpkg-version }}-${{ matrix.os-image }}-${{ matrix.vcpkg-features-cache-key }}
8494

8595
- name: Install dependencies
8696
run: ci/install.sh

INSTALL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Installing OpenCV is easy through the following sources:
5656
* from [vcpkg](https://docs.microsoft.com/en-us/cpp/build/vcpkg), also install `llvm` package,
5757
necessary for building:
5858
```shell script
59-
vcpkg install llvm opencv4[contrib,nonfree,opencl]
59+
vcpkg install llvm opencv4[contrib,nonfree]
6060
```
6161
You most probably want to set environment variable `VCPKGRS_DYNAMIC` to "1" unless you're specifically
6262
targeting a static build.

build.rs

Lines changed: 128 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,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

276349
fn 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())
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

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();

0 commit comments

Comments
 (0)