use std::{path::PathBuf, process::Command};
use anyhow::{Context, Result};
#[allow(dead_code)]
static SKIP_BINDGEN_HEADERS: &[&str] = &[
"NTL-interface.h",
"config.h",
"flint-config.h",
"flint-mparam.h",
"crt_helpers.h",
"gettimeofday.h",
"gmpcompat.h",
"longlong.h",
"longlong_asm_clang.h",
"longlong_asm_gcc.h",
"longlong_asm_gnu.h",
"longlong_div_gnu.h",
"longlong_msc_arm64.h",
"longlong_msc_x86.h",
"mpf-impl.h",
"mpfr_mat.h", "mpfr_vec.h", "fft_small.h", "machine_vectors.h", "mpn_extras.h",
"profiler.h",
"test_helpers.h",
];
#[allow(dead_code)]
static SKIP_DIRECT_HEADERS: &[&str] = &[
"NTL-interface.h",
"config.h",
"flint-config.h",
"flint-mparam.h",
"crt_helpers.h",
"gettimeofday.h",
"gmpcompat.h",
"longlong.h",
"longlong_asm_clang.h",
"longlong_asm_gcc.h",
"longlong_asm_gnu.h",
"longlong_div_gnu.h",
"longlong_msc_arm64.h",
"longlong_msc_x86.h",
"mpf-impl.h",
"mpfr_mat.h",
"mpfr_vec.h",
"fft_small.h",
"machine_vectors.h",
"mpn_extras.h",
"profiler.h",
"test_helpers.h",
"templates.h",
"fq_templates.h",
"fq_vec_templates.h",
"fq_mat_templates.h",
"fq_poly_templates.h",
"fq_poly_factor_templates.h",
"fq_embed_templates.h",
"acb_types.h",
"acf_types.h",
"arb_types.h",
"arf_types.h",
"ca_types.h",
"fmpq_types.h",
"fmpz_types.h",
"fmpz_mod_types.h",
"fq_types.h",
"fq_nmod_types.h",
"fq_zech_types.h",
"gr_types.h",
"limb_types.h",
"mpoly_types.h",
"n_poly_types.h",
"nmod_types.h",
"padic_types.h",
];
#[allow(dead_code)]
static BINDGEN_ALLOWLIST_FUNCTIONS: &[&str] = &[
"^_?(acb|acf|aprcl|arb|arf|arith|bernoulli|bool|bsplit|butterfly|ca|calcium|clear|compute|d|di|dirichlet|dlog|double|evil|extract|fexpr|ff|fft|flint|fmpq|fmpz|fmpzi|fq|free|gr|hypgeom|ifft|insert|jacobi|long|mag|mpf|mpn|mpoly|mul|n|nf|nfixed|nfloat|nmod|pack|padic|parse|partitions|perm|poly|psl2z|qadic|qfb|qqbar|qs|qsieve|radix|reduce|slong|sp2gz|swap|thread|truth|tuple|ui|ulong|unity|z|zassenhaus)_.*",
"^new_bitfield_.*",
];
#[allow(dead_code)]
static BINDGEN_ALLOWLIST_TYPES: &[&str] = &[
"^_?(acb|acf|aprcl|arb|arf|bernoulli|bool|bsplit|ca|calcium|d|di|dirichlet|dlog|dot|fexpr|flint|fmpq|fmpz|fmpzi|fq|gr|hash|hypgeom|la|limb|lnf|mag|mantissa|mp_limb|mp_ptr|mp_size|mp_srcptr|mpf|mpn|mpoly|mpq|mpz|n|nf|nfloat|nmod|ordering|padic|partitions|perm|polynomial|prime|qadic|qfb|qnf|qqbar|qs|radix|relation|slong|thread|truth|ulong|unity|vector|z|zz).*",
"^(__Bindgen|__builtin|__mpq|__mpz|__mpfr|__va|_IO|FILE|FLINT_FILE|pthread|size_t).*",
];
#[allow(dead_code)]
static BINDGEN_ALLOWLIST_VARS: &[&str] = &[
"^_?(acb|acf|arb|arf|ca|dlog|fexpr|flint|fmpq|fmpz|fq|gr|mag|nmod|padic|qqbar|thread|truth)_.*",
"^(ACB|ARB|ARF|BELL|BERNOULLI|CA|CRT|D_|DFT|DLOG|FEXPR|FLINT|FPWRAP|FQ|GR|LSYM|MAG|MAX|MPOLY|MUL|NF|NMOD|PADIC|QQBAR|SMALL|SQUARING|UWORD|WEAK|WORD)_.*",
];
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),
)
}
};
}
struct Conf {
out_dir: PathBuf, bindgen_flint_rs: PathBuf, flint_include_dir: PathBuf, flint_lib_dir: PathBuf, }
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_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("-Rp") .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);
make.env("MAKEFLAGS", std::env::var("CARGO_MAKEFLAGS").unwrap());
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(())
}
}
#[cfg(not(feature = "force-bindgen"))]
impl Conf {
fn bindgen(&self) -> Result<()> {
println!("cargo::rerun-if-changed=./bindgen/flint.rs");
let mut cp = Command::new("cp");
cp.arg("-Rp") .arg("./bindgen/flint.rs")
.arg(&self.bindgen_flint_rs);
cmd! { cp };
Ok(())
}
}
#[cfg(feature = "force-bindgen")]
impl Conf {
fn flint_headers(&self, skip_headers: &[&str]) -> 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 flint_inline_macros(&self) -> Result<Vec<String>> {
let src_dir = self.out_dir.join("flint/src");
let mut macros = Vec::new();
for entry in std::fs::read_dir(&src_dir)
.context(format!("Failed to read `{}`", src_dir.display()))?
{
let inlines = entry?.path().join("inlines.c");
if !inlines.is_file() {
continue;
}
let contents = std::fs::read_to_string(&inlines)
.context(format!("Failed to read `{}`", inlines.display()))?;
for line in contents.lines() {
let Some(rest) = line.trim_start().strip_prefix("#define ") else {
continue;
};
let Some(name) = rest.split_whitespace().next() else {
continue;
};
if name.ends_with("_INLINES_C") {
macros.push(name.to_owned());
}
}
}
macros.sort();
macros.dedup();
Ok(macros)
}
fn bindgen(&self) -> Result<()> {
let allowlist_headers: Vec<_> = self.flint_headers(SKIP_BINDGEN_HEADERS)?;
let direct_headers: Vec<_> = self.flint_headers(SKIP_DIRECT_HEADERS)?;
let mut builder = bindgen::Builder::default();
for macro_name in self.flint_inline_macros()? {
builder = builder.clang_arg(format!("-D{macro_name}"));
}
for header in &allowlist_headers {
let h = header.to_str().context("Non unicode path")?;
builder = builder.allowlist_file(h);
}
for header in &direct_headers {
let h = header.to_str().context("Non unicode path")?;
builder = builder.header(h);
}
for pattern in BINDGEN_ALLOWLIST_FUNCTIONS {
builder = builder.allowlist_function(pattern);
}
for pattern in BINDGEN_ALLOWLIST_TYPES {
builder = builder.allowlist_type(pattern);
}
for pattern in BINDGEN_ALLOWLIST_VARS {
builder = builder.allowlist_var(pattern);
}
let bindings = builder
.derive_default(false) .derive_copy(false) .derive_debug(false) .generate_cstr(true) .blocklist_function("__.*") .blocklist_var("__.*") .rust_target(bindgen::RustTarget::stable(82, 0).ok().unwrap())
.rust_edition(bindgen::RustEdition::Edition2021)
.layout_tests(false)
.formatter(bindgen::Formatter::None)
.generate()?;
bindings.write_to_file(&self.bindgen_flint_rs)?;
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()
))?;
}
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=mpfr");
println!("cargo::rustc-link-lib=gmp");
println!(
"cargo::rustc-link-search=native={}",
conf.flint_lib_dir.display()
);
if cfg!(feature = "gmp-mpfr-sys") {
println!(
"cargo::rustc-link-search=native={}",
std::env::var("DEP_GMP_LIB_DIR")?
);
}
conf.bindgen()?;
anyhow::ensure!(conf.bindgen_flint_rs.is_file(), "Cannot find `flint.rs`");
Ok(())
}