use std::{env, path::Path}; // https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html fn main() -> Result<(), Box> { println!("cargo:rerun-if-changed=build.rs"); // XXX: prevent re-run indead // By default, cargo always re-run the build script if any file within the package // is changed, and no any rerun-if instruction is emitted. //println!("cargo:rerun-if-changed=src"); println!("cargo:rustc-env=BUILD_TIMESTAMP={}", chrono::Local::now().format("%H:%M:%S%z %Y-%m-%d")); let output = std::process::Command::new("git") .args(["rev-parse", "--short", "HEAD"]).output()?; println!("cargo:rustc-env=BUILD_GIT_HASH={}", String::from_utf8(output.stdout)?); println!("cargo:rerun-if-changed={}", Path::new(".git").join("index").display()); //std::process::Command::new(Path::new("3rdparty").join("layout.sh")).status()?; #[allow(unused)] let path = std::path::PathBuf::from(env::var("OUT_DIR")?); #[cfg(feature = "ftg")] binding_ftg(&path)?; #[cfg(feature = "evg")] binding_evg(&path)?; #[cfg(feature = "b2d")] binding_b2d(&path)?; #[cfg(feature = "ovg")] binding_ovg(&path)?; #[cfg(feature = "ugl")] binding_ugl(&path)?; Ok(()) } #[cfg(feature = "ftg")] fn binding_ftg(path: &Path) -> Result<(), Box> { #[derive(Debug)] struct DoctestComment; impl bindgen::callbacks::ParseCallbacks for DoctestComment { fn process_comment(&self, comment: &str) -> Option { Some(format!("```c,ignore\n{comment}\n```")) // FIXME: } } let (ftg_dir, module) = (Path::new("3rdparty").join("ftg"), "ftgrays"); cc::Build::new().flag("-std=c17").flag("-pedantic").define("STANDALONE_", None) .define("FALL_THROUGH", "((void)0)").file(ftg_dir.join("ftgrays.c")) .files(glob::glob(&format!("{}/stroke/*.c", ftg_dir.display()))? .filter_map(Result::ok)).file(ftg_dir.join("ftraster.c")) .flag("-Wno-unused").flag("-Wno-implicit-fallthrough") .opt_level(3).define("NDEBUG", None).compile(module); // The bindgen::Builder is the main entry point to bindgen, // and lets you build up options for the resulting bindings. bindgen::builder().header(ftg_dir.join("ftgrays.h").to_string_lossy()) //.header(ftg_dir.join("ftimage.h").to_string_lossy()) .clang_args(["-DSTANDALONE_", "-DFT_BEGIN_HEADER=", "-DFT_END_HEADER=", "-DFT_STATIC_BYTE_CAST(type,var)=(type)(unsigned char)(var)", ]).allowlist_item("FT_OUTLINE_.*|FT_RASTER_FLAG_.*|FT_CURVE_TAG.*") .allowlist_var("ft_grays_raster").allowlist_type("FT_Outline|FT_Pixel_Mode") .allowlist_var("ft_standard_raster").merge_extern_blocks(true) .layout_tests(false).derive_copy(false).derive_debug(false) //.default_visibility(bindgen::FieldVisibilityKind::PublicCrate) .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) .parse_callbacks(Box::new(DoctestComment)).generate_comments(false) // XXX: // Tell cargo to invalidate the built crate whenever any of the // included header files changed. //.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate()?.write_to_file(path.join(format!("{module}.rs")))?; Ok(()) } #[cfg(feature = "evg")] fn binding_evg(path: &Path) -> Result<(), Box> { let (evg_dir, module) = (Path::new("3rdparty").join("evg"), "gpac_evg"); #[allow(unused_mut)] let mut bgen = bindgen::builder(); let mut cc = cc::Build::new(); #[cfg(feature = "evg_fixed")] { cc.define("GPAC_FIXED_POINT", None); bgen = bgen.clang_arg("-DGPAC_FIXED_POINT"); } cc.flag("-std=c17").flag("-Wno-pointer-sign").define("GPAC_DISABLE_LOG", None) .flag("-Wno-unused-parameter").define("GPAC_DISABLE_THREADS", None) .flag("-Wno-implicit-fallthrough").flag("-Wno-unused") .files(glob::glob(&format!("{}/*.c", evg_dir.display()))?.filter_map(Result::ok)) .include(&evg_dir).opt_level(3).define("NDEBUG", None).compile(module); bgen.header(evg_dir.join("gpac").join("evg.h").to_string_lossy()) .clang_args(["-DGPAC_DISABLE_THREADS", &format!("-I{}", evg_dir.display()) ]) .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) //.default_visibility(bindgen::FieldVisibilityKind::PublicCrate) .allowlist_function("gf_evg_s.*").allowlist_function("gf_path_.*") .merge_extern_blocks(true).new_type_alias("Fixed")//.allowlist_item("GF_LINE_.*") .layout_tests(false).derive_copy(false).derive_debug(false) //.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate()?.write_to_file(path.join(format!("{module}.rs")))?; Ok(()) } #[cfg(feature = "b2d")] fn binding_b2d(path: &Path) -> Result<(), Box> { let b2d_src = Path::new("3rdparty").join("blend2d").join("src"); let jit_src = Path::new("3rdparty").join("asmjit") .join("src"); #[allow(unused_mut)] let mut bgen = bindgen::builder(); let module = "blend2d"; // "blend2d_bindings"; let mut cc = cc::Build::new(); #[cfg(feature = "b2d_sfp")] { //println!("cargo:rustc-cfg=feature=\"b2d_sfp\""); #[cfg(target_arch = "aarch64")] cc.flag("-mno-outline-atomics"); cc.define("BLEND2D_NO_DFP", None).flag("-fsingle-precision-constant") .define("BL_BUILD_NO_TLS", None).flag("-Wno-uninitialized").compiler("g++-13"); bgen = bgen.clang_arg("-DBLEND2D_NO_DFP"); } blend2d_simd(&mut cc); cc.cpp(true).flag("-std=c++17").define("ASMJIT_EMBED", None) .define("ASMJIT_NO_STDCXX", None).define("ASMJIT_NO_FOREIGN", None) .files(glob::glob(&format!("{}/**/*.cpp", jit_src.display()))?.filter_map(Result::ok)) .files(glob::glob(&format!("{}/**/*.cpp", b2d_src.display()))? .filter_map(|f| f.ok().filter(|f| f.as_os_str().to_str().is_some_and(|f| !f.contains("_test"))))) .flag("-fvisibility=hidden").flag("-fno-exceptions").flag("-fno-math-errno") .flag("-fmerge-all-constants").flag("-ftree-vectorize").flag("-fno-rtti") .flag("-fno-threadsafe-statics").include(&b2d_src).include(jit_src) //.flag("-mllvm").flag("--disable-loop-idiom-all") .opt_level(3).define("NDEBUG", None).compile(module); //println!("cargo:rustc-link-lib=rt"); // https://blend2d.com/doc/build-instructions.html bgen.header(b2d_src.join("blend2d.h").to_string_lossy()) .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) .default_non_copy_union_style(bindgen::NonCopyUnionStyle::ManuallyDrop) .default_visibility(bindgen::FieldVisibilityKind::PublicCrate) .derive_copy(false).derive_debug(false).merge_extern_blocks(true) //.derive_hash(false).derive_partialeq(false).derive_eq(false) // XXX: not work for enum? .allowlist_function("bl.*").allowlist_type("BL.*").layout_tests(false) //.clang_args(["-x", "c++", "-std=c++17", &format!("-I{b2d_src}")]) //.blocklist_item("*(Virt|Impl)") // XXX: can not be blocked //.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate()?.write_to_file(path.join(format!("{module}.rs")))?; fn blend2d_simd(cc: &mut cc::Build) { if cfg!(target_arch = "arm") { let simd_flag = "-mfpu=neon-vfpv4"; cc.is_flag_supported(simd_flag).is_ok_and(|bl| bl) .then(|| cc.flag(simd_flag).define("BL_BUILD_OPT_ASIMD", None)); } let compiler = cc.get_compiler(); if compiler.is_like_gnu() { cc.flag_if_supported("-fno-semantic-interposition"); } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] if compiler.is_like_msvc() { // refer to blend2d/CMakeLists.txt cc.flag("-MP").flag("-GR-").flag("-GF").flag("-Zc:__cplusplus").flag("-Zc:inline") .flag("-Zc:strictStrings").flag("-Zc:threadSafeInit-").flag("-GS-").flag("-Oi"); let simd_flag = "-arch:AVX512"; cc.is_flag_supported(simd_flag).unwrap_or(false) .then(|| cc.flag(simd_flag).define("BL_BUILD_OPT_AVX512", None)); let simd_flag = "-arch:AVX2"; if cc.is_flag_supported(simd_flag).unwrap_or(false) { cc.flag(simd_flag).define("BL_BUILD_OPT_AVX2", None); if compiler.is_like_clang() { for simd_flag in ["-msse4.2", "-msse4.1", "-mssse3", "-msse3", "-msse2"] { cc.is_flag_supported(simd_flag).unwrap_or(false).then(|| cc.flag(simd_flag).define("BL_BUILD_OPT_SSE2", None)); break; } cc.flag("-clang:-fno-rtti").flag("-clang:-fno-math-errno") .flag("-clang:-fno-trapping-math"); } else { cc.define("__SSSE3__", None).define("__SSE3__", None) // XXX: .define("__SSE4_2__", None).define("__SSE4_1__", None); } //env::var("TARGET").is_ok_and(|v| !v.contains("x86_64")).then(|| ...); // 64-bit MSVC compiler doesn't like -arch:SSE[2] as it's implicit. #[cfg(target_arch = "x86_64")] cc.flag("-arch:SSE2"); } let simd_flag = "-arch:AVX"; cc.is_flag_supported(simd_flag).unwrap_or(false) .then(|| cc.flag(simd_flag).define("BL_BUILD_OPT_AVX", None)); } else { // XXX: https://doc.rust-lang.org/std/arch/index.html /* for simd_flag in ["-mavx512bw", "-mavx512dq", "-mavx512cd", "-mavx512vl"] { cc.is_flag_supported(simd_flag).unwrap_or(false).then(|| cc.flag(simd_flag).define("BL_BUILD_OPT_AVX512", None)); break; } */ is_x86_feature_detected!("avx512bw").then(|| cc.flag("-mavx512bw").define("BL_BUILD_OPT_AVX512", None)); is_x86_feature_detected!("avx512dq").then(|| cc.flag("-mavx512dq").define("BL_BUILD_OPT_AVX512", None)); is_x86_feature_detected!("avx512cd").then(|| cc.flag("-mavx512cd").define("BL_BUILD_OPT_AVX512", None)); is_x86_feature_detected!("avx512vl").then(|| cc.flag("-mavx512vl").define("BL_BUILD_OPT_AVX512", None)); is_x86_feature_detected!("avx2").then(|| { cc.flag("-mavx2").define("BL_BUILD_OPT_AVX2", None); cc.flag_if_supported("-mfma"); }); is_x86_feature_detected!("avx").then(|| cc.flag("-mavx").define("BL_BUILD_OPT_AVX", None)); is_x86_feature_detected!("sse4.2").then(|| cc.flag("-msse4.2").define("BL_BUILD_OPT_SSE4_2", None)); is_x86_feature_detected!("sse4.1").then(|| cc.flag("-msse4.1").define("BL_BUILD_OPT_SSE4_1", None)); is_x86_feature_detected!("ssse3").then(|| cc.flag("-mssse3").define("BL_BUILD_OPT_SSSE3", None)); is_x86_feature_detected!("sse3").then(|| cc.flag("-msse3").define("BL_BUILD_OPT_SSE3", None)); is_x86_feature_detected!("sse2").then(|| cc.flag("-msse2").define("BL_BUILD_OPT_SSE2", None)); /* for simd_flag in ["-msse4.2", "-msse4.1", "-mssse3", "-msse3", "-msse2"] { cc.is_flag_supported(simd_flag).unwrap_or(false).then(|| cc.flag(simd_flag).define("BL_BUILD_OPT_SSE2", None)); break; } */ } } Ok(()) } #[cfg(feature = "ovg")] fn binding_ovg(path: &Path) -> Result<(), Box> { let mut ovg_dir = Path::new("3rdparty").join("amanithvg").join("include"); //env::var("CARGO_MANIFEST_DIR").unwrap() // XXX: need to set environment variable before `cargo r/t`: // DYLD_FALLBACK_LIBRARY_PATH=$PWD/3rdparty/amanithvg/lib/macosx/ub/sre/standalone bindgen::builder().clang_arg(format!("-I{}", ovg_dir.display())) .header(ovg_dir.join("VG").join("vgext.h").to_string_lossy()) .derive_copy(false).derive_debug(false).merge_extern_blocks(true) .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) .allowlist_function("vg.*").allowlist_type("VG.*").layout_tests(false) //.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate()?.write_to_file(path.join("openvg.rs"))?; ovg_dir.pop(); ovg_dir.push("lib"); ovg_dir.push(env::consts::OS); //ovg_dir.push("ub"); ovg_dir.push(env::consts::ARCH); ovg_dir.push("sre"); ovg_dir.push("standalone"); println!("cargo:rustc-link-search=native={}", ovg_dir.display()); println!("cargo:rustc-link-lib=dylib=AmanithVG"); Ok(()) } #[cfg(feature = "ugl")] fn binding_ugl(path: &Path) -> Result<(), Box> { let ugl_inc = Path::new("3rdparty").join("micro-gl").join("include"); let module = "microgl"; let mut ugl_cpp = Path::new("src").join(module).with_extension("cpp"); cc::Build::new().cpp(true).flag("-std=c++17").file(&ugl_cpp) .flag("-Wno-unused-parameter").flag("-Wno-unused").flag("-Wno-sign-compare") .flag("-Wno-deprecated-copy").flag("-Wno-uninitialized") .flag("-Wno-reorder").flag("-Wno-misleading-indentation") .include(&ugl_inc).opt_level(3).define("NDEBUG", None).compile(module); println!("cargo:rerun-if-changed={}", ugl_cpp.display()); ugl_cpp.set_extension("h"); bindgen::builder().header(ugl_cpp.to_string_lossy()).opaque_type("(canvas|path)_t") .clang_args(["-x", "c++", "-std=c++17", &format!("-I{}", ugl_inc.display())]) .derive_copy(false).derive_debug(false).merge_extern_blocks(true) .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) .allowlist_function("(canvas|path).*").layout_tests(false) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate()?.write_to_file(path.join(format!("{module}.rs")))?; Ok(()) }