Skip to content

Commit d4c3879

Browse files
committed
Include additional core headers conditionally
1 parent 60b1abc commit d4c3879

File tree

14 files changed

+310
-215
lines changed

14 files changed

+310
-215
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: 20 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
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

97
use binding_generator::handle_running_binding_generator;
108
use docs::handle_running_in_docsrs;
119
use generator::BindingGenerator;
10+
use header::IncludePath;
1211
use library::Library;
1312
use once_cell::sync::Lazy;
1413
use semver::{Version, VersionReq};
@@ -21,6 +20,8 @@ pub mod cmake_probe;
2120
mod docs;
2221
#[path = "build/generator.rs"]
2322
mod generator;
23+
#[path = "build/header.rs"]
24+
mod header;
2425
#[path = "build/library.rs"]
2526
pub mod library;
2627

@@ -144,6 +145,8 @@ static SUPPORTED_MODULES: [&str; 73] = [
144145
"xstereo",
145146
];
146147

148+
static SUPPORTED_INHERENT_FEATURES: [&str; 2] = ["hfloat", "opencl"];
149+
147150
/// The contents of these vars will be present in the debug log, but will not cause the source rebuild
148151
static DEBUG_ENV_VARS: [&str; 1] = ["PATH"];
149152

@@ -169,64 +172,6 @@ fn files_with_extension<'e>(dir: &Path, extension: impl AsRef<OsStr> + 'e) -> Re
169172
})
170173
}
171174

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);
180-
}
181-
None
182-
}
183-
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-
}
189-
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;
200-
}
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()?);
213-
}
214-
_ => {}
215-
}
216-
}
217-
if major.is_some() && minor.is_some() && revision.is_some() {
218-
break;
219-
}
220-
}
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
227-
}
228-
}
229-
230175
fn make_modules_and_alises(
231176
opencv_dir: &Path,
232177
opencv_version: &Version,
@@ -264,13 +209,18 @@ fn make_modules_and_alises(
264209
Ok((modules, aliases))
265210
}
266211

267-
fn emit_inherent_features(opencv_version: &Version) {
212+
fn emit_inherent_features(opencv: &Library) {
268213
if VersionReq::parse(">=4.10")
269214
.expect("Static version requirement")
270-
.matches(opencv_version)
215+
.matches(&opencv.version)
271216
{
272217
println!("cargo::rustc-cfg=ocvrs_has_inherent_feature_hfloat");
273218
}
219+
for feature in &opencv.enabled_features {
220+
if SUPPORTED_INHERENT_FEATURES.contains(&feature.as_str()) {
221+
println!("cargo::rustc-cfg=ocvrs_has_inherent_feature_{feature}");
222+
}
223+
}
274224
}
275225

276226
fn make_compiler(opencv: &Library, ffi_export_suffix: &str) -> cc::Build {
@@ -374,9 +324,7 @@ fn main() -> Result<()> {
374324
for module in SUPPORTED_MODULES {
375325
println!("cargo::rustc-check-cfg=cfg(ocvrs_has_module_{module})");
376326
}
377-
// MSRV: switch to #[expect] when MSRV is 1.81
378-
#[allow(clippy::single_element_loop)]
379-
for inherent_feature in ["hfloat"] {
327+
for inherent_feature in SUPPORTED_INHERENT_FEATURES {
380328
println!("cargo::rustc-check-cfg=cfg(ocvrs_has_inherent_feature_{inherent_feature})");
381329
}
382330

@@ -419,10 +367,10 @@ fn main() -> Result<()> {
419367
let opencv_header_dir = opencv
420368
.include_paths
421369
.iter()
422-
.find(|p| get_version_header(p).is_some())
423-
.expect("Discovered OpenCV include paths is empty or contains non-existent paths");
370+
.find(|p| p.get_version_header().is_some())
371+
.expect("Discovered OpenCV include paths do not contain valid OpenCV headers");
424372

425-
if let Some(header_version) = get_version_from_headers(opencv_header_dir) {
373+
if let Some(header_version) = opencv_header_dir.find_version() {
426374
if header_version != opencv.version {
427375
panic!(
428376
"OpenCV version from the headers: {header_version} (at {}) must match version of the OpenCV library: {} (include paths: {:?})",
@@ -442,7 +390,9 @@ fn main() -> Result<()> {
442390
)
443391
}
444392

445-
let opencv_module_header_dir = get_module_header_dir(opencv_header_dir).expect("Can't find OpenCV module header dir");
393+
let opencv_module_header_dir = opencv_header_dir
394+
.get_module_header_dir()
395+
.expect("Can't find OpenCV module header dir");
446396
eprintln!(
447397
"=== Detected OpenCV module header dir at: {}",
448398
opencv_module_header_dir.display()
@@ -452,7 +402,7 @@ fn main() -> Result<()> {
452402
println!("cargo::rustc-cfg=ocvrs_has_module_{module}");
453403
}
454404

455-
emit_inherent_features(&opencv.version);
405+
emit_inherent_features(&opencv);
456406

457407
setup_rerun()?;
458408

build/binding-generator.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ 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::IncludePath;
9+
use super::{GenerateFullBindings, Result};
910

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

build/header.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use std::fs::File;
2+
use std::io::{BufRead, BufReader};
3+
use std::path::{Path, PathBuf};
4+
use std::process::Command;
5+
6+
use semver::Version;
7+
8+
pub trait IncludePath {
9+
fn get_module_header_dir(&self) -> Option<PathBuf>;
10+
fn get_version_header(&self) -> Option<PathBuf>;
11+
fn get_config_header(&self) -> Option<PathBuf>;
12+
fn find_version(&self) -> Option<Version>;
13+
fn find_enabled_features(&self) -> Option<Vec<String>>;
14+
}
15+
16+
impl IncludePath for Path {
17+
fn get_module_header_dir(&self) -> Option<PathBuf> {
18+
let mut out = self.join("opencv2.framework/Headers");
19+
if out.is_dir() {
20+
return Some(out);
21+
}
22+
out = self.join("opencv2");
23+
if out.is_dir() {
24+
return Some(out);
25+
}
26+
None
27+
}
28+
29+
fn get_version_header(&self) -> Option<PathBuf> {
30+
self
31+
.get_module_header_dir()
32+
.map(|dir| dir.join("core/version.hpp"))
33+
.filter(|hdr| hdr.is_file())
34+
}
35+
36+
fn get_config_header(&self) -> Option<PathBuf> {
37+
self
38+
.get_module_header_dir()
39+
.map(|dir| dir.join("cvconfig.h"))
40+
.filter(|hdr| hdr.is_file())
41+
}
42+
43+
fn find_version(&self) -> Option<Version> {
44+
let version_hpp = self.get_version_header()?;
45+
let mut major = None;
46+
let mut minor = None;
47+
let mut revision = None;
48+
let mut line = String::with_capacity(256);
49+
let mut reader = BufReader::new(File::open(version_hpp).ok()?);
50+
while let Ok(bytes_read) = reader.read_line(&mut line) {
51+
if bytes_read == 0 {
52+
break;
53+
}
54+
if let Some(line) = line.strip_prefix("#define CV_VERSION_") {
55+
let mut parts = line.split_whitespace();
56+
if let (Some(ver_spec), Some(version)) = (parts.next(), parts.next()) {
57+
match ver_spec {
58+
"MAJOR" => {
59+
major = Some(version.parse().ok()?);
60+
}
61+
"MINOR" => {
62+
minor = Some(version.parse().ok()?);
63+
}
64+
"REVISION" => {
65+
revision = Some(version.parse().ok()?);
66+
}
67+
_ => {}
68+
}
69+
}
70+
if major.is_some() && minor.is_some() && revision.is_some() {
71+
break;
72+
}
73+
}
74+
line.clear();
75+
}
76+
if let (Some(major), Some(minor), Some(revision)) = (major, minor, revision) {
77+
Some(Version::new(major, minor, revision))
78+
} else {
79+
None
80+
}
81+
}
82+
83+
fn find_enabled_features(&self) -> Option<Vec<String>> {
84+
let config_h = self.get_config_header()?;
85+
let mut out = Vec::with_capacity(64);
86+
let mut line = String::with_capacity(256);
87+
let mut reader = BufReader::new(File::open(config_h).ok()?);
88+
while let Ok(bytes_read) = reader.read_line(&mut line) {
89+
if bytes_read == 0 {
90+
break;
91+
}
92+
if let Some(feature) = line.strip_prefix("#define HAVE_") {
93+
out.push(feature.trim().to_lowercase());
94+
}
95+
line.clear();
96+
}
97+
Some(out)
98+
}
99+
}
100+
101+
/// Something like `/usr/include/x86_64-linux-gnu/opencv4/` on newer Debian-derived distros
102+
///
103+
/// https://wiki.debian.org/Multiarch/Implementation
104+
pub fn get_multiarch_header_dir() -> Option<PathBuf> {
105+
let try_multiarch = Command::new("dpkg-architecture")
106+
.args(["--query", "DEB_TARGET_MULTIARCH"])
107+
.output()
108+
.inspect_err(|e| eprintln!("=== Failed to get DEB_TARGET_MULTIARCH: {e}"))
109+
.ok()
110+
.or_else(|| {
111+
Command::new("cc")
112+
.arg("-print-multiarch")
113+
.output()
114+
.inspect_err(|e| eprintln!("=== Failed to get -print-multiarch: {e}"))
115+
.ok()
116+
})
117+
.and_then(|output| String::from_utf8(output.stdout).ok())
118+
.map_or_else(
119+
|| {
120+
eprintln!("=== Failed to get DEB_TARGET_MULTIARCH, trying common multiarch paths");
121+
vec![
122+
"x86_64-linux-gnu".to_string(),
123+
"aarch64-linux-gnu".to_string(),
124+
"arm-linux-gnueabihf".to_string(),
125+
]
126+
},
127+
|multiarch| vec![multiarch.trim().to_string()],
128+
);
129+
130+
eprintln!("=== Trying multiarch paths: {try_multiarch:?}");
131+
132+
for multiarch in try_multiarch {
133+
let header_dir = PathBuf::from(format!("/usr/include/{multiarch}/opencv4"));
134+
if header_dir.is_dir() {
135+
return Some(header_dir);
136+
}
137+
}
138+
None
139+
}

0 commit comments

Comments
 (0)