use std::path::PathBuf;
use std::process::Command;
use std::{env, fs};
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let debug = env::var("DEBUG").unwrap();
let coreml_enabled = env::var("CARGO_FEATURE_COREML").is_ok();
let metal_enabled = env::var("CARGO_FEATURE_METAL").is_ok();
let cuda_enabled = env::var("CARGO_FEATURE_CUDA").is_ok();
let opencl_enabled = env::var("CARGO_FEATURE_OPENCL").is_ok();
let opengl_enabled = env::var("CARGO_FEATURE_OPENGL").is_ok();
let vulkan_enabled = env::var("CARGO_FEATURE_VULKAN").is_ok();
let manifest_dir_path = PathBuf::from(&manifest_dir);
let mnn_source_dir = get_mnn_source(&manifest_dir_path);
let dst = build_mnn_with_cmake(
&mnn_source_dir,
&arch,
&os,
&debug,
coreml_enabled,
metal_enabled,
cuda_enabled,
opencl_enabled,
opengl_enabled,
vulkan_enabled,
);
build_wrapper(&manifest_dir_path, &mnn_source_dir, &dst, &os);
link_libraries(
&dst,
&os,
coreml_enabled,
metal_enabled,
cuda_enabled,
opencl_enabled,
opengl_enabled,
vulkan_enabled,
);
bind_gen(&manifest_dir_path, &mnn_source_dir, &dst, &os, &arch);
}
fn get_mnn_source(manifest_dir: &PathBuf) -> PathBuf {
if let Ok(mnn_dir) = env::var("MNN_SOURCE_DIR") {
let mnn_path = PathBuf::from(mnn_dir);
if mnn_path.exists() && mnn_path.join("CMakeLists.txt").exists() {
println!(
"cargo:warning=Using MNN source from MNN_SOURCE_DIR: {}",
mnn_path.display()
);
return mnn_path;
} else {
panic!(
"MNN_SOURCE_DIR is set but directory is invalid or missing CMakeLists.txt: {}",
mnn_path.display()
);
}
}
let local_mnn = manifest_dir.join("3rd_party/MNN");
if local_mnn.exists() && local_mnn.join("CMakeLists.txt").exists() {
println!(
"cargo:warning=Using local MNN source: {}",
local_mnn.display()
);
return local_mnn;
}
println!("cargo:warning=MNN source not found, cloning from GitHub...");
let third_party_dir = manifest_dir.join("3rd_party");
fs::create_dir_all(&third_party_dir).expect("Failed to create 3rd_party directory");
let status = Command::new("git")
.args(&[
"clone",
"--depth=1",
"https://github.com/alibaba/MNN.git",
local_mnn.to_str().unwrap(),
])
.status()
.expect("Failed to execute git clone command. Make sure git is installed.");
if !status.success() {
panic!("Failed to clone MNN from GitHub");
}
if !local_mnn.join("CMakeLists.txt").exists() {
panic!("MNN cloned but CMakeLists.txt not found");
}
println!(
"cargo:warning=Successfully cloned MNN to: {}",
local_mnn.display()
);
local_mnn
}
fn build_mnn_with_cmake(
mnn_source_dir: &PathBuf,
arch: &str,
os: &str,
debug: &str,
coreml_enabled: bool,
metal_enabled: bool,
cuda_enabled: bool,
opencl_enabled: bool,
opengl_enabled: bool,
vulkan_enabled: bool,
) -> PathBuf {
let mut config = cmake::Config::new(mnn_source_dir);
config
.define("MNN_BUILD_SHARED_LIBS", "OFF")
.define("MNN_BUILD_TOOLS", "OFF")
.define("MNN_BUILD_DEMO", "OFF")
.define("MNN_BUILD_TEST", "OFF")
.define("MNN_BUILD_BENCHMARK", "OFF")
.define("MNN_BUILD_QUANTOOLS", "OFF")
.define("MNN_BUILD_CONVERTER", "OFF")
.define("MNN_PORTABLE_BUILD", "ON")
.define("MNN_SEP_BUILD", "OFF");
if os == "windows" {
config.define("CMAKE_BUILD_TYPE", "Release");
if env::var("CARGO_CFG_TARGET_FEATURE").map_or(false, |f| f.contains("crt-static")) {
config.define("MNN_WIN_RUNTIME_MT", "ON");
config.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
config.define("CMAKE_C_FLAGS_RELEASE", "/MT /O2 /Ob2 /DNDEBUG");
config.define("CMAKE_CXX_FLAGS_RELEASE", "/MT /O2 /Ob2 /DNDEBUG");
config.define("CMAKE_C_FLAGS", "/MT");
config.define("CMAKE_CXX_FLAGS", "/MT");
}
if arch == "x86" {
config.define("CMAKE_GENERATOR_PLATFORM", "Win32");
}
} else {
if debug == "true" {
config.define("CMAKE_BUILD_TYPE", "Debug");
} else {
config.define("CMAKE_BUILD_TYPE", "Release");
}
}
if os == "android" {
let ndk = env::var("ANDROID_NDK_ROOT")
.or_else(|_| env::var("ANDROID_NDK_HOME"))
.or_else(|_| env::var("ANDROID_NDK"))
.or_else(|_| env::var("NDK_HOME"))
.expect(
"Android NDK not found. Please set one of: ANDROID_NDK_ROOT, ANDROID_NDK_HOME, ANDROID_NDK, NDK_HOME",
);
config
.define(
"CMAKE_TOOLCHAIN_FILE",
PathBuf::from(&ndk).join("build/cmake/android.toolchain.cmake"),
)
.define("ANDROID_STL", "c++_static")
.define("ANDROID_NATIVE_API_LEVEL", "android-21")
.define("ANDROID_TOOLCHAIN", "clang")
.define("MNN_BUILD_FOR_ANDROID_COMMAND", "ON")
.define("MNN_USE_SSE", "OFF");
match arch {
"arm" => {
config.define("ANDROID_ABI", "armeabi-v7a");
}
"aarch64" => {
config.define("ANDROID_ABI", "arm64-v8a");
}
"x86" => {
config.define("ANDROID_ABI", "x86");
}
"x86_64" => {
config.define("ANDROID_ABI", "x86_64");
}
_ => {}
}
}
if os == "ios" {
config
.define("CMAKE_SYSTEM_NAME", "iOS")
.define("MNN_BUILD_FOR_IOS", "ON");
if arch == "aarch64" {
config.define("CMAKE_OSX_ARCHITECTURES", "arm64");
} else if arch == "x86_64" {
config.define("CMAKE_OSX_ARCHITECTURES", "x86_64");
}
}
if matches!(arch, "x86" | "x86_64") && os != "android" {
config.define("MNN_USE_SSE", "ON");
}
if coreml_enabled && matches!(os, "macos" | "ios") {
config.define("MNN_COREML", "ON");
}
if metal_enabled && matches!(os, "macos" | "ios") {
config.define("MNN_METAL", "ON");
}
if cuda_enabled && matches!(os, "linux" | "windows") {
config.define("MNN_CUDA", "ON");
}
if opencl_enabled {
config.define("MNN_OPENCL", "ON");
}
if opengl_enabled && matches!(os, "android" | "linux") {
config.define("MNN_OPENGL", "ON");
}
if vulkan_enabled {
config.define("MNN_VULKAN", "ON");
}
println!("cargo:rerun-if-changed=MNN/CMakeLists.txt");
config.build()
}
fn build_wrapper(manifest_dir: &PathBuf, mnn_source_dir: &PathBuf, mnn_dst: &PathBuf, os: &str) {
let wrapper_file = manifest_dir.join("cpp/src/mnn_wrapper.cpp");
println!("cargo:rerun-if-changed=cpp/src/mnn_wrapper.cpp");
println!("cargo:rerun-if-changed=cpp/include/mnn_wrapper.h");
let mut build = cc::Build::new();
build
.cpp(true)
.file(&wrapper_file)
.include(mnn_dst.join("include"))
.include(mnn_source_dir.join("include"))
.include(manifest_dir.join("cpp/include"));
if os == "windows" {
build.flag("/std:c++14").flag("/EHsc").flag("/W3");
} else {
build.flag("-std=c++14").flag("-fvisibility=hidden");
}
build.compile("mnn_wrapper");
}
fn link_libraries(
mnn_dst: &PathBuf,
os: &str,
coreml_enabled: bool,
metal_enabled: bool,
cuda_enabled: bool,
opencl_enabled: bool,
opengl_enabled: bool,
vulkan_enabled: bool,
) {
println!("cargo:rustc-link-search=native={}", mnn_dst.display());
println!(
"cargo:rustc-link-search=native={}",
mnn_dst.join("lib").display()
);
println!("cargo:rustc-link-lib=static=MNN");
match os {
"macos" | "ios" => {
println!("cargo:rustc-link-lib=c++");
}
"linux" => {
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=m");
println!("cargo:rustc-link-lib=pthread");
}
"android" => {
println!("cargo:rustc-link-lib=c++_static");
println!("cargo:rustc-link-lib=log");
}
"windows" => {
}
_ => {}
}
if coreml_enabled && matches!(os, "macos" | "ios") {
println!("cargo:rustc-link-lib=framework=CoreML");
println!("cargo:rustc-link-lib=framework=Foundation");
println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=MetalPerformanceShaders");
}
if metal_enabled && matches!(os, "macos" | "ios") {
println!("cargo:rustc-link-lib=framework=Foundation");
println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=MetalPerformanceShaders");
}
if cuda_enabled && matches!(os, "linux" | "windows") {
println!("cargo:rustc-link-lib=cuda");
println!("cargo:rustc-link-lib=cudart");
println!("cargo:rustc-link-lib=cublas");
println!("cargo:rustc-link-lib=cudnn");
}
if opencl_enabled {
if os == "macos" {
println!("cargo:rustc-link-lib=framework=OpenCL");
} else {
println!("cargo:rustc-link-lib=OpenCL");
}
}
if opengl_enabled && matches!(os, "android" | "linux") {
if os == "android" {
println!("cargo:rustc-link-lib=GLESv3");
println!("cargo:rustc-link-lib=EGL");
} else {
println!("cargo:rustc-link-lib=GL");
}
}
if vulkan_enabled {
println!("cargo:rustc-link-lib=vulkan");
}
}
fn bind_gen(
manifest_dir: &PathBuf,
mnn_source_dir: &PathBuf,
mnn_dst: &PathBuf,
os: &str,
arch: &str,
) {
let header_path = manifest_dir.join("cpp/include/mnn_wrapper.h");
let mut builder = bindgen::Builder::default()
.header(header_path.to_string_lossy())
.allowlist_function("mnnr_.*")
.allowlist_type("MNN.*")
.allowlist_type("MNNR.*")
.clang_arg(format!("-I{}", mnn_dst.join("include").display()))
.clang_arg(format!("-I{}", mnn_source_dir.join("include").display()))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.layout_tests(false);
if os == "android" {
let target = match arch {
"aarch64" => "aarch64-linux-android",
"arm" => "armv7-linux-androideabi",
"x86_64" => "x86_64-linux-android",
"x86" => "i686-linux-android",
_ => "aarch64-linux-android",
};
builder = builder.clang_arg(format!("--target={}", target));
}
let bindings = builder.generate().expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
fs::write(out_path.join("mnn_bindings.rs"), bindings.to_string())
.expect("Couldn't write bindings!");
}