mosek 11.0.16

Rust API for MOSEK optimization tools
Documentation
use std::env;
use std::path::PathBuf;
use std::fs::File;
use std::str::FromStr;
use std::io::prelude::*;
//use curl::easy::Easy;
use std::process::Command;


fn get_platform_name(majorver : i32,minorver : i32) -> (String,String) {
    if cfg!(target_os = "windows") {
        if      cfg!(target_arch = "x86_64") {
            ("win64x86".to_string(),  format!("mosek64_{}_{}",majorver,minorver))
        }
        //else if cfg!(target_arch = "x86") {
        //    ("win32x86".to_string(),  format!("mosek{}_{}",majorver,minorver))
        //}
        else {
            panic!("Unsupported architecture")
        }
    }
    else if cfg!(target_os = "linux") {
        if      cfg!(target_arch = "x86_64") {
            ("linux64x86".to_string(),"mosek64".to_string())
        }
        else if cfg!(target_arch = "aarch64") {
            ("linuxaarch64".to_string(),"mosek64".to_string())
        }
        else {
            panic!("Unsupported architecture")
        }
    }
    else if cfg!(target_os = "macos") {
        if      cfg!(target_arch = "aarch64") {
            ("osxaarch64".to_string(),  "mosek64".to_string())
        }
        else if cfg!(target_arch = "x86_64") {
            ("osx64x86".to_string(),  "mosek64".to_string())
        }
        else {
            panic!("Unsupported architecture")
        }
    }
    else {
        panic!("Unsupported operating system")
    }
}

// Given platform name and version, look for a MOSEK installation in the default locations:
// - $MOSEK_INST_BASE (all platforms)
// - $HOME/mosek (on linux/osx)
// - %HOMEDRIVE%%HOMEPATH%\mosek (on windows)
// 
// Returns `Some(path: String)` if found, otherwise `None`
fn find_mosek_installation(pfname : &String, majorver : i32, minorver : i32) -> Option<String> {
    let bindirvar = format!("MOSEK_BINDIR_{}{}",majorver,minorver);

    let mut bindir_b = PathBuf::new();
    match env::var_os(bindirvar.as_str()) {
        Some(p) => bindir_b.push(p),
        None    => {
            let inst_base =
                if let Some(p) = env::var_os("MOSEK_INST_BASE") { p }
                else if let Some(p) = env::var_os("HOME") { p }
                else if let (Some(homed),Some(homep)) = (env::var_os("HOMEDRIVE"),env::var_os("HOMEPATH")) {
                    let mut r = homed;
                    r.push(homep);
                    r
                }
                else { return None; };

            bindir_b.push(inst_base);
            bindir_b.push("mosek");
            bindir_b.push(format!("{}.{}",majorver,minorver));
            bindir_b.push("tools");
            bindir_b.push("platform");
            bindir_b.push(pfname);
            bindir_b.push("bin");
            },
        }

    if ! bindir_b.as_path().is_dir() {
        return None
    }

    let mut mosekbin = bindir_b.clone(); mosekbin.push("mosek");
    let res = Command::new(mosekbin).arg("-v").output().expect("Failed to check mosek version");
    let text : String = String::from_utf8_lossy(res.stdout.as_ref()).to_string();
    if let Some(text) = text.strip_prefix("MOSEK version ") {
        if let Some(p) = text.find('\n') {
            let mut ver = text[0..p].split('.');
            let vmajor = ver.next();
            let vminor = ver.next();
        
            if let (Some(vmajor),Some(vminor)) = (vmajor,vminor) {
                let vmajor : Option<i32> = FromStr::from_str(vmajor).ok();
                let vminor : Option<i32> = FromStr::from_str(vminor).ok();

                if let (Some(vmajor),Some(vminor)) = (vmajor,vminor) {
                    if vmajor == majorver && vminor == minorver { 
                        return Some(bindir_b.as_path().to_str().unwrap().to_string())
                    }
                }
            }
        }
    }
    None
}

// Given platform name and version, attempt to download and install the MOSEK distro.
// 
// This requires the external commands
// - `curl` (all platforms)
// - `zip`/`unzip` (on windows)
// - `tar` and `bzip` (in linux/osx)
// 
// Returns `Some(path: String)` on success, otherwise `None`.
fn getmosek(pfname : &String,majorver : i32, minorver : i32) -> String {
    let mut outdir = PathBuf::new();
    outdir.push(env::var_os("OUT_DIR").unwrap());
    let targetdir = outdir.as_path();
    let (archname,iszip) = match pfname.as_str() {
        "linux64x86"   => ("mosektoolslinux64x86.tar.bz2",false),
        "linuxaarch64" => ("mosektoolslinuxaarch64.tar.bz2",false),
        "osx64x86"     => ("mosektoolsosx64x86.tar.bz2",false),
        "osxaarch64"   => ("mosektoolsosxaarch64.tar.bz2",false),
        "win32x86"     => ("mosektoolswin32x86.zip",true),
        "win64x86"     => ("mosektoolswin64x86.zip",true),
        _ => panic!("Invalid platform")
    };
    let mut archfile = PathBuf::new();
    archfile.push(targetdir);
    archfile.push(archname);

    if ! archfile.exists() {
        let res =  Command::new("curl")
            .arg("--silent")
            .arg(format!("https://download.mosek.com/stable/{}.{}/version",majorver,minorver).as_str())
            .output()
            .expect("Failed to get latest version");
        let verstr = match String::from_utf8_lossy(res.stdout.as_ref()) {
            std::borrow::Cow::Owned(s) => s,
            std::borrow::Cow::Borrowed(s) => s.to_string()
        };
        
        let ver = verstr.trim();

       
        Command::new("curl")
            .arg("-o").arg(archfile.as_path())
            .arg("--silent")
            .arg(format!("https://download.mosek.com/stable/{}/{}",ver,archname).as_str())
            .status()
            .expect("Failed to get distro file");

        // File written, now we have to unpack
        if iszip {
            panic!("Not implemented: Unzipping distro on Windows");
        }
        else {
            Command::new("tar")
                .arg("xjf").arg(archfile)
                .arg("-C").arg(outdir.as_path())
                .status()
                .expect("Failed to unpack distro");
        }
    }

    let mut res = PathBuf::new();
    res.push(outdir.as_path());
    res.push("mosek");
    res.push(format!("{}.{}",majorver,minorver).as_str());
    res.push("tools");
    res.push("platform");
    res.push(pfname.as_str());
    res.push("bin");
    res.as_path().to_str().unwrap().to_string()
}


fn extract_version(text : &String) -> Option<(i32,i32)> {
    let mosekverstr = text.trim();
    match mosekverstr.find('.') {
        None => None,
        Some(p) => {
            let vmajor : i32 = FromStr::from_str(&mosekverstr[0..p]).unwrap();
            let vminor : i32 = FromStr::from_str(&mosekverstr[p+1..mosekverstr.len()]).unwrap();

            Some((vmajor,vminor))
        }
    }
}
// Read a version stored in a file. The version must have the format `[0-9]+ '.' [0-9]+`
fn readversion(filename : &str) -> (i32,i32) {
    let mut mosekverstr = String::new();
    match File::open(filename) {
        Err(_) => panic!("Failed to open version file '{}'",filename),
        Ok(mut f) => { let _ = f.read_to_string(& mut mosekverstr).unwrap(); }
    }
   
    match extract_version(&mosekverstr) {
        None => panic!("Invalid version file '{}'",filename),
        Some(v) => { v }
    }
}

fn main() {
    let (mskvermajor,mskverminor) = readversion("MOSEKVERSION");

    let mosekrs_force_download = 
        if let Some(s) = env::var_os("MOSEKRS_FORCE_DOWNLOAD") {
            if let Some(s) = s.to_str() {
                s.eq("YES")  || s.eq("ON") || s.eq("TRUE")
            }
            else {
                false
            }
        }
        else {
            false
        };

    let (pfname, libname) = get_platform_name(mskvermajor,mskverminor);
    let libdir = 
        if ! mosekrs_force_download {
            if let Some(p) = find_mosek_installation(&pfname,mskvermajor,mskverminor) { p } 
            else { getmosek(&pfname, mskvermajor, mskverminor) }
        }
        else { getmosek(&pfname, mskvermajor, mskverminor) };

    println!("cargo:rustc-link-search={}",libdir);
    println!("cargo:rustc-flags=-L {} -l {}",libdir,libname);

    if cfg!(target_os = "linux") {
        println!("cargo:rustc-link-arg=-Wl,-rpath={}",libdir);
    }
    else if cfg!(target_os = "macos") {
        println!("cargo:rustc-link-arg=-Wl,-rpath,{}",libdir);
    }
}