use crate::error::*;
use std::{
collections::HashSet,
fs,
hash::Hash,
io::{self, BufRead},
path::*,
process::Command,
};
#[derive(Debug, Clone, Default)]
pub struct LinkFlags {
pub search_paths: Vec<PathBuf>,
pub libs: Vec<String>,
}
fn as_sorted_vec<T: Hash + Ord>(set: HashSet<T>) -> Vec<T> {
let mut v: Vec<_> = set.into_iter().collect();
v.sort();
v
}
impl LinkFlags {
pub fn parse(line: &str) -> Result<Self, Error> {
let mut search_paths = HashSet::new();
let mut libs = HashSet::new();
for entry in line.split(' ') {
if entry.starts_with("-L") {
let path = PathBuf::from(entry.trim_start_matches("-L"));
if !path.exists() {
continue;
}
search_paths.insert(
path.canonicalize()
.map_err(|_| Error::CannotCanonicalizePath { path })?,
);
}
if entry.starts_with("-l") {
libs.insert(entry.trim_start_matches("-l").into());
}
}
Ok(LinkFlags {
search_paths: as_sorted_vec(search_paths),
libs: as_sorted_vec(libs),
})
}
}
#[derive(Debug, Clone, Default)]
pub struct MakeConf {
pub os_name: String,
pub no_fortran: bool,
pub c_extra_libs: LinkFlags,
pub f_extra_libs: LinkFlags,
}
impl MakeConf {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let mut detail = MakeConf::default();
let f = fs::File::open(&path).map_err(|_| Error::MakeConfNotExist {
out_dir: path.as_ref().to_owned(),
})?;
let buf = io::BufReader::new(f);
for line in buf.lines() {
let line = line.expect("Makefile.conf should not include non-UTF8 string");
if line.is_empty() {
continue;
}
let entry: Vec<_> = line.split('=').collect();
if entry.len() != 2 {
continue;
}
match entry[0] {
"OSNAME" => detail.os_name = entry[1].into(),
"NOFORTRAN" => detail.no_fortran = true,
"CEXTRALIB" => detail.c_extra_libs = LinkFlags::parse(entry[1])?,
"FEXTRALIB" => detail.f_extra_libs = LinkFlags::parse(entry[1])?,
_ => continue,
}
}
Ok(detail)
}
}
#[derive(Debug, Clone)]
pub struct LibInspect {
pub libs: Vec<String>,
pub symbols: Vec<String>,
}
impl LibInspect {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path = path.as_ref();
if !path.exists() {
return Err(Error::LibraryNotExist {
path: path.to_owned(),
});
}
let nm_out = Command::new("nm").arg("-g").arg(path).output()?;
let mut symbols: Vec<_> = nm_out
.stdout
.lines()
.flat_map(|line| {
let line = line.expect("nm output should not include non-UTF8 output");
let entry: Vec<_> = line.trim().split(' ').collect();
if entry.len() == 3 && entry[1] == "T" {
Some(entry[2].into())
} else {
None
}
})
.collect();
symbols.sort(); let mut libs: Vec<_> = Command::new("objdump")
.arg("-p")
.arg(path)
.output()?
.stdout
.lines()
.flat_map(|line| {
let line = line.expect("objdump output should not include non-UTF8 output");
if line.trim().starts_with("NEEDED") {
Some(line.trim().trim_start_matches("NEEDED").trim().to_string())
} else {
None
}
})
.collect();
libs.sort();
Ok(LibInspect { libs, symbols })
}
pub fn has_cblas(&self) -> bool {
for sym in &self.symbols {
if sym.starts_with("cblas_") {
return true;
}
}
false
}
pub fn has_lapack(&self) -> bool {
for sym in &self.symbols {
if sym == "dsyev_" {
return true;
}
}
false
}
pub fn has_lapacke(&self) -> bool {
for sym in &self.symbols {
if sym.starts_with("LAPACKE_") {
return true;
}
}
false
}
pub fn has_lib(&self, name: &str) -> bool {
for lib in &self.libs {
if let Some(stem) = lib.split('.').next() {
if stem == format!("lib{}", name) {
return true;
}
};
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detail_from_makefile_conf() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Makefile.conf");
assert!(path.exists());
let detail = MakeConf::new(path).unwrap();
assert!(!detail.no_fortran);
}
#[test]
fn detail_from_nofortran_conf() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("nofortran.conf");
assert!(path.exists());
let detail = MakeConf::new(path).unwrap();
assert!(detail.no_fortran);
}
}