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> {
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 = 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<P: AsRef<Path>>(
inc_dirs: impl IntoIterator<Item = P>,
out_file: impl AsRef<Path>,
) {
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<Path>, dest_file: impl AsRef<Path>) {
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);
#[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<Option<PathBuf>, 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);
Ok(Some(ndk_path.join("sysroot")))
} else {
Ok(None)
}
}
#[cfg(feature = "bindgen")]
fn detect_stdlib(target: &str) -> Option<String> {
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
}