grb-sys 0.1.7

Low level bindings to Gurobi
/*
 * MIT License
 *
 * Copyright (c) 2019-2020 Frank Fischer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

use regex::Regex;
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;

fn main() -> Result<(), Box<dyn Error>> {
    let out_dir = env::var("OUT_DIR")?;
    let out_dir = Path::new(&out_dir);
    let gurobi_include = match env::var("GUROBI_HOME") {
        Ok(grb) => {
            println!("cargo:rustc-link-search={}/lib", grb);
            Path::new(&grb).join("include")
        }
        Err(_) => {
            if let Some(dir) = ["/usr/include", "/usr/local/include"].iter().find(|dir| {
                let f = Path::new(dir).join("gurobi_c.h");
                f.exists()
            }) {
                Path::new(dir).to_path_buf()
            } else {
                panic!("Can't find include file `gurobi_c.h` (maybe set GUROBI_HOME environment variable)");
            }
        }
    };

    let mut text = String::new();
    File::open(gurobi_include.join("gurobi_c.h"))
        .expect("Can't open header file 'gurobi_c.h'")
        .read_to_string(&mut text)?;

    let major: usize = Regex::new(r"#define\s+GRB_VERSION_MAJOR\s+(\d+)")?
        .captures(&text)
        .expect("Cannot find GRB_VERSION_MAJOR in gurobi_c.h")[1]
        .parse()?;
    let minor: usize = Regex::new(r"#define\s+GRB_VERSION_MINOR\s+(\d+)")?
        .captures(&text)
        .expect("Cannot find GRB_VERSION_MINOR in gurobi_c.h")[1]
        .parse()?;

    println!("cargo:rustc-link-lib=gurobi{}{}", major, minor);

    let bindings = bindgen::Builder::default()
        .header(gurobi_include.join("gurobi_c.h").to_str().unwrap())
        .allowlist_function("GRB.*")
        .allowlist_type("GRB.*")
        .allowlist_var("GRB.*")
        .blocklist_type("FILE")
        .blocklist_type("_.*")
        .opaque_type("GRBmodel")
        .opaque_type("GRBenv")
        .opaque_type("GRBsvec")
        .opaque_type("GRBbatch")
        .generate()
        .map_err(|_| "Can't generate bindings".to_string())?;

    // Replace string constants with `*const c_char`
    let result = bindings.to_string();
    let result = Regex::new(r#"pub const (.*) &(?:'static )?\[u8;.*?\](.*");"#)?.replace_all(
        &result,
        "pub const $1 *const ::std::os::raw::c_char $2 as *const u8 as *const ::std::os::raw::c_char;",
    );

    // Replace usual integer constants with `c_int`
    let result = Regex::new(r#": [ui]32 = (.*);"#)?.replace_all(&result, ": ::std::os::raw::c_int = $1;");

    // Replace character constants with `c_char`
    let result = Regex::new(r#": [ui]8 = (.*)([ui]8)?;"#)?
        .replace_all(&result, ": ::std::os::raw::c_char = $1 as ::std::os::raw::c_char;");

    std::fs::write(out_dir.join("gurobi-extern.rs"), result.into_owned())?;

    Ok(())
}