use std::env; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; fn main() { println!("cargo:rerun-if-env-changed=MAKEPAD_GGML_METAL_PRECOMPILE"); println!("cargo:rerun-if-env-changed=MAKEPAD_GGML_CUDA_ARCH"); println!("cargo:rerun-if-env-changed=CUDA_HOME"); println!("cargo:rerun-if-env-changed=CUDA_PATH"); println!("cargo:rustc-check-cfg=cfg(makepad_ggml_cuda_kernels)"); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); if target_os == "macos" { build_metallib(); } if target_os == "linux" || target_os == "windows" { build_cuda_backends(&target_os); } } fn build_metallib() { let precompile_default = env::var_os("CARGO_FEATURE_METAL_PRECOMPILE").is_some(); let precompile_enabled = env::var("MAKEPAD_GGML_METAL_PRECOMPILE") .ok() .map(|v| { let v = v.trim().to_ascii_lowercase(); !(v.is_empty() || v == "0" || v == "false" || v == "no" || v == "off") }) .unwrap_or(precompile_default); let out_dir = env::var("OUT_DIR").unwrap(); let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let metal_dir = format!("{}/src/backend/metal/ggml", manifest_dir); let metal_src = format!("{}/ggml-metal.metal", metal_dir); let common_h = format!("{}/ggml-common.h", metal_dir); let impl_h = format!("{}/ggml-metal-impl.h", metal_dir); println!("cargo:rerun-if-changed={}", metal_src); println!("cargo:rerun-if-changed={}", common_h); println!("cargo:rerun-if-changed={}", impl_h); let _ = fs::create_dir_all(&out_dir); let air_path = format!("{}/ggml-metal.air", out_dir); let metallib_path = format!("{}/ggml-default.metallib", out_dir); if !precompile_enabled { let _ = fs::write(&metallib_path, []); println!("cargo:rustc-env=MAKEPAD_GGML_METALLIB={}", metallib_path); return; } let metal_status = Command::new("xcrun") .args([ "--sdk", "macosx", "metal", "-O3", "-fno-fast-math", "-c", &metal_src, "-I", &metal_dir, "-o", &air_path, ]) .status(); let ok = metal_status.as_ref().is_ok_and(|s| s.success()); if !ok { println!( "cargo:warning=failed to compile ggml-metal.metal to AIR; runtime source compile will be used" ); let _ = fs::write(&metallib_path, []); println!("cargo:rustc-env=MAKEPAD_GGML_METALLIB={}", metallib_path); return; } let metallib_status = Command::new("xcrun") .args([ "--sdk", "macosx", "metallib", &air_path, "-o", &metallib_path, ]) .status(); let ok = metallib_status.as_ref().is_ok_and(|s| s.success()); if !ok { println!( "cargo:warning=failed to build ggml default metallib; runtime source compile will be used" ); let _ = fs::write(&metallib_path, []); } println!("cargo:rustc-env=MAKEPAD_GGML_METALLIB={}", metallib_path); } fn build_cuda_backends(target_os: &str) { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let src_paths = [ manifest_dir.join("src/backend/cuda/affine.cu"), manifest_dir.join("src/backend/cuda/gated_delta_net.cu"), manifest_dir.join("src/backend/cuda/nvfp4.cu"), manifest_dir.join("src/backend/cuda/nvfp4_mmq.cu"), manifest_dir.join("src/backend/cuda/ops.cu"), manifest_dir.join("src/backend/cuda/qwen_ops.cu"), manifest_dir.join("src/backend/cuda/ssm_conv.cu"), ]; for src_path in &src_paths { println!("cargo:rerun-if-changed={}", src_path.display()); } let Some(cuda_root) = cuda_root(target_os) else { println!("cargo:warning=CUDA toolkit root not found; CUDA backends disabled"); return; }; let nvcc = if target_os == "windows" { cuda_root.join("bin").join("nvcc.exe") } else { cuda_root.join("bin").join("nvcc") }; if !nvcc.exists() { println!( "cargo:warning=CUDA nvcc not found at {}; CUDA backends disabled", nvcc.display() ); return; } let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let lib_path = if target_os == "windows" { out_dir.join("ggml_cuda_affine.lib") } else { out_dir.join("libggml_cuda_affine.a") }; let obj_ext = if target_os == "windows" { "obj" } else { "o" }; let arch = env::var("MAKEPAD_GGML_CUDA_ARCH").unwrap_or_else(|_| "120a".to_string()); let include_dir = cuda_root.join("include"); let msvc_bin_dir = if target_os == "windows" { find_msvc_tool("cl.exe").and_then(|path| path.parent().map(Path::to_path_buf)) } else { None }; let lib_exe = if target_os == "windows" { match find_msvc_tool("lib.exe") { Some(path) => Some(path), None => { println!("cargo:warning=MSVC lib.exe not found; CUDA backends disabled"); return; } } } else { None }; let mut obj_paths = Vec::new(); for src_path in &src_paths { let stem = src_path.file_stem().unwrap().to_string_lossy(); let obj_path = out_dir.join(format!("ggml_cuda_{stem}.{obj_ext}")); let arch_flag = format!("arch=compute_{arch},code=sm_{arch}"); let mut command = Command::new(&nvcc); command.args(["-std=c++17", "-O3"]); if target_os == "windows" { if let Some(msvc_bin_dir) = &msvc_bin_dir { command.arg("-ccbin").arg(msvc_bin_dir); } command.args(["-Xcompiler", "/EHsc"]); command.args(["-Xcompiler", "/MD"]); } else { command.args(["-Xcompiler", "-fPIC"]); } let status = command .args([ "-c", "-I", include_dir.to_string_lossy().as_ref(), "-gencode", arch_flag.as_str(), "-o", obj_path.to_string_lossy().as_ref(), src_path.to_string_lossy().as_ref(), ]) .status(); let ok = status.as_ref().is_ok_and(|s| s.success()); if !ok { println!( "cargo:warning=failed to compile CUDA backend source {}; CUDA path disabled", src_path.display() ); return; } obj_paths.push(obj_path); } let archive_ok = if target_os == "windows" { let mut lib = Command::new(lib_exe.unwrap()); lib.arg("/NOLOGO") .arg(format!("/OUT:{}", lib_path.to_string_lossy())); for obj_path in &obj_paths { lib.arg(obj_path); } lib.status().as_ref().is_ok_and(|s| s.success()) } else { let mut ar = Command::new("ar"); ar.arg("crus").arg(lib_path.to_string_lossy().as_ref()); for obj_path in &obj_paths { ar.arg(obj_path.to_string_lossy().as_ref()); } ar.status().as_ref().is_ok_and(|s| s.success()) }; if !archive_ok { println!("cargo:warning=failed to archive CUDA backends; CUDA path disabled"); return; } println!("cargo:rustc-link-search=native={}", out_dir.display()); println!("cargo:rustc-link-lib=static=ggml_cuda_affine"); if target_os == "linux" { println!("cargo:rustc-link-lib=dylib=stdc++"); } println!("cargo:rustc-cfg=makepad_ggml_cuda_kernels"); } fn cuda_root(target_os: &str) -> Option { env::var_os("CUDA_HOME") .or_else(|| env::var_os("CUDA_PATH")) .map(PathBuf::from) .filter(|path| path.exists()) .or_else(|| { if target_os == "windows" { latest_windows_cuda_root() } else { let default = Path::new("/usr/local/cuda"); default.exists().then(|| default.to_path_buf()) } }) } fn latest_windows_cuda_root() -> Option { let cuda_root = env::var_os("ProgramFiles") .map(PathBuf::from) .map(|program_files| { program_files .join("NVIDIA GPU Computing Toolkit") .join("CUDA") })?; let mut entries = fs::read_dir(cuda_root) .ok()? .filter_map(|entry| entry.ok()) .filter(|entry| entry.file_type().ok().is_some_and(|ty| ty.is_dir())) .collect::>(); entries.sort_by_key(|entry| entry.file_name()); entries.pop().map(|entry| entry.path()) } fn find_msvc_tool(tool_name: &str) -> Option { if let Some(paths) = env::var_os("PATH") { if let Some(path) = env::split_paths(&paths) .map(|path| path.join(tool_name)) .find(|candidate| candidate.exists()) { return Some(path); } } let find_pattern = format!(r"VC\Tools\MSVC\**\bin\Hostx64\x64\{tool_name}"); for installer_root in [ r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe", r"C:\Program Files\Microsoft Visual Studio\Installer\vswhere.exe", ] { let vswhere = Path::new(installer_root); if !vswhere.exists() { continue; } let output = match Command::new(vswhere) .args([ "-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-find", find_pattern.as_str(), ]) .output() { Ok(output) => output, Err(_) => continue, }; if !output.status.success() { continue; } if let Some(path) = String::from_utf8_lossy(&output.stdout) .lines() .map(str::trim) .find(|line| !line.is_empty()) .map(PathBuf::from) .filter(|path| path.exists()) { return Some(path); } } None }