extern crate bindgen;
use std::env;
use std::path::{Path, PathBuf};
use std::fs;
use std::io;
use std::process::Command;
fn get_catboost_version() -> String {
env::var("CATBOOST_VERSION").unwrap_or_else(|_| "1.2.8".to_string())
}
fn get_platform_info() -> (String, String) {
let target = env::var("TARGET").unwrap();
let os = if target.contains("apple-darwin") {
"darwin"
} else if target.contains("linux") {
"linux"
} else if target.contains("windows") {
"windows"
} else {
panic!("Unsupported target: {}", target);
};
let arch = if target.contains("x86_64") {
"x86_64"
} else if target.contains("aarch64") || target.contains("arm64") {
"aarch64"
} else if target.contains("i686") || target.contains("i586") {
"i686"
} else {
panic!("Unsupported architecture for target: {}", target);
};
(os.to_string(), arch.to_string())
}
fn check_libclang_available() -> bool {
if env::var("LIBCLANG_PATH").is_ok() {
return true;
}
if Command::new("pkg-config").arg("--exists").arg("libclang").status().is_ok() {
return true;
}
let common_paths = vec![
"/usr/lib",
"/usr/lib/x86_64-linux-gnu",
"/usr/lib64",
"/usr/local/lib",
"/opt/homebrew/lib", "/usr/local/opt/llvm/lib", ];
for path in common_paths {
if Path::new(path).join("libclang.so").exists() ||
Path::new(path).join("libclang.dylib").exists() ||
Path::new(path).join("libclang.dll").exists() {
return true;
}
}
false
}
fn download_libclang(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let (os, arch) = get_platform_info();
let (version, download_url) = match (os.as_str(), arch.as_str()) {
("linux", "x86_64") => {
let version = "16.0.6";
let url = format!(
"https://github.com/llvm/llvm-project/releases/download/llvmorg-{}/clang+llvm-{}-x86_64-linux-gnu-ubuntu-18.04.tar.xz",
version, version
);
(version, url)
},
("linux", "aarch64") => {
let version = "16.0.6";
let url = format!(
"https://github.com/llvm/llvm-project/releases/download/llvmorg-{}/clang+llvm-{}-aarch64-linux-gnu.tar.xz",
version, version
);
(version, url)
},
("darwin", "x86_64") => {
let version = "16.0.6";
let url = format!(
"https://github.com/llvm/llvm-project/releases/download/llvmorg-{}/clang+llvm-{}-x86_64-apple-darwin.tar.xz",
version, version
);
(version, url)
},
("darwin", "aarch64") => {
let version = "16.0.6";
let url = format!(
"https://github.com/llvm/llvm-project/releases/download/llvmorg-{}/clang+llvm-{}-arm64-apple-darwin.tar.xz",
version, version
);
(version, url)
},
_ => return Err("Unsupported platform for automatic libclang download".into()),
};
println!("cargo:warning=Downloading libclang v{} from: {}", version, download_url);
let download_dir = out_dir.join("libclang_download");
fs::create_dir_all(&download_dir)?;
let archive_path = download_dir.join("libclang.tar.xz");
let response = ureq::get(&download_url).call()?;
let status = response.status();
if status < 200 || status >= 300 {
return Err(format!("Failed to download libclang: HTTP {}", status).into());
}
let mut file = fs::File::create(&archive_path)?;
io::copy(&mut response.into_reader(), &mut file)?;
let extract_dir = out_dir.join("libclang");
fs::create_dir_all(&extract_dir)?;
let file = fs::File::open(&archive_path)?;
let xz = xz2::read::XzDecoder::new(file);
let mut archive = tar::Archive::new(xz);
archive.unpack(&extract_dir)?;
let entries = fs::read_dir(&extract_dir)?;
let libclang_dir = entries
.filter_map(|entry| entry.ok())
.find(|entry| entry.file_name().to_string_lossy().contains("clang+llvm"))
.ok_or("Could not find extracted libclang directory")?;
let libclang_path = libclang_dir.path();
let lib_path = libclang_path.join("lib");
if lib_path.exists() {
env::set_var("LIBCLANG_PATH", lib_path.to_string_lossy().as_ref());
println!("cargo:warning=Set LIBCLANG_PATH to: {}", lib_path.display());
}
fs::remove_file(archive_path)?;
fs::remove_dir_all(download_dir)?;
Ok(())
}
fn download_model_interface_headers(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let version = get_catboost_version();
let model_interface_dir = out_dir.join("libs/model_interface");
fs::create_dir_all(&model_interface_dir)?;
let c_api_url = format!(
"https://raw.githubusercontent.com/catboost/catboost/v{}/catboost/libs/model_interface/c_api.h",
version
);
println!("cargo:warning=Downloading c_api.h from: {}", c_api_url);
let response = ureq::get(&c_api_url).call()?;
let status = response.status();
if status < 200 || status >= 300 {
return Err(format!("Failed to download c_api.h: HTTP {}", status).into());
}
let c_api_path = model_interface_dir.join("c_api.h");
let mut file = fs::File::create(&c_api_path)?;
io::copy(&mut response.into_reader(), &mut file)?;
Ok(())
}
fn download_compiled_library(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let (os, arch) = get_platform_info();
let version = get_catboost_version();
let download_url = match (os.as_str(), arch.as_str()) {
("linux", "x86_64") => format!(
"https://github.com/catboost/catboost/releases/download/v{}/catboost-linux-x86_64-{}",
version, version
),
("linux", "aarch64") => format!(
"https://github.com/catboost/catboost/releases/download/v{}/catboost-linux-aarch64-{}",
version, version
),
("darwin", "x86_64") => format!(
"https://github.com/catboost/catboost/releases/download/v{}/catboost-darwin-universal2-{}",
version, version
),
("darwin", "aarch64") => format!(
"https://github.com/catboost/catboost/releases/download/v{}/catboost-darwin-universal2-{}",
version, version
),
("windows", "x86_64") => format!(
"https://github.com/catboost/catboost/releases/download/v{}/catboost-windows-x86_64-{}.exe",
version, version
),
_ => return Err(format!("Unsupported platform: {}-{}", os, arch).into()),
};
println!("cargo:warning=Downloading CatBoost v{} binary from: {}", version, download_url);
let download_dir = out_dir.join("download");
fs::create_dir_all(&download_dir)?;
let response = ureq::get(&download_url).call()?;
let status = response.status();
if status < 200 || status >= 300 {
return Err(format!("Failed to download binary: HTTP {}", status).into());
}
let archive_path = download_dir.join("catboost-binary");
let mut file = fs::File::create(&archive_path)?;
io::copy(&mut response.into_reader(), &mut file)?;
let lib_dir = out_dir.join("libs");
fs::create_dir_all(&lib_dir)?;
if download_url.ends_with(".tar.gz") {
let file = fs::File::open(&archive_path)?;
let gz = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(gz);
archive.unpack(&lib_dir)?;
} else if download_url.ends_with(".zip") {
let file = fs::File::open(&archive_path)?;
let mut archive = zip::ZipArchive::new(file)?;
archive.extract(&lib_dir)?;
} else {
let final_path = lib_dir.join("catboost");
fs::copy(&archive_path, &final_path)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&final_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&final_path, perms)?;
}
}
fs::remove_file(archive_path)?;
fs::remove_dir_all(download_dir)?;
Ok(())
}
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let cb_model_interface_root = out_dir.join("libs/model_interface");
if !check_libclang_available() {
println!("cargo:warning=libclang not found, attempting to download...");
if let Err(e) = download_libclang(&out_dir) {
eprintln!("Failed to download libclang: {}", e);
eprintln!("Please install libclang-dev or set LIBCLANG_PATH manually");
panic!("Cannot proceed without libclang");
}
} else {
println!("cargo:warning=libclang found on system");
}
if let Err(e) = download_model_interface_headers(&out_dir) {
eprintln!("Failed to download model interface headers: {}", e);
panic!("Cannot proceed without headers");
}
if let Err(e) = download_compiled_library(&out_dir) {
eprintln!("Failed to download compiled library: {}", e);
panic!("Cannot proceed without compiled library");
}
let bindings = bindgen::Builder::default()
.header("wrapper.h")
.clang_arg(format!("-I{}", cb_model_interface_root.display()))
.size_t_is_usize(true)
.rustfmt_bindings(true)
.generate()
.expect("Unable to generate bindings.");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings.");
let lib_search_path = out_dir.join("libs");
if lib_search_path.exists() {
println!(
"cargo:rustc-link-search={}",
lib_search_path.display()
);
}
println!("cargo:rustc-link-lib=dylib=catboostmodel");
}