use std::path::Path; #[cfg(feature = "bindgen")] use std::path::PathBuf; const LIB_NAME: &str = "msdfgen"; fn main() { use std::env; #[cfg(any(not(feature = "bindgen"), feature = "update-bindings"))] fn bindings_filename() -> String { format!( "{}-{}-{}.rs", env::var("CARGO_CFG_TARGET_ARCH").unwrap(), env::var("CARGO_CFG_TARGET_OS").unwrap(), env::var("CARGO_CFG_TARGET_ENV").unwrap() ) } #[cfg(any(not(feature = "bindgen"), feature = "update-bindings"))] fn bindings_filepath(filename: &str) -> impl AsRef { Path::new("src").join("bindings").join(filename) } #[cfg(not(feature = "bindgen"))] { let bindings_file = bindings_filename(); if bindings_filepath(&bindings_file).as_ref().is_file() { println!("cargo:rustc-env=MSDFGEN_BINDINGS={}", bindings_file); } else { panic!("No prebuilt bindings. Try use `bindgen` feature.",); } } let out_dir = env::var("OUT_DIR").expect("The OUT_DIR is set by cargo."); let out_dir = Path::new(&out_dir); let src_dir = Path::new("lib"); #[cfg(feature = "bindgen")] { //let inc_dirs = try_find_library_inc_dirs().unwrap_or_else(|| vec![src_dir.join("include")]); let inc_dirs = vec![src_dir]; let bindings = out_dir.join("bindings.rs"); generate_bindings(inc_dirs, &bindings); #[cfg(feature = "update-bindings")] { let out_path = bindings_filepath(&bindings_filename()); update_bindings(&bindings, &out_path); } } let lib_dir = out_dir; build_library(src_dir, lib_dir); } #[cfg(feature = "bindgen")] fn generate_bindings>( inc_dirs: impl IntoIterator, out_file: impl AsRef, ) { let target = std::env::var("TARGET").expect("TARGET is set by cargo."); let ctarget = into_c_target(&target); let sysroot = detect_sysroot(&target).expect("Detect sysroot."); let stdlib = detect_stdlib(&target); let bindings = bindgen::Builder::default() .detect_include_paths(true) .clang_arg("-xc++") .clang_arg("-std=c++11") .clang_args(stdlib.map_or_else(Vec::new, |stdlib| vec![format!("--stdlib={}", stdlib)])) .clang_arg("-target") .clang_arg(ctarget) .clang_args(sysroot.map_or_else(Vec::new, |sysroot| { vec!["-isysroot".into(), sysroot.display().to_string()] })) .clang_args( inc_dirs .into_iter() .map(|dir| format!("-I{}", dir.as_ref().display())), ) .clang_arg("-DMSDFGEN_USE_CPP11") .header("msdfgen.h") .header( Path::new("src") .join("msdfgen_extras.h") .display() .to_string(), ) .opaque_type("std::.*") .allowlist_var("MSDFGEN_.*") .allowlist_type("msdfgen::.*") .allowlist_function("msdfgen::.*") .blocklist_function("msdfgen::(read|write)ShapeDescription") .blocklist_type("FILE") .blocklist_type("_IO_.*") .generate() .expect("Generated bindings."); bindings.write_to_file(out_file).expect("Written bindings."); } #[cfg(feature = "update-bindings")] fn update_bindings(bind_file: impl AsRef, dest_file: impl AsRef) { use std::{env, fs, io::Write}; let dest_file = dest_file.as_ref(); fs::create_dir_all(&dest_file.parent().unwrap()).unwrap(); fs::copy(&bind_file, &dest_file).unwrap(); if let Ok(github_env) = env::var("GITHUB_ENV") { let mut env_file = fs::OpenOptions::new() .create(true) .append(true) .open(github_env) .unwrap(); writeln!( env_file, "MSDFGEN_SYS_BINDINGS_FILE={}", dest_file.display() ) .unwrap(); } } fn build_library(src_dir: &Path, lib_dir: &Path) { let extra_src_dir = Path::new("src"); let extra_srcs = ["msdfgen_extras.cpp"] .into_iter() .map(|name| extra_src_dir.join(name)); let core_src_dir = src_dir.join("core"); let core_srcs = [ "contour-combiners.cpp", "Contour.cpp", "edge-coloring.cpp", "EdgeHolder.cpp", "edge-segments.cpp", "edge-selectors.cpp", "equation-solver.cpp", "msdf-error-correction.cpp", "MSDFErrorCorrection.cpp", "msdfgen.cpp", "Projection.cpp", "rasterization.cpp", "render-sdf.cpp", "Scanline.cpp", "sdf-error-estimation.cpp", "Shape.cpp", "shape-description.cpp", "SignedDistance.cpp", "Vector2.cpp", ] .into_iter() .map(|name| core_src_dir.join(name)); let profile = std::env::var("PROFILE").expect("PROFILE is set by cargo."); let mut build = cc::Build::new(); build .cpp(true) .opt_level(if profile == "release" { 3 } else { 1 }) .debug(profile != "release") .define( if profile == "release" { "NDEBUG" } else { "DEBUG" }, None, ) .define("MSDFGEN_USE_CPP11", None) .flag("-std=c++11") .flag("-ffunction-sections") .flag("-fdata-sections") .warnings(false) .extra_warnings(false) .include(&src_dir) .include(&core_src_dir) .files(core_srcs) .files(extra_srcs) .out_dir(lib_dir); /* if cfg!(feature = "libcxx") { build.cpp_set_stdlib("c++"); build.cargo_metadata(false); } */ #[cfg(feature = "shared")] build.shared_flag(true); #[cfg(feature = "static")] build.static_flag(true); build.compile(LIB_NAME); } #[cfg(feature = "bindgen")] fn into_c_target(target: &str) -> String { use std::env; let mut result_target = target.to_string(); if target.contains("-android") { let android_api = env::var("ANDROID_PLATFORM") .or_else(|_| env::var("ANDROID_API")) .unwrap_or_else(|_| "16".into()); result_target.push_str(&android_api); } result_target } #[cfg(feature = "bindgen")] fn detect_sysroot(target: &str) -> Result, String> { use std::env; println!("cargo:rerun-if-env-changed=TARGET_SYSROOT"); if let Ok(path) = env::var("TARGET_SYSROOT") { return Ok(Some(path.into())); } if target.contains("-apple-") { let sdk_name = if target.contains("-darwin") { "macosx" } else if target.contains("-ios") { if target.contains("i386") || target.contains("x86_64") { "iphonesimulator" } else { "iphoneos" } } else { return Err(format!("Invalid apple target: {}", target)); }; let raw_path = std::process::Command::new("xcrun") .args(&["--sdk", sdk_name, "--show-sdk-path"]) .output() .map_err(|err| err.to_string())? .stdout; let str_path = std::str::from_utf8(&raw_path).map_err(|_| "Invalid apple SDK path string")?; Ok(Some(str_path.trim().into())) } else if target.contains("-android") { let ndk_home = env::var("ANDROID_NDK_HOME") .or_else(|_| env::var("NDK_HOME")) .map_err(|_| "Either ANDROID_NDK_HOME or NDK_HOME should be set.".to_string())?; let ndk_path = Path::new(&ndk_home); //let ndk_bin = ndk_path.join("toolchains/llvm/prebuilt/linux-x86_64/bin"); Ok(Some(ndk_path.join("sysroot"))) } else { Ok(None) } } #[cfg(feature = "bindgen")] fn detect_stdlib(target: &str) -> Option { use std::env; println!("cargo:rerun-if-env-changed=CXX_STDLIB"); if let Ok(stdlib) = env::var("CXX_STDLIB") { return Some(stdlib); } if target.contains("-ios") { return Some("libc++".into()); } None }