Rework cmake probing (no longer uses deprecated --find-package) and add combined vcpkg+cmake probe
MODIFY
.github/workflows/opencv-rust.yml
.github/workflows/opencv-rust.yml
@@ -16,17 +16,13 @@ jobs:
os:
- image: ubuntu-18.04
family: linux
- - image: windows-2019
- family: windows
rust:
- stable
- beta
version:
- opencv: 3.4.11
- vcpkg: 3
features: opencv-34
- opencv: 4.4.0
- vcpkg: 4
features: opencv-4
runs-on: ${{ matrix.os.image }}
env:
@@ -34,7 +30,6 @@ jobs:
RUST_TOOLCHAIN: ${{ matrix.rust }}
Atlas_ROOT_DIR: /usr/include/ # for cmake to find lapacke.h
OPENCV_VERSION: ${{ matrix.version.opencv }}
- VCPKG_OPENCV_VERSION: ${{ matrix.version.vcpkg }}
CARGO_FEATURES: ${{ matrix.version.features }}
steps:
- uses: actions/checkout@v2
@@ -66,6 +61,60 @@ jobs:
name: generated-failed-bindings-${{ runner.os }}-${{ matrix.version.opencv }}
path: bindings
+ vcpkg:
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - image: ubuntu-18.04
+ family: linux
+ - image: windows-2019
+ family: windows
+ rust:
+ - stable
+ - beta
+ version:
+ - vcpkg: 3
+ features: opencv-34
+ - vcpkg: 4
+ features: opencv-4
+ runs-on: ${{ matrix.os.image }}
+ env:
+ OS_FAMILY: ${{ matrix.os.family }}
+ RUST_TOOLCHAIN: ${{ matrix.rust }}
+ Atlas_ROOT_DIR: /usr/include/ # for cmake to find lapacke.h
+ VCPKG_OPENCV_VERSION: ${{ matrix.version.vcpkg }}
+ CARGO_FEATURES: ${{ matrix.version.features }}
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache dependencies
+ uses: actions/cache@v1
+ with:
+ path: ~/build
+ key: ${{ runner.os }}-${{ matrix.version.vcpkg }}-build
+
+ - name: Install dependencies
+ run: ci/install.sh
+ shell: bash
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.rust }}
+ default: true
+
+ - name: Test project
+ run: ci/script.sh
+ shell: bash
+
+ - name: Upload bindings if test failed
+ uses: actions/upload-artifact@v1
+ if: failure()
+ with:
+ name: generated-failed-bindings-${{ runner.os }}-vcpkg-${{ matrix.version.vcpkg }}
+ path: bindings
+
framework:
strategy:
fail-fast: false

MODIFY
Cargo.toml
Cargo.toml
@@ -60,6 +60,7 @@ docs-only = []
binding-generator = { package = "opencv-binding-generator", version = "0.21.0", path = "binding-generator", optional = true }
cc = { version = "1.0", features = ["parallel"] }
clang = { version = "1.0", features = ["clang_6_0"], optional = true }
+dunce = "1.0"
glob = "0.3"
jobserver = { version = "0.1", optional = true }
once_cell = "1.0"

MODIFY
README.md
README.md
@@ -163,13 +163,23 @@ on any platform, the specified values will override those automatically discover
library.
E.g. "opencv_world411".
+ If this list starts with '+' (plus sign) then the specified items will be appended to whatever the system
+ probe returned. E.g. a value of "+dc1394" will do a system discovery of the OpenCV library and its linked
+ libraries and then will additionally link `dc1394` library at the end. Can be useful if the system probe
+ produces a mostly working setup, but has incomplete link list, or the order is wrong (especially important
+ during static linking).
+
* `OPENCV_LINK_PATHS`
Comma separated list of paths to search for libraries to link. E.g. "C:\tools\opencv\build\x64\vc15\lib".
+ The path list can start with '+', see `OPENCV_LINK_LIBS` for a detailed explanation (e.g.
+ "+/usr/local/lib").
* `OPENCV_INCLUDE_PATHS`
Comma separated list of paths to search for system include files during compilation. E.g.
"C:\tools\opencv\build\include". One of the directories specified therein must contain
"opencv2/core/version.hpp" or "core/version.hpp" file, it's used to detect the version of the headers.
+ The path list can start with '+', see `OPENCV_LINK_LIBS` for a detailed explanation (e.g.
+ "+/opt/cuda/targets/x86_64-linux/include/").
The following variables are rarely used, but you might need them under some circumstances:
@@ -196,8 +206,12 @@ The following variables are rarely used, but you might need them under some circ
* `OPENCV_DISABLE_PROBES`
Comma separated list of OpenCV package auto-discovery systems to exclude from running. Might be useful if
one of the higher priority systems is producing incorrect results. Can contain the following values:
+ * environment - reads data only from the `OPENCV_LINK_LIBS`, `OPENCV_LINK_PATHS` and `OPENCV_INCLUDE_PATHS`
+ environment variables
* pkg_config
* cmake
+ * vcpkg_cmake - like vcpkg, but only uses vcpkg for path discovery, the actual OpenCV probe is done using
+ cmake (cmake related environment variables are applicable with this probe)
* vcpkg
* `OPENCV_CLANG_STDLIB_PATH`

MODIFY
build.rs
build.rs
@@ -8,14 +8,15 @@ use std::{
io::{BufRead, BufReader},
iter::{self, FromIterator},
path::{Path, PathBuf},
- process::Command,
};
+use dunce::canonicalize;
use glob::glob;
use once_cell::sync::{Lazy, OnceCell};
use semver::{Version, VersionReq};
-use shlex::Shlex;
+#[path = "build_cmake_probe.rs"]
+mod cmake_probe;
#[cfg(feature = "buildtime-bindgen")]
#[path = "build_generator.rs"]
mod generator;
@@ -60,7 +61,7 @@ static MANIFEST_DIR: Lazy<PathBuf> = Lazy::new(|| PathBuf::from(env::var_os("CAR
static SRC_DIR: Lazy<PathBuf> = Lazy::new(|| MANIFEST_DIR.join("src"));
static SRC_CPP_DIR: Lazy<PathBuf> = Lazy::new(|| MANIFEST_DIR.join("src_cpp"));
-static ENV_VARS: [&str; 15] = [
+static ENV_VARS: [&str; 16] = [
"OPENCV_HEADER_DIR",
"OPENCV_PACKAGE_NAME",
"OPENCV_PKGCONFIG_NAME",
@@ -76,8 +77,42 @@ static ENV_VARS: [&str; 15] = [
"OpenCV_DIR",
"PKG_CONFIG_PATH",
"VCPKG_ROOT",
+ "VCPKGRS_DYNAMIC",
];
+
+fn cleanup_lib_filename(filename: &OsStr) -> Option<&OsStr> {
+ let mut strip_performed = false;
+ let mut filename_path = Path::new(filename);
+ const LIB_EXTS: [&str; 7] = ["so", "a", "dll", "lib", "dylib", "framework", "tbd"];
+ if let (Some(stem), Some(extension)) = (filename_path.file_stem(), filename_path.extension().and_then(OsStr::to_str)) {
+ if LIB_EXTS.iter().any(|e| e.eq_ignore_ascii_case(extension)) {
+ filename_path = Path::new(stem);
+ strip_performed = true;
+ }
+ }
+
+ // same, but with dots therearound
+ const LIB_EXTS_INNER: [&str; 7] = [".so.", ".a.", ".dll.", ".lib.", ".dylib.", ".framework.", ".tbd."];
+ if let Some(mut file) = filename_path.file_name().and_then(OsStr::to_str) {
+ let orig_len = file.len();
+ file = file.strip_prefix("lib").unwrap_or(file);
+ LIB_EXTS_INNER.iter()
+ .for_each(|&inner_ext| if let Some(inner_ext_idx) = file.find(inner_ext) {
+ file = &file[..inner_ext_idx];
+ });
+ if orig_len != file.len() {
+ strip_performed = true;
+ filename_path = Path::new(file);
+ }
+ }
+ if strip_performed {
+ Some(filename_path.as_os_str())
+ } else {
+ None
+ }
+}
+
struct PackageName;
impl PackageName {
@@ -175,39 +210,17 @@ struct Library {
}
impl Library {
- fn strip_lib_file_decorations(path: &mut PathBuf) {
- const LIB_EXTS: [&str; 7] = ["so", "a", "dll", "lib", "dylib", "framework", "tbd"];
- // same, but with dots therearound
- const LIB_EXTS_INNER: [&str; 7] = [".so.", ".a.", ".dll.", ".lib.", ".dylib.", ".framework.", ".tbd."];
- if let Some(extension) = path.extension().and_then(OsStr::to_str) {
- if LIB_EXTS.iter().any(|e| e.eq_ignore_ascii_case(extension)) {
- path.set_extension("");
- }
- }
- if let Some(mut file) = path.file_name().and_then(OsStr::to_str).map(str::to_owned) {
- let orig_len = file.len();
- const LIB_PREFIX: &str = "lib";
- if file.starts_with(LIB_PREFIX) {
- file.drain(..LIB_PREFIX.len());
- }
- LIB_EXTS_INNER.iter()
- .for_each(|&inner_ext| if let Some(inner_ext_idx) = file.find(inner_ext) {
- file.drain(inner_ext_idx..);
- });
- if orig_len != file.len() {
- path.set_file_name(file);
- }
- }
- }
-
fn process_library_list(libs: impl IntoIterator<Item=impl Into<PathBuf>>) -> impl Iterator<Item=String> {
libs.into_iter()
- .map(|x| {
+ .filter_map(|x| {
let mut path: PathBuf = x.into();
let is_framework = path.extension()
.and_then(OsStr::to_str)
.map_or(false, |e| e.eq_ignore_ascii_case("framework"));
- Self::strip_lib_file_decorations(&mut path);
+ if let Some(filename) = path.file_name().and_then(cleanup_lib_filename) {
+ let filename = filename.to_owned();
+ path.set_file_name(filename);
+ }
path.file_name()
.and_then(|f| f.to_str()
.map(|f| if is_framework {
@@ -215,7 +228,7 @@ impl Library {
} else {
f.to_owned()
})
- ).expect("Invalid library name")
+ )
})
}
@@ -327,120 +340,44 @@ impl Library {
})
}
- pub fn probe_cmake(include_paths: Option<EnvList>, link_paths: Option<EnvList>, link_libs: Option<EnvList>) -> Result<Self> {
- eprintln!("=== Probing OpenCV library using cmake");
- let cmake_pkg = PackageName::cmake();
- let cmake_bin = env::var_os("OPENCV_CMAKE_BIN").unwrap_or_else(|| "cmake".into());
+ pub fn probe_cmake(include_paths: Option<EnvList>, link_paths: Option<EnvList>, link_libs: Option<EnvList>, toolchain: Option<&Path>, cmake_bin: Option<&Path>, ninja_bin: Option<&Path>) -> Result<Self> {
+ eprintln!(
+ "=== Probing OpenCV library using cmake{}",
+ toolchain.map(|tc| format!(" with toolchain: {}", tc.display())).unwrap_or_else(|| "".to_string())
+ );
- let include_paths = include_paths
- .map(|paths| Self::list(paths)
+ let src_dir = MANIFEST_DIR.join("cmake");
+ let package_name = PackageName::cmake();
+ let cmake = cmake_probe::CmakeProbe::new(
+ env::var_os("OPENCV_CMAKE_BIN")
.map(PathBuf::from)
- .collect::<Vec<_>>()
- )
- .ok_or_else(|| "Nobody is going to see that")
- .or_else(|_| Command::new(&cmake_bin)
- .current_dir(&*OUT_DIR)
- .args(&[
- "--find-package",
- "-DCOMPILER_ID=GNU",
- "-DLANGUAGE=CXX",
- "-DMODE=COMPILE",
- ])
- .arg(format!("-DNAME={}", cmake_pkg))
- .output()
- .map_err(Box::<dyn std::error::Error>::from)
- .and_then(|output| {
- if output.status.success() {
- let mut include_paths = Vec::with_capacity(4);
- let stdout = String::from_utf8(output.stdout)?;
- eprintln!("=== cmake include arguments: {:#?}", stdout);
- for mut arg in Shlex::new(stdout.trim()) {
- const INCLUDE_PREFIX: &str = "-I";
- if arg.starts_with(INCLUDE_PREFIX) {
- arg.drain(..INCLUDE_PREFIX.len());
- // todo possibly handle leading whitespace
- include_paths.push(PathBuf::from(arg));
- } else {
- eprintln!("=== Unexpected cmake compile argument found: {}", arg);
- }
- }
- Ok(include_paths)
- } else {
- Err(format!(
- "cmake returned an error\n stdout: {:?}\n stderr: {:?}",
- String::from_utf8_lossy(&output.stdout),
- String::from_utf8_lossy(&output.stderr)
- ).into())
- }
- })
- )?;
-
- if let Some(version) = Self::version_from_include_paths(include_paths.iter()) {
- let mut cargo_metadata = Vec::with_capacity(64);
- link_paths.map(|link_paths| Self::process_manual_link_search(&mut cargo_metadata, link_paths));
- link_libs.map(|link_libs| Self::process_manual_link_libs(&mut cargo_metadata, link_libs));
- if link_paths.is_none() || link_libs.is_none() {
- Command::new(&cmake_bin)
- .current_dir(&*OUT_DIR)
- .args(&[
- "--find-package",
- "-DCOMPILER_ID=GNU",
- "-DLANGUAGE=CXX",
- "-DMODE=LINK",
- ])
- .arg(format!("-DNAME={}", cmake_pkg))
- .output()
- .map_err(Box::<dyn std::error::Error>::from)
- .and_then(|output| if output.status.success() {
- let mut cmake_link_paths = if link_paths.is_some() { HashSet::new() } else { HashSet::with_capacity(4) };
- let stdout = String::from_utf8(output.stdout)?;
- eprintln!("=== cmake link arguments: {:#?}", stdout);
- for mut arg in Shlex::new(stdout.trim()) {
- const RPATH_PREFIX: &str = "-Wl,-rpath,";
- if arg.starts_with(RPATH_PREFIX) {
- arg.drain(..RPATH_PREFIX.len());
- cmake_link_paths.insert(PathBuf::from(arg));
- } else if arg.starts_with("-") {
- eprintln!("=== Unexpected cmake link argument found: {}", arg);
- } else {
- let mut path = PathBuf::from(arg);
- if link_paths.is_none() {
- if let Some(parent) = path.parent() {
- cmake_link_paths.insert(parent.to_owned());
- }
- }
- if link_libs.is_none() {
- Self::strip_lib_file_decorations(&mut path);
- if let Some(file) = path.file_name().and_then(OsStr::to_str) {
- cargo_metadata.push(Self::emit_link_lib(file, None));
- }
- }
- }
- }
- cargo_metadata.extend(
- cmake_link_paths.into_iter()
- .map(|link_path|
- Self::emit_link_search(link_path.to_str().expect("Invalid link path"), Some("native"))
- )
+ .or_else(|| cmake_bin.map(PathBuf::from)),
+ &OUT_DIR,
+ &src_dir,
+ package_name.as_ref(),
+ toolchain,
+ env::var_os("PROFILE").map_or(false, |p| p == "release"),
);
- Ok(())
- } else {
- Err(format!(
- "cmake returned an error\n stdout: {:?}\n stderr: {:?}",
- String::from_utf8_lossy(&output.stdout),
- String::from_utf8_lossy(&output.stderr)
- ).into())
- })?
- };
+ let (
+ version,
+ opencv_include_paths,
+ opencv_link_paths,
+ opencv_link_libs,
+ ) = cmake.probe_ninja(ninja_bin)
+ .or_else(|e| {
+ eprintln!("=== Probing with cmake ninja generator failed, will try makefile generator, error: {}", e);
+ cmake.probe_makefile()
+ })?;
+
+ let mut cargo_metadata = Vec::with_capacity(opencv_link_paths.len() + opencv_link_libs.len());
+ cargo_metadata.extend(Self::process_link_paths(link_paths, opencv_link_paths.into_iter().collect(), None));
+ cargo_metadata.extend(Self::process_link_libs(link_libs, opencv_link_libs, None));
Ok(Self {
- include_paths,
+ include_paths: Self::process_env_var_list(include_paths, opencv_include_paths),
+ version: version.unwrap_or_else(|| "0.0.0".to_string()),
cargo_metadata,
- version,
})
- } else {
- Err(format!("cmake discovery problem: OpenCV version not found in include paths: {:?}", include_paths).into())
- }
}
pub fn probe_vcpkg(include_paths: Option<EnvList>, link_paths: Option<EnvList>, link_libs: Option<EnvList>) -> Result<Self> {
@@ -451,17 +388,75 @@ impl Library {
let version = Self::version_from_include_paths(&opencv.include_paths);
+ let include_paths = Self::process_env_var_list(include_paths, opencv.include_paths);
+
+ let mut cargo_metadata = opencv.cargo_metadata;
+
+ if let Some(link_paths) = link_paths {
+ if !link_paths.is_extend() {
+ cargo_metadata = cargo_metadata.into_iter()
+ .filter(|p| !p.starts_with("cargo:rustc-link-search="))
+ .collect();
+ }
+ cargo_metadata.extend(link_paths.iter().map(|s| Self::emit_link_search(&Path::new(s), None)))
+ }
+
+ if let Some(link_libs) = link_libs {
+ if !link_libs.is_extend() {
+ cargo_metadata = cargo_metadata.into_iter()
+ .filter(|p| !p.starts_with("cargo:rustc-link-lib="))
+ .collect();
+ }
+ cargo_metadata.extend(link_libs.iter().map(|s| Self::emit_link_lib(s, None)))
+ }
+
Ok(Self {
- include_paths: opencv.include_paths,
- version: version.unwrap_or_else(|| "0.0.0".to_owned()),
- cargo_metadata: opencv.cargo_metadata,
+ include_paths,
+ version: version.unwrap_or_else(|| "0.0.0".to_string()),
+ cargo_metadata,
})
}
+ pub fn probe_vcpkg_cmake(include_paths: Option<EnvList>, link_paths: Option<EnvList>, link_libs: Option<EnvList>) -> Result<Self> {
+ eprintln!("=== Probing OpenCV library using vcpkg");
+ let mut config = vcpkg::Config::new();
+ config.cargo_metadata(false);
+ // don't care about the error here, only need to have dlls copied to outdir
+ let _ = config.find_package(&PackageName::vcpkg());
+
+ let vcpkg_root = canonicalize(vcpkg::find_vcpkg_root(&mut config)?)?;
+ eprintln!("=== Discovered vcpkg root: {}", vcpkg_root.display());
+ let vcpkg_cmake = vcpkg_root.to_str()
+ .and_then(|vcpkg_root| {
+ glob(&format!("{}/downloads/tools/cmake*/*/bin/cmake", vcpkg_root)).ok()
+ .and_then(|cmake_iter| glob(&format!("{}/downloads/tools/cmake*/*/bin/cmake.exe", vcpkg_root)).ok()
+ .map(|cmake_exe_iter| cmake_iter.chain(cmake_exe_iter))
+ )
+ })
+ .and_then(|paths| paths.filter_map(|r| r.ok())
+ .filter_map(|p| canonicalize(p).ok())
+ .find(|p| p.is_file())
+ );
+ let vcpkg_ninja = vcpkg_root.to_str()
+ .and_then(|vcpkg_root| {
+ glob(&format!("{}/downloads/tools/ninja*/ninja", vcpkg_root)).ok()
+ .and_then(|ninja_iter| glob(&format!("{}/downloads/tools/ninja*/*/ninja.exe", vcpkg_root)).ok()
+ .map(|ninja_exe_iter| ninja_iter.chain(ninja_exe_iter))
+ )
+ })
+ .and_then(|paths| paths.filter_map(|r| r.ok())
+ .filter_map(|p| canonicalize(p).ok())
+ .find(|p| p.is_file())
+ );
+ let toolchain = vcpkg_root.join("scripts/buildsystems/vcpkg.cmake");
+ Self::probe_cmake(include_paths, link_paths, link_libs, Some(&toolchain), vcpkg_cmake.as_deref(), vcpkg_ninja.as_deref())
+ }
+
pub fn probe_system(include_paths: Option<EnvList>, link_paths: Option<EnvList>, link_libs: Option<EnvList>) -> Result<Self> {
let probe_paths = || Self::probe_from_paths(include_paths, link_paths, link_libs);
let probe_pkg_config = || Self::probe_pkg_config(include_paths, link_paths, link_libs);
- let probe_cmake = || Self::probe_cmake(include_paths, link_paths, link_libs);
+ let probe_cmake = || Self::probe_cmake(include_paths, link_paths, link_libs, None, None, None);
+ let probe_vcpkg_cmake = || Self::probe_vcpkg_cmake(include_paths, link_paths, link_libs);
let probe_vcpkg = || Self::probe_vcpkg(include_paths, link_paths, link_libs);
let explicit_pkg_config = env::var_os("PKG_CONFIG_PATH").is_some() || env::var_os("OPENCV_PKGCONFIG_NAME").is_some();
@@ -480,21 +475,27 @@ impl Library {
("environment", &probe_paths as &dyn Fn() -> Result<Self>),
("pkg_config", &probe_pkg_config),
("cmake", &probe_cmake),
+ ("vcpkg_cmake", &probe_vcpkg_cmake),
("vcpkg", &probe_vcpkg),
];
if explicit_pkg_config {
if explicit_vcpkg {
probes.swap(2, 3);
+ probes.swap(3, 4);
}
} else if explicit_cmake {
probes.swap(1, 2);
if explicit_vcpkg {
probes.swap(2, 3);
+ probes.swap(3, 4);
}
} else if explicit_vcpkg {
probes.swap(2, 3);
+ probes.swap(3, 4);
+
probes.swap(1, 2);
+ probes.swap(2, 3);
}
let probe_list = probes.iter()

ADD
build_cmake_probe.rs
+use std::{
+ collections::HashSet,
+ fs::{self, File},
+ io::{BufRead, BufReader},
+ path::{Path, PathBuf},
+ process::Command,
+};
+use std::process::Output;
+
+use shlex::Shlex;
+
+use super::Result;
+
+pub struct CmakeProbe<'r> {
+ cmake_bin: PathBuf,
+ build_dir: PathBuf,
+ src_dir: &'r Path,
+ package_name: &'r str,
+ toolchain: Option<&'r Path>,
+ is_release: bool,
+}
+
+impl<'r> CmakeProbe<'r> {
+ pub fn new(cmake_bin: Option<PathBuf>, build_dir: &Path, src_dir: &'r Path, package_name: &'r str, toolchain: Option<&'r Path>, is_release: bool) -> Self {
+ CmakeProbe {
+ cmake_bin: cmake_bin.unwrap_or_else(|| "cmake".into()),
+ build_dir: build_dir.join("cmake_probe_build"),
+ src_dir,
+ package_name,
+ toolchain,
+ is_release,
+ }
+ }
+
+ fn prepare(&self) -> Result<()> {
+ self.cleanup()?;
+ fs::create_dir(&self.build_dir)?;
+ Ok(())
+ }
+
+ fn cleanup(&self) -> Result<()> {
+ if self.build_dir.exists() {
+ fs::remove_dir_all(&self.build_dir)?;
+ }
+ Ok(())
+ }
+
+ fn make_cmd(&self) -> Command {
+ let mut out = Command::new(&self.cmake_bin);
+ out.current_dir(&self.build_dir)
+ .args(&[
+ "--log-level=NOTICE",
+ "-S"
+ ])
+ .arg(&self.src_dir)
+ .arg(format!("-DOCVRS_PACKAGE_NAME={}", &self.package_name));
+
+ if let Some(toolchain) = self.toolchain {
+ out.arg(format!("-DCMAKE_TOOLCHAIN_FILE={}", toolchain.to_str().expect("Non-UTF-8 toolchain location")));
+ }
+ if self.is_release {
+ out.arg("-DCMAKE_BUILD_TYPE=Release");
+ } else {
+ out.arg("-DCMAKE_BUILD_TYPE=Debug");
+ }
+ out
+ }
+
+ fn extract_from_output(output: &Output, version: &mut Option<String>, opencv_include_paths: &mut Vec<PathBuf>) -> Result<()> {
+ if output.status.success() {
+ let mut line = String::new();
+ let mut reader = BufReader::new(output.stderr.as_slice());
+ while let Ok(bytes_read) = reader.read_line(&mut line) {
+ if bytes_read == 0 {
+ break;
+ }
+ if line.starts_with("OCVRS") {
+ let mut name_value = line.splitn(2, ':');
+ let (name, value) = (name_value.next(), name_value.next().unwrap_or_default());
+ match name {
+ Some("OCVRS_INCLUDE_DIRS") => {
+ opencv_include_paths.extend(value.split(';')
+ .filter_map(|s| if !s.is_empty() {
+ Some(PathBuf::from(s.trim()))
+ } else {
+ None
+ })
+ );
+ }
+ Some("OCVRS_VERSION") => {
+ *version = Some(value.trim().to_string());
+ }
+ _ => {}
+ }
+ }
+ line.clear();
+ }
+ Ok(())
+ } else {
+ Err(format!(
+ "cmake returned an error\n stdout: {:?}\n stderr: {:?}",
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ ).into())
+ }
+ }
+
+ fn extract_from_cmdline(cmdline: &str, link_paths: &mut HashSet<PathBuf>, link_libs: &mut Vec<String>) {
+ for arg in Shlex::new(cmdline.trim()) {
+ let arg = arg.trim();
+ if let Some(path) = arg.strip_prefix("-L") {
+ link_paths.insert(PathBuf::from(path.trim_start()));
+ } else if let Some(lib) = arg.strip_prefix("-l") {
+ link_libs.push(lib.trim_start().to_string());
+ } else if !arg.starts_with('-') {
+ let path = Path::new(arg);
+ let filename = path.file_name().and_then(super::cleanup_lib_filename);
+ if let Some(file) = filename {
+ if let Some(parent) = path.parent() {
+ if !link_paths.contains(parent) {
+ link_paths.insert(parent.to_owned());
+ }
+ } else {
+ panic!(arg.to_string());
+ }
+ link_libs.push(file.to_str().expect("Non-UTF8 filename").to_string());
+ }
+ }
+ }
+ }
+
+ fn extract_from_makefile(&self, link_paths: &mut HashSet<PathBuf>, link_libs: &mut Vec<String>) -> Result<()> {
+ let link_cmdline = fs::read_to_string(self.build_dir.join("CMakeFiles/ocvrs_probe.dir/link.txt"))?;
+ Self::extract_from_cmdline(link_cmdline.trim(), link_paths, link_libs);
+ Ok(())
+ }
+
+ fn extract_from_ninja(&self, link_paths: &mut HashSet<PathBuf>, link_libs: &mut Vec<String>) -> Result<()> {
+ let mut link_cmdline = BufReader::new(File::open(self.build_dir.join("build.ninja"))?);
+ let mut line = String::new();
+ #[derive(Copy, Clone)]
+ enum State {
+ Searching,
+ Reading,
+ }
+ let mut state = State::Searching;
+ while let Ok(bytes_read) = link_cmdline.read_line(&mut line) {
+ if bytes_read == 0 {
+ break;
+ }
+ match state {
+ State::Searching => {
+ if line.starts_with("build ocvrs_probe") {
+ state = State::Reading;
+ }
+ }
+ State::Reading => {
+ if let Some(paths) = line.trim_start().strip_prefix("LINK_PATH = ") {
+ Self::extract_from_cmdline(paths, link_paths, link_libs);
+ } else if let Some(libs) = line.trim_start().strip_prefix("LINK_LIBRARIES = ") {
+ Self::extract_from_cmdline(libs, link_paths, link_libs);
+ }
+ }
+ }
+ line.clear();
+ }
+ Ok(())
+ }
+ pub fn probe_makefile(&self) -> Result<(Option<String>, Vec<PathBuf>, Vec<PathBuf>, Vec<String>)> {
+ self.prepare()?;
+
+ let mut cmd = self.make_cmd();
+ cmd.args(&["-G", "Unix Makefiles"]);
+
+ let mut version = None;
+ let mut opencv_include_paths = Vec::with_capacity(2);
+ let mut opencv_link_paths = HashSet::with_capacity(2);
+ let mut opencv_link_libs = Vec::with_capacity(64);
+
+ eprintln!("=== cmake makefiles probe command: {:?}", cmd);
+ cmd.output()
+ .map_err(Box::<dyn std::error::Error>::from)
+ .and_then(|output| Self::extract_from_output(&output, &mut version, &mut opencv_include_paths))?;
+
+ self.extract_from_makefile(&mut opencv_link_paths, &mut opencv_link_libs)?;
+
+ self.cleanup()?;
+ Ok((version, opencv_include_paths, opencv_link_paths.into_iter().collect(), opencv_link_libs))
+ }
+
+ pub fn probe_ninja(&self, ninja_bin: Option<&Path>) -> Result<(Option<String>, Vec<PathBuf>, Vec<PathBuf>, Vec<String>)> {
+ self.prepare()?;
+
+ let mut cmd = self.make_cmd();
+ cmd.args(&["-G", "Ninja"]);
+ if let Some(ninja_bin) = ninja_bin {
+ cmd.arg(format!("-DCMAKE_MAKE_PROGRAM={}", ninja_bin.to_str().expect("Non-UTF-8 ninja location")));
+ }
+
+ let mut version = None;
+ let mut opencv_include_paths = Vec::with_capacity(2);
+ let mut opencv_link_paths = HashSet::with_capacity(2);
+ let mut opencv_link_libs = Vec::with_capacity(64);
+
+ eprintln!("=== cmake ninja probe command: {:?}", cmd);
+ cmd.output()
+ .map_err(Box::<dyn std::error::Error>::from)
+ .and_then(|output| Self::extract_from_output(&output, &mut version, &mut opencv_include_paths))?;
+
+ self.extract_from_ninja(&mut opencv_link_paths, &mut opencv_link_libs)?;
+
+ self.cleanup()?;
+ Ok((version, opencv_include_paths, opencv_link_paths.into_iter().collect(), opencv_link_libs))
+ }
+}

ADD
ci/install-bionic-vcpkg.sh
+#!/bin/bash
+
+set -vex
+
+export VCPKG_ROOT="$HOME/build/vcpkg"
+if [[ -e "$VCPKG_ROOT" && ! -e "$VCPKG_ROOT/.git" ]]; then
+ rm -rf "$VCPKG_ROOT"
+fi
+if [ ! -e "$VCPKG_ROOT" ]; then
+ git clone --depth=1 https://github.com/Microsoft/vcpkg.git "$VCPKG_ROOT"
+fi
+pushd "$VCPKG_ROOT"
+git pull
+./bootstrap-vcpkg.sh -disableMetrics
+#./vcpkg integrate install
+echo "set(VCPKG_BUILD_TYPE release)" >> triplets/x64-linux.cmake
+export VCPKG_DEFAULT_TRIPLET=x64-linux
+#./vcpkg install llvm # takes very long time
+sudo apt-get install -y clang
+# workaround to make clang_sys crate detect installed libclang
+sudo ln -s libclang.so.1 /usr/lib/llvm-6.0/lib/libclang.so
+./vcpkg install "opencv${VCPKG_OPENCV_VERSION}[contrib,nonfree]"
+./vcpkg upgrade --no-dry-run
+popd

MODIFY
ci/install-windows-vcpkg.sh
ci/install-windows-vcpkg.sh
set -vex
export VCPKG_ROOT="$HOME/build/vcpkg"
+if [[ -e "$VCPKG_ROOT" && ! -e "$VCPKG_ROOT/.git" ]]; then
+ rm -rf "$VCPKG_ROOT"
+fi
if [ ! -e "$VCPKG_ROOT" ]; then
git clone --depth=1 https://github.com/Microsoft/vcpkg.git "$VCPKG_ROOT"
fi
@@ -11,6 +14,7 @@ git pull
cmd "/C bootstrap-vcpkg.bat -disableMetrics"
#./vcpkg integrate install
echo "set(VCPKG_BUILD_TYPE release)" >> triplets/x64-windows.cmake
+echo "set(VCPKG_BUILD_TYPE release)" >> triplets/x64-windows-static.cmake
echo "set(VCPKG_BUILD_TYPE release)" >> triplets/x86-windows.cmake
export VCPKG_DEFAULT_TRIPLET=x64-windows
#./vcpkg install llvm # takes very long time
@@ -18,4 +22,3 @@ choco install llvm
./vcpkg install "opencv${VCPKG_OPENCV_VERSION}[contrib,nonfree]"
./vcpkg upgrade --no-dry-run
popd
-

MODIFY
ci/install.sh
ci/install.sh
@@ -5,7 +5,11 @@ set -vex
ci_dir="$(dirname "$0")"
if [[ "$OS_FAMILY" == "linux" ]]; then
+ if [[ "$VCPKG_OPENCV_VERSION" != "" ]]; then # vcpkg build
+ "$ci_dir/install-bionic-vcpkg.sh"
+ else
"$ci_dir/install-bionic.sh"
+ fi
elif [[ "$OS_FAMILY" == "osx" ]]; then
if [[ "$BREW_OPENCV_VERSION" != "" ]]; then # brew build
"$ci_dir/install-macos-brew.sh"

MODIFY
ci/script.sh
ci/script.sh
@@ -42,6 +42,11 @@ elif [[ "$OS_FAMILY" == "linux" ]]; then
# 3.2.0 version from the repository doesn't have dnn module, that's why contrib feature is not enabled
CARGO_FEATURES="$CARGO_FEATURES,contrib"
fi
+ if [[ "$VCPKG_OPENCV_VERSION" != "" ]]; then # vcpkg build
+ export VCPKG_ROOT="$HOME/build/vcpkg"
+ echo "=== Installed vcpkg packages:"
+ "$VCPKG_ROOT/vcpkg" list
+ fi
fi
echo "=== Current directory: $(pwd)"

ADD
cmake/.gitignore
+*.sh

ADD
cmake/CMakeLists.txt
+project(OpenCVProbe)
+
+cmake_minimum_required(VERSION 3.0)
+
+find_package(${OCVRS_PACKAGE_NAME} REQUIRED)
+
+message(NOTICE "OCVRS_INCLUDE_DIRS:${OpenCV_INCLUDE_DIRS}")
+message(NOTICE "OCVRS_VERSION:${OpenCV_VERSION}")
+
+include_directories(${OpenCV_INCLUDE_DIRS})
+add_executable(ocvrs_probe ocvrs_probe.cpp)
+target_link_libraries(ocvrs_probe ${OpenCV_LIBS})

ADD
cmake/ocvrs_probe.cpp
cmake/ocvrs_probe.cpp
Rework cmake probing (no longer uses deprecated --find-package) and add combined vcpkg+cmake probe