use std::{path::PathBuf, process::Command};
use anyhow::{Context, Result};
#[allow(dead_code)]
static SKIP_HEADERS: &[&str] = &[
"NTL-interface.h",
"config.h",
"crt_helpers.h",
"longlong_asm_clang.h",
"longlong_asm_gcc.h",
"longlong_div_gnu.h",
"longlong_msc_arm64.h",
"longlong_msc_x86.h",
"mpfr_mat.h", "mpfr_vec.h", "gmpcompat.h",
"fft_small.h", "machine_vectors.h", "mpn_extras.h",
"gettimeofday.h",
];
struct Conf {
out_dir: PathBuf, bindgen_extern_c: PathBuf, bindgen_flint_rs: PathBuf, flint_include_dir: PathBuf, flint_lib_dir: PathBuf, }
macro_rules! cmd {
($cmd: expr) => {
let mut cmd = $cmd;
let cmd_string = format!("{:?}", cmd);
let exit = cmd
.output()
.context(format!("Command {} did not execute normally", cmd_string))?;
if !exit.status.success() {
anyhow::bail!(
"Command failed\nCommand: {}\n===== stdout\n{}===== stderr\n{}\n",
cmd_string,
String::from_utf8_lossy(&exit.stdout),
String::from_utf8_lossy(&exit.stderr),
)
}
};
}
impl Conf {
fn new() -> Self {
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap())
.canonicalize()
.unwrap();
Conf {
out_dir: out_dir.clone(),
bindgen_extern_c: out_dir.join("extern.c"),
bindgen_flint_rs: out_dir.join("flint.rs"),
flint_include_dir: out_dir.join("include"),
flint_lib_dir: out_dir.join("lib"),
}
}
fn build_flint(&self) -> Result<()> {
let flint_root_dir = self.out_dir.join("flint");
let mut cp = Command::new("cp");
cp.arg("--recursive")
.arg("--archive") .arg("--update")
.arg("flint")
.arg(&self.out_dir);
cmd! { cp }
if !flint_root_dir.join("configure").is_file() {
let mut bootstrap = Command::new("sh");
bootstrap.current_dir(&flint_root_dir).arg("./bootstrap.sh");
cmd! { bootstrap }
}
if !flint_root_dir.join("Makefile").is_file() {
let mut configure = Command::new("sh");
configure
.current_dir(&flint_root_dir)
.arg("./configure")
.arg("--prefix")
.arg(&self.out_dir)
.arg("--disable-shared");
if cfg!(feature = "gmp-mpfr-sys") {
configure
.arg(format!(
"--with-gmp-lib={}",
std::env::var("DEP_GMP_LIB_DIR")?
))
.arg(format!(
"--with-gmp-include={}",
std::env::var("DEP_GMP_INCLUDE_DIR")?
));
}
cmd! { configure }
}
if !flint_root_dir.join("libflint.a").is_file() {
let mut make = Command::new("make");
make.current_dir(&flint_root_dir).arg("-j");
cmd! { make }
}
let mut make_install = Command::new("make");
make_install.current_dir(&flint_root_dir).arg("install");
cmd! { make_install };
println!("cargo::metadata=LIB_DIR={}", self.flint_lib_dir.display());
println!(
"cargo::metadata=INCLUDE_DIR={}",
self.flint_include_dir.display()
);
Ok(())
}
fn build_extern(&self) -> Result<()> {
cc::Build::new()
.file(&self.bindgen_extern_c)
.include(&self.flint_include_dir)
.flags(["-lflint", "-lgmp", "-lmpfr"])
.flags([
"-Wno-old-style-declaration",
"-Wno-unused-parameter",
"-Wno-sign-compare",
])
.try_compile("extern")?;
Ok(())
}
}
#[cfg(not(feature = "force-bindgen"))]
impl Conf {
fn bindgen(&self) -> Result<()> {
println!(
"cargo::rerun-if-changed={}",
&self.bindgen_flint_rs.display()
);
println!(
"cargo::rerun-if-changed={}",
&self.bindgen_extern_c.display()
);
let mut cp = Command::new("cp");
cp.arg("--archive")
.arg("./bindgen/flint.rs")
.arg(&self.bindgen_flint_rs);
cmd! { cp };
let mut cp = Command::new("cp");
cp.arg("--archive")
.arg("./bindgen/extern.c")
.arg(&self.bindgen_extern_c);
cmd! { cp };
Ok(())
}
}
#[cfg(feature = "force-bindgen")]
impl Conf {
fn flint_headers(&self) -> Result<Vec<PathBuf>> {
use std::{collections::HashSet, ffi::OsStr};
let flint_header_dir = self.flint_include_dir.join("flint");
anyhow::ensure!(
flint_header_dir.join("flint.h").is_file(),
"Cannot find `flint.h`"
);
let mut headers = Vec::new();
let mut skip = HashSet::new();
for file in SKIP_HEADERS {
skip.insert(OsStr::new(*file));
}
let entries = flint_header_dir.read_dir()?;
let header_extension = OsStr::new("h");
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.extension() != Some(header_extension) {
continue;
}
if skip.contains(&path.file_name().unwrap()) {
continue;
}
headers.push(path)
}
Ok(headers)
}
fn bindgen(&self) -> Result<()> {
let headers: Vec<_> = self.flint_headers()?;
let mut builder = bindgen::Builder::default();
for header in &headers {
let h = header.to_str().context("Non unicode path")?;
builder = builder.allowlist_file(h).header(h);
}
let extern_tmp = self.out_dir.join("extern-abs.c");
std::fs::write(&extern_tmp, b"")?;
let bindings = builder
.derive_default(false) .derive_copy(false) .derive_debug(false) .wrap_static_fns(true) .wrap_static_fns_path(&extern_tmp) .generate_cstr(true) .merge_extern_blocks(true) .blocklist_function("__.*") .blocklist_var("__.*") .rust_target(bindgen::RustTarget::stable(82, 0).ok().unwrap())
.rust_edition(bindgen::RustEdition::Edition2021)
.formatter(bindgen::Formatter::Prettyplease)
.generate()?;
bindings.write_to_file(&self.bindgen_flint_rs)?;
{
use std::io::Write;
let mut extern_rel = std::io::BufWriter::new(
std::fs::File::create(&self.bindgen_extern_c).context("Cannot open `extern.c`")?,
);
let include_re = regex::Regex::new(r##"^#include\s+".+(flint/[^/]+\.h)""##)?;
let extern_tmp = std::fs::read_to_string(&extern_tmp)
.context(format!("Cannot read `{}`", extern_tmp.display()))?;
for line in extern_tmp.lines() {
match include_re.captures(&line) {
Some(capt) => writeln!(
extern_rel,
r##"#include "{}""##,
capt.get(1).context("no capturing group")?.as_str()
)?,
None => writeln!(extern_rel, "{line}")?,
}
}
}
println!("cargo::rerun-if-env-changed=KEEP_BINDGEN_OUTPUT");
if std::env::var("KEEP_BINDGEN_OUTPUT").is_ok() {
std::fs::copy(
&self.bindgen_flint_rs,
&std::path::Path::new("./bindgen/flint.rs"),
)
.context(format!(
"Failed to copy `{}`",
self.bindgen_flint_rs.display()
))?;
std::fs::copy(
&self.bindgen_extern_c,
&std::path::Path::new("./bindgen/extern.c"),
)
.context(format!(
"Failed to copy `{}`",
self.bindgen_extern_c.display()
))?;
}
Ok(())
}
}
fn main() -> Result<()> {
let conf = Conf::new();
conf.build_flint()?;
anyhow::ensure!(
conf.flint_include_dir.join("flint/flint.h").is_file(),
"Compilation is successful, but `flint/flint.h` is not where it should"
);
anyhow::ensure!(
conf.flint_lib_dir.join("libflint.a").is_file(),
"Compilation is successful, but `libflint.a` is not where it should"
);
println!("cargo::rustc-link-lib=flint");
println!("cargo::rustc-link-lib=gmp");
println!("cargo::rustc-link-lib=mpfr");
println!(
"cargo::rustc-link-search=native={}",
conf.flint_lib_dir.display()
);
conf.bindgen()?;
anyhow::ensure!(conf.bindgen_extern_c.is_file(), "Cannot find `extern.c`");
anyhow::ensure!(conf.bindgen_flint_rs.is_file(), "Cannot find `flint.rs`");
conf.build_extern()?;
Ok(())
}