helics-sys 0.1.2

HELICS cosimulation library
use cmake;

use cmake::Config;
use curl::easy::Easy;
use flate2::read::GzDecoder;
use lazy_static::lazy_static;
use log::*;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::{env, fs};
use tar::Archive;

const VERSION: &str = "v3.0.0-alpha.2";

lazy_static! {
    static ref SOURCE_URL: String = format!(
        "https://github.com/GMLC-TDC/HELICS/releases/download/{ver}/Helics-{ver}-source.tar.gz",
        ver = VERSION,
    );
}

#[derive(Clone, Debug, PartialEq)]
enum Error {
    DownloadFailure { response_code: u32, url: String },
    UrlFailure,
    IOError,
}

impl From<std::io::Error> for Error {
    fn from(_: std::io::Error) -> Error {
        Error::IOError
    }
}

impl From<curl::Error> for Error {
    fn from(_: curl::Error) -> Error {
        Error::UrlFailure
    }
}

fn download_and_install() -> Result<(), Error> {
    let crate_dir = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap());
    let target_dir = crate_dir.join("target");
    debug!("target_dir = {:?}", &target_dir);
    if !target_dir.exists() {
        fs::create_dir(&target_dir).unwrap();
    }

    let download_dir = target_dir.join(format!("helics-{}-source", VERSION));
    debug!("download_dir = {:?}", &download_dir);
    if !download_dir.exists() {
        fs::create_dir(&download_dir).unwrap();
    }

    // Build destination path
    let tarball_path = download_dir.join("helics.tar.gz");
    debug!("tarball_path = {:?}", &tarball_path);

    let binary_url = &SOURCE_URL;

    download_tarball(&tarball_path, &binary_url)?;

    extract_tarball(tarball_path, &download_dir);

    // Configure and compile
    // We shall compile helics in the same mode we build the sys library. This will allow users
    // to debug the internals of helics more easily.
    let debug: bool = env::var("DEBUG").unwrap().parse().unwrap();
    debug!("debug build? {}", debug);

    let mut config = Config::new(download_dir);
    if let Ok(s) = env::var("HELICS_CMAKE_GENERATOR") {
        config.generator(s);
    }
    let build = config.build();

    let out_dir = env::var("OUT_DIR").unwrap();
    println!("cargo:rustc-link-search=native={}/lib", build.display());
    if cfg!(debug_assertions) {
        println!("cargo:rustc-link-lib=dylib=helicsd");
    } else {
        println!("cargo:rustc-link-lib=dylib=helics");
    };
    println!("cargo:include={}/include", build.display());
    println!("cargo:include={}/include/helics", build.display());
    println!(
        "cargo:include={}/include/helics/shared_api_library",
        build.display()
    );
    println!("cargo:outdir={}", out_dir);

    dbg!(build.display());

    // https://rust-lang-nursery.github.io/rust-bindgen
    // https://docs.rs/bindgen
    let bindings = bindgen::Builder::default()
        .clang_arg(format!("-I{}/include/", build.display()))
        .clang_arg(format!("-I{}/include/helics/", build.display()))
        .clang_arg(format!(
            "-I{}/include/helics/shared_api_library/",
            build.display()
        ))
        .rustified_enum("*")
        .header(format!(
            "{}/include/helics/shared_api_library/helics.h",
            build.display()
        ))
        .generate()
        .expect("Unable to generate bindings");
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings");

    Ok(())
}

/// Download a tarball if it doesn't already exist.
fn download_tarball(tarball_path: &Path, binary_url: &str) -> Result<(), Error> {
    if !tarball_path.exists() {
        info!("Tarball doesn't exist, downloading...");
        let f = File::create(tarball_path).unwrap();
        let mut writer = BufWriter::new(f);
        let mut easy = Easy::new();
        easy.follow_location(true)?;
        easy.url(binary_url).unwrap();
        easy.write_function(move |data| Ok(writer.write(data).unwrap()))
            .unwrap();
        easy.perform().unwrap();

        let response_code = easy.response_code().unwrap();
        if response_code != 200 {
            return Err(Error::DownloadFailure {
                response_code,
                url: binary_url.to_string(),
            });
        } else {
            info!("Download successful!");
        }
    }

    Ok(())
}

fn extract_tarball<P: AsRef<Path> + std::fmt::Debug, P2: AsRef<Path> + std::fmt::Debug>(
    archive_path: P,
    extract_to: P2,
) {
    info!(
        "Extracting tarball {:?} to {:?}",
        &archive_path, &extract_to
    );

    let file = File::open(archive_path).unwrap();
    let mut a = Archive::new(GzDecoder::new(file));
    a.unpack(extract_to).unwrap();
}

fn main() {
    println!("cargo:rerun-if-changed=build.rs");
    download_and_install().unwrap();
}