Skip to content

Commit a00a24e

Browse files
committed
Improve multilarch directory detection in cross-compilation scenarios
1 parent 8af2673 commit a00a24e

File tree

3 files changed

+129
-48
lines changed

3 files changed

+129
-48
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: 110 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use std::env;
12
use std::fs::File;
23
use std::io::{BufRead, BufReader};
34
use std::path::{Path, PathBuf};
45
use std::process::Command;
56

67
use semver::Version;
78

9+
use super::TARGET_OS_LINUX;
10+
811
pub trait IncludePath {
912
fn get_module_header_dir(&self) -> Option<PathBuf>;
1013
fn get_version_header(&self) -> Option<PathBuf>;
@@ -100,42 +103,119 @@ impl IncludePath for Path {
100103
}
101104
}
102105

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

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

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);
183+
let mut out = Vec::with_capacity(non_multiarch_include_dirs.len());
184+
for multiarch in try_multiarches {
185+
for non_multiarch_include_dir in non_multiarch_include_dirs {
186+
if let Some(multiarch_include_dir) = add_multiarch_dir_before_opencv4(non_multiarch_include_dir, &multiarch) {
187+
if multiarch_include_dir.is_dir() {
188+
out.push(multiarch_include_dir);
189+
}
190+
} else if let Some(multiarch_include_dir) = add_multiarch_dir_after_include(non_multiarch_include_dir, &multiarch) {
191+
if multiarch_include_dir.is_dir() {
192+
out.push(multiarch_include_dir);
193+
}
194+
}
138195
}
139196
}
140-
None
197+
eprintln!("=== Found multiarch header dirs: {out:?}");
198+
out
199+
}
200+
201+
/// Finds the "opencv4" component in the path and inserts the multiarch directory before it
202+
///
203+
/// This works starting with OpenCV 4.0.0
204+
fn add_multiarch_dir_before_opencv4(path: &Path, multiarch: &str) -> Option<PathBuf> {
205+
let opencv4_idx = path
206+
.components()
207+
.position(|comp| comp.as_os_str().to_str().is_some_and(|s| s == "opencv4"))?;
208+
let mut comps = path.components();
209+
let mut out = PathBuf::with_capacity(path.as_os_str().len() + multiarch.len() + 1);
210+
out.extend((&mut comps).take(opencv4_idx));
211+
out.push(multiarch);
212+
out.extend(comps);
213+
Some(out)
214+
}
215+
216+
/// Finds the "include" component in the path and inserts the multiarch directory after it
217+
///
218+
/// This works starting for OpenCV 3.4 as it doesn't have the "opencv4" component
219+
fn add_multiarch_dir_after_include(path: &Path, multiarch: &str) -> Option<PathBuf> {
220+
path.ends_with("include").then(|| path.join(multiarch))
141221
}

build/library.rs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -181,20 +181,21 @@ pub struct Library {
181181
}
182182

183183
impl Library {
184-
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-
}
189-
}
190-
}
191-
192184
fn version_from_include_paths(include_paths: impl IntoIterator<Item = impl AsRef<Path>>) -> Option<Version> {
193185
include_paths.into_iter().find_map(|x| x.as_ref().find_version())
194186
}
195187

196-
fn inherent_features_from_include_paths(include_paths: impl IntoIterator<Item = impl AsRef<Path>>) -> Option<Vec<String>> {
197-
include_paths.into_iter().find_map(|x| x.as_ref().find_inherent_features())
188+
fn inherent_features_from_include_paths(include_paths: &[PathBuf]) -> Option<Vec<String>> {
189+
include_paths.iter().find_map(|x| x.find_inherent_features()).or_else(|| {
190+
// Check the multiarch header directories if no `cvconfig.h` found in the provided include paths
191+
//
192+
// On the newer Debian-derived distros the `cvconfig.h` is located in the multiarch include directory, it won't be reported
193+
// by pkg-config/cmake/vcpkg. It looks like `/usr/include/x86_64-linux-gnu/opencv4/` and contains architecture specific
194+
// headers: https://wiki.debian.org/Multiarch/Implementation
195+
header::get_multiarch_header_dirs(include_paths)
196+
.into_iter()
197+
.find_map(|x| x.find_inherent_features())
198+
})
198199
}
199200

200201
fn process_env_var_list<'a, T: From<&'a str>>(env_list: Option<EnvList<'a>>, sys_list: Vec<T>) -> Vec<T> {
@@ -215,8 +216,9 @@ impl Library {
215216
Self::process_env_var_list(link_paths, sys_link_paths)
216217
.into_iter()
217218
.flat_map(move |search| {
218-
iter::once(search.clone())
219-
.chain((*TARGET_VENDOR_APPLE && search.0 != Linkage::Framework).then(|| LinkSearch(Linkage::Framework, search.1)))
219+
iter::once(search.clone()).chain(
220+
(*TARGET_VENDOR_APPLE && search.0 != Linkage::Framework).then_some(LinkSearch(Linkage::Framework, search.1)),
221+
)
220222
})
221223
.inspect(|s| eprintln!("=== Link search path: {s:?}"))
222224
.map(|search| search.emit_cargo_rustc_link_search())
@@ -348,8 +350,7 @@ impl Library {
348350
));
349351
}
350352

351-
let mut include_paths = Self::process_env_var_list(include_paths, opencv.include_paths);
352-
Self::add_multiarch_dir_if_needed(&mut include_paths);
353+
let include_paths = Self::process_env_var_list(include_paths, opencv.include_paths);
353354
let inherent_features =
354355
Self::inherent_features_from_include_paths(&include_paths).ok_or("Could not get OpenCV config from include_paths")?;
355356

@@ -408,8 +409,7 @@ impl Library {
408409
cargo_metadata.extend(Self::process_link_paths(link_paths, probe_result.link_paths));
409410
cargo_metadata.extend(Self::process_link_libs(link_libs, probe_result.link_libs));
410411

411-
let mut include_paths = Self::process_env_var_list(include_paths, probe_result.include_paths);
412-
Self::add_multiarch_dir_if_needed(&mut include_paths);
412+
let include_paths = Self::process_env_var_list(include_paths, probe_result.include_paths);
413413
let inherent_features =
414414
Self::inherent_features_from_include_paths(&include_paths).ok_or("Could not get OpenCV config from include_paths")?;
415415

@@ -468,8 +468,7 @@ impl Library {
468468
}
469469
cargo_metadata.extend(Self::process_link_libs(link_libs, vec![]));
470470

471-
let mut include_paths = Self::process_env_var_list(include_paths, opencv_include_paths);
472-
Self::add_multiarch_dir_if_needed(&mut include_paths);
471+
let include_paths = Self::process_env_var_list(include_paths, opencv_include_paths);
473472
let inherent_features =
474473
Self::inherent_features_from_include_paths(&include_paths).ok_or("Could not get OpenCV config from include_paths")?;
475474

0 commit comments

Comments
 (0)