use std::env;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use std::process::Command;
use flate2::read::GzDecoder;
use tar::Archive;
const LRSLIB_TAG: &str = "073a";
const PERF_FLAGS: &[&str] = &[
"-O3",
"-DNDEBUG",
"-g0",
"-fomit-frame-pointer",
"-march=native",
"-mtune=native",
];
#[derive(Clone)]
struct LrsLayout {
archive_path: PathBuf,
source_dir: PathBuf,
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_GMP");
let layout = lrs_layout();
println!("cargo:rerun-if-changed={}", layout.archive_path.display());
ensure_lrslib_source(&layout);
build_lrslib(&layout);
generate_bindings(&layout);
if env::var("CARGO_CFG_TARGET_FAMILY").as_deref() == Ok("unix") {
println!("cargo:rustc-link-lib=m");
}
}
fn vendor_dir() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be provided"))
.join("vendor")
}
fn lrs_layout() -> LrsLayout {
let archive_path = vendor_dir().join(format!("lrslib-{LRSLIB_TAG}.tar.gz"));
if !archive_path.is_file() {
panic!(
"missing vendored lrslib archive at {}",
archive_path.display()
);
}
let cache_root = cache_root();
let dir_key = sanitize_component(LRSLIB_TAG);
let root = cache_root.join(dir_key);
LrsLayout {
archive_path,
source_dir: root.join(format!("lrslib-{LRSLIB_TAG}")),
}
}
fn ensure_lrslib_source(layout: &LrsLayout) -> PathBuf {
if layout.source_dir.join("lrslib.c").exists() {
return layout.source_dir.clone();
}
if let Some(parent) = layout.source_dir.parent() {
fs::create_dir_all(parent).expect("failed to create lrslib source parent directory");
}
let root = layout
.source_dir
.parent()
.unwrap_or_else(|| panic!("missing parent for {}", layout.source_dir.display()));
extract_archive(&layout.archive_path, root);
if layout.source_dir.join("lrslib.c").exists() {
return layout.source_dir.clone();
}
panic!(
"lrslib source tree not found under {} after extraction",
layout.source_dir.display()
);
}
fn build_lrslib(layout: &LrsLayout) {
let arith_dir = layout.source_dir.join("lrsarith-011");
let use_gmp = env::var_os("CARGO_FEATURE_GMP").is_some();
let mut sources = vec![
layout.source_dir.join("lrslib.c"),
layout.source_dir.join("lrsdriver.c"),
];
if use_gmp {
sources.push(arith_dir.join("lrsgmp.c"));
sources.push(arith_dir.join("mini-gmp.c"));
} else {
sources.push(arith_dir.join("lrslong.c"));
}
for src in &sources {
if !src.is_file() {
panic!("missing lrslib source file {}", src.display());
}
}
let mut build = cc::Build::new();
build
.warnings(false)
.files(&sources)
.include(&layout.source_dir)
.include(&arith_dir)
.flag_if_supported("-std=gnu99")
.define("LRS_QUIET", None)
.define("SIGNALS", None);
if use_gmp {
build.define("GMP", None).define("MGMP", None);
} else {
build.define("LRSLONG", None).define("SAFE", None);
if env::var("CARGO_CFG_TARGET_POINTER_WIDTH").as_deref() == Ok("64") {
build.define("B128", None);
}
}
for flag in PERF_FLAGS {
build.flag(flag);
}
build.compile("lrslib");
}
fn generate_bindings(layout: &LrsLayout) {
let gcc_include = gcc_include_dir();
let arith_dir = layout.source_dir.join("lrsarith-011");
let use_gmp = env::var_os("CARGO_FEATURE_GMP").is_some();
let header = "#include <stdio.h>\n#include \"lrsrestart.h\"\n#include \"lrslib.h\"\n";
let builder = bindgen::Builder::default()
.header_contents("lrslib_rs.h", header)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.allowlist_function("lrs_init")
.allowlist_function("lrs_close")
.allowlist_function("lrs_alloc_dat")
.allowlist_function("lrs_free_dat")
.allowlist_function("lrs_alloc_dic")
.allowlist_function("lrs_free_dic")
.allowlist_function("lrs_getfirstbasis")
.allowlist_function("lrs_getnextbasis")
.allowlist_function("lrs_getsolution")
.allowlist_function("lrs_getvertex")
.allowlist_function("lrs_getray")
.allowlist_function("lrs_set_row")
.allowlist_function("lrs_set_row_mp")
.allowlist_function("lrs_alloc_mp_vector")
.allowlist_function("lrs_clear_mp_vector")
.allowlist_function("lrs_alloc_mp_matrix")
.allowlist_function("lrs_clear_mp_matrix")
.allowlist_function("atomp")
.allowlist_function("rattodouble")
.allowlist_function("cpmp")
.allowlist_function("cprat")
.allowlist_var("GE")
.allowlist_var("EQ")
.allowlist_var("TRUE")
.allowlist_var("FALSE")
.allowlist_type("lrs_dic")
.allowlist_type("lrs_dat")
.allowlist_type("lrs_restart_dat")
.allowlist_type("lrs_mp")
.allowlist_type("lrs_mp_t")
.allowlist_type("lrs_mp_vector")
.allowlist_type("lrs_mp_matrix")
.clang_arg("-std=gnu99")
.clang_arg(format!("-I{}", layout.source_dir.display()))
.clang_arg(format!("-I{}", arith_dir.display()))
.clang_arg(format!("-I{}", gcc_include.display()))
.clang_arg("-DLRS_QUIET")
.clang_arg("-DSIGNALS");
let builder = if use_gmp {
builder.clang_arg("-DGMP").clang_arg("-DMGMP")
} else {
let builder = builder.clang_arg("-DLRSLONG").clang_arg("-DSAFE");
if env::var("CARGO_CFG_TARGET_POINTER_WIDTH").as_deref() == Ok("64") {
builder.clang_arg("-DB128")
} else {
builder
}
};
let bindings = builder
.generate()
.expect("Unable to generate lrslib bindings with bindgen");
let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR must be provided"));
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
fn gcc_include_dir() -> PathBuf {
let output = Command::new("gcc")
.arg("-print-file-name=include")
.output()
.expect("failed to invoke gcc -print-file-name=include");
if !output.status.success() {
panic!(
"gcc -print-file-name=include failed with status {}",
output.status
);
}
let path = String::from_utf8(output.stdout)
.expect("gcc include path contained non-UTF8 data")
.trim()
.to_string();
let include = PathBuf::from(path);
if !include.join("stddef.h").exists() {
panic!(
"gcc include directory {} missing stddef.h",
include.display()
);
}
include
}
fn cache_root() -> PathBuf {
let target_root = target_root();
let pkg = sanitize_component(
&env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME must be provided by cargo"),
);
let target_triple = sanitize_component(
&env::var("TARGET").expect("TARGET must be provided by cargo for build scripts"),
);
target_root.join("build-deps").join(pkg).join(target_triple)
}
fn target_root() -> PathBuf {
if let Ok(dir) = env::var("CARGO_TARGET_DIR") {
return PathBuf::from(dir);
}
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR must be provided by cargo"));
if let Some(target_dir) = out_dir
.ancestors()
.find(|p| p.file_name().is_some_and(|name| name == "target"))
{
return target_dir.to_path_buf();
}
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be provided"))
.join("target")
}
fn sanitize_component(value: &str) -> String {
value
.chars()
.map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '-' })
.collect()
}
fn extract_archive(archive_path: &Path, out_dir: &Path) {
let file = File::open(archive_path)
.unwrap_or_else(|e| panic!("failed to open {}: {e}", archive_path.display()));
let gz = GzDecoder::new(file);
let mut archive = Archive::new(gz);
archive.unpack(out_dir).unwrap_or_else(|e| {
panic!(
"failed to extract archive {} into {}: {e}",
archive_path.display(),
out_dir.display()
)
});
}