extern crate autotools;
use autotools::Config;
use std::env;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::process::Command;
use MD5Status::{Mismatch, Unknown};
const PACKAGE_URL: &str = "https://github.com/ivmai/cudd/archive/refs/tags/cudd-3.0.0.tar.gz";
const PACKAGE_MD5: &str = "edca9c69528256ca8ae37be9cedef73f";
#[derive(Debug)]
enum FetchError {
CommandError(std::process::ExitStatus),
IOError(std::io::Error),
PathExists,
}
enum MD5Status {
Match,
Mismatch,
Unknown,
}
impl From<std::io::Error> for FetchError {
fn from(err: std::io::Error) -> FetchError {
FetchError::IOError(err)
}
}
fn run_command(cmd: &mut Command) -> Result<(String, String), FetchError> {
let output = cmd.output()?;
return if output.status.success() {
Ok((
String::from_utf8(output.stdout).unwrap(),
String::from_utf8(output.stderr).unwrap(),
))
} else {
eprintln!("Command {:?} exited with status {}", cmd, output.status);
Err(FetchError::CommandError(output.status))
};
}
fn fetch_package(out_dir: &str, url: &str, md5: &str) -> Result<(PathBuf, MD5Status), FetchError> {
let out_path = Path::new(&out_dir);
let target_path = out_path.join(Path::new(url).file_name().unwrap());
let target_path_str = target_path.clone().into_os_string().into_string().unwrap();
match target_path.metadata() {
Err(error) if error.kind() == ErrorKind::NotFound => {
println!("Downloading {} to {}", url, target_path_str);
let mut command = Command::new("curl");
command.args(&["-L", url, "-o", target_path_str.as_str()]);
run_command(&mut command)?;
}
Ok(data) if data.is_file() => {
println!("{} exists. Skipping download.", target_path_str);
}
Ok(_) => return Err(FetchError::PathExists),
Err(error) => return Err(FetchError::from(error)),
}
let mut command_1 = Command::new("md5sum");
command_1.arg(target_path.clone());
let mut command_2 = Command::new("md5");
command_2.arg(target_path.clone());
let md5_result = run_command(&mut command_1).or_else(|_| run_command(&mut command_2));
let md5_status = match md5_result {
Err(_) => MD5Status::Unknown,
Ok((output, _)) if output.contains(md5) => MD5Status::Match,
_ => MD5Status::Mismatch,
};
Ok((target_path, md5_status))
}
fn main() -> Result<(), String> {
let build_cudd = env::var_os("CARGO_FEATURE_BUILD_CUDD").is_some();
if !build_cudd {
return Ok(());
}
let out_dir = env::var("OUT_DIR")
.map_err(|_| "Environmental variable `OUT_DIR` not defined.".to_string())?;
let (tar_path, md5_status) = fetch_package(&out_dir, PACKAGE_URL, PACKAGE_MD5)
.map_err(|e| format!("Error downloading CUDD package: {:?}.", e))?;
let tar_path_str = tar_path.to_str().unwrap().to_string();
match md5_status {
Unknown => eprintln!("WARNING: MD5 not computed. Package validation skipped."),
Mismatch => return Err("CUDD package MD5 hash mismatch.".to_string()),
_ => (),
}
let cudd_path = tar_path.with_extension("").with_extension("");
let cudd_path_str = cudd_path.clone().into_os_string().into_string().unwrap();
if !cudd_path.exists() {
std::fs::create_dir_all(cudd_path.clone())
.map_err(|e| format!("Cannot create CUDD directory: {:?}", e))?;
}
let mut tar_command = Command::new("tar");
tar_command.args(&[
"xf",
&tar_path_str,
"--strip-components=1",
"-C",
&cudd_path_str,
]);
run_command(&mut tar_command).map_err(|e| format!("Error decompressing CUDD: {:?}", e))?;
let build_output = Config::new(cudd_path).enable("dddmp", None).build();
println!(
"cargo:rustc-link-search=native={}",
build_output.join("lib").display()
);
println!("cargo:rustc-link-lib=static=cudd");
Ok(())
}