use std::env;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct CustomCargoCallbacks {
exclude_dir: Option<PathBuf>,
}
impl bindgen::callbacks::ParseCallbacks for CustomCargoCallbacks {
fn header_file(&self, filename: &str) {
if matches!(&self.exclude_dir, Some(exclude_dir) if Path::new(filename).starts_with(exclude_dir))
{
return;
}
println!("cargo:rerun-if-changed={filename}");
}
fn include_file(&self, filename: &str) {
if matches!(&self.exclude_dir, Some(exclude_dir) if Path::new(filename).starts_with(exclude_dir))
{
return;
}
println!("cargo:rerun-if-changed={filename}");
}
fn read_env_var(&self, key: &str) {
println!("cargo:rerun-if-env-changed={key}");
}
}
fn generate_bindings<'a>(
exclude_dir: Option<PathBuf>,
include_paths: impl IntoIterator<Item = &'a Path>,
) {
let target = env::var("TARGET").unwrap();
let emscripten = target.contains("emscripten");
let builder = include_paths
.into_iter()
.fold(bindgen::Builder::default(), |builder, path| {
builder.clang_arg(format!("-I{}", path.to_string_lossy()))
});
let builder = if emscripten {
println!("cargo:rerun-if-env-changed=HOST");
let host = env::var("HOST")
.expect("TARGET is emscripten but HOST is not set; needed for bindgen host parsing");
builder
.clang_arg(format!("--target={host}"))
.layout_tests(false)
} else {
builder
};
let c_bindings = builder
.header("wrapper.h")
.parse_callbacks(Box::new(CustomCargoCallbacks { exclude_dir }))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
c_bindings
.write_to_file(out_path.join("c_bindings.rs"))
.expect("Couldn't write bindings!");
}
#[cfg(feature = "build")]
fn build() -> bool {
use cmake::Config;
let target = env::var("TARGET").unwrap();
let emscripten = target.contains("emscripten");
let mut dst = Config::new("HiGHS");
if cfg!(feature = "ninja") {
dst.generator("Ninja");
}
dst.define("BUILD_CXX_EXE", "OFF");
dst.define("BUILD_EXAMPLES", "OFF");
if emscripten {
dst.no_default_flags(true);
dst.cxxflag("-fexceptions");
}
if cfg!(feature = "highs_release") {
dst.profile("Release");
}
let dst = dst
.define("FAST_BUILD", "ON")
.define("BUILD_SHARED_LIBS", "OFF")
.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL")
.define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", "FALSE")
.define("ZLIB", if cfg!(feature = "libz") { "ON" } else { "OFF" })
.build();
let include_path = dst.join("include").join("highs");
generate_bindings(Some(dst.clone()), [include_path.as_path()]);
println!("cargo:rustc-link-search=native={}/lib", dst.display());
println!("cargo:rustc-link-search=native={}/lib64", dst.display());
println!("cargo:rustc-link-lib=static=highs");
if cfg!(feature = "libz") {
println!("cargo:rustc-link-lib=z");
}
let apple = target.contains("apple");
let linux = target.contains("linux");
let mingw = target.contains("pc-windows-gnu");
if apple || emscripten {
println!("cargo:rustc-link-lib=c++");
} else if linux || mingw {
println!("cargo:rustc-link-lib=stdc++");
}
println!("cargo:rerun-if-changed=HiGHS/highs/interfaces/highs_c_api.h");
true
}
#[cfg(not(feature = "build"))]
fn build() -> bool {
false
}
#[cfg(feature = "discover")]
fn discover() -> bool {
let lib = match pkg_config::Config::new()
.atleast_version("1.5.0")
.probe("highs")
{
Ok(lib) => lib,
Err(_e) => return false,
};
generate_bindings(None, lib.include_paths.iter().map(|p| p.as_path()));
true
}
#[cfg(not(feature = "discover"))]
fn discover() -> bool {
false
}
fn main() {
if cfg!(all(
any(
feature = "highs_release",
feature = "libz",
feature = "ninja"
),
not(feature = "build")
)) {
panic!(
"You have enabled features that control how HiGHS is built, but have not enabled the 'build' feature.\n\
Thus, your features will never have any effect. Please enable the 'build' feature on highs-sys if you want to build HiGHS or disable the 'libz', 'ninja' and 'highs_release' features."
);
}
if !discover() && !build() {
panic!("Could neither discover nor build HiGHS");
}
}