Skip to content

Commit 74f8108

Browse files
committed
Improve multilarch directory detection in cross-compilation scenarios
1 parent 3b9e166 commit 74f8108

File tree

3 files changed

+120
-35
lines changed

3 files changed

+120
-35
lines changed

build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ static TARGET_ENV_MSVC: LazyLock<bool> =
4242
LazyLock::new(|| env::var("CARGO_CFG_TARGET_ENV").is_ok_and(|target_env| target_env == "msvc"));
4343
static TARGET_VENDOR_APPLE: LazyLock<bool> =
4444
LazyLock::new(|| env::var("CARGO_CFG_TARGET_VENDOR").is_ok_and(|target_vendor| target_vendor == "apple"));
45+
static TARGET_OS_LINUX: LazyLock<bool> =
46+
LazyLock::new(|| env::var("CARGO_CFG_TARGET_OS").is_ok_and(|target_os| target_os == "linux"));
4547

4648
/// Environment vars that affect the build, the source will be rebuilt if those change, the contents of those vars will also
4749
/// be present in the debug log

build/header.rs

Lines changed: 106 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::env;
12
use std::fs::File;
23
use std::io::{BufRead, BufReader};
34
use std::path::{Path, PathBuf};
@@ -100,42 +101,117 @@ impl IncludePath for Path {
100101
}
101102
}
102103

103-
/// Something like `/usr/include/x86_64-linux-gnu/opencv4/` on newer Debian-derived distros
104+
const POSSIBLE_MULTIARCHES: &[&str] = &[
105+
"x86_64-linux-gnu",
106+
"aarch64-linux-gnu",
107+
"arm-linux-gnueabihf",
108+
"i386-linux-gnu",
109+
"arm-linux-gnueabi",
110+
];
111+
112+
/// When cross-compiling, try to derive the target's multiarch from Cargo's target triple
104113
///
105-
/// https://wiki.debian.org/Multiarch/Implementation
106-
pub fn get_multiarch_header_dir() -> Option<PathBuf> {
107-
let try_multiarch = Command::new("dpkg-architecture")
108-
.args(["--query", "DEB_TARGET_MULTIARCH"])
109-
.output()
110-
.inspect_err(|e| eprintln!("=== Failed to get DEB_TARGET_MULTIARCH: {e}"))
111-
.ok()
112-
.or_else(|| {
113-
Command::new("cc")
114-
.arg("-print-multiarch")
114+
/// https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
115+
/// https://wiki.debian.org/Multiarch/Tuples#Architectures_in_Debian
116+
fn target_linux_multiarch_from_cargo() -> Option<&'static str> {
117+
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").ok()?;
118+
let target_abi = env::var("CARGO_CFG_TARGET_ABI").ok()?;
119+
match (target_arch.as_str(), target_abi.as_str()) {
120+
("x86", _) => Some("i386-linux-gnu"),
121+
("x86_64", _) => Some("x86_64-linux-gnu"),
122+
("aarch64", _) => Some("aarch64-linux-gnu"),
123+
("arm", "eabihf") => Some("arm-linux-gnueabihf"),
124+
("arm", _) => Some("arm-linux-gnueabi"),
125+
_ => None,
126+
}
127+
}
128+
129+
fn target_linux_multiarch_from_path(cmake_dir: &Path) -> Option<&str> {
130+
cmake_dir.components().rev().find_map(|comp| {
131+
comp
132+
.as_os_str()
133+
.to_str()
134+
.filter(|comp_str| POSSIBLE_MULTIARCHES.contains(comp_str))
135+
})
136+
}
137+
138+
pub fn get_multiarch_header_dirs(non_multiarch_include_dirs: &[PathBuf]) -> Vec<PathBuf> {
139+
let mut try_multiarches = vec![];
140+
141+
try_multiarches.extend(
142+
target_linux_multiarch_from_cargo()
143+
.map(|s| s.to_string())
144+
.inspect(|target_arch| eprintln!("=== Detected target multiarch from Cargo target: {target_arch}")),
145+
);
146+
try_multiarches.extend(
147+
env::var_os("OpenCV_DIR")
148+
.and_then(|d| target_linux_multiarch_from_path(&PathBuf::from(d)).map(|s| s.to_string()))
149+
.inspect(|target_arch| eprintln!("=== Detected target multiarch from cmake OpenCV_DIR: {target_arch}")),
150+
);
151+
if try_multiarches.is_empty() {
152+
// Fallback to detecting the host's multiarch
153+
try_multiarches.extend(
154+
Command::new("dpkg-architecture")
155+
.args(["--query", "DEB_TARGET_MULTIARCH"])
115156
.output()
116-
.inspect_err(|e| eprintln!("=== Failed to get -print-multiarch: {e}"))
157+
.inspect_err(|e| eprintln!("=== Failed to query DEB_TARGET_MULTIARCH: {e}"))
117158
.ok()
118-
})
119-
.and_then(|output| String::from_utf8(output.stdout).ok())
120-
.map_or_else(
121-
|| {
122-
eprintln!("=== Failed to get DEB_TARGET_MULTIARCH, trying common multiarch paths");
123-
vec![
124-
"x86_64-linux-gnu".to_string(),
125-
"aarch64-linux-gnu".to_string(),
126-
"arm-linux-gnueabihf".to_string(),
127-
]
128-
},
129-
|multiarch| vec![multiarch.trim().to_string()],
159+
.or_else(|| {
160+
Command::new("cc")
161+
.arg("-print-multiarch")
162+
.output()
163+
.inspect_err(|e| eprintln!("=== Failed to get -print-multiarch: {e}"))
164+
.ok()
165+
})
166+
.and_then(|output| String::from_utf8(output.stdout).ok())
167+
.map_or_else(
168+
|| {
169+
eprintln!("=== Failed to detect multiarch path, trying common paths");
170+
POSSIBLE_MULTIARCHES.iter().map(|s| s.to_string()).collect::<Vec<_>>()
171+
},
172+
|multiarch| vec![multiarch.trim().to_string()],
173+
),
130174
);
175+
};
131176

132-
eprintln!("=== Trying multiarch paths: {try_multiarch:?}");
177+
eprintln!("=== Trying multiarches: {try_multiarches:?}");
133178

134-
for multiarch in try_multiarch {
135-
let header_dir = PathBuf::from(format!("/usr/include/{multiarch}/opencv4"));
136-
if header_dir.is_dir() {
137-
return Some(header_dir);
179+
let mut out = Vec::with_capacity(non_multiarch_include_dirs.len());
180+
for multiarch in try_multiarches {
181+
for non_multiarch_include_dir in non_multiarch_include_dirs {
182+
if let Some(multiarch_include_dir) = add_multiarch_dir_before_opencv4(non_multiarch_include_dir, &multiarch) {
183+
if multiarch_include_dir.is_dir() {
184+
out.push(multiarch_include_dir);
185+
}
186+
} else if let Some(multiarch_include_dir) = add_multiarch_dir_after_include(non_multiarch_include_dir, &multiarch) {
187+
if multiarch_include_dir.is_dir() {
188+
out.push(multiarch_include_dir);
189+
}
190+
}
138191
}
139192
}
140-
None
193+
eprintln!("=== Found multiarch header dirs: {out:?}");
194+
out
195+
}
196+
197+
/// Finds the "opencv4" component in the path and inserts the multiarch directory before it
198+
///
199+
/// This works starting with OpenCV 4.0.0
200+
fn add_multiarch_dir_before_opencv4(path: &Path, multiarch: &str) -> Option<PathBuf> {
201+
let opencv4_idx = path
202+
.components()
203+
.position(|comp| comp.as_os_str().to_str().is_some_and(|s| s == "opencv4"))?;
204+
let mut comps = path.components();
205+
let mut out = PathBuf::with_capacity(path.as_os_str().len() + multiarch.len() + 1);
206+
out.extend((&mut comps).take(opencv4_idx));
207+
out.push(multiarch);
208+
out.extend(comps);
209+
Some(out)
210+
}
211+
212+
/// Finds the "include" component in the path and inserts the multiarch directory after it
213+
///
214+
/// This works starting for OpenCV 3.4 as it doesn't have the "opencv4" component
215+
fn add_multiarch_dir_after_include(path: &Path, multiarch: &str) -> Option<PathBuf> {
216+
path.ends_with("include").then(|| path.join(multiarch))
141217
}

build/library.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use semver::Version;
1010
use super::cmake_probe::CmakeProbe;
1111
use super::header::IncludePath;
1212
use super::path_ext::{LibraryKind, PathExt};
13-
use super::{header, Result, MANIFEST_DIR, OUT_DIR, TARGET_VENDOR_APPLE};
13+
use super::{header, Result, MANIFEST_DIR, OUT_DIR, TARGET_OS_LINUX, TARGET_VENDOR_APPLE};
1414

1515
struct PackageName;
1616

@@ -181,11 +181,18 @@ pub struct Library {
181181
}
182182

183183
impl Library {
184+
/// Adds the multiarch directories if no `cvconfig.h` found in the provided include paths
185+
///
186+
/// On the newer Debian-derived distros the `cvconfig.h` is located in the multiarch include directory, it won't be reported
187+
/// by pkg-config/cmake/vcpkg. It looks like `/usr/include/x86_64-linux-gnu/opencv4/` and contains architecture specific
188+
/// headers: https://wiki.debian.org/Multiarch/Implementation
184189
fn add_multiarch_dir_if_needed(include_paths: &mut Vec<PathBuf>) {
185-
if include_paths.iter().all(|p| p.get_config_header().is_none()) {
186-
if let Some(multiarch_include_path) = header::get_multiarch_header_dir() {
187-
include_paths.push(multiarch_include_path);
188-
}
190+
if *TARGET_OS_LINUX && include_paths.iter().all(|p| p.get_config_header().is_none()) {
191+
include_paths.extend(
192+
header::get_multiarch_header_dirs(include_paths)
193+
.into_iter()
194+
.filter(|p| p.get_config_header().is_some()),
195+
);
189196
}
190197
}
191198

0 commit comments

Comments
 (0)