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))
}
}